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
评论