gdb调试

  • gdb的命令都可以使用help 命令来查看如何使用
  • 启动调试gdb main(main为可执行文件的名字)
  • 开始调试start

常用的gdb调试命令:

命令 解释
start 开始执行程序,停在main函数第一行语句前面等待命令
list(或l) 列出源代码,接着上次的位置往下列,每次列10行
list 行号 列出从第几行开始的源代码
list 函数名 列出某个函数的源代码
next(或n) 执行下一行语句
step(或s) 执行下一行语句,如果有函数调用则进入到函数中
backtrace(或bt) 查看各级函数调用及参数
info(或i) locals 查看当前栈帧局部变量的值
finish 执行到当前函数返回,然后停下来等待命令
print(或p) 打印表达式的值,通过表达式可以修改变量的值或者调用函数
frame(或f) 帧编号 选择栈帧
set var 修改变量的值
quit (q) 结束调试

动手试一下吧(用上面的命令调试下面的这个程序):

任务

  1. 开始调试,并查看add_range函数
  2. 执行下一条语句
  3. 进入add_range(0, 10)函数内部,并查看函数调用的栈帧,和add_range的局部变量
  4. 循环中打印查看sum的值
  5. 结束当前函数调用,返回main函数
  6. 结束调试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>

int add_range(int low, int high)
{
int i, sum = 0;
for (i = low; i <= high; i++)
sum = sum + i;
return sum;
}

int main(void)
{
int result[100];
result[0] = add_range(1, 10);
result[1] = add_range(1, 100);
printf("result[0]=%d\nresult[1]=%d\n", result[0], result[1]);
return 0;
}

设置断点

命令 解释
break(或b) 行号 在某一行设置断点
break 函数名 在某个函数开头设置断点
break…if… 设置条件断点
continue(或c) 从当前位置开始连续而非单步执行程序
delete breakpoints(断点的Num) 删除断点
display 变量名 跟踪查看一个变量,每次停下来都显示它的值
disable breakpoints 禁用断点
enable breakpoints 启用断点
info(或i) breakpoints 查看当前设置了哪些断点
run(或r) 从头开始连续而非单步执行程序
undisplay 取消对先前设置的那些变量的跟踪

动手试一下吧(用上面的命令调试下面的这个程序):

该程序预期实现将字符数字串转为整型数字,但是现在结果并不理想,请你找出bug并修复

任务
找出bug,实现预期功能

  1. 开始调试,并跟踪查看sum
  2. 在while(1)这一行设置一个断点
  3. 查看转换过程
  4. 查看已设置的断点
  5. 删除断点
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>

int main(void)
{
int sum = 0, i = 0;
char input[5];

while (1) {
scanf("%s", input);
for (i = 0; input[i] != '\0'; i++)
sum = sum*10 + input[i] - '0';
printf("input=%d\n", sum);
}
return 0;
}

观察点

接着上一节的调试,我们知道上节中bug在于在每次循环中,sum并没有赋初值,所以导致从第一次后,转换就出现bug。现改正为下面代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>

int main(void)
{
int sum = 0, i = 0;
char input[5];

while (1) {
sum = 0;
scanf("%s", input);
for (i = 0; input[i] != '\0'; i++)
sum = sum*10 + input[i] - '0';
printf("input=%d\n", sum);
}
return 0;
}

使用scanf函数是非常凶险的,即使修正了这个Bug还存在很多问题。如果输入的字符串超长了会怎么样?我们知道数组访问越界是不会检查的,所以scanf会写出界。(这个bug很容易修复,可以自己尝试一下)

提示:for的终止条件为’\0’,查看input元素(多查看几个,大于数组长度)寻找问题

这节,我们将学习一下监视点的使用如何查看内存

  • 查看内存使用命令examine,简写为x
    使用: $ x/(n,f,u为可选参数) 地址

    • n: 需要显示的内存单元个数,也就是从当前地址向后显示几个内存单元的内容,一个内存单元的大小由后面的u定义

    • f:显示格式

      1
      2
      3
      4
      5
      6
      7
      8
      x(hex) 按十六进制格式显示变量。
      d(decimal) 按十进制格式显示变量。
      u(unsigned decimal) 按十进制格式显示无符号整型。
      o(octal) 按八进制格式显示变量。
      t(binary) 按二进制格式显示变量。
      a(address) 按十六进制格式显示变量。
      c(char) 按字符格式显示变量。
      f(float) 按浮点数格式显示变量
    • u:每个单元的大小,按字节数来计算。默认是4 bytes。GDB会从指定内存地址开始读取指定字节,并把其当作一个值取出来,并使用格式f来显示

      1
      2
      3
      b:1 byte
      h:2 bytes
      w:4 bytes g:8 bytes
    • 举例(用上面函数):

      例子 解释
      x/7b input 默认以10进制打印input开始,4个单元(每单元1byte)
      x/7cb input 按字符格式,打印input开始,4个单元(每单元1byte)
  • 观察点

    我们知道断点是当程序执行到某一代码行时中断,而观察点是当程序访问某一存储单元时中断。

    有两种使用情况:

    1. 如果我们不知道某一存储单元是在哪里被改动的,这时候观察点尤其有用(直接watch 变量名
    2. 在不确定发生问题的地方时,通过使用观察点的条件表达式,可以非常方便地找出问题代码。(watch i>99)

    其他相关命令:查看、删除、禁用、启用均与断点相同