【Linux】gdb调试器
目录
- 什么是gdb
- 怎么使用gdb
- debug版与release版
- 进入和退出调试状态
- 代码打印
- 运行程序
- 打断点
- 逐过程和逐语句调试
- 变量打印
- 快速跳过代码
- 修改变量值
- 查看调用堆栈
什么是gdb
gdb(GNU Debugger)是GNU项目中的一款功能强大的命令行调试工具,主要用于调试C、C++等编程语言编写的程序。它是开发者排查程序错误(如崩溃、逻辑错误、内存问题等)的核心工具之一,支持多种平台(Linux、Windows、macOS等)。其实说实在的,gdb本身说不上好用,在Windows、macOS等图形化界面(GUI)的操作系统上,因为图形化界面简洁明了,所以往往有着更好的选择。但是在Linux下,由于其是文本界面(TUI)的缘故,gdb已经是很好用很强大的工具了。
怎么使用gdb
debug版与release版
在vs上写过程序的应该见过,程序在编译时是有debug版与release版之分的,debug版的程序可以调试,release版的则不能,但是release版的程序会比debug版的跑的更快。为什么会这样呢?因为debug版的加入了调试信息,禁用了编译器的优化,保证代码严格按照代码顺序执行,确保调试能顺利进行,而release版则没有加入调试信息,对代码进行了极致优化,所以才出现了以上差别。在实际当中,代码在开发测试阶段会编译成debug版确保各种调试和错误排查工作的进行,代码正式上线时才会改成release版给到用户,从而确保代码足够快,性能足够高,保证用户的使用体验。所以想要使用gdb调试,毫无疑问要将代码编译成debug版,怎么编译呢?Linux中使用gcc/g++编译器编译时,默认是编译成release版的,我们需要加上-g选项才行。
g++ -o debug版目标文件 源文件 -g
进入和退出调试状态
使用指令,
gdb debug版目标文件
就会进入调试状态了。
这时我们会发现命令行前面变成了(gdb),这就表示我们进入调试状态了,这时使用指令q或quit,可以退出调试状态。
代码打印
在调试状态我们要是想要查看代码,我们可以使用指令list或l来打印,
默认是只会打印10行,如果我们想要继续往下看,可以再次使用指令,这时就会接着上次的行数往下打印10行,我们也能什么都输,直接回车,这时也会自动往下打印10行。
l 后面跟数字可以直接打印指定行数往下10行的代码,
我们也能指定函数名,打印指定函数附近的代码,
在有多个源文件时,我们也可以指定文件名来打印代码,使用指令
l/list 指定文件:行号
就行。
执行过一次之后按回车也能接着打印。
运行程序
使用指令r或run可以在调试状态运行程序
打断点
调试过程序的应该对断点都不陌生,打上断点的程序直接运行时会在断点处停下来,是很方便很实用的功能,gdb也是支持断点的。使用指令b或break可以打断点
b/break 数字
数字多少,就在对应的行号打上断点。
使用指令,
info b/break/breakpoints
可以查看断点信息。
字段 | 含义 |
---|---|
Num | 断点编号 |
Type | 断点类型 |
Disp | 命中后的处理方式 |
Enb | 是否启用 |
Address | 断点的内存地址 |
What | 断点位置描述 |
断点的编号是从1开始往下递增,每创建一个断点系统就会自动分配一个。断点类型分为breakpoint(普通代码断点)、watchpoint(数据断点,用于监视变量变化)、catchpoint(事件断点,捕捉异常/信号等),我们直接创建的就是普通断点,最为实用。断点命中后的处理方式有keep(保持启用(默认))、del(自动删除(一次断点))、dis(自动禁用(下次需要手动启用))。是否启用意味着断点是否在发挥作用,断点没有启用的话就和没有一样,程序不会在该断点处停下来了。断点的内存地址表示断点在内存中的绝对地址。断点位置描述表示断点在代码中的位置。
断点信息还会记录断点的命中次数,即实际运行时在断点处停下来的次数,上面的代码信息截图中第一个断点就显示breakpoint already hit 1 time,表示被命中过一次了,这是因为之前我已经r指令跑了一次,且在第一个断点处停了下来。
我们在有多个断点的程序中如果已经在一个断点处停下,想要程序运行到下一个断点处该怎么做呢?我们可以使用continue或c指令,这时代码就会继续运行到下一个断点处,如果没有断点了,就是跑到程序结束。
前面断点信息那里我也讲过,断点是可以设置启用或不启用的,我们在创建断点时默认断点是启用的,使用指令
disable 要禁用的断点编号
或
disable breakpoint 要禁用的断点编号
可以禁用指定的断点,如果我们不加数字编号,就会直接禁用所有的断点。禁用断点之后,我们想要恢复怎么办呢?使用指令
enable 启用断点
或
enable breakpoint 启用断点
可以启用断点。
我们使用b打断点时还可以指定函数名,这时断点就会打在指定函数的第一行,
我们同样也能在有多个源文件指定源文件来打断点,
最后需要注意,断点信息会在退出调试状态时全部清除。
逐过程和逐语句调试
我们在调试代码时,经常会用到逐过程和逐语句来进行调试。逐过程和逐语句在一般场景下是一样的,但是在遇到函数时就不一样了,逐过程的会直接跳过进入函数的过程,直接执行完这个函数,而逐语句会进入函数内一步步走。熟悉vs的都知道,在vs中,逐过程是F10,逐语句是F11,在gdb中,逐过程是指令n,逐语句是指令s。
需要注意的是,我们没法使用这条指令直接使程序运行起来,也就是我们无法像vs一样按F10或F11直接让程序一步一步的跑。n指令和s指令只能在程序跑起来但是没跑完时用,我们可以在想要仔细调试的位置设置断点,再r指令跑到断点处,然后使用n或s指令一步步跑。如果我们想要从一开始就一步步跑,我们可以在开头打一个断点,也可以使用指令start,这条指令会自动在main函数开头生成一个临时断点(用完自动清除),直接在开头停下来,这是我们就能使用n指令或s指令一步步跑了。
在使用n或s指令时,我们也能像打印代码一样,再输过一次指令之后直接回车自动重复刚刚的指令。
变量打印
我们在调试时,经常会想要关注变量的变化情况,vs中我们常用监视窗口来完成,gdb中我们也是有对应的指令的。
使用p指令可以直接打印变量值。
$1 是gdb自动创建的临时变量,用来记录a的值,数字是gdb分配一个递增编号,从1开始,每使用一次就递增1。这个指令只能打印变量一次,可以对不那么想频繁关注的变量使用,如果我们想频繁地关注一个变量的变化情况,我们可以使用display指令常打印一个变量
此时我们使用n指令或s指令时下面会自动打印变量的值。如果我们不想要显示之前常打印的值时,可以使用undisplay指令指定显示变量的编号
这时就不会再打印这个变量了。
快速跳过代码
我们在实际调试代码时,可能会遇到想要暂时想要跳过一段的情况,这时用断点又不够方便,我们这时就可以用until指令
指定行号就能跑到到指定位子了除非中间有断点。我们有时在进入到函数内部之后觉得这个函数没什么问题,想要直接跑完这个函数,我们就可以使用finish指令,可以直接跑完当前函数。
修改变量值
我们在代码调试时有时会想要调整一个变量的值到一种极端情况看看会不会出错,gdb中我们可以直接使用指令
set var 变量=指定值
来修改。
查看调用堆栈
gdb中我们可以使用bt指令查看调用堆栈来检查程序运行情况,确认错误原因。