1-1样例代码
wlw.c
#include <stdio.h>
int Sum(int s, int e)
{int result = 0;for (int i = s; i <= e; i++){result += i;}return result;
}
int main()
{int start = 1;int end = 100;printf("开始运行\n");int n = Sum(start, end);printf("结果是: [%d-%d]=%d\n", start, end, n);return 0;
}
1-2调试工具
-
程序的发布⽅式有两种,默认是 debug 模式和 release 模式, Linux gcc/g++ 出来的⼆进制程序,默认是release 模式。
-
要使⽤gdb调试,必须在源代码⽣成⼆进制程序的时候,加上 -g 选项,如果没有添加,程序⽆法被编译
⚠️:不能用gcc 因为 gdb是调试工具,gcc是编译工具
make和Makefile 在这有讲解
生成可执行文件a1
1-3 cgdb常⻅使⽤
第一步 先下载
安装cgdb:
• Ubuntu: sudo apt-get install -y cgdb
• Centos: sudo yum install -y cgdb
第二步进入cgdb
cgdb 可执行文件
1-4 指令
命令格式 | 作用 | 样例 |
---|---|---|
list / l | 显示源代码,从上次位置开始,每次列出 10 行 | list 10 |
list / l 函数名 | 列出指定函数的源代码 | list main |
list / l 文件名:行号 | 列出指定文件的源代码 | list mycmd.c:1 |
r / run | 从程序开始连续执行 | run |
n / next | 单步执行,不进入函数内部 | next |
s / step | 单步执行,进入函数内部 | step |
break / b [文件名:] 行号 | 在指定行号设置断点 | break 10 break test.c:10 |
break / b 函数名 | 在函数开头设置断点 | break main |
info break / info b | 查看当前所有断点的信息 | info break |
finish | 执行到当前函数返回,然后停止 | finish |
print / p 表达式 | 打印表达式的值 | print start+end |
p 变量 | 打印指定变量的值 | p x |
set var 变量 = 值 | 修改变量的值 | set var i=10 |
continue / c | 从当前位置开始连续执行程序 | continue |
delete / d breakpoints | 删除所有断点 | delete breakpoints |
delete / d breakpoints n | 删除序号为 n 的断点 | delete breakpoints 1 |
disable breakpoints | 禁用所有断点 | disable breakpoints |
enable breakpoints | 启用所有断点 | enable breakpoints |
info breakpoints / info i breakpoints | 查看当前设置的断点列表 | info breakpoints |
display 变量名 | 跟踪显示指定变量的值(每次停止时) | display x |
undisplay 编号 | 取消对指定编号的变量的跟踪显示 | undisplay 1 |
until 行号 | 执行到指定行号 | until 20 |
backtrace / bt | 查看当前执行栈的各级函数调用及参数 | backtrace |
info locals / info i locals | 查看当前栈帧的局部变量值 | info locals |
quit | 退出 GDB 调试器 | quit |
小知识:进入页面 esc 暂停 用⬆️⬇️箭头可以翻代码,i 回到指令页面
list/l
显⽰源代码,从上次位置开始,每次列出 10⾏
list/l 函数名
列出指定函数的源代码
list/l ⽂件名:⾏号
列出指定⽂件的源代码
r/run
从程序开始连续执⾏
break/b [⽂件名:]⾏号
在指定⾏号设置断点
break 10 break test.c:10
info break/b
查看当前所有断点的信息
⚠️:End 断点可以被使能 ---》开始或关闭
print/p 表达式
打印表达式的值 print start+end
p 变量
打印指定变量的值k
扩展
watch <变量名>
1. watch
命令概述
watch
是 GDB 中一个非常实用的命令,主要用于在程序运行过程中监视特定表达式(通常是变量)的值。一旦被监视的表达式的值发生改变,GDB 就会暂停程序的执行,这样开发者可以及时捕获变量值的变化,进而分析程序的运行状态和可能存在的问题。
2. 基本语法和示例
2.1 监视变量
- 语法:
watch <变量名>
- 示例:
假设我们有一个简单的 C 程序:
#include <stdio.h>int main() {int num = 0;for (int i = 0; i < 5; i++) {num += i;}return 0;
}
在 GDB 中对 num
变量进行监视:
(gdb) watch num
Hardware watchpoint 1: num
这里设置了一个硬件监视点,当 num
的值发生变化时,GDB 会暂停程序。当程序执行到 num += i;
语句时,由于 num
的值会改变,GDB 就会暂停程序并给出相应提示。
2.2 监视表达式
- 语法:
watch <表达式>
- 示例:
如果我们要监视num + i
这个表达式的值的变化,在 GDB 中可以这样操作:
(gdb) watch num + i
Hardware watchpoint 2: num + i
当 num + i
的计算结果发生变化时,GDB 会暂停程序。
3. 相关配套命令
3.1 info watchpoints
- 作用:查看当前设置的所有监视点的信息,包括监视点编号、监视的表达式、触发条件等。
- 示例:
(gdb) info watchpoints
Num Type Disp Enb Address What
1 hw watchpoint keep y num
2 hw watchpoint keep y num + i
这里显示了两个监视点的相关信息。
3.2 delete watchpoints <编号>
- 作用:删除指定编号的监视点。
- 示例:
(gdb) delete watchpoints 2
(gdb) info watchpoints
Num Type Disp Enb Address What
1 hw watchpoint keep y num
删除了编号为 2 的监视点后,再次查看监视点信息,只剩下编号为 1 的监视点。
3.3 disable watchpoints <编号>
和 enable watchpoints <编号>
- 作用:
disable watchpoints <编号>
用于禁用指定编号的监视点,禁用后该监视点不会触发程序暂停;enable watchpoints <编号>
则用于重新启用被禁用的监视点。 - 示例:
(gdb) disable watchpoints 1
(gdb) info watchpoints
Num Type Disp Enb Address What
1 hw watchpoint keep n num
(gdb) enable watchpoints 1
(gdb) info watchpoints
Num Type Disp Enb Address What
1 hw watchpoint keep y num
先禁用了编号为 1 的监视点(Enb
列变为 n
表示禁用),之后又重新启用(Enb
列变为 y
表示启用)。
4. 注意事项
- 硬件监视点限制:大多数系统对硬件监视点的数量是有限制的,一般可能只有 4 个左右。如果需要监视的变量较多,可能会受到硬件资源的限制。
- 性能影响:使用
watch
命令会对程序的性能产生一定的影响,因为 GDB 需要不断检查监视表达式的值是否发生变化。在调试大型程序时,这种性能影响可能会更加明显。
(提示:此处可以插入一张 GDB 中设置监视点并触发暂停的终端截图,展示 watch
命令的实际效果)
set var
一、验证变量值错误引发的问题
程序运行异常可能源于变量值错误。利用 set var
赋予变量预期值,观察程序是否恢复正常,从而判断该变量是否为问题根源。
- 场景:计算结果错误,怀疑某变量取值异常。
- 示例
#include <stdio.h> int main() { int a = 5, b = 3; int result = a + b; // 预期结果为 10,实际错误 printf("Result: %d\n", result); return 0; }
调试操作(gdb) break 4 (gdb) run (gdb) print a // 查看 a=5 (gdb) print b // 查看 b=3 (gdb) set var a = 7 // 假设 a 应为 7 (gdb) continue // 若结果正确,说明原 a 的值错误是问题原因
二、修正变量值排查程序流程
通过修改变量值,改变程序执行路径,验证逻辑是否符合预期。
- 场景:条件判断因变量值错误进入错误分支。
- 示例
#include <stdio.h> int main() { int flag = 0; if (flag) { printf("Enter correct branch\n"); } else { printf("Enter wrong branch\n"); // 预期应进正确分支 } return 0; }
调试操作:(gdb) break 4 (gdb) run (gdb) set var flag = 1 // 强制设为 1 (gdb) continue // 若进入正确分支,说明原 flag 值错误导致流程问题
三、模拟特殊值触发隐藏问题
给变量赋边界值、特殊值,暴露隐藏的逻辑错误(如数组越界)。
- 场景:数组索引越界导致程序崩溃。
- 示例:
调试操作:#include <stdio.h> int main() { int arr[5] = {1,2,3,4,5}; int index = 5; printf("Value: %d\n", arr[index]); // 越界 return 0; }
(gdb) break 4 (gdb) run (gdb) set var index = 4 // 设为合法值 (gdb) continue // 若程序正常,说明原 index 越界是问题源头
通过 set var
主动干预变量值,观察程序行为变化,可快速缩小问题范围,精准定位因变量值异常导致的程序错误,是调试中分析问题原因的关键手段。
条件断点概述
在调试程序时,普通断点会在每次执行到断点位置时暂停程序,这在某些情况下可能会导致不必要的暂停,浪费调试时间。而条件断点可以根据特定的条件来决定是否暂停程序,这样可以更精准地定位问题,提高调试效率。以下详细介绍添加条件断点的两种常见方式。
方式一:直接添加条件断点
基本语法
在 GDB 中,直接添加条件断点的基本语法为:
break <行号或函数名> if <条件表达式>
其中,<行号或函数名>
指定了断点的位置,<条件表达式>
是一个布尔表达式,当程序执行到断点位置且该条件表达式的值为真时,GDB 才会暂停程序。
示例代码
以下是一个简单的 C 语言示例代码,用于演示直接添加条件断点:
#include <stdio.h>int main() {int i;for (i = 0; i < 10; i++) {printf("i = %d\n", i);}return 0;
}
调试过程
假设我们希望在 i
等于 5 时暂停程序,在 GDB 中可以这样操作:
(gdb) break 5 if i == 5
Breakpoint 1 at 0x11223344: file test.c, line 5.
(gdb) run
Starting program: /path/to/your/program
i = 0
i = 1
i = 2
i = 3
i = 4
Breakpoint 1, main () at test.c:5
5 printf("i = %d\n", i);
(gdb) print i
$1 = 5
在上述示例中,我们使用 break 5 if i == 5
命令在第 5 行添加了一个条件断点,只有当 i
的值等于 5 时,程序才会暂停。
方式二:给已有断点追加条件
基本语法
给已有断点追加条件的基本语法为:
condition <断点编号> <条件表达式>
其中,<断点编号>
是已存在断点的编号,可以通过 info breakpoints
命令查看,<条件表达式>
是要添加的条件。
示例代码
还是使用上面的示例代码。
调试过程
首先,我们先添加一个普通断点:
(gdb) break 5
Breakpoint 1 at 0x11223344: file test.c, line 5.
然后,使用 condition
命令给这个断点追加条件:
(gdb) condition 1 i == 5
(gdb) info breakpoints
Num Type Disp Enb Address What
1 breakpoint keep y 0x0000555555555122 in main at test.c:5stop only if i == 5
接着运行程序:
(gdb) run
Starting program: /path/to/your/program
i = 0
i = 1
i = 2
i = 3
i = 4
Breakpoint 1, main () at test.c:5
5 printf("i = %d\n", i);
(gdb) print i
$1 = 5
在这个示例中,我们先添加了一个普通断点,然后使用 condition
命令给该断点追加了条件 i == 5
,使得只有当 i
等于 5 时,断点才会触发。
注意事项
- 条件表达式的合法性:条件表达式必须是有效的布尔表达式,并且在断点位置处引用的变量必须已经定义。
- 断点编号的准确性:在使用
condition
命令时,要确保指定的断点编号是正确的,否则可能会导致条件添加到错误的断点上。