跳转到内容
新建笔记

gdb

GDB(GNU 调试器)是一个强大的开源调试工具,用于调试 C、C++等程序。以下是一个简要的中文 GDB 教程,介绍基本的 GDB 命令和用法。

编译时加入调试信息

为了使用 GDB 进行调试,你需要在编译时包含调试信息。使用 -g 选项编译你的程序:

终端窗口
gcc -g -o your_program your_program.c

使用 GDB:

  1. 启动 GDB:

    在命令行中运行以下命令启动 GDB,并指定要调试的可执行文件:

    终端窗口
    # 当某些程序需要我们进行传参输入
    # gdb --args 参数
    # gdb之后,使用:set 参数
    # gdb之后,使用:r 参数
    # 当一个进程已经运行了,我们对它进行调试,可以如下两种命令
    gdb attach pid
    gdb --pid pid
    gdb your_program
    start # 程序停在第一行
    run # 遇到断点才停
    # 调到多少行
    line
    set p pretty # 结构体和数组显示规则更好看,每行一个
  2. 设置断点:

    在你想要暂停程序执行的地方设置断点。例如,在 main 函数的第 10 行设置断点:

    终端窗口
    break main:10 # b
    # 查看断点设置信息
    info breakpoints # 显示观察点信息
    clear
    disable xx
    enable xx
    break xx
    tb # 设置临时断点。对于一个循环来说,虽然断点在这,但只会执行一次,就不执行循环了
    rb # 对于带这个名字的函数都会进行标记,包括虚函数,或者命名不全的函数
    save breakpoints 文件名.txt # 保存断点信息
    source 文件名.txt # 还原断点信息
    #观察点
    watch <where>
    delete xx # d
    enable xx # ena
    disable xx
    info watch # 显示观察点信息
  3. 运行程序

    启动被调试的程序:

    终端窗口
    (gdb) run
    next # n, 单步跟踪,若未指定count值,默认执行单条语句,否则执行后续的count条语句。Next指令遇到函数调用时,不会进行该函数。
    step # s, 单步跟踪,若未指定count值,默认执行单条语句,否则执行后续的count条语句。Step指令遇到函数调用时,会进入该函数。
    continue # c, 恢复程序运行,直到程序结束或者遇到断点
    until # 运行程序直到退出当前循环
    finish # 运行程序,直到当前函数完成,函数返回时打印出函数堆栈地址、参数值以及返回值等信息
    kill # 终止当前运行的程序
    set var 变量名 = 变量值
  4. 执行程序:

    一旦程序停在断点处,你可以使用以下命令控制程序的执行:

    • continue(缩写为 c):继续执行程序直到下一个断点。
    • step(缩写为 s):逐行执行程序,进入函数内部。
    • next(缩写为 n):逐行执行程序,不进入函数内部。
  5. 查看变量值:

    在程序暂停时,你可以查看变量的值。例如:

    终端窗口
    print <format> <expr> # p, 使用指定的格式查看程序中的数据。命令兼容C语言中访问变量的方式,如数组下标、解引用、数据类型转换等操作。
    ptype # 打印变量类型
    display xx # 追踪查看变量的值
    undisplay xx
    whatis var # 查看变量具体信息,例如属于哪一个类,是基类还是派生类
    #
    list # l, 查看当前上下文的源码
    list <filename>:<function> # 查看指定函数的源码
    set listsize <count> # 设置list查看的代码行数
    # 搜索
    search # 往下搜索
    forward-search # 往下搜索
    reverse-search # 往上搜索
    show directory # 显示当前代码搜索路径
    directory xxpath # 设置搜索代码的文件路径。一般有当前路径+工作路径
    # 反汇编
    disassemble [<where>] # a
  6. 查看堆栈:

    查看当前调用堆栈:

    终端窗口
    backtrace # bt, 查看函数栈信息,可简写为bt。若num是正数,打印栈顶上num层的函数调用信息;
    # 若num是负数,只打印栈顶下num层的函数调用信息;默认打印当前所有函数调用栈的信息
    frame # f 切换到frame#(frame#为栈层次编号,可使用info frame进行查询)指定的层次的函数栈
    up # 切换到上一层函数栈
    down
    info frame # i
    info local
    info args
    #查看内存
    x/nfu addr
    n: 待显示的内存单元个数
    f:显示格式
    u:每个内存单元的大小,按字节数来计算。默认是4 bytes.
    call malloc_stats() # 查看当前的内存分布
    call malloc_info(0,stdout) # 把内存分布情况按照xml格式输出,
    # 加入 -fsanitize=address选项,在发生内存泄漏,堆栈溢出,野指针,全局内存会直接定位到具体代码
    #查看寄存器
    register # r, rdi rsi rdx rcx r8 r9分别代表函数前6个。多了的就在栈里面
    info register
    info all-register
    # rip和pc寄存器用来存放下一个需要执行指令的地址,因此我们就是找到需要执行指令的地址,然后设置rip寄存器
    # 调试时调用内部函数和外部函数
    p 函数(参数)
    call 函数(参数)
    # 多线程调试
    info threads # 显示当前可调试的所有线程,每个线程会有一个GDB为其分配的ID,后面操作线程的时候会用到这个ID。 前面有*的是当前调试的线程
    thread ID(1,2,3…) # 切换当前调试的线程为指定ID的线程
    set scheduler-locking on/off # 设置on,只有当前被调试程序会执行;设置off不锁定任何线程,也就是所有线程都执行,这是默认值
    thread find # 查找线程(只能查找地址,id,名字。后面的函数不能)
    thread name # 设置线程名字(先切换到像改名字的线程,然后执行)
    b breakpoint thread id # 为线程设置断点(只有设置的线程会在中断处停止,其他线程不会)
    thread apply 2 3 i args # 显示线程2,线程3的参数
    thread apply all i args # 显示所有线程的参数
    thread apply 2 -s i args# 对线程2执行 i args命令,如果命令错误就没有提示
    thread apply 2 3 -q i args # 会把线程2,线程3的信息连在一起显示
    set follow-fork-mode child # 默认是父进程调试。如果要设置子进程需要进行设置
    i inferiors # 查看当前进程及其子进程信息
    inferior num # 切换进程号码
    set logging file xx.txt # 设置保存debug文件
    set logging on # 保存文件
    # 远程调试 利用客户端调试远程服务端。服务端需要按照gdbserver
    服务端:gdbserver ip:9988 ./调试文件
    客户端:1: gdb 2:target remote ip:9988
    # 反向执行
    undo
    # 软件密码“破解” : 通过 ri 汇编执行到出错位置,查看输入寄存器rsi和rdi,对比结果找到答案

    backtrace(或简写为bt)命令用于显示当前线程的调用栈。调用栈显示了程序执行过程中的函数调用顺序,它从最内层(当前正在执行的函数)开始,向外层(调用当前函数的函数)展开。

    • 最上面的是内层函数,也就是当前执行点所在的函数。
    • 最下面的是外层函数,也就是程序开始执行的入口点。
    寄存器功能描述存放数值(十六进制)数值(十进制)说明
    rax通用寄存器,返回值0x11可能表示布尔值或标志位
    rbx基指针寄存器,用于调用约定0x00可能未被使用或已清零
    rcx计数寄存器,用于循环和参数0x55可能用于循环计数或函数参数
    rdx通用寄存器,第二个返回值0x7ff7ee25bce8140702829100264可能是指向内存的指针
    rsi源索引寄存器,参数传递0x89c87f756c591774315884可能是函数参数或数据结构的指针
    rdi目的索引寄存器,参数传递0x89c87f75a4591774315940同上
    rbp基指针,访问局部变量0x00基指针可能已经被清零
    rsp栈指针,指向栈顶0x89c87f7330-栈顶地址,用于访问栈上的数据
    r8-r15用于参数传递和扩展r8: 0x7ff7ee262cf0
    r9: 0x762
    r8: 140702829128944
    r9: 1890
    通常用于传递函数的第4-6个参数
    rip指令指针,指向下一条指令0x7ffe952f924c-指向 ucrtbased!_invoke_watson 异常处理函数
    eflags状态标志寄存器0x202-包含CPU状态标志,如中断启用标志(IF)
    cs代码段寄存器0x3351用于选择代码段
    ss堆栈段寄存器0x2b43用于选择堆栈段
    ds数据段寄存器0x2b43用于选择数据段
    es附加段寄存器0x2b43用于选择附加段
    fs额外段寄存器0x5383用于线程特定数据
    gs机器特定数据寄存器0x2b43用于机器特定数据
    st0浮点寄存器-无效的浮点值,可能未初始化或状态异常
    st1浮点寄存器1.07587300579586992195e-4931-非常小的正浮点数,接近于0
    st2浮点寄存器-无效的浮点值
    st3浮点寄存器-同上
    st4浮点寄存器-4.77986657081093372053e+986-非常大的负浮点数,可能是数值溢出
    st5浮点寄存器-无效的浮点值
    st6浮点寄存器-同上
  7. 退出 GDB:

    在调试会话结束后,使用 quit 或 q 命令退出 GDB:

    终端窗口
    (gdb) quit # q

这是一个简单的入门教程,涵盖了一些基本的 GDB 命令。GDB 有更多强大的功能,可以帮助你诊断和修复程序中的错误。你可以通过阅读 GDB 的官方文档和教程深入了解更多功能。