`
helloyesyes
  • 浏览: 1311412 次
  • 性别: Icon_minigender_2
  • 来自: 武汉
文章分类
社区版块
存档分类
最新评论

调试(三)

阅读更多


使用gdb调试

我们将会使用GNU调试器,gdb,来调试这个程序。这是一个可以免费得到并且可以用于多个Unix平台的功能强大的调试器。他也是Linux系统上的默认调试器。gdb已经被移植到许多其他平台上,并且可以用于调试嵌入式实时系统。

启动gdb

让我们重新编译我们的程序用于调试并且启动gdb。

$ cc -g -o debug3 debug3.c
$ gdb debug3
GNU gdb 5.2.1
Copyright 2002 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type “show copying” to see the conditions.
There is absolutely no warranty for GDB. Type “show warranty” for details.
This GDB was configured as “i586-suse-linux”...
(gdb)

gdb具有丰富的在线帮助,以及可以使用info程序进行查看或是在Emacs中进行查看的完整手册。

(gdb) help
List of classes of commands:
aliases — Aliases of other commands
breakpoints — Making program stop at certain points
data — Examining data
files — Specifying and examining files
internals — Maintenance commands
obscure — Obscure features
running — Running the program
stack — Examining the stack
status — Status inquiries
support — Support facilities
tracepoints — Tracing of program execution without stopping the program
user-defined — User-defined commands
Type “help” followed by a class name for a list of commands in that class.
Type “help” followed by command name for full documentation.
Command name abbreviations are allowed if unambiguous.
(gdb)

gdb本身是一个基于文本的程序,但是他确实了一些有助于重复任务的简化操作。许多版本具有一个命令行编辑历史,从而我们可以在命令历史中进行滚动并且再次执行相同的命令。所有的版本都支持一个"空白命令",敲击Enter会再次执行上一条命令。当我们使用step或是next命令在一个程序中分步执行特殊有用。

运行一个程序

我们可以使用run命令执行这个程序。我们为run命令所指定的所有命令都作为参数传递给程序。在这个例子中,我们并不需要任何参数。

我们在这里假设我们的系统与作者的类似,也产生了内存错误的错误信息。如果不是,请继续阅读。我们就会发现当我们自己的程序生成一个内存错误时应怎么办。如果我们并不没有得到内存错误信息,但是我们在阅读本书时希望运行这个例子,我们可以拾起第一个内存访问问题已经被修复的debug4.c。

(gdb) run
Starting program: /home/neil/BLP3/chapter10/debug3
Program received signal SIGSEGV, Segmentation fault.
0x080483c0 in sort (a=0x8049580, n=5) at debug3.c:23
23 /* 23 */ if(a[j].key > a[j+1].key) {
(gdb)

如前面一样,我们程序并没有正确运行。当程序失败时,gdb会向我们显示原因以及位置。现在我们可以检测问题背后的原因。

依据于我们的内核,C库,以及编译器选项,我们所看到的程序错误也许有所不同,例如,也许当数组元素交换时是在25行,而不是数组元素比较时的23行。如果是这种情况,我们也许会看到如下的输出:

Program received signal SIGSEGV, Segmentation fault.
0x8000613 in sort (a=0x8001764, n=5) at debug3.c:25
25 /* 25 */ a[j] = a[j+1];

我们仍然可以遵循如下的gdb例子会话。

栈追踪

程序已经在源文件debug3.c的第23行处的sort函数停止。如果我们并没有使用额外的调试信息来编译这个程序,我们就不能看到程序在哪里失败,也不能使用变量名来检测数据。

我们可以通过使用backstrace命令来查看我们是如何到达这个位置的。

(gdb) backtrace
#0 0x080483c0 in sort (a=0x8049580, n=5) at debug3.c:23
#1 0x0804849b in main () at debug3.c:37
#2 0x400414f2 in __libc_start_main () from /lib/libc.so.6
(gdb)

这个是一个非常简单的程序,而且追踪信息很短小,因为我们并没有在其他的函数内部来调用许多函数。我们可以看到sort是由同一个文件debug3.c中37行处的main来调用的。通常,问题会更为复杂,而我们可以使用backtrace来发现我们到达错误位置的路径。

backtrace命令可以简写为bt,而且为了与其他调试器兼容,where命令也具有相同的功能。

检测变量

当程序停止时由gdb所输出的信息以及在栈追踪中的信息向我们显示了函数能数的值。

sort函数是使用一个参数a来调用的,而其值为0x8049580。这是数组的地址。依据于所使用的编译器以及操作系统,这个值在不同的操作系统也会不同。

所影响的行号23,是一个数组元素与另一个数组元素进行比较的地方。

/* 23 */ if(a[j].key > a[j+1].key) {

我们可以使用调试器来检测函数参数,局部变量以及全局数据的内容。print命令可以向我们显示变量以及其他表达式的内容。

(gdb) print j
$1 = 4

在这里我们可以看到局部变量j的值为4。类似这样由gdb命令所报告的所有值都会保存在伪变量中以备将来使用。在这里变量$1赋值为4以防止我们在以后使用。以后的命令将他们的结果存储为$2,$3,依次类推。

j的值为4的事实意味着程序试着执行语句

if(a[4].key > a[4+1].key)

我们传递给sort的数组,array,只有5个元素,由0到4进行索引。所以这条语句读取并不存在的array[5]。循环变量j已经读取一个错误的值。

如果我们尝试这个例子,而我们程序在25行发生错误,我们系统只有在交互元素时才会检测到一个超过数组边界的读取,执行

/* 25 */ a[j] = a[j+1];

此时将j设置为4,结果为

a[4] = a[4+1];

我们可以使用print通过表达式来查看所传递的数组元素。使用gdb,我们几乎可以使用任何合法的C表达式来输出变量,数组元素,以及指针的值。

(gdb) print a[3]
$2 = {data = “alex”, ‘\000’ <repeats 4091 times>, key = 1}
(gdb)

gdb将命令的结果保存在一个伪变量中,$<number>。上一个结果总是为$,而之前的一个为$$。这可以使得在一个结果可以用在另一个命令中。例如,

(gdb) print j
$3 = 4
(gdb) print a[$-1].key
$4 = 1

列出程序

我们可以使用list命令在gdb内查看程序源代码。这会打印出当前位置周围的部分代码。持续的使用list会输出更多的代码。我们也可以为list指定一个行号或是函数名作为一个参数,而gdb就会显示那个位置的代码。

(gdb) list
18 /* 18 */ int s = 1;
19 /* 19 */
20 /* 20 */ for(; i < n && s != 0; i++) {
21 /* 21 */ s = 0;
22 /* 22 */ for(j = 0; j < n; j++) {
23 /* 23 */ if(a[j].key > a[j+1].key) {
24 /* 24 */ item t = a[j];
25 /* 25 */ a[j] = a[j+1];
26 /* 26 */ a[j+1] = t;
27 /* 27 */ s++;
(gdb)

我们可以看到在22行循环设置为当变量j小于n时才会执行。在这个例子中,n为5,所以j的最终值为4,总是小1。4会使得a[4]与a[5]进行比较并且有可能进行交换。这个问题的解决方法就是修正循环的结束条件为j < n-1。

让我们做出修改,将这个新程序称之为debug4.c,重新编译,并再次运行。

/* 22 */ for(j = 0; j < n-1; j++) {

$ cc -g -o debug4 debug4.c
$ ./debug4
array[0] = {john, 2}
array[1] = {alex, 1}
array[2] = {bill, 3}
array[3] = {neil, 4}
array[4] = {rick, 5}

程序仍不能正常工作,因为他输出了一个不正确的排序列表。下面我们使用gdb在程序运行时分步执行。

设置断点

查找出程序在哪里失败,我们需要能够查看程序运行他都做了什么。我们可以通过设置断点在任何位置停止程序。这会使得程序停止并将控制权返回调试器。我们将能够监视变量并且允许程序继续执行。

在sort函数中有两个循环。外层循环,使用循环变时i,对于数组中的每一个元素运行一次。内层循环将其与列表中的下一个元素进行交换。这具有将最小的元素交换到最上面的效果。在外层循环的每一次执行之后,最大的元素应位置底部。我们可通过在外层循环停止程序进行验证并且检测数组状态。

有许多命令可以用于设置断点。通过gdb的help breakpoint命令可以列表这些命令:

(gdb) help breakpoint
Making program stop at certain points.
List of commands:
awatch — Set a watchpoint for an expression
break — Set breakpoint at specified line or function
catch — Set catchpoints to catch events
clear — Clear breakpoint at specified line or function
commands — Set commands to be executed when a breakpoint is hit
condition — Specify breakpoint number N to break only if COND is true
delete — Delete some breakpoints or auto-display expressions
disable — Disable some breakpoints
enable — Enable some breakpoints
hbreak — Set a hardware assisted breakpoint
ignore — Set ignore-count of breakpoint number N to COUNT
rbreak — Set a breakpoint for all functions matching REGEXP
rwatch — Set a read watchpoint for an expression
tbreak — Set a temporary breakpoint
tcatch — Set temporary catchpoints to catch events
thbreak — Set a temporary hardware assisted breakpoint
watch — Set a watchpoint for an expression
Type “help” followed by command name for full documentation.
Command name abbreviations are allowed if unambiguous.

让我们在20行设置一个断点并且运行这个程序:

$ gdb debug4
(gdb) break 20
Breakpoint 1 at 0x804835d: file debug4.c, line 20.
(gdb) run
Starting program: /home/neil/BLP3/chapter10/debug4
Breakpoint 1, sort (a=0x8049580, n=5) at debug4.c:20
20 /* 20 */ for(; i < n && s != 0; i++) {

我们可以输出数组值并且使用cont可以使得程序继续执行。这个会使得程序继续运行直到遇到下一个断点,在这个例子中,直到他再次执行到20行。在任何时候我们都可以有多个活动断点。

(gdb) print array[0]
$1 = {data = “bill”, ‘\000’ <repeats 4091 times>, key = 3}

要输出多个连续的项目,我们可以使用@<number>结构使得gdb输出多个数组元素。要输出array的所有五个元素,我们可以使用

(gdb) print array[0]@5
$2 = {{data = “bill”, ‘\000’ <repeats 4091 times>, key = 3}, {
data = “neil”, ‘\000’ <repeats 4091 times>, key = 4}, {
data = “john”, ‘\000’ <repeats 4091 times>, key = 2}, {
data = “rick”, ‘\000’ <repeats 4091 times>, key = 5}, {
data = “alex”, ‘\000’ <repeats 4091 times>, key = 1}}

注意,输出已经进行简单的处理从而使其更易于阅读。因为这是第一次循环,数组并没有发生变量。当我们允许程序继续执行,我们可以看到当处理执行时array的成功修改:

(gdb) cont
Continuing.
Breakpoint 1, sort (a=0x8049580, n=4) at debug4.c:20
20 /* 20 */ for(; i < n && s != 0; i++) {
(gdb) print array[0]@5
$3 = {{data = “bill”, ‘\000’ <repeats 4091 times>, key = 3}, {
data = “john”, ‘\000’ <repeats 4091 times>, key = 2}, {
data = “neil”, ‘\000’ <repeats 4091 times>, key = 4}, {
data = “alex”, ‘\000’ <repeats 4091 times>, key = 1}, {
data = “rick”, ‘\000’ <repeats 4091 times>, key = 5}}
(gdb)

我们可以使用display命令来设置gdb当程序在断点处停止时自动显示数组:

(gdb) display array[0]@5
1: array[0] @ 5 = {{data = “bill”, ‘\000’ <repeats 4091 times>, key = 3}, {
data = “john”, ‘\000’ <repeats 4091 times>, key = 2}, {
data = “neil”, ‘\000’ <repeats 4091 times>, key = 4}, {
data = “alex”, ‘\000’ <repeats 4091 times>, key = 1}, {
data = “rick”, ‘\000’ <repeats 4091 times>, key = 5}}

而且我们可以修改断点,从而他只是简单的显示我们所请求的数据并且继续执行,而不是停止程序。在这样做,我们可以使用commands命令。这会允许我们指定当遇到一个断点时执行哪些调试器命令。因为我们已经指定了一个display命令,我们只需要设置断点命令继续执行。

(gdb) commands
Type commands for when breakpoint 1 is hit, one per line.
End with a line saying just “end”.
> cont
> end

现在我们允许程序继续,他会运行完成,在每次运行到外层循环时输出数组的值。

(gdb) cont
Continuing.
Breakpoint 1, sort (a=0x8049684, n=3) at debug4.c:20
20 /* 20 */ for(; i < n && s != 0; i++) {
1: array[0] @ 5 = {{data = “john”, ‘\000’ <repeats 4091 times>, key = 2}, {
data = “bill”, ‘\000’ <repeats 4091 times>, key = 3}, {
data = “alex”, ‘\000’ <repeats 4091 times>, key = 1}, {
data = “neil”, ‘\000’ <repeats 4091 times>, key = 4}, {
data = “rick”, ‘\000’ <repeats 4091 times>, key = 5}}
Breakpoint 1, sort (a=0x8049684, n=2) at debug4.c:20
20 /* 20 */ for(; i < n && s != 0; i++) {
1: array[0] @ 5 = {{data = “john”, ‘\000’ <repeats 4091 times>, key = 2}, {
data = “alex”, ‘\000’ <repeats 4091 times>, key = 1}, {
data = “bill”, ‘\000’ <repeats 4091 times>, key = 3}, {
data = “neil”, ‘\000’ <repeats 4091 times>, key = 4}, {
data = “rick”, ‘\000’ <repeats 4091 times>, key = 5}}
array[0] = {john, 2}
array[1] = {alex, 1}
array[2] = {bill, 3}
array[3] = {neil, 4}
array[4] = {rick, 5}
Program exited with code 044.
(gdb)

gdb报告程序并没有以通常的退出代码退出。这是因为程序本身并没有调用exit也没有由main返回一个值。在这种情况下,这个退出代码是无意义的,而一个有意义的退出代码应由调用exit来提供。

这个程序看起来似乎外层循环次数并不是我们所期望的。我们可以看到循环结束条件所使用的参数值n在每个断点处减小。这意味着循环并没有执行足够的次数。问题就在于30行处n的减小。

/* 30 */ n--;

这是一个利用在每一次外层循环结束时array的最大元素都会位于底部的事实来优化程序的尝试,所以就会有更少的排序。但是,正如我们所看到的,这是与外层循环的接口,并且造成了问题。最简单的修正方法就是删除引起问题的行。让我们通过使用调试器来应用补丁测试这个修正是否有效。

使用调试进行补丁

我们已经看到了我们可以使用调试器来设置断点与检测变量的值。通过使用带动作的断点,我们可以在修改源代码与重新编译之前试验一个修正,称之为补丁。在这个例子中,我们需要在30行设置断点,并且增加变量n。然后,当30行执行,这个值将不会发生变化。

让我们从头启动程序。首先,我们必须删除我们的断点与显示。我们可以使用info命令来查看我们设置了哪些断点与显示:

(gdb) info display
Auto-display expressions now in effect:
Num Enb Expression
1: y array[0] @ 5
(gdb) info break
Num Type Disp Enb Address What
1 breakpoint keep y 0x0804835d in sort at debug4.c:20
breakpoint already hit 4 times
cont

我们可以禁止这些或是完全删除他们。如果我们禁止他们,那么我们可以在以后需要他们时重新允许这些设置:

(gdb) disable break 1
(gdb) disable display 1
(gdb) break 30
Breakpoint 2 at 0x8048462: file debug4.c, line 30.
(gdb) commands 2
Type commands for when breakpoint 2 is hit, one per line.
End with a line saying just “end”.
>set variable n = n+1
>cont
>end
(gdb) run
Starting program: /home/neil/BLP3/chapter10/debug4
Breakpoint 2, sort (a=0x8049580, n=5) at debug4.c:30
30 /* 30 */ n--;
Breakpoint 2, sort (a=0x8049580, n=5) at debug4.c:30
30 /* 30 */ n--;
Breakpoint 2, sort (a=0x8049580, n=5) at debug4.c:30
30 /* 30 */ n--;
Breakpoint 2, sort (a=0x8049580, n=5) at debug4.c:30
30 /* 30 */ n--;
Breakpoint 2, sort (a=0x8049580, n=5) at debug4.c:30
30 /* 30 */ n--;
array[0] = {alex, 1}
array[1] = {john, 2}
array[2] = {bill, 3}
array[3] = {neil, 4}
array[4] = {rick, 5}
Program exited with code 044.
(gdb)

这个程序运行结束并且会输出正确的结果。现在我们可以进行修正并且继续使用更多的数据进行测试。

分享到:
评论

相关推荐

    调试“三板斧”

    这个讲座从Visual Studio .Net调试环境的介绍和配置入手,首先介绍了基本的调试技术。接着重点讲述.net web应用程序的调试方法和技巧,并对asp.net 应用程序调试过程中常见的问题及处理办法作了讲解。

    西门子 变频器 G120C快速调试

    快速调试是变频器调试的重要步骤之一,包括参数复位、快速调试、功能调试三个步骤。在快速调试中,需要使用BOP-2操作面板设置变频器的各种参数,以确保变频器的正常运行。 一、变频器参数设置 在快速调试中,需要...

    IE6调试三件套

    1、IE Developer Toolbar 可以查看dom,修改css 2、Companion JS 可以进行脚本调试 3、HttpWatch 可以查看网络

    BDM调试方法与技巧

    TTBDM(Total Terminal Background Debugger for S12)是飞思卡尔官方提供的BDM调试工具,它集成了应用程序下载与更新、单片机内部资源配置与修复、以及动态调试三大核心功能。通过与CodeWarrior集成,TTBDM为开发者...

    中科曙光HPC培训教程汇总:D17-集群调试—GPU调试.pptx

    其中,GPU基础调试部分涵盖了GPU环境安装、GPU基础测试和GPU基础调试三个方面的内容。 首先,在GPU环境安装部分,介绍了使用cuda安装包安装GPU环境的步骤,包括安装GPU驱动、安装Cuda Tookit SDK和安装Cuda Samples...

    三层楼电梯PLC控制系统设计与调试

    三层楼电梯PLC控制系统设计与调试 三层楼电梯PLC控制系统设计与调试

    调试定额指南.docx

    1. **编制说明**:调试工程的项目划分遵循启动调试的顺序,划分为单体调试、分系统调试和整套启动调试三个阶段。单体调试关注设备个体,分系统调试涉及设备之间的相互配合,整套启动调试则是在所有系统调试合格后...

    消防系统调试方案.doc

    分为单体设备调试、子系统调试和整体联动调试三个阶段,逐步验证系统各部分的性能和兼容性。 十、调试内容及方法 详细列出每一步的调试内容,如设备功能测试、信号传输测试、联动控制测试等,并说明具体的操作方法...

    弱电智能化系统调试方案.doc

    综合布线系统的调试包括综合布线调试概述、综合布线调试要求和测试调试三个方面。综合布线调试概述对综合布线系统进行了总体的介绍,包括系统的组成、工作原理和功能等;综合布线调试要求则对综合布线系统的调试提出...

    GDB基本调试原理及三种调试方式

    GDB提供了三种调试方式: 1. **调试已运行的进程**:用户首先确定需要调试的进程ID,然后在GDB中输入`attach pid`命令,GDB会通过ptrace(PTRACE_ATTACH, pid, 0, 0)附加到该进程,从而开始调试。 2. **运行并调试...

    电脑灯灯光程序调试.doc

    该过程涉及到硬件连接、软件使用和灯光调试三个主要方面。本文将详细介绍电脑灯光程序调试的知识点。 一、 硬件连接 硬件连接是电脑灯光程序调试的基础,它包括灯具安装、灯具接头制作和硬件整体连接三个步骤。 1...

    ColdFire系列微处理器背景调试模式设计.pdf

    BDM调试模式分为实时跟踪调试、背景调试和实时调试三种类型。实时跟踪调试可以追踪程序的执行路径,通过8位并行输出总线提供处理器状态和数据信息。背景调试则利用3个引脚实现串行全双工通信,处理器暂停,外部仿真...

    A2O工艺调试方案.pdf

    调试过程分为单机调试、设备联动调试和工艺调试三步。单机调试旨在验证设备功能符合设计要求,设备联动调试测试整个系统的运行,而工艺调试则着重于培养和积累处理水体所需的微生物,以及驯化适应实际水质的微生物。...

    Simotion第三方电机调试手册.pdf

    在工业自动化领域,Simotion扮演着关键角色,尤其在与第三方电机配合使用时,其强大的兼容性和调试能力凸显出来。这份“Simotion第三方电机调试手册”是工程师们进行设备集成和系统优化的重要参考资料。 Simotion的...

    电气调试及典型案例.pptx

    调试工作主要分为单体调试、分系统调试和整套启动调试三个阶段。 单体调试是针对设备本身的独立测试,确保设备在安装后仍保持出厂时的技术指标和性能。这一阶段的目的是验证设备在运输、安装过程中是否受到影响,...

    串口调试助手,三种调试工具

    本文将详细介绍三种不同的串口调试助手,以及它们各自的特点和功能。 首先,"UartAssist.exe" 是一款常见的串口调试软件,它允许用户通过自定义波特率与设备进行通信。波特率是串行通信中的一个重要参数,它决定了...

    自动化生产线安装与调试综合实践.docx

    在本次实践中,我通过自身的学习基本了解了自动化生产线安装与调试内容、自动化生产线的组成局部与功能介绍、安装与调试三大类自动化生产线内容。对于生产线安装调试也进行了数次的单独尝试,虽然还是有很多缺乏或者...

    工程调试及试运行方案.doc

    - **调试程序**:调试分为调试前的准备、清水试车和污水调试三个阶段。 - **调试前的准备**:检查构筑物施工质量,设备、仪表、阀件是否满足设计要求,流量匹配情况,控制系统可靠性,设备异常情况,电气系统,...

    精品资料(2021-2022年收藏)调试方案commissioning中英文双语版.doc

    本篇文档主要讲述了单机调试、系统调试和联动调试三个阶段的调试工作,并针对电气系统、通风空调系统以及给排水及消防系统进行了详细的调试准备条件说明。 首先,单机调试是针对每个设备或装置进行的独立测试,旨在...

Global site tag (gtag.js) - Google Analytics