zoukankan      html  css  js  c++  java
  • GDB高级一点的命令

    GDB 是我们平时调试 c/c++程序的利器, 查起复杂的 bug 问题,比打印大法要好的多,但是也不得不说, gdb 在默认情况下用起来并不是很好用,最近学习到几个高级点的技巧,分享下:

    一 美化打印

    先上个例子:

    #include <stdio.h>

    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 <stdio.h>
      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 <stdio.h>
      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 <printf@plt>

    9     printf("print a test str ");
       0x0000000000400668 <+43>: mov    $0x40072d,%edi
       0x000000000040066d <+48>: callq  0x400530 <puts@plt>

    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 <puts@plt>

    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 来测试下这个地址是否合法,这个地址不是栈的地址,也不是我们申请的堆地址,直接写,就引起的段错误.

    七 诗词欣赏

    滚滚长江东逝水,浪花淘尽英雄。

    出自明代[杨慎]

    滚滚长江东逝水,浪花淘尽英雄。
    是非成败转头空。青山依旧在,几度夕阳红。
    白发渔樵江渚上,惯看秋月春风。
    一壶浊酒喜相逢。古今多少事,都付笑谈中。


    作者:明翼(XGogo)
    -------------
    公众号:TSparks
    微信:shinelife
    扫描关注我的微信公众号感谢
    -------------

  • 相关阅读:
    实验一 命令解释程序的编写
    试验二
    实验一 命令解释程序的编写(重交)
    Sqlserver数据库帮助类(EFTools)
    js验证
    sqlserver中从日期字段取得月份
    IIS不可用或者有问题解决方法
    professional email address collections
    从psd文件到html
    空白符对HTML结构的影响与解决方案
  • 原文地址:https://www.cnblogs.com/seaspring/p/14644608.html
Copyright © 2011-2022 走看看