您的位置:首页 > 汽车 > 时评 > GDB使用总结

GDB使用总结

2024/9/22 22:03:28 来源:https://blog.csdn.net/2301_80909286/article/details/141536219  浏览:    关键词:GDB使用总结

文章目录

  • GDB
    • 基本命令
      • 启动调试与程序执行
      • 断点管理
      • 查看/修改变量
      • 查看/修改内存
      • 寄存器的查看和修改
      • 源代码的查看和管理
      • 函数调用栈管理
    • 中级使用
      • 断点
      • 文本用户界面(TUI模式)
      • 查看变量类型信息
      • 线程管理
      • 执行命令及结果输出
    • 高级使用
      • 跳转执行
      • 反向调试undo
      • 多进程调试
      • 调用程序内外部函数
      • 调试时跳过指定函数
      • 调试发行版程序
      • 修改可执行文件
    • 补充内容
      • 内存泄漏检查
      • 内存检查(通过gcc-g++编译器来实现)
      • 远程调试
      • 核心转储
    • 相关资料

GDB

GDB
Xshell快捷键:
Ctrl + Alt + T 新建一个会话

gdb调试-从入门实践到原理
https://mp.weixin.qq.com/s/XO462ijLN3i0q7QKcb-f7Q

gdb使用总结
https://www.cnblogs.com/dongxb/p/17135808.html

基本命令

启动调试与程序执行

  • 启动调试并传递参数

    • gdb --args

    • set args

    • r

  • 附加到进程

    • 适用于程序已经启动了

      如果此时再用gdb启动一个程序(gdb ),就是对新启动的程序进行调试而不是对现在已经运行的程序进行调试

    • gdb attach

    • gdb --pid

  • next / n

    • 逐过程调试

      • 单步执行(step-over),遇到函数跳过函数
  • step / s

    • 逐语句调试

      • 单步执行(step-into),遇到函数进入函数
  • continue / c

    • 运行到下一个断点处
  • until

    • 运行到某一行并停止

      • 如果目标line在当前行之前,该命令就相当于continue
  • finish

    • 直接执行完当前函数并返回

      • finish不能在main函数中使用
  • return

    • 什么也不执行直接返回,返回值是随机值

      • return可以用在main函数中
  • 退出调试

    • 分离

      • detach

        • detach之后进程不再受gdb控制,且变成后台进程,会继续向后执行。如果该进程能够正常退出会变成僵尸进程(因为它的父进程gdb没有时间去回收它的资源),如果不能够正常退出(如死循环)就需要我们通过kill来手动杀死
    • 退出

      • quit/q

断点管理

gdb-断点
https://zhuanlan.zhihu.com/p/638013587

  • 设置断点

    • break/b

      • 在源代码的某一行设置断点

        • 当调试的程序为单文件程序时,可以省略文件名:
    • b <func_name>

      • 为函数设置断点

        • 如果有同名函数,就为所有同名函数设置断点

        • 如果只想为特定的函数设置断点,就需要添加限定符,以便区分到底是为哪个函数设置断点

          b class::test_func()
          b test_func(int)
          此时,只会对class中的test_func和test_func(int)两个函数设置断点

    • rb

      • 为满足正则表达式的函数设置断点

        rb test_func*
        这样就为所有以test_func开头的函数设置了断点

    • b if

      b func if a = 10
      当a等于10时,为全部的func函数设置断点

      location适用于上文全部的设置断点的方式

      • 设置条件断点

        • 常用在循环中,但其他场景也能用
    • tb

      • 设置临时断点

        • 该断点只会命中一次
    • b [+ -]

      • 通过偏移量设置断点

        • 当前代码执行到某一行时,如果要为当前代码行的前面某一行或者后面某一行设置断点,就可以通过偏移量来达到快速设置断点的目的
  • 查看/禁用/删除断点

    • i b(info breakpoints / info break)

      • 查看所有断点
    • i b

      • 查看某一个断点
    • disable/enable

      • 禁用/启用断点

        • disable <id1 - id2>

          • 禁用id1到id2号断点
        • enable once

          • 启动一次断点
        • enable delete

          • 启动断点,在断点被击中后删除
        • enable count

          • 启动断点,在该断点被击中times次后自动禁用
    • ignore

      • 忽略前times次击中断点
    • delete / clear

      • 删除所有断点

        • 包括观察点和捕获点
    • clear

      • 清除当前行断点

        • 无法删除观察点和捕获点
    • delete

      • 删除断点

        • delete <id1 id2 …>

          • 删除id1、id2、…号断点
        • delete <id1 - id2>

          • 删除id1到id2号断点
        • delete <id1 - id2 id3 - id4 …>

          • 删除id1到id2、id3到id4、…号断点
    • clear <func_name>

      • 删除函数内所有的断点

        • 如果存在同名函数,那么所有同名函数中的断点都会被删除
    • clear filename:line

      • 删除文件中某一行的断点

查看/修改变量

  • 查看变量

    • show args

      • 查看命令行参数
    • info/i args

      • 查看函数参数

        • 参数必须要有名字才能查看
    • i locals

      • 查看局部变量
    • print/p <变量名>

      • 查看变量的值
    • set print null-stop

      • 设置字符串的显示规则

        • 设置查看字符串变量时到’'0’停止
    • set print pretty

      • 设置结构体显示规则

        • 让结构体中每个字段占一行
    • set print array on

      • 设置数组显示规则

        • 让数组中的每个元素各占一行
    • p

      • 执行表达式并得到结果

        • p 1 + 1
    • display

      • 跟踪查看一个变量,每次停下来都显示它的值
    • undisplay

      • 取消对先前设置的那些变量的跟踪
  • 修改变量

    • print/p <变量名> = <值>

      • 包括普通变量,成员变量,结构体,类等

        • 可以用来控制程序的执行流程
    • p <gdb内置函数>

      • p strcpy(str, “this is string”)

查看/修改内存

  • 查看内存

    • x /nfu

      • n 是显示内存的长度
        f 是显示格式(x是十六进制,d是十进制,u是无符号十六进制,o是八进制,t是二进制,f是浮点,s是字符串)
        u 是单位(b是单字节,h是双字节,w是四字节,g是八字节)

        • nfu都可省略
          n省略时默认为1
          f省略时为你上一次指定的格式(如果没有则为x)
          u省略时为你上一次指定的单位(如果没有则为w)
  • 修改内存

    • set / p {type} =

      • 一定要指定type(通过大括号包裹),不然不知道要修改几个字节的内存。char/unsigned char为修改一个字节,int/unsigned int 修改四个字节等

寄存器的查看和修改

  • 查看寄存器

    • i registers

      • 查看所有通用寄存器
    • i all-registers

      • 查看所有寄存器
    • 查看某一个寄存器

      • i r

      • p $

    • 当函数的参数小于等于6个时,会将参数放在寄存器中;否则会放入函数栈中

    • 如果查看的寄存器中的值是一个字符串,可以结合p (char*) 来查看其中的内容

  • 修改寄存器

    • i line <行号>

      • 查看行号对应代码的汇编地址
    • disassemble [option] [function | addr]

      • option为 /mr 时为源代码与汇编一一对应展示

      • 当不指定函数时为全文反汇编

      • 如果存在同名函数,只会展示最后一个定义的

      • 除了可以指定函数名外,还可通过指定函数定义的汇编地址来展反汇编

    • pc/rip(program counter)寄存器,用来保存程序中下一条要执行的指令,可以通过修改pc/rip寄存器来改变程序执行的流程

      • set var $pc/rip =

      • p $pc/rip =

源代码的查看和管理

  • 显示源代码

    • list/l

      • 默认显示10行(前后各5行)

        • 之后每次执行都向后显示10行

        • l - 向前显示

  • 查看指定文件指定行代码

    • list :
  • 设置每次显示的行数

    • set listsize
  • 查看指定函数的代码

    • list

      • 如果有同名函数,就会把所有同名函数显示出来

        • 可以添加域限定符::来指定显示哪一个同名函数

        • 可以通过添加

          : 来限定查看哪一个文件中的函数

  • 搜索源代码

    • search

      • 从当前行开始向后搜索第一个满足正则表达式的源代码
    • forward-search

      • 同search
    • reverse-search

      • 从当前行开始向前搜索第一个满足正则表达式的源代码
  • 查看/设置源代码的查找目录

    • show directories

      • 一般是程序的工作目录和当前所在目录
    • directory

函数调用栈管理

  • backtrace/bt/where

    • 查看栈回溯信息

      • 如果指定line,就是查看line行信息,不指定就是查看全部栈帧信息
  • frame/f <frame id / frame addr>

    • 切换栈帧
  • info f

    • 查看栈帧信息

中级使用

断点

  • 观察点

    • 观察点是一个特殊的断点,当表达式(表达式可以是一个简单的变量)的值发生变化时,它将中断下来。表达式可以是一个变量的值,也可以包含由运算符组合的一个或多个变量的值。有时被称为数据断点(vc中)

      • gdb中观察点分为hardware watchpoint和software watchpoint。它们分别由硬件和软件生成,hardware watchpoint不会对程序有任何影响,而software watchpoint会降低程序的运行效率。
    • 主要作用

      • 定位堆上的结构体内部成员何时被修改
    • 使用

      • watch

        • 为表达式设置写断点,当变量被目标线程写入时会击中断点
      • rewatch

        • 为表达式设置读断点,当变量被目标线程读取时会击中断点
      • awatch

        • 为表达式设置读写断点,当变量被目标线程读取或写入时会击中断点
      • info watch

        • 查看所有观察点
      • delete/disable/enable

        • 删除/禁用/启用观察点
    • 注意事项

      • 当监控变量为局部变量时,一旦局部变量失效,数据断点也会失效

      • 如果监控的是指针变量p,则watch *p监控的是p所指内存数据的变化情况,而watch p监控的是p指针本身有没有改变指向

  • 捕获点

    • 捕获点是一个特殊的断点,当捕获到某个事件时就会中断下来

    • catch

  • 为断点执行命令

    • commands

      end

      • 如果不指定某一个断点,就默认为最后一个断点
  • 保留断点信息到文件

    • save breakpoints
  • 从文件中读取断点信息

    • source

      • 自己在文件中添加的断点信息也能够读取出来

文本用户界面(TUI模式)

TUI官网
https://sourceware.org/gdb/current/onlinedocs/gdb.html/TUI.html

TUI模式无法使用小键盘和鼠标

  • layout src / asm / reg

    • 显示源码 / 汇编 / 寄存器窗口
  • layout split

    • 切分窗口

      • 最多有3个窗口,其中必有命令窗口,剩下两个窗口在源码 / 汇编 / 寄存器之间显示
  • focus(fs) cmd / src / asm / reg

    • 将焦点切换到对应窗口

      • 具有焦点的界面可以通过上下按键来移动
  • focus(fs) next(n)/ prev(p)

    • 切换到当前焦点的下一个 / 上一个界面
  • info win

    • 查看当前拥有焦点的窗口
  • Ctrl + n / p

    • 下 / 上 一条命令
  • Ctrl + x + a

    • 关闭界面模式
  • 源码界面行号旁边显示的b是还未击中的断点,B是已经击中过1次及以上的断点,h为硬件断点

    • 断点旁的+号代表该断点是可用的

查看变量类型信息

  • whatis <variable / type>

    • 单纯查看变量的类型
  • ptype [] <varibale / type>

    • 显示类型的定义如成员变量、成员函数、类型重定义(只有使用了的类型重定义才会显示出来)

    • option(可有可无)

      • /m

        • 不显示成员函数
      • /t

        • 不显示类型重定义
      • /o

        • 显示类和结构体的内存对齐状态
  • set print object on

    • 显示对象真实的类型,用于显示多态的接收类型
  • i variable

    • 查看变量的定义位置

      • 只支持全局变量和静态变量

线程管理

当thread命令在开头就能简称为t,其余位置都不能简称

  • info / i threads

    ps -aL 在bash中查看全部轻量级进程(线程)

    • 查看所有线程信息

      • 线程id前的 * 号代表当前线程
  • thread

    • 切换线程
  • thread find

    • 通过正则表达式匹配来查找线程

      • 查找的材料可以是线程的名字、LWP、地址
  • thread name

    • 为当前线程设置名字
  • b thread

    • 为特定线程设置断点

      • 如果是直接设置断点,如b 6,那么所有线程都会在第6行停下来
  • thread apply <id … > []

    这里的 <id …>可以参考删除断点,删除断点的那几种写id的方法这里都可以

    • 为线程执行命令

      • 当id栏为all时,为所有线程执行命令

      • option(可有可无)

        • -s 不显示gdb命令的错误信息,如使用不存在的gdb命令就不会提示错误

        • -q 将多个线程执行任务的结果打印在一起,不用线程id做划分

  • set print thread-events on / off

    • 开启 / 关闭打印线程事件信息
  • show print thread-event

    • 显示当前打印线程事件信息状态
  • 设置线程锁

    使用GDB调试多线程程序时,默认的调试模式是:一个线程暂停运行,其他线程也随即暂停;一个线程启动运行,其他线程也随即启动。但在一些场景中,我们希望只让特定线程运行,其他线程都维持在暂停状态,即要防止线程切换,要达到这种效果,需要借助 set scheduler-locking 命令。

    • set scheduler-locking off

      • 不锁定线程,会有线程切换
    • set scheduler-locking on

      • 锁定线程,只有当前或指定线程可以运行
    • set scheduler-locking step

      • 当单步执行某一线程时,其他线程不会执行,同时保证在调试过程中当前线程不会发生改变。但如果在该模式下执行 continue、until、finish 命令,则其他线程也会执行
    • show scheduler-locking

      • 查看线程锁定状态

执行命令及结果输出

  • 调用bash命令

    • shell / !
  • 通过管道对命令的结果进行再处理

    • pipe / | |

      • 在管道后面执行bash命令不需要加bash或!
  • set logging on / off

    • 启用 / 关闭将gdb中的打印信息全部储存到文件中
  • set logging file

    • 设置输出文件

      • 如果没有设置输出文件就会默认输出当当前目录的gdb.txt中
  • set logging overwrite

    • 将输出模式设置为覆盖

      • 默认输出模式为追加

高级使用

跳转执行

  • jump / j

    • jump

      • 跳转到第line行
    • jump [+ | -]

      • 跳转当当前行向下 \ 上 num行
    • jump *

      • 跳转到addr地址的代码处,地址前要加*号
    • jump

      • 跳转到指定标签,该标签是goto语句的标签
    • jump

      • 跳转到指定函数内,跳转的函数可以没有出现在当前函数中

        • 只推荐在当前函数内跳转,跳转到其他函数可能会出问题
    • 中间跳过的代码是不会执行的,跳到的位置如果没有断点,那么GDB会自动继续往后执行,所以一般会在跳转到的地方添加断点

      • 跳转命令不会更改当前栈帧、堆栈指针、程序计数器以外的任何寄存器

反向调试undo

探索gdb7.0的新特性反向调试(reverse debug)
https://www.oschina.net/question/12_9396

  • Ubuntu 24.04、gdb (Ubuntu 15.0.50.20240403-0ubuntu1)环境下,在record模式下无法执行cout、printf等与外设有关的函数

    通过 lsb_release -a 可以查看Linux发行版本
    通过 gdb -v 可以查看gdb版本

    https://www.saoniuhuo.com/question/detail-2683546.html

    • 其他环境没有测试
  • record

    • 开始录制回放,只有录制了回放的部分才能反向调试
  • record-stop

    • 结束录制回放
  • reverse-finish

    • 回退执行到在所选函数被调用之前
  • reverse-next / rn

    • 倒退执行,不进入函数
  • reverse-setp / rs

    • 倒退执行,进入函数
  • reverse-continue / rc

    • 倒退执行到上一个断点处

多进程调试

  • set follow-fork-mode <child | parent>

    • 设置多进程的跟随模式,child为fork后跟随子进程,parent为跟随父进程,默认为parent模式
  • show follow-fork-mode

    • 查询当前多进程跟随模式
  • set detach-on-fork <on | off>

    • 设置在fork函数创建进程后,gdb是否会detach另一个不被跟随的进程,默认为on

      • 如果想调试多进程,必须设置为off
  • i inferiors

    • 查看当前被调试的进程

      • id旁的*号代表是当前进程
    • gdb用inferior来表示一个被调试进程的状态信息(包括内存空间,进程空间等)。通常情况下,一个inferior代表一个进程,有时候一个inferior不一定会绑定一个进程。这是gdb内部的概念与定义

      • inferior可以为空

  • add-inferior

    • 添加一个空inferior

      • 空inferior可以用于attch另一个需要调试的进程,但必须先切换到空inferior
  • remove-inferiors

    • 移除一个空inferior,且不能删除当前inferior
  • inferior

    • 切换到对应inferior
  • detach inferiors

    • detach对应inferior的进程,也可以切换到对应inferior让然后直接detach
  • show schedule-multiple

    • 显示当前多进程运行模式
  • set schedule-multiple <on | off>

    • 设置多进程运行模式,如果为off,那么只有当前inferior绑定的进程才会受gdb控制运行,其余进程都会被阻塞。如果为on,那么所有inferior对应的进程都会受gdb命令控制,而不是一直阻塞在一个地方

      • 为on模式下,n就是所有进程一起向下执行一步,c就是所有进程一起执行到下一个断点处。在off模式下,n和c就只有当前进程会执行命令,其余进程全部阻塞

调用程序内外部函数

  • p / call

    • 对表达式求值,并打印结果值。表达式可以是被调试程序中的自定义函数,也可以是C库函数和操作符(即使是在原程序中没有调用这些函数也可以直接调用)。如果是执行函数,需要在函数中传入参数

      • 如果是通过p调用,那么即使返回值是void也会显示出来;如果是通过call调用,如果返回值是void就不会显示

      • 这种调用方式会真实地执行一次函数,所以可以通过在函数内加断点来调试函数

      • 调用C库函数malloc示例

    • 在release程序(没有调试符号很多事都做不了)中仍然可以通过p和call来调用函数,如果不知掉程序中有哪些函数,可以通过 i functions来打印程序中的全部函数

调试时跳过指定函数

  • s命令不会进入该函数,但是仍然会执行

    • 用于跳过构造函数等一些不重要或不需要关注或连环调用的函数(如srand(static_cast(time(nullptr))),调用srand的过程中连环调用了time)

  • skip

    • 如果存在同名函数就会跳过所有同名函数,可以通过添加限定符来解决这个问题,如 skip test()就只会跳过无参的test函数,skip test(int)就只会跳过int类型的test函数
  • skip file

    • 跳过目标文件中的所有函数
  • skip -gfi

    / < .>

    • 通过通配符的方式来跳过函数,可以替换。
      如:skip -gfi lib/
      .cpp就是跳过lib目录中所有后缀为cpp的文件中的所有函数

调试发行版程序

make -f
可以让make使用指定的makefile文件,该makefile文件不需要叫makefile,但必须是makefile的格式
如:
make -f debugMaker

strip -g <debug文件> -o <release文件>
通过带有调试信息的debug文件来生成没有调试信息的release文件

  • objcopy --only-keep-debug

    • 将debug版本程序中的调试符号单独提取出来存到一个文件中
  • gdb --symbol=<debug file | debuginfo file> -exec=

    • 通过加载debug版本的调试符号来调试release版本的程序
  • gdb

    • 调试release或debug版本的core dump文件

修改可执行文件

  • gdb -write

    • 通过写方式来调试程序,可以是debug程序,也可以是release程序,一般情况下gdb都是以只读方式调试的
  • disassemble /mr

    • 查看需要修改的函数的反汇编
  • 然后通过 p / set来修改内存,记住要判断机器是大端字节序还是小端字节序

  • q退出

补充内容

内存泄漏检查

  • call malloc_stats()

    • 查看当前进程和当前线程的内存分配情况

      • 主要用于检查某个函数是否发生了内存泄漏,在函数执行前调用一下,在函数执行后调用一下,看看二者是否相同,如果不相同那么就内存泄漏了
  • call malloc_info(0, stdout)

    • malloc_info的输出格式为xml,主要需要关心的字段为rest(剩了多少)。然后也是通过比较被检查函数调用前后的rest字段对应数值的大小是否变化来判断是否内存泄漏

内存检查(通过gcc-g++编译器来实现)

gcc/g++编译选项详解
https://www.cnblogs.com/blizzard8204/p/17519125.html

C/C++ Sanitizer工具
https://www.cnblogs.com/xiaohuidi/p/17611771.html

  • gcc / g++ -fsanitize=address

    • 检查内存泄漏

    • 检查栈溢出

    • 检查堆溢出

    • 检查全局内存溢出

    • 检查释放后再使用

  • 只有在debug模式下支持

远程调试

  • 服务端/被调试机

    • 安装gdbserver

      Ubuntu的安装方式为
      sudo apt install gdbserver -y

    • 启动gdbserver

      • 通过gdbserver新启动一个程序来调试
        gdbserver <本机ip:gdbserver将使用的端口>

      • 通过gdbserver附上一个已经启动的程序来调试
        gdbserver <本机ip:gdbserver将使用的端口> --attach

      • 被调试机在启动gdbserver后就不能自主退出了,只有在连接上调试机且调试机退出后才会自动退出(kill除外)

  • 客户端/调试机

    • gdb远程连接并调试

      • 先直接启动gdb

      • 连接被调试机器

        • target remote <被调试机ip:被调试机gsbserver使用的端口)
      • 远端调试几乎与本机调试一模一样

        • 远端调试不能使用run,是使用continue来开始执行程序的

        • 远程调试需要需要两端配合起来调试,因为虽然gdb的控制权在调试机手上,但是输入、输出都在被调试机器上

      • 退出调试

        • quit / q

        • detach

        • 调试机退出了,被调试机会自动退出

核心转储

其他知识:
alloca 是Linux平台下的一个内存分配函数,在<alloca.h>头文件中,它可以做到在申请内存之后自动释放空间。
值得注意的是,alloca是在栈上分配空间的,虽然效率相较于在堆上分配空间更快,但是容易栈溢出,Linux平台下每个进程的栈大小一般为8M(可以通过ulimit -s查看)。且它自动释放空间是在调用其的函数结束的时候(如我在main函数中调用,那么它就会在main函数结束时才会释放空间,与作用域无关,即使用大括号包裹起来也不会提前释放)。

VC++上用这个函数定义了两个宏
#define A2W(lpa) … 用来将ANSI字符串转变成Unicode字符串
#define W2A(lpw) … 用来将Unicode字符串转变成ANSI字符串

Linux下查看内存使用的指令:
free -h

  • 是某个时刻某个进程的内存映射信息,即包含了生成转储文件时该进程的所有内存信息和寄存器信息。转储文件可以是某个进程的,也可以是整个系统的,可以是进程存在时生成的,也可以是进程或者系统崩溃时自动产生的

  • generate-core-file / gcore [filename]

    • 为正在运行的程序生成core dump文件,如果不指定core dump文件的名字,就会按照core.pid的方式来命名
  • core dump配置

    gdb调试Segmentation fault (core dumped)
    https://blog.csdn.net/qq_62783912/article/details/130566836

    linux 调试系列(一)coredump环境配置
    https://blog.csdn.net/haysonzeng/article/details/129731390

  • 可以通过查看寄存器来调试release版本的程序、核心转储,也可以通过加载调试符号的形式来调试

相关资料

GDB思维导图,密码kazamata

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com