博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
linux debug : addr2line追踪出错地址
阅读量:6978 次
发布时间:2019-06-27

本文共 5641 字,大约阅读时间需要 18 分钟。

调 试zSeries上的Linux应用程序类似于调试其他体系结构上的Linux应用程序。对于有经验的Linux开发人员,最大的挑战是理解新的系统体系 结构。对于刚接触Linux的大型机开发人员,掌握新的调试工具似乎是一项令人畏惧的任务。不要害怕。本文将提供一些有用的提示来帮助您入门。

学问来自实践,但是对于调试工具,在没有出现问题而迫使您去修复它们之前,“实践”是不会发生的。考虑到这点,下面将提供让您入门的“速成”指南。

User Debug 日志记录

调试一个崩溃的程序的第一步是弄清哪里出了错。zSeries 上的Linux内核具有这样一个内置特性,它在用户进程崩溃时记录一些基本的调试信息。要启用这个特性,请以 root 用户身份执行如下命令:

<ccid_nobr>
echo 1 >> /proc/sys/kernel/userprocess_debug

当某个进程崩溃时,日志文件(/var/log/messages)中就会给出附加的信息,包括程序终止原因、故障地址,以及包含程序状态字(PSW)、通用寄存器和访问寄存器的简要寄存器转储。

<ccid_nobr>
Mar 31 11:34:28 l02 kernel: User process fault: interruption code 0x10 Mar 31 11:34:28 l02 kernel: failing address: 0 Mar 31 11:34:28 l02 kernel: CPU: 1 Mar 31 11:34:28 l02 kernel: Process simple (pid: 30122, stackpage=05889000) Mar 31 11:34:28 l02 kernel: Mar 31 11:34:28 l02 kernel: User PSW: 070dc000 c00ab738 Mar 31 11:34:28 l02 kernel: task: 05888000 ksp: 05889f08 pt_regs: 05889f68 Mar 31 11:34:28 l02 kernel: User GPRS: Mar 31 11:34:28 l02 kernel: 00000000 004019a0 004019a0 00000000 Mar 31 11:34:28 l02 kernel: 00000003 c00ab732 004008f8 00400338 Mar 31 11:34:28 l02 kernel: 40018ffc 0040061c 40018e34 7ffff800 Mar 31 11:34:28 l02 kernel: 00400434 80400624 8040066e 7ffff800 Mar 31 11:34:28 l02 kernel: User ACRS: Mar 31 11:34:28 l02 kernel: 00000000 00000000 00000000 00000000 Mar 31 11:34:28 l02 kernel: 00000001 00000000 00000000 00000000 Mar 31 11:34:28 l02 kernel: 00000000 00000000 00000000 00000000 Mar 31 11:34:28 l02 kernel: 00000000 00000000 00000000 00000000 Mar 31 11:34:28 l02 kernel: User Code: Mar 31 11:34:28 l02 kernel: 44 40 50 00 07 fe a7 4a 00 01 18 54 18 43 18 35 a8 24 00 00
图 1

图 1 表明程序(名为“simple”)以一个程序中断代码 0x10 终止(操作系统原理表明这是一个段转换错误),而故障地址为 0。毫无疑问,有人使用了空指针。现在我们知道发生了什么,下面需要弄清它发生在何处。

基本的诊断

User Debug日志条目所提供的信息可用于确定程序的崩溃位置。一些可用的工具可帮助解决您可能会遇到的各种程序终止问题。我们将在本文中逐步介绍那些工具。

首 先,让我们检查一下该日志条目中的用户 PSW。该 PSW 包含指令地址、状态码以及关于机器状态的其他信息。眼下,我们仅关心指令地址(第33至第63位)。为简化起见,让我们假设用户PSW是 070dc000 80400618。记住,我们是在考察一个 ESA/390(31 位寻址)PSW。第32位不是指令地址的一部分,它是指示 31 位寻址模式的标志,但是在研究 PSW 值时必须处理它。为了获得实际的指令指针,可把PSW的第二个字减去 0x80000000。结果是一个指令地址 0x400618。为了定位代码,您需要可执行文件中的一些信息。首先使用readelf来打印一些程序头信息。

<ccid_nobr>
Elf file type is EXEC (Executable file) Entry point 0x400474 There are 6 program headers, starting at offset 52 Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align PHDR 0x000034 0x00400034 0x00400034 0x000c0 0x000c0 R E 0x4 INTERP 0x0000f4 0x004000f4 0x004000f4 0x0000d 0x0000d R 0x1 [Requesting program interpreter: /lib/ld.so.1] LOAD 0x000000 0x00400000 0x00400000 0x00990 0x00990 R E 0x1000 LOAD 0x000990 0x00401990 0x00401990 0x000fc 0x00114 RW 0x1000 DYNAMIC 0x0009ac 0x004019ac 0x004019ac 0x000a0 0x000a0 RW 0x4 NOTE 0x000104 0x00400104 0x00400104 0x00020 0x00020 R 0x4 Section to Segment mapping: Segment Sections... 00 01 .interp 02 .interp .note.ABI-tag .hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.got .rela.plt .init .plt .text .fini .rodata 03 .data .eh_frame .dynamic .ctors .dtors .got .bss 04 .dynamic 05 .note.ABI-tag
图 2

图 2 显示了readelf -l simple的结果(记住“simple”是我们的测试程序的名称)。在Program Headers部分,第一个 LOAD 行提供了关于程序从哪里加载的信息。在 Flg 列,该段被标记为 R(read)E(executable)。VirtAddr是程序开始加载的地址。MemSiz是正在被加载到这个段中的代码长度。把它加到 VirtAddr上,这个程序的基本地址范围就是0x400000-0x400990。程序发生崩溃的指令地址为0x400618,在程序的加载范围之 内。现在我们知道了问题直接发生在代码中。

如果可执行文件包括调试符号,那么确定哪一行代码导致了问题是可以做到的。对该地址和可执行文件使用addr2line 程序,如下所示:

<ccid_nobr>
addr2line -e simple 0x400618

将返回:

<ccid_nobr>
/home/devuser/simple.c:34

要研究该问题,可以检查第 34 行。

对 于图1中原始的程序崩溃,PSW 为070dc000 c00ab738。要获得指令地址,可减去0x80000000。结果为0x400ab738。这个地址并不准确地落在我们的小程序之内。那么,它是什么 呢?是来自共享库的代码。如果对可执行文件运行ldd 命令(ldd simple),将会返回程序运行所需的共享对象的列表,以及该库在那里可用的地址。

<ccid_nobr>
libc.so.6 => /lib/libc.so.6 (0x40021000) /lib/ld.so.1 => /lib/ld.so.1 (0x40000000)

该指令地址对应于加载libc.so.6的地址。在我们的简单测试案例中,只需要两个共享对象。其他应用程序可能需要更多共享对象,这使得ldd的输出更加复杂。我们将以perl作为例子。 输入:

<ccid_nobr>
ldd /usr/bin/perl

将得到:

<ccid_nobr>
libnsl.so.1 => /lib/libnsl.so.1 (0x40021000) libdl.so.2 => /lib/libdl.so.2 (0x40039000) libm.so.6 => /lib/libm.so.6 (0x4003d000) libc.so.6 => /lib/libc.so.6 (0x40064000) libcrypt.so.1 => /lib/libcrypt.so.1 (0x4018f000) /lib/ld.so.1 => /lib/ld.so.1 (0x40000000)

所需要的一切都在那里了,但是我发现对于这个进程,下面的内容读起来更快一点:

<ccid_nobr>
ldd /usr/bin/perl | awk ‘{print? $4 “ “ $3 }’ | sort (0x40000000) /lib/ld.so.1 (0x40021000) /lib/libnsl.so.1 (0x40039000) /lib/libdl.so.2 (0x4003d000) /lib/libm.so.6 (0x40064000) /lib/libc.so.6 (0x4018f000) /lib/libcrypt.so.1

现 在我们来确定崩溃发生在libc中的何处。假设libc.so.6的加载地址是0x40021000,从指令地址 0x400ab738减去它,结果为0x8a738。这是进入libc.so.6 的偏移。使用nm命令,从libc.so.6转储符号,然后尝试确定该地址位于哪个函数中。对于libc.so.6,nm将生成7,000多行输出。通过 对计算得出的偏移部分执行 grep(正则表达式查找程序)可以削减必须检查的数据量。输入:

<ccid_nobr>
nm /lib/libc.so.6 | sort | grep 0008a

将返回 66 行,在该输出的中间,我们会发现:

<ccid_nobr>
0008a6fc T memcpy 0008a754 t _wordcopy_fwd_aligned

该 偏移落在memcpy中的某个位置。在此例中,一个空指针被当作目标地址传递给了memcpy。我们在何处调用的memcpy呢?问得好。我们可以通过检 查输出在日志文件中的寄存器转储来确定目标区域。寄存器14包含执行某个函数调用时的返回地址。根据图1,R14是0x8040066e,它在截去高位之 后产生一个地址 0x40066e。这个地址落在我们的程序范围之内,因此可以运行addr2line来确定该地址在何处。输入:

<ccid_nobr>
addr2line -e simple 0x40066e

将返回:

<ccid_nobr>
/home/devuser/simple.c:36

这是我们调用memcpy之后的那一行。关于addr2line的一点补充:如果可执行文件中没有包括调试符号,您将获得??:0 作为响应。

转载地址:http://ueupl.baihongyu.com/

你可能感兴趣的文章
一个COM示例程序
查看>>
通过改进算法来优化程序性能的真实案例(Ransac)
查看>>
head命令
查看>>
软件开发经验总结(一)细节决定软件的成败
查看>>
python tar.gz格式压缩、解压
查看>>
JNDI概述(转载)
查看>>
利用java反射机制 读取配置文件 实现动态类载入以及动态类型转换
查看>>
第 7 章 项目运作
查看>>
PYTHON黑帽编程1.5 使用WIRESHARK练习网络协议分析
查看>>
.NET平台开源项目速览(18)C#平台JSON实体类生成器JSON C# Class Generator
查看>>
C# 格式串(收藏)
查看>>
浅谈SQL Server中统计对于查询的影响
查看>>
WF4 Beta,RC版文章总结
查看>>
WPF 与Surface 2.0 SDK 亲密接触–LibraryContainer 篇
查看>>
C# 对应 Oracle 存储过程 的 SYS_REFCURSOR 应该 传入什么类型的参数?
查看>>
Unity3D移植到自己的Android程序
查看>>
【转】用示例说明索引数据块中出现热块的场景,并给出解决方案
查看>>
HDU 2034 人见人爱A-B
查看>>
【AngularJS】—— 12 独立作用域
查看>>
使用工作集(Working Set)整理项目
查看>>