GDB调试的高级技巧
GDB是我们平时调试c/c++程序的利器, 查起复杂的bug问题,比打印大法要好得多,但是也不得不说, gdb在默认情况下用起来并不是很好用,最近学习到几个高级点的技巧,分享下: 一 美化打印
先上个例子: #include typedef struct { int i ; int j; char * str; int array[10]; } test_type_t; int main (int argc, char * argv[]) { test_type_t t = {1,2,"test a str" ,{1,2,3,4,5,6,7,8,9,10}}; printf("i:%d,j:%d ", t.i,t.j); return 0; }
gdb调试: (gdb) n 14 printf("i:%d,j:%d ", t.i,t.j); (gdb) p t $1 = {i = 1, j = 2, str = 0x400710 "test a str", array = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}} (gdb) set print pretty (gdb) p t $2 = { i = 1, j = 2, str = 0x400710 "test a str", array = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10} } (gdb)
set print pretty 通过这个命令可以美化gdb的打印,特别是复杂的数据结构,比较推荐. set print array-indexes on 打印数组的下标
这些命令如果记不住怎么办, 类似linux启动,会执行些初始化脚本,gdb也一样,脚本名为: .gdbinit 将刚才的两个命令加入进去,就不用每次执行这个命令调试.二 动态打印
一般情况下,我们用gdb调试的时候,先在产生问题的代码的调用逻辑链上设置端点,调试,有时候为了更方便调试,我们会在这些关键地方加上打印语句. 有时候加的打印代码不够全,来回要反复几次. 每次加好打印代码后,需要编译程序,来回折腾几次,确实比较麻烦.
GDB提供动态打印功能,可以在不修改代码情况下,动态插入打印语句,说的比较抽象,举例说明: 2 #include 3 4 typedef struct { 5 int i ; 6 int j; 7 char * str; 8 int array[10]; 9 } test_type_t; 10 11 int main (int argc, char * argv[]) 12 { 13 test_type_t t = {1,2,"test a str" ,{1,2,3,4,5,6,7,8,9,10}}; 14 printf("i:%d,j:%d ", t.i,t.j); 15 16 for (int k = 0; k <= 10; k++ ) { 17 t.array[k] += 10; 18 } 19 printf("index 0 :%d" ,t.array[0]); 20 return 0; 21 }
上面位置 k <= 10; 这一行越界,调试下:(gdb) dprintf 17,"k:%d,array[k]:%d ",k,t.array[k] Dprintf 2 at 0x40067f: file 1.c, line 17. (gdb) r Starting program: /test/a.out Breakpoint 1, main (argc=1, argv=0x7fffffffe648) at 1.c:13 13 test_type_t t = {1,2,"test a str" ,{1,2,3,4,5,6,7,8,9,10}}; Missing separate debuginfos, use: debuginfo-install glibc-2.17-196.el7_4.2.x86_64 libgcc-4.8.5-16.el7_4.2.x86_64 libstdc++-4.8.5-16.el7_4.2.x86_64 (gdb) c Continuing. i:1,j:2 k:0,array[k]:1 k:1,array[k]:2 k:2,array[k]:3 k:3,array[k]:4 k:4,array[k]:5 k:5,array[k]:6 k:6,array[k]:7 k:7,array[k]:8 k:8,array[k]:9 k:9,array[k]:10 k:10,array[k]:0 index 0 :11[Inferior 1 (process 150109) exited normally] (gdb) info b Num Type Disp Enb Address What 1 breakpoint keep y 0x00000000004005fc in main(int, char**) at 1.c:13 breakpoint already hit 1 time 2 dprintf keep y 0x000000000040067f in main(int, char**) at 1.c:17 breakpoint already hit 11 times printf "k:%d,array[k]:%d ",k,t.array[k]
我们通过 dprintf 17,"k:%d,array[k]:%d ",k,t.array[k] 在17行动态插入打印语句,然后执行的时候会直接打印出来,17 为行号,后面的语法类似printf.
动态的语句实际是一个断点(通过info b 可以看到),只不过这个断点会自动的恢复,这种动态语句是否可以继续在上面设置端点的,答案是不行的,通过next执行的时候也执行不到这个语句,只是运行到这里面的时候,可以自动打印变量. 三 断点信息的保存和加载
调试大型程序的时候,往往会设置很多断点,如果需要多次调试,来回敲这些断点信息,也是很烦的,可以通过 save breakpoints filename 的方法,将断点设置信息保存到filename 文件中,GDB启动后,在通过source filename 方式加载:(gdb) save breakpoints break.bp Saved to file "break.bp". (gdb) quit [root@localhost test]# ls 1.c a.out break.bp [root@localhost test]# cat break.bp break main dprintf /test/1.c:17,"k:%d,array[k]:%d ",k,t.array[k]
加载: [root@localhost test]# gdb ./a.out Reading symbols from /test/a.out...done. (gdb) source break.bp Breakpoint 1 at 0x4005fc: file 1.c, line 13. Dprintf 2 at 0x40067f: file 1.c, line 17. (gdb) info b Num Type Disp Enb Address What 1 breakpoint keep y 0x00000000004005fc in main(int, char**) at 1.c:13 2 dprintf keep y 0x000000000040067f in main(int, char**) at 1.c:17 printf "k:%d,array[k]:%d ",k,t.array[k]
是不是很方便那. 四 宏打印
如下示例代码: 1 #include 2 3 #define MAX(x,y) (x)>=(y)?(x):(y) 4 5 int main(int argc , char * argv[]) 6 { 7 char * p = (char*)0x12345; 8 printf("max(3,5):%d ",MAX(3,5)); 9 printf("print a test str "); 10 *p = 1; 11 printf("end "); 12 }
如果通过 -g 选项编译是无法查看宏定义的,可以通过-g3 编译,就可以轻松查看宏定义和宏展开了,如下:[root@localhost test]# gdb ./2 Reading symbols from /test/2...done. (gdb) b main Breakpoint 1 at 0x40064c: file 2.c, line 7. (gdb) r Starting program: /test/./2 Breakpoint 1, main (argc=1, argv=0x7fffffffe648) at 2.c:7 7 char * p = (char*)0x12345; Missing separate debuginfos, use: debuginfo-install glibc-2.17-196.el7_4.2.x86_64 libgcc-4.8.5-16.el7_4.2.x86_64 libstdc++-4.8.5-16.el7_4.2.x86_64 (gdb) n 8 printf("max(3,5):%d ",MAX(3,5)); (gdb) p MAX(3,5) $1 = 5 (gdb) macro expand MAX(3,5) expands to: (3)>=(5)?(3):(5)
可以直接通过p命令打印宏,也可以进行 macro expand MAX(3,5) 宏展开,调试宏的时候比较有用.五 gdb调试界面
gdb的调试一般是通过n执行下一条命令,然后通过l查看当前的代码,不够直观,gdb其实也可以类似IDE那样,显示一行行代码执行,简单的直接输入 layout 切换布局:
再输入n的时候,箭头自动下移一行.
更棒的是可以同时显示源码和汇编,输入 layout split
六 Debug汇编
有时候,比较麻烦的bug,可以需要反编译为汇编(disassemble /m main),然后调试, 还是如上的例子程序,调试如下,汇编模式常需要看寄存器的值,输入 layout regs 可以同时展示汇编和寄存器的值:
这个模式下好处,可以通过n命令执行一条条看汇编指令.
我还是更喜欢用以下的方式调试: gdb) disassemble /m main Dump of assembler code for function main(int, char**): 6 { 0x000000000040063d <+0>: push %rbp 0x000000000040063e <+1>: mov %rsp,%rbp 0x0000000000400641 <+4>: sub $0x20,%rsp 0x0000000000400645 <+8>: mov %edi,-0x14(%rbp) 0x0000000000400648 <+11>: mov %rsi,-0x20(%rbp) 7 char * p = (char*)0x12345; => 0x000000000040064c <+15>: movq $0x12345,-0x8(%rbp) 8 printf("max(3,5):%d ",MAX(3,5)); 0x0000000000400654 <+23>: mov $0x5,%esi 0x0000000000400659 <+28>: mov $0x400720,%edi 0x000000000040065e <+33>: mov $0x0,%eax 0x0000000000400663 <+38>: callq 0x400510 9 printf("print a test str "); 0x0000000000400668 <+43>: mov $0x40072d,%edi 0x000000000040066d <+48>: callq 0x400530 10 *p = 1; 0x0000000000400672 <+53>: mov -0x8(%rbp),%rax 0x0000000000400676 <+57>: movb $0x1,(%rax) 11 printf("end "); 0x0000000000400679 <+60>: mov $0x40073e,%edi 0x000000000040067e <+65>: callq 0x400530 12 } 0x0000000000400683 <+70>: mov $0x0,%eax 0x0000000000400688 <+75>: leaveq 0x0000000000400689 <+76>: retq End of assembler dump. (gdb) ni 8 printf("max(3,5):%d ",MAX(3,5)); (gdb) ni 0x0000000000400659 8 printf("max(3,5):%d ",MAX(3,5)); (gdb) ni 0x000000000040065e 8 printf("max(3,5):%d ",MAX(3,5)); (gdb) ni 0x0000000000400663 8 printf("max(3,5):%d ",MAX(3,5)); (gdb) ni max(3,5):5 9 printf("print a test str "); (gdb) ni 0x000000000040066d 9 printf("print a test str "); (gdb) ni print a test str 10 *p = 1; (gdb) info register
通过 disassemble /m main 将源码和汇编一起显示,ni 执行下一条汇编指令. info register 来显示汇编信息,core的位置的指令为: 0x0000000000400676 <+57>: movb $0x1,(%rax) 将1赋值给rax寄存器保存的地址, 我们可以看下rax寄存器,p $rax 直接打印,然后通过x /x 0x12345 来测试下这个地址是否合法,这个地址不是栈的地址,也不是我们申请的堆地址,直接写,就引起的段错误.七 诗词欣赏# 滚滚长江东逝水,浪花淘尽英雄。 出自明代[杨慎] 滚滚长江东逝水,浪花淘尽英雄。 是非成败转头空。青山依旧在,几度夕阳红。 白发渔樵江渚上,惯看秋月春风。 一壶浊酒喜相逢。古今多少事,都付笑谈中。