• 隐藏侧边栏
  • 展开分类目录
  • 关注微信公众号
  • 我的GitHub
  • QQ:1753970025
Chen Jiehua

gdb调试入门笔记 

面对一个卡住的进程我们要怎么定位是哪里卡住了,当服务异常退出了我们要怎么从coredump分析原因,没有IDE我又要如何来做断点调试?对于服务器开发而言,诸多摸不着头脑的问题都可以通过gdb这把利器来解决。

前提要求

对于C/C++程序,在make的时候需要指定 -g 参数,cmake的话则可以指定 -D CMAKE_BUILD_TYPE=Debug,这样编译出来的对象才会保留调试信息,也才能够是用gdb进行调试。

那对于已经编译好的可执行文件或动态链接库,我们怎么才能判断它是否带有调试信息呢?

我们先用同一份代码分别编译两次:

$g++ -o demo1 main.cpp
$g++ -g -o demo2 main.cpp
  • gdb:
$gdb demo1
Reading symbols from demo1...(no debugging symbols found)...done.
$gdb demo2
Reading symbols from demo...done.
  • readelf: -S 可以看每一段的头部
$readelf -S demo1 | grep debug
$readelf -S demo2 | grep debug
  [27] .debug_aranges    PROGBITS         0000000000000000  0000306c
  [28] .debug_info       PROGBITS         0000000000000000  0000309c
  [29] .debug_abbrev     PROGBITS         0000000000000000  00005c8a
  [30] .debug_line       PROGBITS         0000000000000000  000062a8
  [31] .debug_str        PROGBITS         0000000000000000  000066a8
  • file
$file demo1
demo1: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=76644e5f74dc835b10c85fb5a6654e5084d9e66c, not stripped
$file demo2
demo2: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=da336f89cd602173b84a3205bd7f88f614ee5323, with debug_info, not stripped

常用命令

开始调试

通过gdb <executable>启动调试:

$gdb demo
// 可以先设置一些断点和变量信息
(gdb) ...  
// 开始执行
(gdb) run
// 如果需要带参数,直接在跟在run后面
(gdb) run <args>
// 也可以用set args 设置参数
(gdb) set args <args>

对于正在运行的程序,可以根据pid进行调试:

$nohup python mytest.py &
[1] 2959

// 启动gdb并开始调试
$gdb python 2959

// 或者使用 attach 命令
$gdb
(gdb) attach 2959

设置断点

先写段简单的代码main.cpp,编译后生成可执行文件demo

#include <iostream>

using namespace std;

int main(){
    int a = 99, b = 2;
    cout << "a = " << a << ", b = " << b << endl;
    int c;
    c = a / b;
    cout << "c = " << c << endl;
}

启动gdb:

$gdb demo
......
Reading symbols from demo...done.
(gdb)

查看代码(list,缩写 l):

(gdb) list
1       #include <iostream>
2
3       using namespace std;
4
5       int main(){
6           int a = 99, b = 2;
7           cout << "a = " << a << ", b = " << b << endl;
8           int c;
9           c = a / b;
10          cout << "c = " << c << endl;

设置断点(break,缩写 b):

// 可以直接指定行号,或者 <文件名:行号>
(gdb) break 7
Breakpoint 1 at 0x118b: file main.cpp, line 7.
(gdb) break 10
Breakpoint 2 at 0x11ec: file main.cpp, line 10.

查看断点(info,缩写 i):

(gdb) info breakpoints
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x000000000000118b in main() at main.cpp:7
2       breakpoint     keep y   0x00000000000011ec in main() at main.cpp:10

开始运行(run),进程在断点的位置停住:

(gdb) run
Starting program: /home/jachua/learncpp/test/demo

Breakpoint 1, main () at main.cpp:7
7           cout << "a = " << a << ", b = " << b << endl;

打印相关的变量(print,缩写 p):

(gdb) print a
$1 = 99
(gdb) print b
$2 = 2

也可以使用display,以后每次断点都会打印出来:

(gdb) display a
1: a = 99
(gdb) display b
2: b = 2
(gdb) info display
Auto-display expressions now in effect:
Num Enb Expression
1:   y  a
2:   y  b

进行单步调试(next,缩写 n),也可以指定执行到某一行(until,缩写 u),或者单步进入某个函数(step,缩写 s):

(gdb) n
a = 99, b = 2
9           c = a / b;
1: a = 99
2: b = 2

// 进入到我们之前打的第二个断点
(gdb) n

Breakpoint 2, main () at main.cpp:10
10          cout << "c = " << c << endl;
1: a = 99
2: b = 2

让程序继续运行,直到断点(continue,缩写 c):

// 没有断点了,所以进程正常退出
(gdb) continue
Continuing.
[Inferior 1 (process 3092) exited normally]

删除其中一个断点后重新开始执行(delete):

(gdb) delete 2
(gdb) info breakpoints
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x000055555555518b in main() at main.cpp:7
        breakpoint already hit 1 time
(gdb) run
Starting program: /home/jachua/learncpp/test/demo

Breakpoint 1, main () at main.cpp:7
7           cout << "a = " << a << ", b = " << b << endl;
1: a = 99
2: b = 2

让进程运行到指定位置后停住(advance):

(gdb) advance 10
a = 99, b = 2
main () at main.cpp:10
10          cout << "c = " << c << endl;
1: a = 99
2: b = 2

临时屏蔽断点(disable、enable):

(gdb) disable
(gdb) run

// 进程直接运行到结尾
Starting program: /home/jachua/learncpp/test/demo
a = 99, b = 2
c = 49
[Inferior 1 (process 3130) exited normally]

在断点的时候,也可以修改变量(set var):

(gdb) set var b=3
(gdb) continue
Continuing.
a = 99, b = 3

Breakpoint 2, main () at main.cpp:10
10          cout << "c = " << c << endl;
(gdb) print c
$1 = 33

调试Coredump

首先检查一下系统是否正常产生core文件:

$ulimit -a
-t: cpu time (seconds)              unlimited
-f: file size (blocks)              unlimited
-d: data seg size (kbytes)          unlimited
-s: stack size (kbytes)             8192
-c: core file size (blocks)         0
-m: resident set size (kbytes)      unlimited
-u: processes                       31797
-n: file descriptors                1024
-l: locked-in-memory size (kbytes)  65536
-v: address space (kbytes)          unlimited
-x: file locks                      unlimited
-i: pending signals                 31797
-q: bytes in POSIX msg queues       819200
-e: max nice                        0
-r: max rt priority                 0
-N 15:                              unlimited

// 修改core文件限制
$ulimit -c unlimited

等进程异常退出后就可以对生成的core文件进行调试了:

$gdb <executable> <core file>

如果想要自定义coredump的默认文件名,可以修改 /proc/sys/kernal/core_pattern

$vim /proc/sys/kernal/core_pattern
/data/coredump/core.%e.%p

除了修改coredump的文件名,core_pattern 也支持以管道(pipe)的形式重定向到另一个程序,这样子就可以实现更多功能了(比如将coredump采集到其它机器、进行报警通知等):

$vim /proc/sys/kernal/core_pattern
// 注意这里必须以 | 开头,紧跟着另一个程序的路径
|/path/to/program core.%e.%p

查看调用栈

在上面的调试中,我们将变量b改为0,重新编译后运行:

$./demo
a = 99, b = 0
[1]    3246 floating point exception  ./demo

可以看到进程异常退出,并生成了core文件:

$gdb demo core
Reading symbols from demo...done.
[New LWP 3256]
Core was generated by `./demo'.
Program terminated with signal SIGFPE, Arithmetic exception.
#0  0x00005587261331e6 in main () at main.cpp:9
9           c = a / b;
(gdb) bt
#0  0x00005587261331e6 in main () at main.cpp:9

(gdb) print a
$1 = 99
(gdb) print b
$2 = 0
码字很辛苦,转载请注明来自ChenJiehua《gdb调试入门笔记》

评论