美文网首页
GDB 常用命令

GDB 常用命令

作者: wayyyy | 来源:发表于2019-02-10 21:40 被阅读0次

    官方文档

    http://sourceware.org/gdb/current/onlinedocs/gdb/

    确定文件是否可以gdb调试

    gdb 直接调试查看
    • 没有调试信息


      not gdb.png
    • readelf 查看段信息

    • file命令查看strip
      如果最后是stripped,则说明该文件的符号表信息和调试信息已被去除,不能使用gdb调试。但是not stripped的情况并不能说明能够被调试。

    使用GDB调试

    编译时使用-g编译选项
    $ gcc -g program.c -o programname
    
    在gdb中启动程序
    $ gdb programname 
    (gdb) run arg1 "arg2" ...
    
    在gdb中重启程序
    (gdb) kill
    Kill the program being debugged? (y or n) y
    (gdb) run
    
    退出gdb
    (gdb) quit
    The program is running. Exit anyway? (y or n) 
    
    调试已经运行的程序

    假设已经获取到的进程PID=20829

    $ gdb
    (gdb) attach 20829
    

    也可以直接使用gdb program pid,例如:

    gdb hello 20829
    
    当运行程序没有调试信息

    为了节省磁盘空间,已经运行的程序通常没有调试信息。但如果又不能停止当前程序重新启动调试,那怎么办呢?
    还有办法,那就是同样的代码,再编译出一个带调试信息的版本。然后使用和前面提到的方式操作。对于attach方式,在attach之前,使用file命令即可。

    $ gdb
    (gdb) file a.out
    
    单步调试
    • 单步执行
      (gdb) n
      
    • 单步进入函数
      (gdb) s
      
    until命令
    • 跳出函数

      (gdb) finish
      
    • 跳出循环
      until执行直到达到当前栈帧中当前行后的某一行(用于跳过循环、递归函数调用)

    • 跳到指定行

      until [num]
      

    查看变量

    最常见的使用便是使用print(可简写为p)打印变量内容。

    (gdb) p a
    

    当然有时候,多个函数或者多个文件会有同一个变量名,这个时候可以在前面加上函数名或者文件名来区分。

    (gdb) p 'main'::b
    
    • 按照特定格式打印变量
      • x 按十六进制格式显示变量。
      • d 按十进制格式显示变量。
      • u 按十六进制格式显示无符号整型。
      • o 按八进制格式显示变量。
      • t 按二进制格式显示变量。
      • a 按十六进制格式显示变量。
      • c 按字符格式显示变量。
      • f 按浮点数格式显示变量。
      (gdb) p/x mask
      $16 = 0x1
      
    动态数组

    int *array = (int *) malloc (len * sizeof (int));

    (gdb) p *array@len
    
    查看变量类型
    (gdb) ptype el->fired
    type = struct aeFiredEvent {
        int fd;
        int mask;
    } *
    
    自动显示变量

    我们希望程序断住时,就显示某个变量的值,可以在断点被断住时使用display命令

    (gdb) display e
    
    • 查看哪些变量设置了display
      info display
      
    • 清除display
      del display [num]
      
    • 去使能display enbale
      disable display [num]
      

    执行shell命令

    在gdb命令行界面可以执行外部的Shell命令

    (gdb) !ls
    a.out test.c
    

    或者:

    (gdb) shell ls
    a.out test.c
    

    查看当前代码运行位置
    (gdb) where
    #0  event_base_loop (base=0x602010, flags=0) at event.c:466
    #1  0x00007ffff7bbb5c7 in event_base_dispatch (event_base=0x602010) at event.c:405
    #2  0x0000000000400858 in main () at test.c:20
    

    查看源代码
    (gdb) l 
    
    • 设置源码一次列出行数
      l命令默认每次只显示10行。
      (gdb) set listsize 20
      (gdb) show listsize
      Number of source lines gdb will list by default is 20.
      
    • 列出指定行附近的源码
      (gdb) l main.cpp:8
      
    • 列出指定函数附近的源码
      (gdb) l printnum 
      
    • 列出指定行之间的源码
      (gdb) l 3,15
      
    • 列出指定文件的源码
      (gdb) l test.c:1
      

    指定源码路径

    在查看源码之前,首先要确保我们的程序能够关联到源码,一般来说,我们在自己的机器上加上-g参数编译完之后,使用gdb都能查看到源码,但是如果出现下面的情况呢?源码被移走。gdb调试就会提示找不到源码文件了,那么怎么办呢?可以使用dir命名指定源码路径

    (gdb) dir ./temp
    
    • 更换源码目录

    断点

    断点的设置原理:在程序中设置断点,就是先将该位置的原来的指令保存,然后向该位置写入int 3指令,当执行到int 3的时候,发生软中断,内核会给子进程发出SIGTRAP信号,当然这个信号会被转发给父进程。然后用保存的指令替换int 3,等待恢复运行。

    断点的实现原理:就是在指定的位置插入断点指令,当被调试的程序运行到断点的时候,产生SIGTRAP信号,该信号被gdb捕获并进行断点命中判定,当gdb判断出这次SIGTRAP是断点命中之后就会转入等待用户输入进行下一步处理,否则继续。

    • 设置行号断点
      (gdb) break test.c:8 
      
    • 设置函数断点
      • 设置C函数断点
        (gdb) break func1
        
      • 设置C++函数断点
        因为C++ 具有多态的特性。
        (gdb) break TestClass::testFunc(int) 
        
    • 设置临时断点
      临时断点只会断住一次,然后会被移除掉。
      (gdb) tbreak 8
      
    • 设置条件断点
      b 11 if i == 3
      
      • 修改条件
        假设上面的断点号为1。
        condition 1 i == 0  
        
    • 查看断点
      (gdb) info b
      Num     Type           Disp Enb Address            What
      4       breakpoint     keep y   0x000000000040053f in main at test.c:6
      
    • 使能断点
      (gdb) disable 2
      (gdb) info breakpoints
      Num Type           Disp Enb Address    What
      2   breakpoint     keep n   0x080483c3 in func2 at test.c:5
      3   breakpoint     keep y   0x080483da in func1 at test.c:10
      
    • 跳过断点
      假设在某个地方,我们知道可能出错,前面30次都没有问题,在这里我们设置了断点,但是我们想跳过前面30次,可以使用ignore命令,如下:第一个参数表示断点编号,第二个表示跳过次数。
      (gdb) ignore 2 5
      Will ignore next 5 crossings of breakpoint 2.
      

    堆栈

    首先理解函数与调用栈的关系,参见:关于函数调用浅析

    • 查看调用栈
      (gdb) backtrace
      #0  func2 (x=30) at test.c:5
      #1  0x80483e6 in func1 (a=30) at test.c:10
      #2  0x8048414 in main (argc=1, argv=0xbffffaf4) at test.c:19
      #3  0x40037f5c in __libc_start_main () from /lib/libc.so.6
      (gdb) 
      
    • 查看所有线程的调用栈
       #  thread apply all bt
      
    • 选择栈帧
      (gdb) frame 2
      #2  0x8048414 in main (argc=1, argv=0xbffffaf4) at test.c:19
      19        x = func1(x);
      (gdb)
      
    • 查看栈帧
      (gdb) info frame
      Stack level 2, frame at 0xbffffa8c:
       eip = 0x8048414 in main (test.c:19); saved eip 0x40037f5c
       called by frame at 0xbffffac8, caller of frame at 0xbffffa5c
       source language c.
       Arglist at 0xbffffa8c, args: argc=1, argv=0xbffffaf4
       Locals at 0xbffffa8c, Previous frame's sp is 0x0
       Saved registers:
        ebp at 0xbffffa8c, eip at 0xbffffa90
      
      (gdb) info locals
      x = 30
      s = 0x8048484 "Hello World!\n"
      
      (gdb) info args
      argc = 1
      argv = (char **) 0xbffffaf4
      

    观察点

    查看内存
    • 命令格式
      x/[n][f][u] [address]

      • n 表示显示内存长度,默认值为1
      • f 表示显示格式,如同上面打印变量定义
        • x 按十六进制格式显示变量。
        • d 按十进制格式显示变量。
        • u 按十六进制格式显示无符号整型。
        • o 按八进制格式显示变量。
        • t 按二进制格式显示变量。
        • a 按十六进制格式显示变量。
        • c 按字符格式显示变量。
        • f 按浮点数格式显示变量。
      • u 表示每次读取的字节数,默认是4bytes
        • b 表示单字节
        • h 表示双字节
        • w 表示四字节
        • g 表示八字节
    • 以字符串的形式查看

      char *s = "hello world";  // 源码
      
      (gdb) x/s s
      0x4005a0: "hello world"
      
    • 以字符的形式查看

      (gdb) x/c s
      0x4005a0: 104 'h'
      
    • 以二进制查看

      (gdb) x/t s
      0x4005a0: 01101000
      
    • 以八进制查看

      (gdb) x/x s
      0x4005a0: 0x68
      

    假设,需要把float变量e按照二进制的方式打印,并且打印单位时一字节。

    (gdb) x/4tb &e
    0x7fffffffdbd4:    00000000    00000000    00001000    01000001
    

    寄存器
    (gdb) info registers
    rax            0x4004f0 4195568
    rbx            0x0  0
    rcx            0x400510 4195600
    rdx            0x7fffffffe598   140737488348568
    rsi            0x7fffffffe588   140737488348552
    rdi            0x1  1
    rbp            0x7fffffffe4a0   0x7fffffffe4a0
    rsp            0x7fffffffe4a0   0x7fffffffe4a0
    r8             0x7ffff7dd6e80   140737351872128
    r9             0x0  0
    r10            0x7fffffffe2f0   140737488347888
    r11            0x7ffff7a3ca40   140737348094528
    r12            0x400400 4195328
    r13            0x7fffffffe580   140737488348544
    r14            0x0  0
    r15            0x0  0
    rip            0x400503 0x400503 <main+19>
    eflags         0x246    [ PF ZF IF ]
    cs             0x33 51
    ss             0x2b 43
    ds             0x0  0
    es             0x0  0
    fs             0x0  0
    gs             0x0  0
    
    • 实验1:探究x86参数传递

      #include <stdio.h>
      #include <stdlib.h>
      
      int v1 = 1;
      float v2 = 0.01;
      
      void func(int a, long b, short c, char d, long long e, float f, double g, int *h, float *i, char *j) 
      {
          printf("a: %d, b: %ld, c: %d, d: %c, e: %lld\n"
               "f: %.3e, g: %.3e\n"
               "h: %p, i: %p, j: %p\n", a, b, c, d, e, f, g, h, i, j);                                                     
      }
      int main()
      {
          func(100, 35000, 5, 'A', 123456789LL, 3.14, 2.99792458e8, &v1, &v2, "string");
          return 0;
      }
      
      (gdb) b *func
      (gdb) r
      (gdb) i r
      rax            0x41b1de784a000000 4733809291562057728
      rbx            0x0    0
      rcx            0x41   65
      rdx            0x5    5
      rsi            0x88b8 35000
      rdi            0x64   100
      rbp            0x7fffffffe490 0x7fffffffe490
      rsp            0x7fffffffe468 0x7fffffffe468
      r8             0x75bcd15  123456789
      r9             0x601034   6295604
      r10            0x7fffffffe2e0 140737488347872
      r11            0x7ffff7a3ca40 140737348094528
      r12            0x400440   4195392
      r13            0x7fffffffe570 140737488348528
      r14            0x0    0
      r15            0x0    0
      rip            0x400530   0x400530 <func>
      eflags         0x202  [ IF ]
      cs             0x33   51
      ss             0x2b   43
      ds             0x0    0
      es             0x0    0
      fs             0x0    0
      gs             0x0    0
      

      可以发现,开头的5个参数a,b,c, d,e分别保存到了rdi, rsi, rdx, rcx, 和 r8中。


    查看汇编代码
    (gdb) disassemble main
    Dump of assembler code for function main:
       0x00000000004004f0 <+0>: push   %rbp
       0x00000000004004f1 <+1>: mov    %rsp,%rbp
       0x00000000004004f4 <+4>: mov    %edi,-0x14(%rbp)
       0x00000000004004f7 <+7>: mov    %rsi,-0x20(%rbp)
       0x00000000004004fb <+11>:    movq   $0x4005a0,-0x8(%rbp)
    => 0x0000000000400503 <+19>:    pop    %rbp
       0x0000000000400504 <+20>:    retq   
    End of assembler dump.
    

    常用设置
    • print pretty

      • set print pretty on 打开该设置,结构体显示更好看。
      • set print pretty off
      • show print pretty
    • print elements

      • set print elements 300 更改打印字符串变量的长度
      • show print elements 查看设置的长度
    信号

    GDB可以在你调试程序的时候处理任何一种信号,你可以告诉GDB需要处理哪一种信号,你可以要求GDB收到你所指定的信号时,马上停住正在运行的程序,以供你进行调试。

    handle <signal> <keywords>
    
    • nostop
      当被调试的程序收到信号时,GDB不会停住程序的运行,但会打出消息告诉你收到这种信号。
    • stop
      当被调试的程序收到信号时,GDB会停住你的程序。
    • noignore
      当被调试的程序收到信号时,GDB不处理信号。这表示,GDB会把这个信号交给被调试程序会处理。
    • ignore
      当被调试的程序收到信号时,GDB不会让被调试程序来处理这个信号。
    结合core文件调试

    使用内核转储(core)的最大好处就是:它能保存问题发生时的状态,只要有问题发生时程序的可执行文件和内核转储,就可以知道进程当时的状态。

    gdb a.out xx.core
    
    • 启用内核转储功能

      $ ulimit -c
      0
      

      -c选项表示内核转储文件的大小限制,0表示内核转储未打开。按照以下命令开启:

      $ ulimit -c unlimited
      $ ulimit -c
      unlimited
      

      ulimit -c unlimited 设置core文件大小不限制,这样设置是一次性的,会话结束就恢复原样
      在用户的~/.bash_profile里加上ulimit -c unlimited来让用户开启内核转储功能
      /etc/profile里加上ulimit -c unlimited来让所有用户开启内核转储功能(?)

    • core文件目录和命名规则
      /proc/sys/kernel/core_pattern可以设置格式化的core文件保存位置和文件名。
      比如core-%e-%p-%t表示在当前目录生成"core-命令-pid-时间戳"为文件名的core文件。
      比如/cfg/core-%e-%p-%t表示在/cfg下生成"core-命令-pid-时间戳"为文件名的core文件

    • 参数列表

      符号 意义
      %p insert pid into filename 添加 pid
      %u insert current uid into filename 添加当前 uid
      %g insert current gid into filename 添加当前 gid
      %s insert signal that caused the coredump into the filename 添加导致产生 core 的信号
      %t insert UNIX time that the coredump occurred into filename 添加 core 文件生成时的 unix 时间戳
      %h insert hostname where the coredump happened into filename 添加主机名
      %e insert coredumping executable name into filename 添加命令名

    多线程调试

    查看当前进程的所有线程
    (gdb) info threads
    
    切换线程栈
    (gdb) thread [线程号]
    
    打印所有线程堆栈
    thread apply all bt
    
    调试时控制线程切换

    在调试多线程程序时,有时候我们希望执行流一直在某个线程执行,而不是切换到其他线程。

    那我们可以使用

    set scheduler-locking on/off/step
    
    set scheduler-locking on

    set scheduler-locking on 可以用来锁定当前线程,只观察这个线程的运行情况, 当锁定这个线程时, 其他线程就处于了暂停状态。
    也就是说你在当前线程执行 nextstepuntilfinishreturn 命令时,其他线程是不会运行的。

    set scheduler-locking off用于关闭锁定当前线程。

    set scheduler-locking step

    也是用来锁定当前线程,当且仅当使用 nextstep 命令做单步调试时会锁定当前线程。
    如果你使用 untilfinishreturn 等线程内调试命令,但是它们不是单步命令,所以其他线程还是有机会运行的。相比较 on 选项值,step 选项值给为单步调试提供了更加精细化的控制


    多进程调试

    gdb的调试默认是调试父进程的,但我们可以通过设置来选择调试哪个进程。

    (gdb) set follow-fork-mode parent/child
    

    如果选择了parent,这个时候就是进行gdb调试父进程。
    如果选择了child,这个时候就是进行gdb调试子进程。
    注意,在调试的过程中更改mode是没有用的,这种设置只对下一次fork后起作用。


    参考
    [1] http://www.unknownroad.com/rtfm/gdbtut/gdbtoc.html
    [2] http://www.cnblogs.com/pannengzhi/p/5203467.html
    [3] https://zhuanlan.zhihu.com/p/74897601

    相关文章

      网友评论

          本文标题:GDB 常用命令

          本文链接:https://www.haomeiwen.com/subject/jjinsqtx.html