美文网首页
gdb的调试方法简介

gdb的调试方法简介

作者: Jamza | 来源:发表于2021-07-26 18:09 被阅读0次

    使用 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,假设运行的程序 PID 为 20289,则使用 gdb 进行调试

    $ gdb
    (gdb) attach 20829
    

    或者,简单直接调用

    $ gdb att 20289
    

    也可以直接使用 gdb program pid 的形式,比如

    $ gdb hello 20829
    

    单步调试

    单步执行

    (gdb) n
    

    单步执行,进入函数

    (gdb) s
    

    跳出函数

    (gdb) finish
    

    执行跳至指定行号

    (gdb) until [num]
    

    查看代码

    查看当前代码的运行位置

    (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 
    

    上述命令默认每次显示 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
    

    执行 shell 命令

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

    (gdb) shell ls
    a.out test.c
    

    断点

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

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

    查看断点

    (gdb) info b
    Num     Type           Disp Enb Address            What
    4       breakpoint     keep y   0x000000000040053f in main at test.c:6
    

    设置行号断点

    (gdb) break test.c:8 
    

    设置 C 函数断点

    (gdb) break func1
    

    设置 C++ 函数断点

    (gdb) break TestClass::testFunc(int) 
    

    设置临时断点,临时断点只会断住一次,然后断点会自动被移除掉

    (gdb) tbreak 8
    

    设置条件断点

    b 11 if i == 3
    

    修改条件断点的条件,假设断点的号为 4

    condition 4 i == 0  
    

    去使能断点

    (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) save breakpoints file-name-to-save
    

    恢复保存的断点

    (gdb) source file-name-to-save
    

    堆栈

    查看调用栈

    (gdb) bt
    #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) 
    

    选择栈帧,可以简写为 f 2

    (gdb) frame 2
    #2  0x8048414 in main (argc=1, argv=0xbffffaf4) at test.c:19
    19        x = func1(x);
    (gdb)
    

    向上或者向下切换函数堆栈

    (gdb) up 2
    (gdb) down 2
    

    查看栈帧

    (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
    

    变量

    最常见的就是使用 print 打印变量内容,其中 print 可以简写为 p

    (gdb) p a
    

    可以在变量名之前加上函数名来区分不同的同名变量

    (gdb) p 'main'::b
    

    可以按照特定的格式打印变量

    • x 按照十六进制格式打印变量
    • d 按照十进制格式打印变量
    • o 按照八进制格式打印变量
    • t 按照二进制格式打印变量
    • c 按照字符格式打印变量
    • f 按照浮点数格式打印变量
    (gdb) p/x mask
    $16 = 0x1
    

    显示动态数组的内容,比如动态数组内存申请为 int *array = (int *) malloc (len * sizeof (int));

    (gdb) p *array@len
    

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

    (gdb) display e
    

    查看哪些变量设置了自动显示

    (gdb) info display
    

    清除自动显示变量

    (gdb) del display [num]
    

    去使能自动显示变量

    (gdb) disable display [num]
    

    使用 gdb 的高阶调试方法

    查看内存

    查看内存的命令格式为 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) 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
    

    查看汇编代码

    (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.
    

    信号

    gdb 可以在调试程序的时候处理任何一种信号,即设置 gdb 需要处理哪种信号,并设置在 gdb 接收到信号后,立即停止程序,忽略信号等操作。

    设置信号的格式为 handle <signal> <keyword>

    keyword 可以为

    • nostop 接收到信号后,gdb 不停止程序,但是会打印消息,通知用户收到了某个信号
    • stop 接收到信号后,gdb 停止程序
    • noignore 接收到信号后,gdb 不处理信号,将其交给调试程序处理
    • ignore 接收到信号后,gdb 不让调试程序处理这个信号

    多线程调试

    查看当前进程的所有线程

    (gdb) info threads
    

    切换线程栈

    (gdb) thread [线程号]
    

    打印所有的线程堆栈

    (gdb) thread apply all bt
    

    打印指定线程堆栈

    (gdb) thread apply 5 bt   # 5为线程id
    

    用 gdb 调试多线程程序时,一旦程序断住,所有的线程都处于暂停状态。此时当你调试其中一个线程时(比如执行“step”,“next”命令),所有的线程都会同时执行,有时候我们希望执行流一直在某个线程执行,而不是切换到其他线程。

    我们可以使用命令 set scheduler-locking on/off/step

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

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

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

    相关文章

      网友评论

          本文标题:gdb的调试方法简介

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