共计 25570 个字符,预计需要花费 64 分钟才能阅读完成。
总述
gdb 虽然只是一个调试器,但如何要用好它,必须深刻理解 linux 下程序是如何编译运行的,比较推荐的书有《CSAPP》(中文版名:深入理解计算机系统)和《APUE》(中文名:Unix 环境高级编程)。
本文只局限对 gdb 的使用上,后面有机会再介绍关于 linux 下程序编译运行相关的知识。
调试的目的
关于调试目的,大家第一印象肯定是发现程序问题。这的确是最主要的目的。另外一点是帮助我们理解他人写的程序。对我来说这也非常有用。如果发现程序有问题,有时不防试试画一个流程图,边理解程序边调试问题。
基本知识
Stack(栈)
当调试的程序暂停时,你首先想知道的是它停在了什么地方,以及它是怎样到达那里的。
调试的程序每调用一个函数,就会产生关于这个调用的相关信息。这些信息包括调用前的位置、传递的参数、以及被调用函数的局部变量。这些信息保存在一个名为栈帧(stack frame)的数据块中。栈帧是在内存中分配的成为“调用栈”(call stack)的一块区域。
当程序暂停的时候,gdb 中一些检查栈的命令允许你查看这些所有的信息。
gdb 和 gdb 命令会选择其中的一个栈帧作为“当前栈帧”。不管什么时候用 gdb 来显示程序中的变量的值,这个变量总是指当前帧中的变量。有一些特殊的 gdb 命令可以让你选择你感兴趣的帧。
被调试的程序暂停时,gdb 自动选择当前程序在其中运行的帧,并对其作一简短描述,类似于 frame 命令。
Frames(帧)
调用栈是被在内存中分割成一些连续的相邻的数据块,它们被称为“栈帧”,简称“帧”。每个帧都包含有调用一个函数的相关数据,包括被调用函数的局部变量和被调用函数的地址等等。
当程序运行时,栈里只有一个帧,即 main 函数的帧,它被称为初始化帧或者最层的帧。每调用一个函数就会产生一个新的帧,当函数返回时,相应的帧就会被删除。如果函数是递归调用的,则可能产生很多针对调用这个函数的帧。每次实际调用一次这个函数,产生的帧被成为“最内层的帧”。这是所有帧里最新产生的。
在程序中,帧是用地址来标识的,每个帧都有很多个字节,每个字节都有自己的地址。每种计算机都有各自不同的约定来选择一个代表帧地址的字节。通常这个地址是记录在名为“帧指针”$FP(Frame Pointer Register)的一个寄存器里。
gdb 将每个存在的栈帧赋予了一个数字,从 0 开始,编号为 0 的帧代表最内层的帧,编号为 1 的帧代表调用它的帧,以此类推。这些数字编号实际上并不存在于被调试的程序中,它们只是由 gdb 赋予的值,以便在 gdb 命令里可以方便地标识帧。
一些编译器提供了一种使程序不使用栈帧的编译方法(例如 gcc 编译器使用 -fomit-frame-pointer 选项时就可以产生不使用帧的函数)。这只是偶尔用在大量的库函数调用时的一种方法,以便程序可以节省建立帧的时间。如果最内层的帧没有栈帧的话,gdb 还是可以像往常一样认为它有一个单独的帧并标识为编号为 0,以便正确地对函数调用链进行跟踪。但是 gdb 没有办法对其它的函数调用帧进行标识。
一. 启动 gdb
1.1 几种方法
gdb program
gdb program core
gdb program
gdb --args gcc -O2 -c foo.c
gdb --silent
上面的 gdb --args gcc -O2 -c foo.c
将调试 gcc, 并把 -O2 -c foo.c 作为参数传递给 gcc
1.2 参数
-symbol file
-s file
-exec file
-e file
-se file #read symbol table from file file and use it as the executable file
-core file
-c file
-pid number
-p number
-command file
-x command
-eval-command command
-ex command #execute a signal GDB command
-init-comamnd file
-ix file
-init-eval-command comand
-iex command
-directory directory
-d directory # add directory to the path to search for the source and scrip files
1.3 shell command
如果要执行 shell comands, 可以用 shell 开头就行
shell command-string
!command-string
如果是 make, 则可以直接输入make make-args
1.4 log
如果你想把 GDB 命令输出到一个文件有,有几种方法控制
set logging on
set logging off
set logging file <filename>
set logging overwrite [on|off] #默认会追加到 logfile 里
set logging redirect [on|off] #默认 GDB 输出会在 terminal 和 logfile 里显示,用 redirect 让它只在 logfile 里显示
show logging
二. 使用帮助
主要有两个命令
2.1 help
如果知道命令名,直接输入help <command_name>
2.2 apropos
如果不记得命令名,可以搜索关键字,apropos <regexp>
(gdb) apropos args
advance -- Continue the program up to the given location (same form as args for break command)
collect -- Specify one or more data items to be collected at a tracepoint
handle -- Specify how to handle a signal
info args -- Argument variables of current stack frame
l -- List specified function or line
list -- List specified function or line
r -- Start debugged program
run -- Start debugged program
set args -- Set argument list to give program being debugged when it is started
show args -- Show argument list to give program being debugged when it is started
u -- Execute until the program reaches a source line greater than the current
until -- Execute until the program reaches a source line greater than the current
三. 运行
包含三个命令:
run : 开始运行
start:运行并停在 main 函数上
continue:继续运行
ignore:忽略某函数或文件
checkpoint: 设置书签
2.1 start
后面可以跟参数, 如start 1 2 3
2.2 ignore
忽略某函数或文件
在调试程序中可能会不关心某个函数或文件,这时可以用 skip
来略过他们。
语法如下:
skip -file <file>
skip -gfile <file_glob_pattern>
skip -function <linespec>
skip -rfunction <regexp>
info skip [range]
<linespec> 是一个冒号分隔的列表,它可以包括文件名,函数名。(filename:linenum, function:label, filename:function, label)
示例如下:
(gdb) skip -rfu ^std::(allocator|basic_string)<.*>::~?\1 *\((gdb) skip -rfu ^std::([a-zA-z0-9_]+)<.*>::~?\1 *\(
第一个命令忽略 std::string 的构造和解构函数
第二个命令忽略所有来自 std namespace 的函数的构造和解构函数
2.3 checkpoint
gdb 可以在程序执行的过程中保留快照 (状态) 信息,称之为 checkpoint,可以在进来返回到该处再次查看当时的信息,比如内存、寄存器以及部分系统状态。通过设置 checkpoint,万一调试的过程中错误发生了但是已经跳过了错误发生的地方,就可以快速返回 checkpoint 再开始调试,而不用重启程序重新来过。
2.3.1 checkpoint
对当前执行状态保存一个快照,gdb 会自动产生一个对应的编号供后续标识使用。从提示信息看来,其实每建立一个 checkpoint,都会 fork 出一个子进程,所以每个 checkpoint 都有一个唯一的进程 ID 所对应着。
2.3.2 info checkpoints
查看所有的 checkpoint,包括进程 ID、代码地址和当时的行号或者符号。
2.3.3 restart checkpoint-id
恢复程序状态到 checkpoint-id 指明的 checkpoint,所有的程序变量、寄存器、栈都会被还原到那个状态,同时 gdb 还会将时钟信息回退到那个点。恢复过程中程序的状态和系统的部分状态是支持的,比如文件指针等信息,但是写入文件的数据、传输到外部设备的数据都无法被回退。
2.3.4 delete checkpoint checkpoint-id
删除指定的 checkpoint。
此外,checkpoint 的作用还在于断点、观测点不是什么情况下都可用的情况下,因为 Linux 系统有时候为了安全考虑,会随机化新进程的地址空间,这样重启调试程序会导致之前使用绝对地址设置的断点、观测点不可用。
四. 断点
在一个位置上设置断点,可以对应多个位置,gdb 要自动在需要的位置插入断点。在动态库里也可以设置断点,不过其地址在加载后才能解析。
断点的设置有几种方法
break
b
break [Function Name]
break [File Name]:[Line Number]
break [Line Number]
break *[Address]
break [...] if [Condition]
break [...] thread [Thread-id]
b [...]
rbreak [regexp]
rbreak [File Name]:[regexp]
tbreak [args]
4.1 Function name 和 Address
不要混淆 [Function name] 和[Address],比如你想在地址 0x40138c 上设定断点,如果用
gdb> break 0x4007d9
会失败,GDB 会 0x40138c 解释成函数而不是地址。正确的做法是
(gdb) break *0x4007d9
可以用下面命令得到相应行的地址:
(gdb) i line gdbprog.cc:14
Line 14 of "gdbprog.cc" starts at address 0x4007d9 <InitArrays(int*)+17> and ends at 0x4007f7 <InitArrays(int*)+47>.
或者
(gdb) disas /m InitArrays
Dump of assembler code for function InitArrays(int*):
8 void InitArrays(int* array)
0x00000000004007c8 <+0>: push %rbp
0x00000000004007c9 <+1>: mov %rsp,%rbp
0x00000000004007cc <+4>: mov %rdi,-0x18(%rbp)
9 {
10
11
12 for(int i = 0;i < 10;i++)
0x00000000004007d0 <+8>: movl $0x0,-0x4(%rbp)
0x00000000004007d7 <+15>: jmp 0x40080a <InitArrays(int*)+66>
0x0000000000400807 <+63>: incl -0x4(%rbp)
0x000000000040080a <+66>: cmpl $0x9,-0x4(%rbp)
0x000000000040080e <+70>: jle 0x4007d9 <InitArrays(int*)+17>
13 {14 ptrArray[i] = array + i;
0x00000000004007d9 <+17>: mov -0x4(%rbp),%ecx
0x00000000004007dc <+20>: mov -0x4(%rbp),%eax
0x00000000004007df <+23>: cltq
0x00000000004007e1 <+25>: shl $0x2,%rax
0x00000000004007e5 <+29>: mov %rax,%rdx
0x00000000004007e8 <+32>: add -0x18(%rbp),%rdx
0x00000000004007ec <+36>: movslq %ecx,%rax
0x00000000004007ef <+39>: mov %rdx,0x6013e0(,%rax,8)
15 iArray[i] = i;
0x00000000004007f7 <+47>: mov -0x4(%rbp),%eax
0x00000000004007fa <+50>: movslq %eax,%rdx
0x00000000004007fd <+53>: mov -0x4(%rbp),%eax
0x0000000000400800 <+56>: mov %eax,0x6013a0(,%rdx,4)
16 }
17 }
0x0000000000400810 <+72>: leaveq
0x0000000000400811 <+73>: retq
End of assembler dump.
4.2. Pending breakpoints
如果试图将断点设在位于还未加载的 shared library 代码内,那么就会显示类似下面 warning
Make breakpoint pending on future shared library load? (y or [n])
但它可能永远不会工作,如果是下列情况:
- shared library 不包含任何 debugging symbols (被用‘strip’命令移除掉)
- GDB 无法检测到 library 被加载 (比如,在 android 上使用 GDB 6.x)
- 你输入的是一个错误文件名或函数名
所以可以用下面几个命令来做调试
-
info sharedlibrary
: 目前加载的 shared library -
info sources
: 被 GDB 识别的源文件 -
info breakpoints
: 创建的断点和它们的状态
4.3. 条件断点
大约有以下几种形式
break main if argc > 1
break 180 if (string == NULL && i < 0)
break test.c:34 if (x & y) == 1
break myfunc if i % (j+3) != 0
break 44 if strlen(mystring) == 0
b 10 if ((int)$gdb_strcmp(a,"chinaunix") == 0)
b 10 if ((int)aa.find("dd",0) == 0)
经常犯的一个错误就是 在 if 和后面的(之间没放空格
另外注意 条件表达式的返回值类型是 int
当设置了断点后,后面可以使用 condition 和 ignore 来修改这个断点条件
4.4. condition
condition <break_list> (condition)
比如
cond 3 (i==4)
上面命令把 breakpoint 3 的条件修改成(i==4)
4.5. ignore
ignore <break_list> count
上面命令表示 break_list 指定的断点将被忽略 count 次
比如现在 i =0, 设置 i >4 为条件,那么忽略 3 次后,它停在 i =8,忽略了 i =5, 6, 7
4.6. commands
commands <break_list>
当断点被触发时,运行相应的命令
可以用 info breakpoints
来查看相应断点上附着的命令
(gdb) i b
Num Type Disp Enb Address What
3 breakpoint keep y 0x00000000004007d9 in InitArrays(int*) at gdbprog.cc:14
stop only if (i>4)
breakpoint already hit 5 times
silent
printf "hz: i=%d\n",i
c
7 breakpoint keep y 0x0000000000400962 in main() at gdbprog.cc:51
breakpoint already hit 1 time
一个断点上只允许附着一条命令,再次调用 commands 3
后会覆盖掉前面的命令
因此如果要删除相应的命令,只需用空的 commands 3 就行了
4.7. define
用 define 可以录制宏,这些宏就像 shell 脚本一样,可以传入参数,依次是 $arg0, $arg1, …
可以把这些宏放在.gdbinit 文件中
录制好宏后就可以在 commands 中使用
宏并不支持所有 GDB 命令,比如 silent 就不能用于宏中, 它会在运行时报错:Undefined commands: “silent”
(gdb) define printAndGo
Type commands for definition of "printAndGo".
End with a line saying just "end".
>printf "i=%d\n",$arg0
>cont
>end
(gdb) commands 3
Type commands for breakpoint(s) 3, one per line.
End with a line saying just "end".
>silent
>printAndGo
>end
(gdb) i b
Num Type Disp Enb Address What
3 breakpoint keep y 0x00000000004007d9 in InitArrays(int*) at gdbprog.cc:14
stop only if (i>4)
silent
printAndGo i
7 breakpoint keep y 0x0000000000400962 in main() at gdbprog.cc:51
如果要查看这些宏的定义,可以用 show user
来查看
(gdb) show user
User command "pbuf":
set $i=0
while ($i<strlen(netiring.supply))
print netiring.supply[$i]
set $i=$i+1
end
User command "pobuf":
set $i=0
while ($i<strlen(netoring.consume))
print netoring.consume[$i]
set $i=$i+1
end
4.8 tbreak
只生效一次临时断点
4.9 rbreak
在所有匹配 regexp 的函数名处都设置断点。它的 regexp 和 grep 相同
4.10 管理
clear localtion
location 可以是 function, file:func, linenum, file:linenumdelete [breakpoints] [range...]
[breakpoints]是可选项 disable|enable [breakpoints] [range...]
禁用 | 启用断点 enable [breakpoints] once range...
启用断点一次 enable [breakpoints] once range...
雇用断点 cnt 次 enable [breakpoints] delete range...
临时启用断点,一旦被激活就会把删除,和 tbreak 相似
五. 检查数据
pirnt
x
display
set
watch
catch
tracepoint
5.1 print
print 接受表达式和计算它的值。任何该语言支持常值,变量和操作符都可以使用,像条件表达式,函数调用,类型转换,字符常量。GDB 还支持数组常量,语法是{element, element…}, 比如 print {1,2,3}.GDB 支持还支持下面操作符
- @
二进制操作符,可以把 momery 当成数组。
int *array = (int*) malloc(len * sizeof(int));
可以使用下面命令来打印它的值:
(gdb) p *array@len
(gdb) p/x (short[2])0x12345678
$1 = {0x1234, 0x5678}
另外还可以自定义变量
set $i = 0
p dtab[$i++]->fv
RET
RET
上面的 RET 表示重复上面的表达式
- ::
定义属于某个文件或函数的变量
- {type} addr
把 addr 的变量按 {type} 类型解释
5.2 x
x/nfu addr
x addr
x
n, the repeat count
f, the display format (x, d, u, o, t, a, c, f, s, i)
u, the unit size (b: bytes, h: halfwords, w: words, g: giant words)
(gdb) x/5i $pc-6
0x804837f <main+11>: mov %esp,%ebp
0x8048381 <main+13>: push %ecx
0x8048382 <main+14>: sub $0x4,%esp
=> 0x8048385 <main+17>: movl $0x8048460,(%esp)
0x804838c <main+24>: call 0x80482d4 <puts@plt>
5.3 display
如果你发现你经常要打印某个表达式,你可以把它加入到 ”automatic display list”
display expr
display/fmt expr
display/fmt addr
undisplay <dnums>
delete display dnums
disable display dnums
enable display dnums
5.4 set
可以使用 set <var>=<expr>
来修改程序中变量的值
5.5 数值历史(value Hisitory)
通过使用 print 命令显示的值会被自动保存在 GDB 的数值历史当中,该值会一直被保留直到符号表被重新读取或者放弃的时候(比如使用 file 或 symbol-file),此时所有的值历史将会被丢弃。在使用 print 打印值的时候,会将值编以整形的历史编号,然后可以使用 $num 的方式方便的引用,单个的 $ 表示最近的数值历史,而 $$ 表示第二新的数值历史,$$n 表示倒数第 n 新的数值历史(所以可以推断 $$0==$; $$1==$$;)。
比如刚刚打印了一个指向结构体的指针,那么 print $ 就可以显示结构体的信息,而命令 print \$.nex 甚至可以显示结构体中的某些字段。
5.6 Convenience 变量
GDB 允许自由创建便捷变量用于保存值,后续可以方便的引用该值,该类型的变量由 GDB 管理而与正在调试的程序没有直接的关联。便捷变量也是使用符号 $ 打头的,可以使用任意名字 (除了 GDB 使用的寄存器名)。
在任意时候使用一个便捷变量都会创建他,如果没有提供初始化值那么该变量就是 void,直到给其进行赋值操作为止。便捷变量没有固定的类型,可以为普通变量、数组、结构体等,而且其类型可以在赋值过程中改变 (为当前值的类型)。show convenience|conv
显示道目前所有的便捷变量和便捷函数,以及其值。init-if-undefined $variable = expr
如果该变量还没有被创建或初始化,则创建这个变量。如果该变量已经被创建了,则不会创建和初始化该变量,并且 expr 表达式也不会被求值,所以这种情况下也不会有 expr 的副作用发生。
便捷变量的一种经典用法,就是之前提到的连续查看变量时候用于计数作用:
set $i = 0
print bar[$i++]->contents
下面的一些便捷变量是 GDB 自动创建的:
- $_exitcode
当调试程序终止的时候,GDB 将会根据程序的退出值自动设置该变量,并且将 $_exitsignal 变量设置为 void。
- $_exitsignal
当调试中的程序因为一个未捕获信号而终止,此时 GDB 会自动将变量 $_exitsignal 设置为该信号,同时重置变量 $_exitcode 为 void。
5.7 Convenience 函数
便捷函数和便捷变量一样使用 $ 打头引用,其可以像一个普通函数一样在表达式中使用,便捷函数只被 GDB 内部使用。`
(gdb) print $_isvoid ($_exitsignal)
$_isvoid (expr): 如果 expr 是 void 则返回 1,否则返回 0。
GDB 还有很多的便捷函数支持,但是需要在编译 GDB 的时候提供 Python 的支持才可以使用。下面的这些函数意义显而易见,就不在啰嗦了。
$_memeq(buf1, buf2, length); $_regex(str, regex); $_streq(str1, str2); $_strlen(str)
5.8 watch
监视点是监视特定内存位置、特定表达式的值是否改变而触发程序暂停执行,而不用去关心该值到底在代码的哪个位置被修改的。监视的表达式可以是:某个变量的引用、强制地址解析(比如(int)0x12345678,你无法 watch 一个地址,因为地址是永远也不会改变的)、合理的表达式(比如 a -b+c/d,gdb 会检测其中引用的各个变量)。
watch [-l|-location] expr [thread thread-id] [mask maskvalue]
thread-id 可以设置特定线程改变 expr 的时候触发中断,默认情况下针对硬件模式的检测点所有的线程都会检测该表达式;-location 会让 gdb 计算 expr 的表达式,并将计算的结果作为地址,并探测该地址上的值 (数据类型由 expr 计算结果决定)。
很多平台都有实现监视点的专有硬件,如果没有 GDB 也会采用虚拟内存技术来实现监视点,而如果前面两种技术都不可用,则 GDB 会采用软件实现监事点本身。软件模式速度会非常慢,因为如果不支持硬件模式 gdb 会每次 step 并计算检测的表达式,x86 构架支持硬件模式,而且监视点的作用也有限,只能当前单个线程能侦测其变化,虽然该值也可能会被其他线程修改。
监视点在跟踪那种变量不知道哪里被修改的情形特别的有效。不过监视点需要变量在当前上下文可访问的情况下才可以使用,所以在使用中会通常配合断点先到达事发的附近位置,然后再创建对应的监测点。
watch 命令还存在两个变体:rwatch 当 expr 被程序读的时候触发中断;awatch 会在程序读取或者写入 expr 的时候被中断。rwatch 和 awatch 只支持硬件模式的检测点
5.9 catch
可以让 gdb 在某些事件发生的时候暂停执行,比如 C ++ 抛出异常、捕获异常、调用 fork()、加载动态链接库以及某些系统调用的时候,其格式为 catch event,还有一个变体 tcatch event 设置临时捕获点,其中 event 的参数可以为:
throw|rethrow|catch|exec|fork|vfork|syscall|exception|unhandled|assert
在 C ++ 异常抛出、重新抛出、捕获的时候触发,可选使用 regex 参数限定特定的异常类型 (在 gcc-4.8 开始支持),内置变量 $_exception 会记录在 catchpoint 激活时候的异常。
当异常发生时候,程序通常会停留在 libstdc++ 的异常支持点,此时可以通过使用 up 命令切换帧跳转到用于异常代码处。
syscall 如果不带参数,则捕获任意系统调用。
syscall [name | number | group:groupname | g:groupname] …
在进入和 / 或返回系统调用的时候触发。name 可以指明 catch 的系统调用名(定义在 /usr/include/asm/unistd.h,且 gdb 会帮助智能补全),group|g:groupname 可以用来指定一组类别的系统调用,比如 g:network,通过智能补全可以查看支持的 group 信息。
load|unload [regex]
: 加载和卸载共享库时候触发,可选 regex 进行过滤。
signal [signal… |‘all’]
可以在软件收到信号的时候触发。gdb 本身会占用 SIGTRAP 和 SIGINT 两个信号,如果不添加额外参数,会 catch 除了这两个信号之外的所有信号。
使用 info break 命令,watchpoint 的信息会被展示出来,可以像普通断点一样管理之。
五. Stack 命令
主要包含以下几个
bt
frame
info frame
where
5.1 bt
bt
bt [Frame count]
bt full
可以使用负数,- 1 是第一层,- 2 是第二层
(gdb) backtrace
#0 level0 () at recursion.cpp:5
#1 0x08048462 in test (level=0) at recursion.cpp:17
#2 0x0804845b in test (level=1) at recursion.cpp:14
#3 0x0804845b in test (level=2) at recursion.cpp:14
#4 0x0804845b in test (level=3) at recursion.cpp:14
#5 0x0804845b in test (level=4) at recursion.cpp:14
#6 0x0804845b in test (level=5) at recursion.cpp:14
#7 0x08048479 in main () at recursion.cpp:22
(gdb) backtrace -2
#6 0x0804845b in test (level=5) at recursion.cpp:14
#7 0x08048479 in main () at recursion.cpp:22
5.2 frame
frame 是非常有用的命令,它可以用来显示当前帧的信息
基本语法是
frame
frame [Frame number]
f
如果没有参数,就是当前行的信息
(gdb) frame
#0 level0 () at recursion.cpp:5
5 printf("Reached level 0\n");
(gdb) info args
No arguments.
(gdb) frame 1
#1 0x08048462 in test (level=0) at recursion.cpp:17
17 level0();
(gdb) info args
level = 0
(gdb) frame 2
#2 0x0804845b in test (level=1) at recursion.cpp:14
14 test(prevLevel);
(gdb) info args
level = 1
可以用 info line *$rip
和list *$rip
获得类似当前行的信息
5.3 info frame
语法如下:
info frame
info frame [addr]
相比直接的frame
,这个命令输出更详细的 stack frame 信息,包括
- frame 的地址
- 下一层 frame 的地址
- 上一层 frame 的地址(caller)
- 当前 frame 是用什么语言所写
- frame 的变量地址
- frame 的 local 变量
- pc 值
- 在当前 frame 里保存的 registers
(gdb) i frame
Stack level 0, frame at 0x7fffffffe250:
rip = 0x4009e0 in PrintArray (gdbprog.cc:40); saved rip 0x400a3f
called by frame at 0x7fffffffe280
source language c++.
Arglist at 0x7fffffffe240, args:
Locals at 0x7fffffffe240, Previous frame's sp is 0x7fffffffe250
Saved registers:
rbp at 0x7fffffffe240, rip at 0x7fffffffe248
(gdb) bt
#0 PrintArray () at gdbprog.cc:40
#1 0x0000000000400a3f in main () at gdbprog.cc:57
(gdb) i f 0x0000000000400a3f
Stack frame at 0x400a3f:
rip = 0x0; saved rip 0x7ffff7ff9720
called by frame at 0x7fffffffe240
Arglist at 0x7fffffffe228, args:
Locals at 0x7fffffffe228, Previous frame's sp is 0x7fffffffe238
Saved registers:
rip at 0x7fffffffe230
六. 程序信息
主要包含
info proc
info variables
info functions
info source
info sources
info sharedlibrary
info flies
6.1 info proc
info proc all
可以打印出所有进程相关信息
info proc mappings
显示正在运行的进程中映射的内存区域的列表。与 /prod/pid/maps 的输出相同
6.2 info functions
列出程序中的函数
语法如下:
info functions
info functions [Regex]
示例如下:
(gdb) info functions
All defined functions:
File gdbprog.cc:
int DoOperation(int**);
void InitArrays(int*);
void PrintArray();
int main();
static void __static_initialization_and_destruction_0(int, int);
static void __tcf_0(void*);
static void global constructors keyed to iArray();
Non-debugging symbols:
0x0000000000400660 _init
0x0000000000400688 std::basic_ostream<char, std::char_traits<char> >::operator<<(int)
0x0000000000400688 _ZNSolsEi@plt
0x0000000000400698 std::ios_base::Init::Init()
...
6.3 info source
显示当前源文件名
info sources
显示加载的 symbols 涉及的源文件
(gdb) info sources
Source files for which symbols have been read in:
<<C++-namespaces>>, /home/harriszh/cpp/e1/gdbprog.cc,
/grid/common/pkgsData/gcc-v4.1.2p2/Linux/RHEL3.0-1H2006-x86_64/include/c++/4.1.2/bits/locale_facets.h,
/grid/common/pkgsData/gcc-v4.1.2p2/Linux/RHEL3.0-1H2006-x86_64/include/c++/4.1.2/x86_64-redhat-linux/bits/ctype_base.h,
/grid/common/pkgsData/gcc-v4.1.2p2/Linux/RHEL3.0-1H2006-x86_64/include/c++/4.1.2/bits/stl_iterator.h,
/grid/common/pkgsData/gcc-v4.1.2p2/Linux/RHEL3.0-1H2006-x86_64/include/c++/4.1.2/bits/allocator.h,
/grid/common/pkgsData/gcc-v4.1.2p2/Linux/RHEL3.0-1H2006-x86_64/include/c++/4.1.2/ext/new_allocator.h,
/grid/common/pkgsData/gcc-v4.1.2p2/Linux/RHEL3.0-1H2006-x86_64/include/c++/4.1.2/bits/stringfwd.h, /usr/include/bits/pthreadtypes.h,
/grid/common/pkgsData/gcc-v4.1.2p2/Linux/RHEL3.0-1H2006-x86_64/include/c++/4.1.2/x86_64-redhat-linux/bits/gthr-default.h,
/grid/common/pkgsData/gcc-v4.1.2p2/Linux/RHEL3.0-1H2006-x86_64/include/c++/4.1.2/bits/locale_classes.h,
/grid/common/pkgsData/gcc-v4.1.2p2/Linux/RHEL3.0-1H2006-x86_64/include/c++/4.1.2/bits/basic_string.tcc,
/grid/common/pkgsData/gcc-v4.1.2p2/Linux/RHEL3.0-1H2006-x86_64/include/c++/4.1.2/bits/basic_string.h,
/grid/common/pkgsData/gcc-v4.1.2p2/Linux/RHEL3.0-1H2006-x86_64/include/c++/4.1.2/limits,
/grid/common/pkgsData/gcc-v4.1.2p2/Linux/RHEL3.0-1H2006-x86_64/include/c++/4.1.2/x86_64-redhat-linux/bits/atomic_word.h,
/grid/common/pkgsData/gcc-v4.1.2p2/Linux/RHEL3.0-1H2006-x86_64/include/c++/4.1.2/bits/ios_base.h,
/grid/common/pkgsData/gcc-v4.1.2p2/Linux/RHEL3.0-1H2006-x86_64/include/c++/4.1.2/iosfwd,
/grid/common/pkgsData/gcc-v4.1.2p2/Linux/RHEL3.0-1H2006-x86_64/include/c++/4.1.2/new, /usr/include/wctype.h,
/grid/common/pkgsData/gcc-v4.1.2p2/Linux/RHEL3.0-1H2006-x86_64/include/c++/4.1.2/cwctype, /usr/include/stdlib.h,
/grid/common/pkgsData/gcc-v4.1.2p2/Linux/RHEL3.0-1H2006-x86_64/include/c++/4.1.2/cstdlib,
/grid/common/pkgsData/gcc-v4.1.2p2/Linux/RHEL3.0-1H2006-x86_64/include/c++/4.1.2/cwchar, /usr/include/time.h,
/grid/common/pkgsData/gcc-v4.1.2p2/Linux/RHEL3.0-1H2006-x86_64/include/c++/4.1.2/ctime, /usr/include/locale.h,
/grid/common/pkgsData/gcc-v4.1.2p2/Linux/RHEL3.0-1H2006-x86_64/include/c++/4.1.2/clocale, /usr/include/wchar.h, /usr/include/_G_config.h,
/usr/include/bits/types.h, /usr/include/libio.h, /usr/include/stdio.h,
...
6.4 info files
显示执行文件名称和加载的段
(gdb) i file
Symbols from "/home/harriszh/trunk/cpp/exercise/gdbprog".
Unix child process:
Using the running image of child process 42226.
While running this, GDB does not access memory from...
Local exec file:
`/home/harriszh/trunk/cpp/exercise/gdbprog', file type elf64-x86-64.
Entry point: 0x4007f0
0x0000000000400200 - 0x000000000040021c is .interp
0x000000000040021c - 0x000000000040023c is .note.ABI-tag
0x000000000040023c - 0x0000000000400260 is .note.gnu.build-id
0x0000000000400260 - 0x0000000000400294 is .gnu.hash
0x0000000000400298 - 0x00000000004003e8 is .dynsym
0x00000000004003e8 - 0x0000000000400592 is .dynstr
0x0000000000400592 - 0x00000000004005ae is .gnu.version
0x00000000004005b0 - 0x0000000000400600 is .gnu.version_r
0x0000000000400600 - 0x0000000000400630 is .rela.dyn
0x0000000000400630 - 0x0000000000400720 is .rela.plt
0x0000000000400720 - 0x0000000000400738 is .init
0x0000000000400738 - 0x00000000004007e8 is .plt
0x00000000004007f0 - 0x0000000000400ba8 is .text
0x0000000000400ba8 - 0x0000000000400bb6 is .fini
0x0000000000400bb8 - 0x0000000000400bf4 is .rodata
0x0000000000400bf4 - 0x0000000000400c40 is .eh_frame_hdr
0x0000000000400c40 - 0x0000000000400d84 is .eh_frame
0x0000000000601000 - 0x0000000000601018 is .ctors
0x0000000000601018 - 0x0000000000601028 is .dtors
0x0000000000601028 - 0x0000000000601030 is .jcr
0x0000000000601030 - 0x00000000006011f0 is .dynamic
0x00000000006011f0 - 0x00000000006011f8 is .got
0x00000000006011f8 - 0x0000000000601260 is .got.plt
0x0000000000601260 - 0x0000000000601264 is .data
0x0000000000601280 - 0x0000000000601438 is .bss
0x000000347b8001c8 - 0x000000347b8001ec is .note.gnu.build-id in /lib64/ld-linux-x86-64.so.2
0x000000347b8001f0 - 0x000000347b8002a8 is .hash in /lib64/ld-linux-x86-64.so.2
...
6.5 info sharedlibrary
显示共享库及它们的地址
语法如下:
info sharedlibrary
info shared
示例如下:
(gdb) info sharedlibrary
From To Syms Read Shared Object Library
0xb7fde820 0xb7ff6b9f Yes /lib/ld-linux.so.2
0xb7fd83a0 0xb7fd84c8 Yes /home/testuser/libtest/libTest.so
0xb7e30f10 0xb7f655cc Yes /lib/i386-linux-gnu/libc.so.6
...
6.6 set stop-on-solib-events command
当一个 shared library 加载或卸载时,是否暂停
set stop-on-solib-events 0
set stop-on-solib-events 1
show stop-on-solib-events
默认是不暂停,如果想暂停设成 1
七. 源文件
主要命令是
list
info line
disassemble
set disassemble-next-line
set disassemble-flavor
7.1 list
查看当前运行源代码
语法如下:
list <linenum>
list <first>, <last>
list <function>
list
list -
list *<addr>
示例如下:
(gdb) list *$pc
0x4009e0 is in PrintArray() (gdbprog.cc:40).
35
36 void PrintArray()
37 {
38 int i;
39
40 for(i = 0;i < 10;i++)
41 {42 if (iArray[i] == 6)
43 cout << "This is an error\n";
44 else
7.2 info line
基本语法是
info line [File]:[Line]
info line [Function]
info line [File]:[Function]
info line *[Address]
比如:
(gdb) info line test.cpp:5
Line 5 of "test.cpp" starts at address 0x80483f0 <main(int, char**)+3> and ends at 0x80483f5 <main(int, char**)+8>.
(gdb) info line *0x80483f0
Line 5 of "test.cpp" starts at address 0x80483f0 <main(int, char**)+3> and ends at 0x80483f5 <main(int, char**)+8>.
(gdb) info line *0x80483f6
Line 6 of "test.cpp" starts at address 0x80483f5 <main(int, char**)+8> and ends at 0x80483f7.
(gdb) info line main
Line 4 of "test.cpp" starts at address 0x80483ed <main(int, char**)> and ends at 0x80483f0 <main(int, char**)+3>.
7.3 disassemble
显示某个函数或某函数中一段代码的汇编
语法如下:
disassemble
disassemble [Function]
disassemble [Address]
disassemble [Start],[End]
disassemble [Function],+[Length]
disassemble [Address],+[Length]
disassemble /m [...]
disassemble /r [...]
/m 表示 把源代码和汇编一一对应显示出来,在新版本中建议中 /s, 它会考虑 compiler 优化
/r 表示 显示汇编指令的原生字节码
set disassemble-next-line
控制断点暂停或 step 后是否显示下一行的汇编,默认是 off
语法如下:
set disassemble-next-line on
set disassemble-next-line off
show disassemble-next-line
示例如下:
(gdb) set disassemble-next-line on
(gdb) next
4 }
=> 0x080483f8 <func+11>: 5d pop %ebp
0x080483f9 <func+12>: c3 ret
(gdb) next
main () at test.c:9
9 }
=> 0x08048414 <main+26>: c9 leave
0x08048415 <main+27>: c3 ret
set disassemble-flavor
控制汇编和 x 命令的风格,有 AT&T 和 intel 两种
语法如下:
set disassembly-flavor att
set disassembly-flavor intel
show disassembly-flavor
八. record & replay
GDB 提供加州功能,可以记录程序的执行过程(execution log),然后在后续时间上进行前进、后退的程序运行。在调试目标运行时,如果接下来的执行指令在 log 中存在,就会进入 replay mode,该模式下不会真正执行指令,而是将事件、寄存器,内存值从日志中提取出来,如果接下来的执行指令不在 log 中,那么 GDB 就会在 record mode 下执行,所有指令将会按归照正常方式执行,GDB 会记录执行。
8.1 Record
默认的方法是使用 full recording, 该模式不支持 non-stop mode 和异步执行模式的多线程调试。只能调试正在执行的进程
record [full| btrace bts|btrace pt]
record stop
record goto [begin|start|end|n]
info record
record delete
8.1.1 record <method>
开启进程的 R &RT 模式
8.1.2 record stop
停止进程的 R &RT,并将所有的 execution log 删除。如果在 record 的中间位置执行本命令,那么会从这个点开始 Live 调试
8.1.3 record goto
中转到指定位置,n 表示 execution log 中的 instruction 的序号。
可以用 info record 查看
8.1.4 record save|restore file
保存和恢复 execution log
8.1.5 set record full insn-number-max limit|unlimited
默认最多 200,000。
8.1.6 set record full stop-at-limit on|off
当保存指令数目超过限制的话,如果 on(默认),那么 GDB 会在首次超过限制的地方停止;如果 off,则会自动删除最旧记录
8.1.7 record delete
在 replay 模式下执行该命令,会删除当前指令之后的所有 log, 并开始新的 record
8.2 Replay
reverse-continue [ignore-count]
rc [ignore-count]
reverse-step [count]
reverse-stepi [count]
reverse-next [count]
reverse-nexti [count]
reverse-finish
set exec-direction [reverse|forward]
它们的行为如他们名字一样,这里就不再多解释
九. 变量查看
9.1 generate-core-file
产生当前运行进程的一份 memory image 和它的 process status(register values etc…)
gcore [file]
9.2 info registers
(gdb) i registers
rax 0x601280 6296192
rbx 0x0 0
rcx 0x400 1024
rdx 0x3487ae9d18 225614667032
rsi 0x7ffff7ff8000 140737354104832
rdi 0xffffffff 4294967295
rbp 0x7fffffffe240 0x7fffffffe240
rsp 0x7fffffffe230 0x7fffffffe230
r8 0x7ffff7ff8002 140737354104834
r9 0x7ffff7ff9720 140737354110752
r10 0xcccccccccccccccd -3689348814741910323
r11 0x246 582
r12 0x4007f0 4196336
r13 0x7fffffffe350 140737488347984
r14 0x0 0
r15 0x0 0
rip 0x4009e0 0x4009e0 <PrintArray()+88>
eflags 0x206 [PF IF]
cs 0x33 51
ss 0x2b 43
ds 0x0 0
es 0x0 0
fs 0x0 0
gs 0x0 0
9.3 info variable
显示全局或静态的变量
info variables
info variables [Regex]
可以用 i var <varName>
来查看某个全局或静态变量是在哪个文件定义的
9.4 info locals
显示当前帧的本地变量,配合 frame, up 和 down 来使用
9.5 info args
显示当前帧的函数的参数,配合 frame,up 和 down 来使用
十. 编辑和搜索
10.1 编辑
可以直接打开编辑器编辑源文件
edit <number>
edit <function>
10.2 搜索
forward-search <regexp>
fo <regexp>
search <regexp>
reverse-search <regex>
rev <regexp>
可以通过 directory dirname ...
来添加源文件路径
10.3 内存查找
使用 find 可以对内存进行序列字节查找
find [/sn] start_addr, +len, val1 [, val2, …]
find [/sn] start_addr, end_addr, val1 [, val2, …]
该命令会在内存中搜索 var1、var2…字符序,其范围从 startaddr 开始,以结束地址或者长度结束。
参数 s 表示搜索的大小,可以是 b、h、w、g(跟之前的 x 命令一样),如果该参数没有指定,GDB 会考虑当前调试程序的语言环境确定,比如 0x43 这种整数就默认为 w(4 字节)类型;n 表示打印最大搜索命中数量,默认行为是全部打印。该命令还可以搜索字符串,字符串使用双引号括住。
对于查找结果,命中数目保存在变量 $numfound 中,而最后一个值命令的地址保存在 **$** 中。
void
hello ()
{static char hello[] = "hello-hello";
static struct {char c; short s; int i;}
__attribute__ ((packed)) mixed
= {'c', 0x1234, 0x87654321};
printf ("%s\n", hello);
}
可以进行如下查找
(gdb) find &hello[0], +sizeof(hello), "hello"
0x804956d <hello.1620+6>
1 pattern found
(gdb) find &hello[0], +sizeof(hello), 'h', 'e', 'l', 'l', 'o'
0x8049567 <hello.1620>
0x804956d <hello.1620+6>
2 patterns found.
(gdb) find &hello[0], +sizeof(hello), {char[5]}"hello"
0x8049567 <hello.1620>
0x804956d <hello.1620+6>
2 patterns found.
(gdb) find /b1 &hello[0], +sizeof(hello), 'h', 0x65, 'l'
0x8049567 <hello.1620>
1 pattern found
(gdb) find &mixed, +sizeof(mixed), (char) 'c', (short) 0x1234, (int) 0x87654321
0x8049560 <mixed.1625>
1 pattern found
(gdb) print $numfound
$1 = 1
(gdb) print $_
$2 = (void *) 0x8049560
十一. 多进程
GDB 对 fork 产生新进程的调试没有特殊的支持,当使用 fork 产生子进程的时候,GDB 会继续调试父进程,子进程不会被阻碍而是继续执行,而此时如果子进程遇到断点后,子进程会收到 SIGTRAP 信号,当在非调试模式下默认会导致子进程中止。如果要调试子进程,可以让子进程启动后睡眠一段时间 (或者监测某个文件是否创建),在这个空隙中使用 ps 查看子进程的进程号,然后再启动 gdb 去 attach 到这个进程上面去调试。
通过 catch 命令可以让 gdb 在 fork/vfork/exec 调用的时候暂停执行。
影响 fork 多进程调试还有以下几个变量:follow-fork-mode
可选值是 parent 或 child,指明当正在被调试的进程 fork 出子进程的时候,如果该值是 parent,那么父进程被调试子进程正常执行,这也是默认行为;而当其为 child 的时候,则新创建的子进程被调试,父进程正常执行。detach-on-fork
可选值是 on 或 off,知名当被调试的进程 fork 出子进程后,是否 detach 某个进程还是调试他们俩,如果是 on,那么子进程 (或父进程,取决于上面 follow-fork-mode 的值) 将会被 detach 正常执行,这是默认行为;否则两个进程都会被 gdb 控制,其中的一个进程被正常调试,另外一个进程被 suspend 住。follow-exec-mode
gdb 对于 exec 调用的反应,其值可以为 new 或 same,当值为 new 的时候,之前 fork 的子进程和 exec 后的进程都会被保留;如果是 same,则 exec 的进程使用之前 fork 的进程,exec 会用新的执行镜像替代原先的可执行程序,这是默认行为。
十二. 多线程
在多线程程序中,有两种工作模式:all-stop 和 non-stop
all-stop
当进程中任意线程因为某种原因停止执行的时候,所有其他线程也同时被 GDB 停止执行。好处是程序停止的时候所有线程的状态都被保留下来了,可以切换线程查看整个程序当时的所有状态 (缺点是整个业务全部阻塞暂停了)。
有些操作系统支持 OS 调试锁定,可以修改 GDB 的默认行为
- scheduler-locking
off:不会锁定,所有线程可以自由运行;
on:当程序 resume 的时候,只有当前线程可以运行;
step:该模式是为单步模式优化的模式,当跟踪的时候阻止其他线程抢占当前线程。当 step 的时候其他线程无法运行,而在使用 continue、until、finish 类似指令的时候其他线程可以自由运行。除非其他线程运行时候触发了断点,否则 GDB 不会切换当前调试的线程。
- schedule-multiple
该设置是针对多进程情况下的调试。
off:只有当前线程所在的进程的所有线程允许恢复执行,该 off 为默认值。
on:所有进程的所有线程都可以执行。
non-stop
当进程中任意线程停止时,在跟踪检查停止线程的时候其他的线程任然继续执行,其好处就是对线上系统进行最小化的干扰入侵,整个程序还是可以继续响应外部请求的。
通过下面的设置可以开启 non-stop 模式,一般都是在 gdb 开始的时候或者连接调试目标的时候设置才会生效,且在调试过程中不能切换该模式,不是所有平台都支持 non-stop 模式,所以即使设置了 GDB 还是有可能 fallback 到 all-stop 模式。
set pagination off
set non-stop on
non-stop 模式的验证也十分的简单,在一个多线程的网络程序中先设置断点,此时发送一个请求那么处理线程会被停止,接下来删除该断点,再次发送一个请求,看该请求是否得到正常响应。如果 non-stop 正常开启生效的话,此时的 info threads 会显示其他线程都是 running 的状态。
non-stop 模式下的命令默认都是针对当前线程的,如果要针对所有线程,通常需要使用 - a 参数,比如 continue -a
十三. 针对语言的支持
13.1 对 C 的特殊支持
C 语言预处理宏
宏本身是个比较麻烦的东西,因为可以某些点定义、某些点取消定义、某些点重新定义,GDB 支持对含有宏的表达式进行展开并显示展开后结果的功能。如果要让编译后的程序具有宏调试功能,需要额外的编译参数 -gdwarf- 2 和 -g3(- g 是不够的)
gdb gcc -gdwarf-2 -g3 sample.c -o sample
上面的 gdwarf- 2 具有 gdwarf-3、gdwarf- 4 等版本,推荐支持的情况下使用最新版本的调试格式。macro exp|expand expression
显示 expression 中所有宏被展开后的结果。因为 GDB 只会进行宏展开而不会对表达式取值,所以这里的 expression 不需要是合法的表达式。info macro [-a|-all] [–] macro
显示当前的或者全部的 macro 宏定义,同时显示该宏定义所在的源代码文件及其所在行号位置信息。其中的–表示参数列表结束,因为 C 中有些宏是可以使用 - 开头的,这里用于消除歧义作用。info macros location
显示所有在 location 位置有效的宏定义及他们的位置信息。(显示结果有点多哦)macro define macro replacement-list
macro define macro(arglist) replacement-list
自定义宏及其展开式,通过该命令创建的宏会在 GDB 求值的每个表达式中都会生效 (只对 GDB 本身作用,与代码中使用的宏无关),直到其显式使用 macro undef 进行删除,同时该宏还会覆盖程序中同名宏的展开(有点诡异)。macro undef macro
只会删除上面使用 macro define 定义的宏,而不会删除程序代码中定义的宏。macro list
显示所有通过 macro define 定义的宏。
除了通常在源代码中执行宏定义,还可以在编译的时候在命令行中通过‘-Dname=value’的方式定义,这样的宏也支持使用 info macro 命令查看,不过其行号信息显示为 0。