Contents

[Linux]GDB调试技巧

命令行参数

gdb有下面几种运行方式:

1
2
3
4
5
6
// 1. 通过coredump文件,或者存在的进程id分析,不会拉起新进程
gdb [options] [executable-file [core-file or process-id]]
// 2. 带参数运行程序
gdb [options] --args executable-file [inferior-arguments ...]
// 3. redhat等含有用于调试python的工具
// gdb [options] [--python|-P] script-file [script-arguments ...]

几个值得注意的参数:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// 和上面的类似,使用参数指定的
--args             Arguments after executable-file are passed to inferior
--core=COREFILE    Analyze the core dump COREFILE.
--pid=PID          Attach to running process PID.
--exec=EXECFILE    Use EXECFILE as the executable.
// 远程调试
-b BAUDRATE        Set serial port baud rate used for remote debugging.
-l TIMEOUT         Set timeout in seconds for remote debugging.
// 运行某个文件中的gdb指令
--command=FILE, -x Execute GDB commands from FILE.
// 运行某gdb指令,如gdb a.out -ex r开启文件并立即运行
--eval-command=COMMAND, -ex   Execute a single GDB command.
--directory=DIR    Search for source files in DIR.
--se=FILE          Use FILE as symbol file and executable file.

简单的例子:coredump分析

gdb的一大用处是通过coredump文件分析程序哪里发生了错误。首先要打开coredump生成开关:

1
2
3
4
// 可以先运行 ulimit -a 查看所有限制,或者运行 ulimit -c 查看当前coredump设置
// 设置成无限可能会生成数GB的coredump文件
// 这条命令重启后无效
ulimit -c unlimited

coredump文件会默认生成在程序相同目录下。如果没有对应文件,可以查看/etc/sysctl.conf:

1
2
3
4
// 生成目录格式,%e 程序名 %p 进程id %s 信号 %t 时间 %e 命令名
kernel.core_pattern =/data/coredump/core%e%p
// 1表示使用procid命名,0表示不使用
kernel.core_uses_pid= 0

我们先编写一个简单的c程序main.c,它试图从非法地址读取数据:

1
2
3
4
int main(){
    int a = *((int *)NULL);
    return 0;
}

编译,一定要加上-g选项:

1
gcc -o test_worker main.c -g

运行,果然dump:

1
2
./test_worker
Segmentation fault (core dumped)

然后使用第一节里的第一种方式启动, 携带coredump文件:

1
gdb ./test_worker core.xxx.xxx

进去后就能看到dump的地方以及原因。使用bt查看coredump时栈顶的信息:

1
2
3
4
5
6
7
Core was generated by `./t'.
Program terminated with signal 11, Segmentation fault.
#0  0x0000000000400564 in main () at t.c:13
13	    int a = *((int *)NULL);
Missing separate debuginfos, use: debuginfo-install glibc-2.17-106.el7_2.4.x86_64
(gdb) bt
#0  0x0000000000400564 in main () at t.c:13

GDB的用法

GDB Online Docs

GDB启动时会读取二进制文件的符号表,然后进入调试命令行。这里可以运行各种命令查看程序的符号表、变量、内存值以及控制程序的运行。

控制命令

  1. file 加载一个二进制文件,直接进入gdb时可以用这个加载。参数为文件名。
  2. run (或者r) 从头运行程序一直到断点。没有断点会一直运行结束,或者直到遇到异常。可以r < a.in 重定向输入输出。
  3. continue (或者c) 运行程序一直到下一个断点。
  4. next/step (或者n/s) 单步调试,next不会在行内进入函数体,step则会跳入函数体。 参数为跳多行。
  5. until (或者 u) 直接跳出当前循环。(但是还是会被断点卡住)。参数为跳到指定行。
  6. finish 直接运行到函数返回
  7. call 运行某函数,参数为函数和参数,如 call foo(2, "hello")
  8. 回车键 重复上个指令

断点

  1. break n (或者b) 在第n行打断点
  2. b main.c:n 指定文件打断点; b main 指定函数入口处打断点; b main:label 指定函数的标签处打断点
  3. b n if i == 5 满足条件打断点,对循环尤其有效。
  4. info b 查看断点号和信息,或者 i b
  5. delete no (或者d) 删除对应编号的断点; d breakpoints 删除所有断点
  6. clear lineno 删除对应行的断点
  7. disable/enable no 屏蔽/使用对应编号的断点

查看代码和变量

  1. list (或者l) 列出10行源文件。每次从上次结束的地方开始列。
  2. list lineno 列出某行的前后源码;l main 列出某函数的源码
  3. print exp (或者p) 打印任意变量、表达式、函数、字符串、数组的值
  4. display exp 每次单步完打印该表达式
  5. watch exp 如果该表达式值改变了,打印并停止程序
  6. whatis 查询变量,函数的类型
  7. info (或者i) 查询信息 i locals 所有变量的值 i args 所有参数的值 i function 函数信息 i frame 栈信息
    i program 程序信息 i threads 线程信息

堆栈

  1. bt 查看栈信息; bt n 栈顶n层;bt -n 栈底n层

    1
    2
    3
    
    (gdb) bt
    #0  foot () at t.c:7
    #1  0x0000000000400525 in main () at t.c:15
    
  2. frame (或者f)查看帧信息。上面一个#号(一层)是一帧。
    frame n 查看第n帧 ;up n 上面n帧; down n 下面n帧;frame addr 查看某地址的帧

  3. info frame n/addr 帧详细信息
    ip是下条命令的地址(pc),bp是栈底的地址,sp是栈顶的地址。
    64位机的帧信息:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    
    (gdb) i f 0
     // 帧地址
     Stack frame at 0x7fffffffe560:
     // rip:帧PC      帧函数名       saved rip:caller帧PC
     rip = 0x4004fd in foot (t.c:7); saved rip 0x400525
     // caller帧地址
     called by frame at 0x7fffffffe570
     source language c.
     // 帧参数地址
     Arglist at 0x7fffffffe550, args: 
     // 帧局部变量地址,caller的栈顶地址
     Locals at 0x7fffffffe550, Previous frame sp is 0x7fffffffe560
     // 帧寄存器列表
     Saved registers:
     rbp at 0x7fffffffe550, rip at 0x7fffffffe558
    

    32位机的帧信息:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    
    (gdb) i f
     Stack level 0, frame at 0xbffff630:
     // eip:帧PC      帧函数名       saved eip:caller帧PC
     eip = 0x80483e4 in main (a.c:8); saved eip = 0xb7e31637
     source language c.
     // 帧参数地址
     Arglist at 0xbffff628, args: 
     // 帧局部变量地址,caller的栈顶地址
     Locals at 0xbffff628, Previous frame sp is 0xbffff630
     // 帧寄存器列表
     Saved registers:
     ebp at 0xbffff628, eip at 0xbffff62c
    

内存

100 GDB Tips

gdb中使用x命令来打印内存的值,格式为x/nfu addr

含义为以f格式打印从addr开始的n个长度单元为u的内存值。参数具体含义如下:
a)n:输出单元的个数。
b)f:是输出格式。比如x是以16进制形式输出,o是以8进制形式输出,等等。
c)u:标明一个单元的长度。b是一个bytesh是两个bytes(halfword),w是四个bytes(word),g是八个bytes(giant word)。

比如对一个字符串arr查看内存:(这个数组越界踩掉了总共10 char的内容,每个char在32位机上是32bit)

1
2
3
4
5
6
7
8
9
(gdb) p arr
$4 = "\000\001\002\003"
// 显示11个byte,十六进制显示
(gdb) x/11xb arr
0x7fffffffe540:	0x00	0x01	0x02	0x03	0x04	0x05	0x06	0x07
0x7fffffffe548:	0x08	0x09	0x40
// 显示3个4-byte,十六进制显示
(gdb) x/3xw arr
0x7fffffffe540:	0x03020100	0x07060504	0x00400908

信号

这部分的详细内容在GDB Online Docs-5.4 Signals中。你可以自由地对程序进行发送信号(signal)、捕获信号(catch)或者处理信号(handle)。

线程

除了上文的查看线程信息之外,还可以查看详细的线程信息,参考GDB Online Docs-4 Threads,或者查看fork的情况,参考GDB Online Docs-5Forks