分享
 
 
 

掌握 Linux 调试技术

王朝system·作者佚名  2006-11-24
窄屏简体版  字體: |||超大  

在 Linux 上找出并解决程序错误的主要方法

Steve Best(sbest@us.ibm.com)

JFS 核心小组成员,IBM

您可以用各种方法来监控运行着的用户空间程序:可以为其运行调试器并单步调试该程序,添加打印语句,或者添加工具来分析程序。本文描述了几种可以用来调试在 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)来显示引发挂起问题的组件的信息。

常见调试方法

当您的程序中包含错误时,很可能在代码中某处有一个条件,您认为它为真(true),但实际上是假(false)。找出错误的过程也就是在找出错误后推翻以前一直确信为真的某个条件过程。

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

在源代码中的某处,某变量有特定的值。

在给定的地方,某个结构已被正确设置。

对于给定的 if-then-else 语句,if 部分就是被执行的路径。

当子例程被调用时,该例程正确地接收到了它的参数。

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

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

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

实用的内存和内核工具

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

用户空间工具:

内存工具:MEMWATCH 和 YAMD

strace

GNU 调试器(gdb)

魔术键控顺序

内核工具:

内核源代码级调试器(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 适用于逻辑卷管理器。

第 3 种情况:使用 gdb 和 Oops

您可以从命令行使用 gdb 程序(Free Software Foundation 的调试器)来找出错误,也可以从诸如 Data Display Debugger(DDD)这样的几个图形工具之一使用 gdb 程序来找出错误。您可以使用 gdb 来调试用户空间程序或 Linux 内核。这一部分只讨论从命令行运行 gdb 的情况。

使用 gdb program name 命令启动 gdb。gdb 将载入可执行程序符号并显示输入提示符,让您可以开始使用调试器。您可以通过三种方式用 gdb 查看进程:

使用 attach 命令开始查看一个已经运行的进程;attach 将停止进程。

使用 run 命令执行程序并从头开始调试程序。

查看已有的核心文件来确定进程终止时的状态。要查看核心文件,请用下面的命令启动 gdb。

gdb programname corefilename

要用核心文件进行调试,您不仅需要程序的可执行文件和源文件,还需要核心文件本身。要用核心文件启动 gdb,请使用 -c 选项:

gdb -c core programname

gdb 显示哪行代码导致程序发生核心转储。

在运行程序或连接到已经运行的程序之前,请列出您觉得有错误的源代码,设置断点,然后开始调试程序。您可以使用 help 命令查看全面的 gdb 在线帮助和详细的教程。

kgdb

kgdb 程序(使用 gdb 的远程主机 Linux 内核调试器)提供了一种使用 gdb 调试 Linux 内核的机制。kgdb 程序是内核的扩展,它让您能够在远程主机上运行 gdb 时连接到运行用 kgdb 扩展的内核机器。您可以接着深入到内核中、设置断点、检查数据并进行其它操作(类似于您在应用程序上使用 gdb 的方式)。这个补丁的主要特点之一就是运行 gdb 的主机在引导过程中连接到目标机器(运行要被调试的内核)。这让您能够尽早开始调试。请注意,补丁为 Linux 内核添加了功能,所以 gdb 可以用来调试 Linux 内核。

使用 kgdb 需要两台机器:一台是开发机器,另一台是测试机器。一条串行线(空调制解调器电缆)将通过机器的串口连接它们。您希望调试的内核在测试机器上运行;gdb 在开发机器上运行。gdb 使用串行线与您要调试的内核通信。

请遵循下面的步骤来设置 kgdb 调试环境:

下载您的 Linux 内核版本适用的补丁。

将组件构建到内核,因为这是使用 kgdb 最简单的方法。(请注意,有两种方法可以构建多数内核组件,比如作为模块或直接构建到内核中。举例来说,日志纪录文件系统(Journaled File System,JFS)可以作为模块构建,或直接构建到内核中。通过使用 gdb 补丁,我们就可以将 JFS 直接构建到内核中。)

应用内核补丁并重新构建内核。

创建一个名为 .gdbinit 的文件,并将其保存在内核源文件子目录中(换句话说就是 /usr/src/linux)。文件 .gdbinit 中有下面四行代码:

set remotebaud 115200

symbol-file vmlinux

target remote /dev/ttyS0

set output-radix 16

将 append=gdb 这一行添加到 lilo,lilo 是用来在引导内核时选择使用哪个内核的引导载入程序。

image=/boot/bzImage-2.4.17

label=gdb2417

read-only

root=/dev/sda8

append="gdb gdbttyS=1 gdb-baud=115200 nmi_watchdog=0"

清单 7 是一个脚本示例,它将您在开发机器上构建的内核和模块引入测试机器。您需要修改下面几项:

best@sfb:用户标识和机器名。

/u

[1] [2] 下一页

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有