GBD linux debuging tools

GDB是一个由GNU开源组织发布的、UNIX/LINUX操作系统下的、基于命令行的、功能强大的程序调试工具。

GDB中的命令固然很多,但我们只需掌握其中十个左右的命令,就大致可以完成日常的基本的程序调试工作。

命令 解释 示例
file <文件名> 加载被调试的可执行程序文件。
因为一般都在被调试程序所在目录下执行GDB,因而文本名不需要带路径。
(gdb) file gdb-sample
r Run的简写,运行被调试的程序。
如果此前没有下过断点,则执行完整个程序;如果有断点,则程序暂停在第一个可用断点处。
(gdb) r
c Continue的简写,继续执行被调试程序,直至下一个断点或程序结束。 (gdb) c
b <行号>
b <函数名称>
b *<函数名称>
b *<代码地址>

d [编号]

b: Breakpoint的简写,设置断点。两可以使用“行号”“函数名称”“执行地址”等方式指定断点位置。
其中在函数名称前面加“*”符号表示将断点设置在“由编译器生成的prolog代码处”。如果不了解汇编,可以不予理会此用法。

d: Delete breakpoint的简写,删除指定编号的某个断点,或删除所有断点。断点编号从1开始递增。

(gdb) b 8
(gdb) b main
(gdb) b *main
(gdb) b *0x804835c

(gdb) d

s, n s: 执行一行源程序代码,如果此行代码中有函数调用,则进入该函数;
n: 执行一行源程序代码,此行代码中的函数调用也一并执行。

s 相当于其它调试器中的“Step Into (单步跟踪进入)”;
n 相当于其它调试器中的“Step Over (单步跟踪)”。

这两个命令必须在有源代码调试信息的情况下才可以使用(GCC编译时使用“-g”参数)。

(gdb) s
(gdb) n
si, ni si命令类似于s命令,ni命令类似于n命令。所不同的是,这两个命令(si/ni)所针对的是汇编指令,而s/n针对的是源代码。 (gdb) si
(gdb) ni
p <变量名称> Print的简写,显示指定变量(临时变量或全局变量)的值。 (gdb) p i
(gdb) p nGlobalVar
display ...

undisplay <编号>

display,设置程序中断后欲显示的数据及其格式。
例如,如果希望每次程序中断后可以看到即将被执行的下一条汇编指令,可以使用命令
“display /i $pc”
其中 $pc 代表当前汇编指令,/i 表示以十六进行显示。当需要关心汇编代码时,此命令相当有用。

undispaly,取消先前的display设置,编号从1开始递增。

(gdb) display /i $pc

(gdb) undisplay 1

i Info的简写,用于显示各类信息,详情请查阅“help i”。 (gdb) i r
q Quit的简写,退出GDB调试环境。 (gdb) q
help [命令名称] GDB帮助命令,提供对GDB名种命令的解释说明。
如果指定了“命令名称”参数,则显示该命令的详细说明;如果没有指定参数,则分类显示所有GDB命令,供用户进一步浏览和查询。
(gdb) help display

/add************************************/

j命令              回跳

ret               设置返回值        (例ret 0/ret -1)

/add************************************/

废话不多说,下面开始实践。gdb-sample.c

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

#include <stdio.h>

int
 nGlobalVar = 0;

int
 tempFunction(int a, int b)
{
 
 
  printf("tempFunction is called, a = %d, b = %d \n", a, b);
 
 
  return (+ b);
}


int
 main()
{
 
 
  int n;
 
 
      n = 1;
 
 
      n++;
 
 
      n--;

 
 
      nGlobalVar += 100;
 
 
      nGlobalVar -= 12;

 
 
  printf("n = %d, nGlobalVar = %d \n", n, nGlobalVar);

 
 
      n = tempFunction(1, 2);
 
 
  printf("n = %d", n);

 
 
  return 0;
}

 

gcc gdb-sample.c -o gdb-sample -g

使用参数 -g 表示将源代码信息编译到可执行文件中。如果不使用参数 -g,会给后面的GDB调试造成不便。当然,如果我们没有程序的源代码,自然也无从使用 -g 参数,调试/跟踪时也只能是汇编代码级别的调试/跟踪。

下面“gdb”命令启动GDB,将首先显示GDB说明,不管它:

GNU gdb Red Hat Linux (5.3post-0.20021129.18rh)
Copyright 2003 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i386-redhat-linux-gnu".
(gdb)

上面最后一行“(gdb) ”为GDB内部命令引导符,等待用户输入GDB命令。

下面使用“file”命令载入被调试程序 gdb-sample(这里的 gdb-sample 即前面 GCC 编译输出的可执行文件):

(gdb) file gdb-sample
Reading symbols from gdb-sample...done.

上面最后一行提示已经加载成功。

下面使用“r”命令执行(Run)被调试文件,因为尚未设置任何断点,将直接执行到程序结束:

(gdb) r
Starting program: /home/liigo/temp/test_jmp/test_jmp/gdb-sample
n = 1, nGlobalVar = 88
tempFunction is called, a = 1, b = 2
n = 3
Program exited normally.

下面使用“b”命令在 main 函数开头设置一个断点(Breakpoint):

(gdb) b main
Breakpoint 1 at 0x804835c: file gdb-sample.c, line 19.

上面最后一行提示已经成功设置断点,并给出了该断点信息:在源文件 gdb-sample.c 第19行处设置断点;这是本程序的第一个断点(序号为1);断点处的代码地址为 0x804835c(此值可能仅在本次调试过程中有效)。回过头去看源代码,第19行中的代码为“n = 1”,恰好是 main 函数中的第一个可执行语句(前面的“int n;”为变量定义语句,并非可执行语句)。

再次使用“r”命令执行(Run)被调试程序:

(gdb) r
Starting program: /home/liigo/temp/gdb-sample

Breakpoint 1, main () at gdb-sample.c:19
19 n = 1;

程序中断在gdb-sample.c第19行处,即main函数是第一个可执行语句处。

上面最后一行信息为:下一条将要执行的源代码为“n = 1;”,它是源代码文件gdb-sample.c中的第19行。

下面使用“s”命令(Step)执行下一行代码(即第19行“n = 1;”):

(gdb) s
20 n++;

上面的信息表示已经执行完“n = 1;”,并显示下一条要执行的代码为第20行的“n++;”。

既然已经执行了“n = 1;”,即给变量 n 赋值为 1,那我们用“p”命令(Print)看一下变量 n 的值是不是 1 :

(gdb) p n
$1 = 1

果然是 1。($1大致是表示这是第一次使用“p”命令——再次执行“p n”将显示“$2 = 1”——此信息应该没有什么用处。)

下面我们分别在第26行、tempFunction 函数开头各设置一个断点(分别使用命令“b 26”“b tempFunction”):

(gdb) b 26
Breakpoint 2 at 0x804837b: file gdb-sample.c, line 26.
(gdb) b tempFunction
Breakpoint 3 at 0x804832e: file gdb-sample.c, line 12.

使用“c”命令继续(Continue)执行被调试程序,程序将中断在第二 个断点(26行),此时全局变量 nGlobalVar 的值应该是 88;再一次执行“c”命令,程序将中断于第三个断点(12行,tempFunction 函数开头处),此时tempFunction 函数的两个参数 a、b 的值应分别是 1 和 2:

(gdb) c
Continuing.

Breakpoint 2, main () at gdb-sample.c:26
26 printf("n = %d, nGlobalVar = %d \n", n, nGlobalVar);
(gdb) p nGlobalVar
$2 = 88
(gdb) c
Continuing.
n = 1, nGlobalVar = 88

Breakpoint 3, tempFunction (a=1, b=2) at gdb-sample.c:12
12 printf("tempFunction is called, a = %d, b = %d \n", a, b);
(gdb) p a
$3 = 1
(gdb) p b
$4 = 2

上面反馈的信息一切都在我们预料之中,哈哈~~~

再一次执行“c”命令(Continue),因为后面再也没有其它断点,程序将一直执行到结束:

(gdb) c
Continuing.
tempFunction is called, a = 1, b = 2
n = 3
Program exited normally.

 

时候需要看到编译器生成的汇编代码,以进行汇编级的调试或跟踪,又该如何操作呢?

这就要用到display命令“display /i $pc”了(此命令前面已有详细解释):

(gdb) display /i $pc
(gdb)

此后程序再中断时,就可以显示出汇编代码了:

(gdb) r
Starting program: /home/liigo/temp/test_jmp/test_jmp/gdb-sample

Breakpoint 1, main () at gdb-sample.c:19
19 n = 1;
1: x/i $pc 0x804835c <main+16>: movl $0x1,0xfffffffc(%ebp)

看到了汇编代码,“n = 1;”对应的汇编代码是“movl $0x1,0xfffffffc(%ebp)”。

并且以后程序每次中断都将显示下一条汇编指定(“si”命令用于执行一条汇编代码——区别于“s”执行一行C代码):

(gdb) si
20 n++;
1: x/i $pc 0x8048363 <main+23>: lea 0xfffffffc(%ebp),%eax
(gdb) si
0x08048366 20 n++;
1: x/i $pc 0x8048366 <main+26>: incl (%eax)
(gdb) si
21 n--;
1: x/i $pc 0x8048368 <main+28>: lea 0xfffffffc(%ebp),%eax
(gdb) si
0x0804836b 21 n--;
1: x/i $pc 0x804836b <main+31>: decl (%eax)
(gdb) si
23 nGlobalVar += 100;
1: x/i $pc 0x804836d <main+33>: addl $0x64,0x80494fc

 

接下来我们试一下命令“b *<函数名称>”。

为了更简明,有必要先删除目前所有断点(使用“d”命令——Delete breakpoint):

(gdb) d
Delete all breakpoints? (y or n) y
(gdb)

当被询问是否删除所有断点时,输入“y”并按回车键即可。

下面使用命令“b *main”在 main 函数的 prolog 代码处设置断点(prolog、epilog,分别表示编译器在每个函数的开头和结尾自行插入的代码):

 

(gdb) b *main
Breakpoint 4 at 0x804834c: file gdb-sample.c, line 17.
(gdb) r
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/liigo/temp/test_jmp/test_jmp/gdb-sample

Breakpoint 4, main () at gdb-sample.c:17
17 {
1: x/i $pc 0x804834c <main>: push %ebp
(gdb) si
0x0804834d 17 {
1: x/i $pc 0x804834d <main+1>: mov %esp,%ebp
(gdb) si
0x0804834f in main () at gdb-sample.c:17
17 {
1: x/i $pc 0x804834f <main+3>: sub $0x8,%esp
(gdb) si
0x08048352 17 {
1: x/i $pc 0x8048352 <main+6>: and $0xfffffff0,%esp
(gdb) si
0x08048355 17 {
1: x/i $pc 0x8048355 <main+9>: mov $0x0,%eax
(gdb) si
0x0804835a 17 {
1: x/i $pc 0x804835a <main+14>: sub %eax,%esp
(gdb) si
19 n = 1;
1: x/i $pc 0x804835c <main+16>: movl $0x1,0xfffffffc(%ebp)

 

此时可以使用“i r”命令显示寄存器中的当前值———“i r”即“Infomation Register”:

 

(gdb) i r
eax 0xbffff6a4 -1073744220
ecx 0x42015554 1107383636
edx 0x40016bc8 1073834952
ebx 0x42130a14 1108544020
esp 0xbffff6a0 0xbffff6a0
ebp 0xbffff6a8 0xbffff6a8
esi 0x40015360 1073828704
edi 0x80483f0 134513648
eip 0x8048366 0x8048366
eflags 0x386 902
cs 0x23 35
ss 0x2b 43
ds 0x2b 43
es 0x2b 43
fs 0x0 0
gs 0x33 51

 

当然也可以显示任意一个指定的寄存器值:

 

(gdb) i r eax
eax 0xbffff6a4 -1073744220

 

最后一个要介绍的命令是“q”,退出(Quit)GDB调试环境:


启动调试

gdb ./prog          # debug prog from start
gdb ./prog pid      # attach running program
gdb ./prog core     # debug core file

gdbserver host:PORT --attach PID        # host: 是字符串
                                        # 远程调试时的远程机调用

设置编辑器

export EDITOR=/usr/bin/vi
gdb                 # 使用EDITOR环境变量
                    # gdb默认使用ex做编辑器,
                    # 使用ex编辑器时,命令 visual 可以开启全屏,避免每次只能查看一行代码

M-C-j               # vi-mode,M是Esc,C是Ctrl
                    # gdb默认的方式是Emacs,可以切换为vi模式

GDB Shell 命令

macro expand macro-name     展开宏定义

file PROG                   加载PROG符号文件
info files                  names of targets and files being debugged
                            show the entire stack of targets current in use

attach pid                  在gdb启动后用attach命令挂载已运行程序

show args                   列出命令行参数,字符串
info args                   argc值/argv地址, 具体查看使用p argv[1], etc.

info signal                 查看信号处理方式
                            info signal SIGALRM
handle SIGNAL nopass        不将信号SIGNAL传递给程序
                            pass/noignore, allow program see this signal
                            nopass/ignore, not allow program see this signal

p *array@len                print array
p/x (unsigned int[])out     强转为unsigned int数组并以16进制打印

p $eax                      查看寄存器的值,eax是上个函数的返回值
info register eax           查看寄存器的值,info registers/all-registers,所有寄存器的值
info registers              查看所有寄存器的值
i registers esp             查看寄存器的值

bt/frame                    查看堆栈,bt(backtrace)

n/next
s/step
si/stepi                    执行一条机器指令
ni/nexti                    执行一条机器指令
until                       跳过for/while循环(execute unitl the program reaches a source line
                            greater than the current or a specified location within the current frame)
finish                      跳出函数(完成函数执行,返回上一级调用栈)

disassemble main            查看汇编代码
show disassembly-flavor     可以是intel/att
set disassemble-flavor intel/att    设置反汇编风格

show directories            源代码搜索路径
directory /path/to/src      添加源代码搜索路径

f                           check where you are
i program                   find out why your program stopped

whatis val                  查看变量类型

p var = 5                   设置程序变量并打印
set var = 5                 设置程序变量
set $var = 5                设置gdb变量

call ntohs(10275)           调用函数

b/break                     设置断点
rb                          对符合正则表达式的位置处设置断点
                            若不带参数,则在所有位置处设置断点

set print pretty on

调试C++

gdb调试C++,涉及到STL容器查看,非常麻烦。有一个GDB STL Viewer的脚本,可以帮助查看STL容器。只需在调用gdb后,执行命令source gdb_stl_viewer.txt加载它即可。

b 'C::foo                   自动补全'
ptype C                     显示类C的声明
info functions C::foo       显示类C的所有foo函数声明

在模板函数中的某一行设置断点,将导致gdb只在某个模板实例中中断。通过info b查看,如:

1   breakpoint     keep y   0x08048774 in void print<char>(char) at tem.cpp:8
必须在函数签名,而非源代码某一行设置断点,如有模板函数 print<T> 要打断点,可通过:
1、 通过命令 objdump -tC ./tem|grep print 
    或 objdump -t ./tem|c++filt|grep print
    找出所有print符号。
2、 在gdb中对这些符号下断点。如 b void print<int>(int)

c++filt

c++filt - Demangle C++ and Java symbols.

配置文件

vim ~/.gdbinit              # 新建gdbinit文件
source ~/gdb_stl_viewer.txt # 添加gdb命令

define commond-name         # gdbinit定义命令语法
commands
end

document command-name       # 给命令添加说明性文字
desc
end

while语法

set $i = 0
set $sz = sizeof(arr)/sizeof(arr[0])
while $i < $sz do
    print arr[$i]
    set $i++                # set
end

(gdb) set $i=0
(gdb) while $i < 10
 >p $i
 >set $i += 1               # set
 >end
history
show commands 20            显示命令的历史记录

调试多线程

info threads        # inquire threads
thread tid          # switch among threads

thread apply all bt # 显示所有的线程堆栈
thread appy tid1 tid2 bt    # 显示tid1, tid2的堆栈。bt可以换成其他任何gdb命令

set scheduler-locking off|on|step
off     不锁定任何线程。在调试某一线程时,其他线程照常执行。默认。
on      锁定其他线程,只有当其线程会执行。
step    除了next过一个函数的情况,step只让当前线程执行。

调试多进程

set detach-on-fork off          # both parent/child process will be held under the control of gdb
set follow-fork-mode child      # default is parent

info forks      # 显示所有进程pid
process pid     # 切换到pid的进程
fork fork-id    # 切换到fork-id的进程,类似process pid

远程调试

gdbserver host:PORT --attach PID    # remote host run gdbserver

gdb -> file ->                      # 本地调用gdb,用file命令加载符号文件
target remote tcp:IP:PORT

bookmark/checkpoint

checkpoint                  # save a snapshot
info checkpoints
restart chekcpoint-id       # wind back the clock :)

hook

给gdb定义钩子,使其在执行gdb命令前、后,执行用户自定义的命令

define hook-print           # 命令必须是全称,不能为缩写
echo --\n
end

define hookpost-print       # 在命令后执行用户指令
echo --\n
end

kgdb

kgdb可对Linux内核进行内核级别源码调试。需要两台机子,用串口相连(或在VMware里模拟串口通讯)。 kdb不能进行内核源码基本调试,但可以只用一台机子。 



Linux系统中在应用程序运行过程中经常会遇到程序突然崩溃,提示:Segmentation fault,这是因为应用程序收到了SIGSEGV信号。这个信号提示当进程发生了无效的存储访问,当接收到这个信号时,缺省动作是:终止w/core。 终止w/core的含义是:在进程当前目录生成core文件,并将进程的内存映象复制到core文件中,core文件的默认名称就是“core”(这是Unix类系统的一个由来已久的功能)。

事实上,并不是只有SIGSEGV信号产生coredump,还有下面一些信号也产生:

SIGFSZ

超过晚间长度限制

.       .

 .      .

终止w/core

SIGABRT

异常终止(abort)

  .       .

  .      .

终止w/core

SIGBUS

硬件故障

          .

  .      .

终止w/core

SIGEMT

硬件故障

  .      .

终止w/core

SIGFPE

算术异常

  .       .

  .      .

终止w/core

SIGILL

非法硬件指令

  .       .

  .      .

终止w/core

SIGIOT

硬件故障

  .      .

终止w/core

SIGQUIT

终端退出符

          .

  .      .

终止w/core

SIGSEGV

无效存储访问

  .       .

  .      .

终止w/core

SIGSYS

无效系统调用

  .      .

终止w/core

SIGTRAP

硬件故障

  .      .

终止w/core

SIGXCPU

超过CPU限制(setrlimit)

  .      .

终止w/core



猜你喜欢

转载自blog.csdn.net/jiang13824690/article/details/7679646
今日推荐