简介
GDB,GNU Debugger,特性如下:
- GDB具备各种调试功效,可对计算机程序的运行进行追踪、警告。使用者可以监控及修改程序内部变量的值,甚至可在程序的正常运行之外调用函数。
- GDB支持多数处理器架构
- 持续开发中
- 支持远程调试
- 支持内核调试,KGDB
从事嵌入式软件开发两年来,主要在以下几方面使用GDB:
- 查看、修改运行时变量;
- 多线程调试,查看当前线程运行状态(以确定当前线程是不是因为等锁等原因挂起);
- 查看coredump文件;
- 碰到难缠的内存非法改写问题,用GDB的断点、物理watch功能查看内存变化以定位改写者;
引用公司一个技术牛人的话:在大型的项目中,使用GDB的单步调试、软件watch是不现实的,因为会运行得实在太慢。
命令小记:
1 | linux提示符 |
启动GDB
GCC选项
想用GDB调试,则在GCC编译的时候要加上-g选项。
启动GDB
启动GDB的方法主要有以下几种:
gdb
gdb executable_file
gdb executable_file corefile
:查看coredump文件信息,定位coredump产生原因、触发源。gdb attach pid
:调度运行时的进程或线程,同gdb -p pid
。
善用help
在GDB提示符下输入help
或help 命令
,能够查看命令的帮助说明。
1 | (gdb) help |
查看调用栈
写一个简单的例子(仅为样例,并不严谨):
1 |
|
编译并运行起来,注意gcc的-g选项,这里使用&让程序运行到后台,[1] 8043指刚刚这个程序运行时的进程号,也可用ps
命令查看。
1 | sunnogo@a3e420:~/test/gdb$ gcc -o prt_mod_var prt_mod_var.c -g -Wall -lpthread |
接下来使用gdb -p 8043
连入正在运行的进程中。还不明白为什么我的计算机中要求使用root权限才能让GDB attach到对应进程。
1 | sunnogo@a3e420:~/test/gdb$ gdb -p 8043 |
重新sudo gdb -p pid
进入进程。
- 使用
bt
查看当前调用栈信息(call stack,即函数调用层次信息),当前进程的是由main() -> sleep() -> nanosleep() -> __kernel_vsyscall()一层一层调入。注意“#数字”,在GDB中这叫stack frames,或直接称为frame,运行栈由一个或多个连续的frame组成,数字越小代表调用层次越深。 - 使用
bt full
查看详细调用栈信息,会把各个frame的入参和局部变量信息显示出来。这里bt是backtrace的缩写,GDB的全命令经常有其简短的写法。
注意:GDB中,按回车默认是执行上一次命令。
先MARK下面的“No symbol table info available.”
1 | sunnogo@a3e420:~/test/gdb$ sudo gdb -p 8043 |
- 使用
frame n
进入“#n”的frame。默认显示当前函数名、函数入参、当前运行处所在源文件的代码行位置,并显示当前行代码。 - 使用
info
命令查看frame详细信息,info命令不是全命令,后面还有子命令。info有很多子命令,除本frame外,还可以查看本进程信息、系统信息,这里仅仅是冰山一角。info frame
显示当前frame信息info args
显示入参信息info local
显示局部变量信息
1 | (gdb) frame 3 |
查看、修改变量
p var
查看变量信息,p是print的缩写。
p var
p *(指针类型)地址
p *结构体指针
p 数组名
1 | # 打印变量 |
print
不仅可以用来查看变量,还可用于设置变量。print var=value
。
设置变量值的命令还有set
,set var=value
。
1 | # set int |
查看内存
examine
查看内存,缩写是x
。命令格式:
1 | x/<n/f/u> <addr> |
n、f、u是可选参数,说明如下:
1 | (gdb) help x |
n
表示要打印的多少个单位的内存,默认是1,单位由u
定义;f
表示打印的格式,格式有:- o,octal,八进制;
- x,hex,十六进制;
- d,decimal,十进制;
- u,unsigned decimal,无符号十进制;
- t,binary,二进制;
- f,float;
- a,address;
- i,instruction,指令;
- c,char,字符;
- s,string,字符串。
u
定义单位,b表示1字节,h表示2字节,w表示4字节,g表示8字节。
1 | # 当前CPU是intel i3,小端 |
查看线程信息
有两种方法可以进入线程调试:
- 设置线程名,用ps查看母进程的线程信息,获取tid,再启动GDB进入;
- 直接启动GDB调试母进程,
info thread
查看所有线程信息,获取到想要的线程的GDB内部编号n,thread n
进入线程的调用栈。
直接获取、调试线程
上面样例中创建5条线程,并使用prctl函数为每条线程命名为”test-n”。
这样可以通过ps -eL | grep test(或者test进程的pid)
来查看刚创建的线程的tid。然后gdb -p tid
进入线程调度。这里进入编号为4的线程。
1 | sunnogo@a3e420:~/test/gdb$ gcc -o test test.c -g -Wall -lpthread |
间接获取、调试线程
注意和上一种方法的对比,相比起来,第一种方法要方便得多。也从侧面看出为每个线程命名的重要性。
1 | sunnogo@a3e420:~/test/gdb$ |
查看所有线程堆栈
使用 thread apply all bt full
,查看所有线程的堆栈,如果线程多,可能会产生短暂刷屏。
gdb中调用调用函数
call func_name(param1, param2, ...)
,目前还没有明白如果参数是结构体要怎么整。注意,只能在进程上下文中才能使用,coredump中无法使用。
gdb中申请内存
p malloc(size)
,结果会返回一个指针,即可正常使用这个指针。注意,只能在进程上下文中才能使用,coredump中无法使用。如下例:
1 | (gdb) p malloc(4) |
查看寄存器信息
to-do
GDB反汇编
to-do
断点设置
to-do
内存监控
to-do
GCC选项对GDB的影响
GCC -g选项的影响
注意上面的,如果gcc编译的时候不加-g
选项,那么frame 3也会显示“No symbol table info available.”,无符号表信息可用,全局变量g_str也打不出来。
1 | (gdb) bt full |