Linux下调试程序方法

您能够用各类方法来监控运行着的用户空间程序:能够为其运行调试器并单步调试该程序,添加打印语句,或者添加工具来分析程序。本文描述了几种能够用来调试在 Linux 上运行的程序的方法。咱们将回顾四种调试问题的状况,这些问题包括段错误,内存溢出和泄漏,还有挂起。
本文讨论了四种调试 Linux 程序的状况。在第 1 种状况中,咱们使用了两个有内存分配问题的样本程序,使用 MEMWATCH 和 Yet Another Malloc Debugger(YAMD)工具来调试它们。在第 2 种状况中,咱们使用了 Linux 中的 strace 实用程序,它可以跟踪系统调用和信号,从而找出程序发生错误的地方。在第 3 种状况中,咱们使用 Linux 内核的 Oops 功能来解决程序的段错误,并向您展现如何设置内核源代码级调试器(kernel source level debugger,kgdb),以使用 GNU 调试器(GNU debugger,gdb)来解决相同的问题;kgdb 程序是使用串行链接的 Linux 内核远程 gdb。在第 4 种状况中,咱们使用 Linux 上提供的魔术键控顺序(magic key sequence)来显示引起挂起问题的组件的信息。html

常见调试方法
当您的程序中包含错误时,极可能在代码中某处有一个条件,您认为它为真(true),但其实是假(false)。找出错误的过程也就是在找出错误后推翻之前一直确信为真的某个条件过程。linux

如下几个示例是您可能确信成立的条件的一些类型:编程

在源代码中的某处,某变量有特定的值。
在给定的地方,某个结构已被正确设置。
对于给定的 if-then-else 语句,if 部分就是被执行的路径。
当子例程被调用时,该例程正确地接收到了它的参数。小程序

找出错误也就是要肯定上述全部状况是否存在。若是您确信在子例程被调用时某变量应该有特定的值,那么就检查一下状况是否如此。若是您相信 if 结构会被执行,那么也检查一下状况是否如此。一般,您的假设都会是正确的,但最终您会找到与假设不符的状况。结果,您就会找出发生错误的地方。数组

调试是您没法逃避的任务。进行调试有不少种方法,好比将消息打印到屏幕上、使用调试器,或只是考虑程序执行的状况并仔细地揣摩问题所在。app

在修正问题以前,您必须找出它的源头。举例来讲,对于段错误,您须要了解段错误发生在代码的哪一行。一旦您发现了代码中出错的行,请肯定该方法中变量的值、方法被调用的方式以及关于错误如何发生的详细状况。使用调试器将使找出全部这些信息变得很简单。若是没有调试器可用,您还可使用其它的工具。(请注意,产品环境中可能并不提供调试器,并且 Linux 内核没有内建的调试器。)编程语言

实用的内存和内核工具
您可使用 Linux 上的调试工具,经过各类方式跟踪用户空间和内核问题。请使用下面的工具和技术来构建和调试您的源代码:
用户空间工具:函数

内存工具:MEMWATCH 和 YAMD
strace
GNU 调试器(gdb)
魔术键控顺序工具

内核工具:post

内核源代码级调试器(kgdb)
内建内核调试器(kdb)
Oops


本文将讨论一类经过人工检查代码不容易找到的问题,并且此类问题只在不多见的状况下存在。内存错误一般在多种状况同时存在时出现,并且您有时只能在部署程序以后才能发现内存错误。

第 1 种状况:内存调试工具
C 语言做为 Linux 系统上标准的编程语言给予了咱们对动态内存分配很大的控制权。然而,这种自由可能会致使严重的内存管理问题,而这些问题可能致使程序崩溃或随时间的推移致使性能降级。

内存泄漏(即 malloc() 内存在对应的 free() 调用执行后永不被释放)和缓冲区溢出(例如对之前分配到某数组的内存进行写操做)是一些常见的问题,它们可能很难检测到。这一部分将讨论几个调试工具,它们极大地简化了检测和找出内存问题的过程。

MEMWATCH
MEMWATCH 由 Johan Lindh 编写,是一个开放源代码 C 语言内存错误检测工具,您能够本身下载它(请参阅本文后面部分的参考资料)。只要在代码中添加一个头文件并在 gcc 语句中定义了 MEMWATCH 以后,您就能够跟踪程序中的内存泄漏和错误了。MEMWATCH 支持 ANSI C,它提供结果日志纪录,能检测双重释放(double-free)、错误释放(erroneous free)、没有释放的内存(unfreed memory)、溢出和下溢等等。

清单 1. 内存样本(test1.c)
代码:
#include
#include
#include "memwatch.h"

int main(void)
{
char *ptr1;
char *ptr2;

ptr1 = malloc(512);
ptr2 = malloc(512);

ptr2 = ptr1;
free(ptr2);
free(ptr1);
}

清单 1 中的代码将分配两个 512 字节的内存块,而后指向第一个内存块的指针被设定为指向第二个内存块。结果,第二个内存块的地址丢失,从而产生了内存泄漏。

如今咱们编译清单 1 的 memwatch.c。下面是一个 makefile 示例:

test1
gcc -DMEMWATCH -DMW_STDIO test1.c memwatch
c -o test1

当您运行 test1 程序后,它会生成一个关于泄漏的内存的报告。清单 2 展现了示例 memwatch.log 输出文件。

清单 2. test1 memwatch.log 文件
MEMWATCH 2.67 Copyright (C) 1992-1999 Johan Lindh

...
double-free: <4> test1.c(15), 0x80517b4 was freed from test1.c(14)
...
unfreed: <2> test1.c(11), 512 bytes at 0x80519e4
{FE FE FE FE FE FE FE FE FE FE FE FE ..............}

Memory usage statistics (global):
N)umber of allocations made: 2
L)argest memory usage : 1024
T)otal of all alloc() calls: 1024
U)nfreed bytes totals : 512

MEMWATCH 为您显示真正致使问题的行。若是您释放一个已经释放过的指针,它会告诉您。对于没有释放的内存也同样。日志结尾部分显示统计信息,包括泄漏了多少内存,使用了多少内存,以及总共分配了多少内存。

YAMD
YAMD 软件包由 Nate Eldredge 编写,能够查找 C 和 C++ 中动态的、与内存分配有关的问题。在撰写本文时,YAMD 的最新版本为 0.32。请下载 yamd-0.32.tar.gz(请参阅参考资料)。执行 make 命令来构建程序;而后执行 make install 命令安装程序并设置工具。

一旦您下载了 YAMD 以后,请在 test1.c 上使用它。请删除 #include memwatch.h 并对 makefile 进行以下小小的修改:

使用 YAMD 的 test1
gcc -g test1.c -o test1

清单 3 展现了来自 test1 上的 YAMD 的输出。

清单 3. 使用 YAMD 的 test1 输出
YAMD version 0.32
Executable: /usr/src/test/yamd-0.32/test1
...
INFO: Normal allocation of this block
Address 0x40025e00, size 512
...
INFO: Normal allocation of this block
Address 0x40028e00, size 512
...
INFO: Normal deallocation of this block
Address 0x40025e00, size 512
...
ERROR: Multiple freeing At
free of pointer already freed
Address 0x40025e00, size 512
...
WARNING: Memory leak
Address 0x40028e00, size 512
WARNING: Total memory leaks:
1 unfreed allocations totaling 512 bytes

*** Finished at Tue ... 10:07:15 2002
Allocated a grand total of 1024 bytes 2 allocations
Average of 512 bytes per allocation
Max bytes allocated at one time: 1024
24 K alloced internally / 12 K mapped now / 8 K max
Virtual program size is 1416 K
End.

YAMD 显示咱们已经释放了内存,并且存在内存泄漏。让咱们在清单 4 中另外一个样本程序上试试 YAMD。

清单 4. 内存代码(test2.c)
代码:
#include
#include

int main(void)
{
char *ptr1;
char *ptr2;
char *chptr;
int i = 1;
ptr1 = malloc(512);
ptr2 = malloc(512);
chptr = (char *)malloc(512);
for (i; i <= 512; i++) {
chptr[i] = 'S';
}
ptr2 = ptr1;
free(ptr2);
free(ptr1);
free(chptr);
}

您可使用下面的命令来启动 YAMD:

./run-yamd /usr/src/test/test2/test2
清单 5 显示了在样本程序 test2 上使用 YAMD 获得的输出。YAMD 告诉咱们在 for 循环中有“越界(out-of-bounds)”的状况。

清单 5. 使用 YAMD 的 test2 输出
Running /usr/src/test/test2/test2
Temp output to /tmp/yamd-out.1243
*********
./run-yamd: line 101: 1248 Segmentation fault (core dumped)
YAMD version 0.32
Starting run: /usr/src/test/test2/test2
Executable: /usr/src/test/test2/test2
Virtual program size is 1380 K
...
INFO: Normal allocation of this block
Address 0x40025e00, size 512
...
INFO: Normal allocation of this block
Address 0x40028e00, size 512
...
INFO: Normal allocation of this block
Address 0x4002be00, size 512
ERROR: Crash
...
Tried to write address 0x4002c000
Seems to be part of this block:
Address 0x4002be00, size 512
...
Address in question is at offset 512 (out of bounds)
Will dump core after checking heap.
Done.

MEMWATCH 和 YAMD 都是颇有用的调试工具,它们的使用方法有所不一样。对于 MEMWATCH,您须要添加包含文件 memwatch.h 并打开两个编译时间标记。对于连接(link)语句,YAMD 只须要 -g 选项。

Electric Fence
多数 Linux 分发版包含一个 Electric Fence 包,不过您也能够选择下载它。Electric Fence 是一个由 Bruce Perens 编写的 malloc() 调试库。它就在您分配内存后分配受保护的内存。若是存在 fencepost 错误(超过数组末尾运行),程序就会产生保护错误,并当即结束。经过结合 Electric Fence 和 gdb,您能够精确地跟踪到哪一行试图访问受保护内存。Electric Fence 的另外一个功能就是可以检测内存泄漏。

第 2 种状况:使用 strace
strace 命令是一种强大的工具,它可以显示全部由用户空间程序发出的系统调用。strace 显示这些调用的参数并返回符号形式的值。strace 从内核接收信息,并且不须要以任何特殊的方式来构建内核。将跟踪信息发送到应用程序及内核开发者都颇有用。在清单 6 中,分区的一种格式有错误,清单显示了 strace 的开头部分,内容是关于调出建立文件系统操做(mkfs)的。strace 肯定哪一个调用致使问题出现。

清单 6. mkfs 上 strace 的开头部分
execve("/sbin/mkfs.jfs", ["mkfs.jfs", "-f", "/dev/test1"], &
...
open("/dev/test1", O_RDWR|O_LARGEFILE) = 4
stat64("/dev/test1", {st_mode=&, st_rdev=makedev(63, 255), ...}) = 0
ioctl(4, 0x40041271, 0xbfffe128) = -1 EINVAL (Invalid argument)
write(2, "mkfs.jfs: warning - cannot setb" ..., 98mkfs.jfs: warning -
cannot set blocksize on block device /dev/test1: Invalid argument )
= 98
stat64("/dev/test1", {st_mode=&, st_rdev=makedev(63, 255), ...}) = 0
open("/dev/test1", O_RDONLY|O_LARGEFILE) = 5
ioctl(5, 0x80041272, 0xbfffe124) = -1 EINVAL (Invalid argument)
write(2, "mkfs.jfs: can\'t determine device"..., ..._exit(1)
= ?

清单 6 显示 ioctl 调用致使用来格式化分区的 mkfs 程序失败。ioctl BLKGETSIZE64 失败。(BLKGET-SIZE64 在调用 ioctl 的源代码中定义。) BLKGETSIZE64 ioctl 将被添加到 Linux 中全部的设备,而在这里,逻辑卷管理器还不支持它。所以,若是 BLKGETSIZE64 ioctl 调用失败,mkfs 代码将改成调用较早的 ioctl 调用;这使得 mkfs 适用于逻辑卷管理器。

 

 

inux c 小程序 gdb调试命令 例子

 

1:调试函数的一系列命令,源代码以下main.c
#include <stdio.h>

int add_range(int low, int high)
{
int i,sum;
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;
}
结果为55 5015 与正确结果不一样,调试以下

1步骤: gcc -g main.c -o main linux下c源文件编译(含有源代码,能够调试):gdb main 进入main函数的调试help帮助l 1或者 l main 查看源代码start开始调试n(next)下一步s(step)跳进函数bt(backtrace)查看函数调用的栈帧i(info) locals 查看方法的局部变量f(frame) 1 选择1号栈帧i locals 查看1号栈帧的局部变量,即main函数的局部变量p(print) sum 查看sum变量的值finish 跳出当前函数,回到main函数set var sum=0 修改变量sum的值为0p(print) result[2]=33 print也能够像set同样设置变量的值2int main(){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;}第一次输入123正确,第二次错误调试命令以下:start 启动调试display sum 每次定下来都显示sum的值undisplay 取消对这个变量的跟踪b(break) 9 在第9行设置一个断点 参数也能够是函数名c(continue) 表示连续运行,跳到下一个断点i breakpoints 显示已经设置的断点delete breakpoints 2 删除断点2delete breakpoints 删除全部的断点disable breakpoints 3 使某个断点失效break 9 if sum != 0 知足条件才可使用该断点r 从新从程序开始连续执行x 命令打印存储器中的内容 x/7b input 7b是打印格式,b表示每一个字节一组,7表示打印7组watch input[5] 跟踪某变量

相关文章
相关标签/搜索