Linux debug : addr2line追踪出错地址-2011年

        学问来自实践,但是对于调试工具,在没有出现问题而迫使您去修复它们之前,“实践”是不会发生的。考虑到这点,下面将提供让您入门的“速成”指南。本文转自2011年的文章,里面的输出和文件所在的位置可能已经发生了变化,但是思路却仍然可用。

User Debug 日志记录

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

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

在ubuntu 20.04上没有上述文件,应该是/proc/sys/debug/exception-trace,默认为1(打开)。

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

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

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

以下面的test程序为例:

#include <iostream>
#include <memory.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

using namespace std;

int main()
{

    char *ptr = "test";
    strcpy(ptr, "TEST");
}

在/var/log/kern.log中发现如下错误:

Aug 28 00:06:10 u20 kernel: [ 1369.831964] test[4492]: segfault at 55c66ab4e005 ip 000055c66ab4d180 sp 00007ffc2facb4b0 error 6 in test[55c66ab4d000+1000]
Aug 28 00:06:10 u20 kernel: [ 1369.832055] Code: 1f 00 c3 0f 1f 80 00 00 00 00 f3 0f 1e fa e9 77 ff ff ff f3 0f 1e fa 55 48 89 e5 48 8d 05 8d 0e 00 00 48 89 45 f8 48 8b 45 f8 <c7> 00 54 45 53 54 c6 40 04 00 b8 00 00 00 00 5d c3 f3 0f 1e fa 55

在/var/log/syslog中,发现如下错误:

Aug 28 00:06:10 u20 kernel: [ 1369.831964] test[4492]: segfault at 55c66ab4e005 ip 000055c66ab4d180 sp 00007ffc2facb4b0 error 6 in test[55c66ab4d000+1000]
Aug 28 00:06:10 u20 kernel: [ 1369.832055] Code: 1f 00 c3 0f 1f 80 00 00 00 00 f3 0f 1e fa e9 77 ff ff ff f3 0f 1e fa 55 48 89 e5 48 8d 05 8d 0e 00 00 48 89 45 f8 48 8b 45 f8 <c7> 00 54 45 53 54 c6 40 04 00 b8 00 00 00 00 5d c3 f3 0f 1e fa 55
Aug 28 00:06:10 u20 systemd[1]: Created slice system-systemd\x2dcoredump.slice.
Aug 28 00:06:10 u20 systemd[1]: Started Process Core Dump (PID 4493/UID 0).
Aug 28 00:06:11 u20 systemd-coredump[4494]: Process 4492 (test) of user 1000 dumped core.#012#012Stack trace of thread 4492:#012#0  0x000055c66ab4d180 n/a (/home/ok/test/memcpySegfault/test + 0x1180)#012#1  0x00007fdf64a470b3 __libc_start_main (libc.so.6 + 0x240b3)#012#2  0x000055c66ab4d0ae n/a (/home/ok/test/memcpySegfault/test + 0x10ae)

使用catchsegv来抓一下错误:

ok@u20:~/test/memcpySegfault$ catchsegv ./test 
Segmentation fault (core dumped)
*** Segmentation fault
Register dump:

 RAX: 0000561a3837e005   RBX: 0000561a3837d200   RCX: 0000000000000100
 RDX: 00007ffca6d1e3a8   RSI: 00007ffca6d1e398   RDI: 0000000000000001
 RBP: 00007ffca6d1e2a0   R8 : 0000000000000000   R9 : 00007fc2f6b77ec0
 R10: 00007fc2f69fa1d5   R11: 00007fc2f6aee5a0   R12: 0000561a3837d080
 R13: 00007ffca6d1e390   R14: 0000000000000000   R15: 0000000000000000
 RSP: 00007ffca6d1e2a0

 RIP: 0000561a3837d180   EFLAGS: 00010246

 CS: 0033   FS: 0000   GS: 0000

 Trap: 0000000e   Error: 00000006   OldMask: 00000000   CR2: 3837e005

 FPUCW: 0000037f   FPUSW: 00000000   TAG: 00000000
 RIP: 00000000   RDP: 00000000

 ST(0) 0000 0000000000000000   ST(1) 0000 0000000000000000
 ST(2) 0000 0000000000000000   ST(3) 0000 0000000000000000
 ST(4) 0000 0000000000000000   ST(5) 0000 0000000000000000
 ST(6) 0000 0000000000000000   ST(7) 0000 0000000000000000
 mxcsr: 1f80
 XMM0:  0000000000000000000000003a7e1a11 XMM1:  0000000000000000000000003a7e1a11
 XMM2:  0000000000000000000000003a7e1a11 XMM3:  0000000000000000000000003a7e1a11
 XMM4:  0000000000000000000000003a7e1a11 XMM5:  0000000000000000000000003a7e1a11
 XMM6:  0000000000000000000000003a7e1a11 XMM7:  0000000000000000000000003a7e1a11
 XMM8:  0000000000000000000000003a7e1a11 XMM9:  0000000000000000000000003a7e1a11
 XMM10: 0000000000000000000000003a7e1a11 XMM11: 0000000000000000000000003a7e1a11
 XMM12: 0000000000000000000000003a7e1a11 XMM13: 0000000000000000000000003a7e1a11
 XMM14: 0000000000000000000000003a7e1a11 XMM15: 0000000000000000000000003a7e1a11

Backtrace:
./test(+0x1180)[0x561a3837d180]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf3)[0x7fc2f67d60b3]
./test(+0x10ae)[0x561a3837d0ae]

Memory map:

561a3837c000-561a3837d000 r--p 00000000 08:01 804755 /home/ok/test/memcpySegfault/test
561a3837d000-561a3837e000 r-xp 00001000 08:01 804755 /home/ok/test/memcpySegfault/test
561a3837e000-561a3837f000 r--p 00002000 08:01 804755 /home/ok/test/memcpySegfault/test
561a3837f000-561a38380000 r--p 00002000 08:01 804755 /home/ok/test/memcpySegfault/test
561a38380000-561a38381000 rw-p 00003000 08:01 804755 /home/ok/test/memcpySegfault/test
561a391b2000-561a391d3000 rw-p 00000000 00:00 0 [heap]
7fc2f6643000-7fc2f6648000 rw-p 00000000 00:00 0
7fc2f6648000-7fc2f664b000 r--p 00000000 08:06 787088 /usr/lib/x86_64-linux-gnu/libgcc_s.so.1
7fc2f664b000-7fc2f665d000 r-xp 00003000 08:06 787088 /usr/lib/x86_64-linux-gnu/libgcc_s.so.1
7fc2f665d000-7fc2f6661000 r--p 00015000 08:06 787088 /usr/lib/x86_64-linux-gnu/libgcc_s.so.1
7fc2f6661000-7fc2f6662000 r--p 00018000 08:06 787088 /usr/lib/x86_64-linux-gnu/libgcc_s.so.1
7fc2f6662000-7fc2f6663000 rw-p 00019000 08:06 787088 /usr/lib/x86_64-linux-gnu/libgcc_s.so.1
7fc2f6663000-7fc2f6670000 r--p 00000000 08:06 785010 /usr/lib/x86_64-linux-gnu/libm-2.31.so
7fc2f6670000-7fc2f6717000 r-xp 0000d000 08:06 785010 /usr/lib/x86_64-linux-gnu/libm-2.31.so
7fc2f6717000-7fc2f67b0000 r--p 000b4000 08:06 785010 /usr/lib/x86_64-linux-gnu/libm-2.31.so
7fc2f67b0000-7fc2f67b1000 r--p 0014c000 08:06 785010 /usr/lib/x86_64-linux-gnu/libm-2.31.so
7fc2f67b1000-7fc2f67b2000 rw-p 0014d000 08:06 785010 /usr/lib/x86_64-linux-gnu/libm-2.31.so
7fc2f67b2000-7fc2f67d4000 r--p 00000000 08:06 784985 /usr/lib/x86_64-linux-gnu/libc-2.31.so
7fc2f67d4000-7fc2f694c000 r-xp 00022000 08:06 784985 /usr/lib/x86_64-linux-gnu/libc-2.31.so
7fc2f694c000-7fc2f699a000 r--p 0019a000 08:06 784985 /usr/lib/x86_64-linux-gnu/libc-2.31.so
7fc2f699a000-7fc2f699e000 r--p 001e7000 08:06 784985 /usr/lib/x86_64-linux-gnu/libc-2.31.so
7fc2f699e000-7fc2f69a0000 rw-p 001eb000 08:06 784985 /usr/lib/x86_64-linux-gnu/libc-2.31.so
7fc2f69a0000-7fc2f69a4000 rw-p 00000000 00:00 0
7fc2f69a4000-7fc2f6a3a000 r--p 00000000 08:06 787081 /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.28
7fc2f6a3a000-7fc2f6b2b000 r-xp 00096000 08:06 787081 /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.28
7fc2f6b2b000-7fc2f6b74000 r--p 00187000 08:06 787081 /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.28
7fc2f6b74000-7fc2f6b75000 ---p 001d0000 08:06 787081 /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.28
7fc2f6b75000-7fc2f6b80000 r--p 001d0000 08:06 787081 /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.28
7fc2f6b80000-7fc2f6b83000 rw-p 001db000 08:06 787081 /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.28
7fc2f6b83000-7fc2f6b86000 rw-p 00000000 00:00 0
7fc2f6b9c000-7fc2f6b9d000 r--p 00000000 08:06 784983 /usr/lib/x86_64-linux-gnu/libSegFault.so
7fc2f6b9d000-7fc2f6ba0000 r-xp 00001000 08:06 784983 /usr/lib/x86_64-linux-gnu/libSegFault.so
7fc2f6ba0000-7fc2f6ba1000 r--p 00004000 08:06 784983 /usr/lib/x86_64-linux-gnu/libSegFault.so
7fc2f6ba1000-7fc2f6ba2000 r--p 00004000 08:06 784983 /usr/lib/x86_64-linux-gnu/libSegFault.so
7fc2f6ba2000-7fc2f6ba3000 rw-p 00005000 08:06 784983 /usr/lib/x86_64-linux-gnu/libSegFault.so
7fc2f6ba3000-7fc2f6ba5000 rw-p 00000000 00:00 0
7fc2f6ba5000-7fc2f6ba6000 r--p 00000000 08:06 784961 /usr/lib/x86_64-linux-gnu/ld-2.31.so
7fc2f6ba6000-7fc2f6bc9000 r-xp 00001000 08:06 784961 /usr/lib/x86_64-linux-gnu/ld-2.31.so
7fc2f6bc9000-7fc2f6bd1000 r--p 00024000 08:06 784961 /usr/lib/x86_64-linux-gnu/ld-2.31.so
7fc2f6bd2000-7fc2f6bd3000 r--p 0002c000 08:06 784961 /usr/lib/x86_64-linux-gnu/ld-2.31.so
7fc2f6bd3000-7fc2f6bd4000 rw-p 0002d000 08:06 784961 /usr/lib/x86_64-linux-gnu/ld-2.31.so
7fc2f6bd4000-7fc2f6bd5000 rw-p 00000000 00:00 0
7ffca6cff000-7ffca6d20000 rw-p 00000000 00:00 0 [stack]
7ffca6def000-7ffca6df3000 r--p 00000000 00:00 0 [vvar]
7ffca6df3000-7ffca6df5000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0 [vsyscall]
ok@u20:~/test/memcpySegfault$ 

 基本的诊断

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

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

首先使用readelf来打印一些程序头信息。

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 程序,如下所示:

addr2line -e simple 0x400618

将返回:

/home/devuser/simple.c:34

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

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

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

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

ldd /usr/bin/perl

将得到:

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)

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

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(正则表达式查找程序)可以削减必须检查的数据量。输入:

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

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

0008a6fc T memcpy
0008a754 t _wordcopy_fwd_aligned

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

addr2line -e simple 0x40066e

将返回:

/home/devuser/simple.c:36

这是我们调用memcpy之后的那一行。

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

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值