Chinaunix首页 | 论坛 | 博客
  • 博客访问: 3100943
  • 博文数量: 94
  • 博客积分: 2599
  • 博客等级: 少校
  • 技术积分: 990
  • 用 户 组: 普通用户
  • 注册时间: 2006-08-30 23:23
文章分类

全部博文(94)

文章存档

2012年(1)

2011年(7)

2010年(24)

2009年(61)

2008年(1)

我的朋友

分类: LINUX

2010-03-24 15:13:49

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

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

User Debug 日志记录

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

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

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

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来打印一些程序头信息。

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 作为响应。

补充:

addr2line



ADDR2LINE(1)                 GNU Development Tools                ADDR2LINE(1)




NAME

       addr2line - convert addresses into file names and line numbers.


SYNOPSIS

       addr2line [-b bfdname--target=bfdname]
                 [-C--demangle[=style]]
                 [-e filename--exe=filename]
                 [-f--functions] [-s--basename]
                 [-H--help] [-V--version]
                 [addr addr ...]


DESCRIPTION

       addr2line  translates  program  addresses into file names and line num-
       bers.  Given an address and an executable, it uses the debugging infor-
       mation  in the executable to figure out which file name and line number
       are associated with a given address.

       The executable to use is specified with the -e option.  The default  is
       the file a.out.

       addr2line has two modes of operation.

       In  the first, hexadecimal addresses are specified on the command line,
       and addr2line displays the file name and line number for each  address.

       In  the  second,  addr2line  reads  hexadecimal addresses from standard
       input, and prints the file name and line number  for  each  address  on
       standard output.  In this mode, addr2line may be used in a pipe to con-
       vert dynamically chosen addresses.

       The format of the output is FILENAME:LINENO.  The file  name  and  line
       number  for  each  address  is  printed  on a separate line.  If the -f
       option is used, then each FILENAME:LINENO line is preceded by  a  FUNC-
       TIONNAME line which is the name of the function containing the address.

       If the file name or function name can not be determined, addr2line will
       print two question marks in their place.  If the line number can not be
       determined, addr2line will print 0.


OPTIONS

       The long and short forms of options, shown here  as  alternatives,  are
       equivalent.

       -b bfdname
       --target=bfdname
           Specify  that  the  object-code format for the object files is bfd-
           name.

       -C
       --demangle[=style]
           Decode (demangle) low-level symbol  names  into  user-level  names.
           Besides  removing  any  initial underscore prepended by the system,
           this makes C++ function names readable.  Different  compilers  have
           different  mangling  styles. The optional demangling style argument
           can be used to choose an appropriate demangling style for your com-
           piler.

       -e filename
       --exe=filename
           Specify  the  name  of the executable for which addresses should be
           translated.  The default file is a.out.

       -f
       --functions
           Display function names as well as file and line number information.

       -s
       --basenames
           Display only the base of each file name.


SEE ALSO

       Info entries for binutils.


COPYRIGHT

       Copyright  (c)  1991,  1992,  1993, 1994, 1995, 1996, 1997, 1998, 1999,
       2000, 2001, 2002, 2003, 2004 Free Software Foundation, Inc.

       Permission is granted to copy, distribute and/or modify  this  document
       under  the  terms of the GNU Free Documentation License, Version 1.1 or
       any later version published by the Free Software  Foundation;  with  no
       Invariant  Sections,  with no Front-Cover Texts, and with no Back-Cover
       Texts.  A copy of the license is included in the section entitled ‘‘GNU
       Free Documentation License’’.



binutils-2.15.94.0.2.2            2005-06-29                      ADDR2LINE(1)
阅读(1192) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~