文章目录
- 一、单步执行和跟踪函数调用
- 1.gdb基本命令1
- 2.函数调试实例
- (1)原始代码如下
- (2)在编译时要加上 -g 选项,生成的可执行文件才能用 gdb 进行源码级调试
- (3)在(gdb)提示符下输入 help 可以查看命令的类别
- (4)也可以进一步查看某一类别中有哪些命令,例如查看 files 类别下有哪些命令可用
- (5)现在试试用 list 命令从第一行开始列出源代码
- (6)gdb 提供了一个很方便的功能:在提示符下直接敲回车表示重复上一条命令
- (7)要列一个函数的源代码:l 函数名(或者list 函数名)
- (8)退出 gdb 的环境
- (9)把源代码改名或移到别处再用 gdb 调试,这样就列不出源代码了
- (10)首先用 start 命令开始执行程序:gdb main
- (11)我们可以用 next 命令(简写为 n ) 控制这些语句一条一条地执行
- (12)现在用 start 命令重新来过,这次用 step 命令(简写为 s ) 钻进 add_range 函数中去跟踪执行
- (13)在函数中有几种查看状态的办法, backtrace 命令(简写为 bt ) 可以查看函数调用的栈帧
- (14)现在可以用 info 命令(简写为 i ) 查看 add_range 函数局部变量的值:i locals
- (15)如果想查看 main 函数当前局部变量的值也可以做到
- (16)用 s 或 n 往下走几步,然后用 print 命令(简写为 p )打印出变量 sum 的值
- (16)可以用 finish 命令让程序一直运行到从当前函数返回为止
- (17)下面用 s 命令进入第二次 add_range 调用,进入之后首先查看参数和局部变量
- (18)如果我们不想浪费这次调试机会,可以在 gdb 中马上把 sum 的初值改为0继续运行,看看这一处改了之后还有没有别的Bug:set var sum=0(print 命令也可以)
- 二、断点
- 1.gdb基本命令2
- 2.函数调试实例如下
- (1)断点调试实例如下
- (2)编译运行,看看出现什么问题
- (3)开始GDB调试:gdb main
- (4) display sum:显示当前 sum 的值;undisplay sum编号:取消跟踪显示
- (5)break(或b) 行数:设置断点
- (6)现在用 continue 命令(简写为 c ) 连续运行而非单步运行,程序到达断点会自动停下来
- (7)一次调试可以设置多个断点,用 info 命令可以查看已经设置的断点
- (8)delete breakpoints 断点编号:每个断点都有一个编号,可以用编号指定删除某个断点
- (9)disable breakpoints 断点编号:有时候一个断点暂时不用可以禁用掉而不必删除
- (10)还可以设置断点在满足某个条件时才激活
- 三、习题:反转字符串初学者常见的一个错误
- 四、观察点
- 1.gdb基本命令3
- 2.断点调试实例如下
- (1)开始调试start
- (2)打印指定存储单元的内容:x/7b input
- (3)设一个条件断点从 i 等于4开始单步调试
- (4)用观察点(Watchpoint) 来跟踪input[4]后面的字节什么时候变的
- 注意:断点是当程序执行到某一代码行时中断,而观察点是当程序访问某个存储单元时中断
- 五、段错误
- 1.段错误调试实例1
- (1)调试代码如下
- (2)查看段错误的配合指令:r和bt
- 2.段错误调试实例2
- (1)调试代码如下
- (2)调试过程如下
一、单步执行和跟踪函数调用
(1)本章我们介绍一种很强大的调试工具 gdb ,可以完全操控程序的运行,使得程序就像
你手里的玩具一样,叫它走就走,叫它停就停,并且随时可以查看程序中所有的内部状态,
比如各变量的值、传给函数的参数、当前执行的代码行等。掌握了 gdb 的用法之后,调试手
段就更加丰富了。
(2)但要注意,即使调试手段丰富了,调试的基本思想仍然是“分析现象->假设错误原因->产生新的现象去验证假设”这样一个循环
1.gdb基本命令1
2.函数调试实例
(1)原始代码如下
(2)在编译时要加上 -g 选项,生成的可执行文件才能用 gdb 进行源码级调试
(3)在(gdb)提示符下输入 help 可以查看命令的类别
(4)也可以进一步查看某一类别中有哪些命令,例如查看 files 类别下有哪些命令可用
(5)现在试试用 list 命令从第一行开始列出源代码
(6)gdb 提供了一个很方便的功能:在提示符下直接敲回车表示重复上一条命令
(7)要列一个函数的源代码:l 函数名(或者list 函数名)
(8)退出 gdb 的环境
(9)把源代码改名或移到别处再用 gdb 调试,这样就列不出源代码了
(10)首先用 start 命令开始执行程序:gdb main
gdb 停在 main 函数中变量定义之后的第一条语句处等待我们发命令, gdb 列出的这条语句是即将执行的下一条语句。
(11)我们可以用 next 命令(简写为 n ) 控制这些语句一条一条地执行
用 n 命令依次执行两行赋值语句和一行打印语句,在执行打印语句时结果立刻打出来了,然
后停在 return 语句之前等待我们发命令。虽然我们完全控制了程序的执行,但仍然看不出哪
里错了,因为错误不在 main 函数中而在 add_range 函数中。
(12)现在用 start 命令重新来过,这次用 step 命令(简写为 s ) 钻进 add_range 函数中去跟踪执行
这次停在了 add_range 函数中变量定义之后的第一条语句处。
(13)在函数中有几种查看状态的办法, backtrace 命令(简写为 bt ) 可以查看函数调用的栈帧
- 可见当前的 add_range 函数是被 main 函数调用的, main 传进来的参数是 low=1,high=10。
- main 函数的栈帧编号为1, add_range 的栈帧编号为0。
(14)现在可以用 info 命令(简写为 i ) 查看 add_range 函数局部变量的值:i locals
(15)如果想查看 main 函数当前局部变量的值也可以做到
注意到 result 数组中有很多元素具有杂乱无章的值,我们知道未经初始化的局部变量具有不
确定的值。到目前为止一切正常。
(16)用 s 或 n 往下走几步,然后用 print 命令(简写为 p )打印出变量 sum 的值
(16)可以用 finish 命令让程序一直运行到从当前函数返回为止
第一个值55确实赋给了 result 数组的第0个元素。
(17)下面用 s 命令进入第二次 add_range 调用,进入之后首先查看参数和局部变量
(18)如果我们不想浪费这次调试机会,可以在 gdb 中马上把 sum 的初值改为0继续运行,看看这一处改了之后还有没有别的Bug:set var sum=0(print 命令也可以)
二、断点
1.gdb基本命令2
2.函数调试实例如下
(1)断点调试实例如下
解释说明
(a)程序的作用:
例如输入是 “2345” ,则循环累加的过程是(((0*10+2)*10+3)*10+4)*10+5=2345。
(b)注意字符型的 ‘2’ 要减去 ‘0’ 的ASCII码才能转换成整数值2。
(2)编译运行,看看出现什么问题
(3)开始GDB调试:gdb main
(4) display sum:显示当前 sum 的值;undisplay sum编号:取消跟踪显示
(5)break(或b) 行数:设置断点
可以用 break 命令(简写为 b ) 在第9行设一个断点(Breakpoint);
break 命令的参数也可以是函数名,表示在某个函数开头设断点;
(6)现在用 continue 命令(简写为 c ) 连续运行而非单步运行,程序到达断点会自动停下来
这样就可以停在下一次循环的开头:
(7)一次调试可以设置多个断点,用 info 命令可以查看已经设置的断点
(8)delete breakpoints 断点编号:每个断点都有一个编号,可以用编号指定删除某个断点
(9)disable breakpoints 断点编号:有时候一个断点暂时不用可以禁用掉而不必删除
(10)还可以设置断点在满足某个条件时才激活
三、习题:反转字符串初学者常见的一个错误
解决办法:
(1)用gdb调试后,发现的结果是:
为啥呢?
因为用空字符串 “” 初始化一个同样长的字符数组 reverse_str ,相当于所有元素用 ‘\0’ 初始化。
而printf函数打印字符串的时候,看到NULL就会停止输出(就是0)。
所以,正确的做法是:
四、观察点
1.gdb基本命令3
2.断点调试实例如下
如果输入的字符串超长了会怎么样?
我们知道数组访问越界是不会检查的,所以 scanf 会写出界。现象是这样的:
(1)开始调试start
(2)打印指定存储单元的内容:x/7b input
(3)设一个条件断点从 i 等于4开始单步调试
(4)用观察点(Watchpoint) 来跟踪input[4]后面的字节什么时候变的
注意:断点是当程序执行到某一代码行时中断,而观察点是当程序访问某个存储单元时中断
说明:
五、段错误
1.段错误调试实例1
(1)调试代码如下
(2)查看段错误的配合指令:r和bt
2.段错误调试实例2
(1)调试代码如下
(2)调试过程如下
解释: