选择显示字体大小

gdb (gnu 调试器):基础


摘要
关于调试 linux 代码的有用技巧(2004-03-02 10:31:01)
by 泛舟, 出处:http://www-900.ibm.com/developerworks/cn/linux/tips/l-gdb/index2.shtml

   就调试本机可执行文件(即不是 java* 或 perl 等)而言,使用 gdb 就对了。gdb 可用于源代码级调试,以及跟踪没有源代码的程序或检查某个终止的程序留下的核心文件。遗憾的是,当您从来没有使用过它,或者已经有一段时间没有使用它时,使用它来做这些工作可能很困难。图 1 展示了使用 gdb 来进行调试所需的每个命令。


command description
file load program
b set breakpoint
r run
c continue
s step (line)
si step (machine instruction)
n next (step over function call)
finish run until function returns
i r show all registers
i r show specific register
l list source
p display value
set args set command line arguments

图 1

   要将 gdb 用作源代码级调试器,请确保在包括调试符号的情况下编译程序;这就是 gcc 的 -g 选项。对于启动 gdb,您可以通过输入 gdb programname(此例中是 gdb simple),或者通过单独运行 gdb 本身并使用 file 命令加载可执行文件来达到目的。

   要设置基本的断点,您可以在某个函数名称或行号上中断。例如,b 27 将在当前文件的第 27 行上设置了一个断点。有两种使用函数名称的方式:b main 在函数 main 中的第一行可执行代码上中断,b *main 在 main 的入口地址上设置一个断点(如果打算单步调试函数的每条指令,这样是很有用的)。

   一旦设置了第一个断点,可使用 run 或 r 来启动程序并运行到第一个断点。还可以不带任何断点运行程序,如果您不知道程序是在何处崩溃的,这样将很有帮助。当您命中一个断点 c 或 continue 时,程序将恢复执行,直至命中下一个断点。

   step“单步”调试源代码行。step instruction (si) 单步调试机器代码行(当您单步调试优化过的代码时,si 指令可能特别有用,这将在后面介绍)。 next 工作起来就像 step,但是它不跟踪进入函数调用(如果的确错误地跟踪进入了函数调用,可使用 finish 来完成该函数,然后在它返回的地方中断)。

   单独的 info register(i r)本身显示所有寄存器的值(浮点值除外),不过您可以指定一个寄存器名称。在 31 位系统上,通用寄存器被命名为 gpr0、gpr1、gpr2,等等;在 64 位系统上,它们被命名为 r0、r1、r2,等等。浮点寄存器遵循相同的命名约定:在 31 位系统上是 fpr0、fpr1、fpr2,等等;在 64 位系统上是 f0、f1、f2,等等。

   “l”列出程序当前停止位置周围的源代码。您还可以指定开始列出代码的行号或要列出的函数名称。print 允许您打印程序中任何变量的值。 print 的一个最好的优点在于,它会为您取出一个结构中的所有值,或允许您直接引用该结构的一部分:


breakpoint 1, main () at simple.c:30
30 boink.boik = &r1;
(gdb) print boink
$3 = {boik = 0x0}
(gdb) print boink.boik
$4 = (int *) 0x0

   最后,set args 为程序设置命令行参数。您也可以在执行 run 时指定命令行参数,但是 set args 将使参数在 run 的多次执行中都有效。


gdb post mortem

   当程序意外地终止时,内核会尝试产生一个核心文件,以图判断发生了什么错误。然而,核心文件通常不是在默认设置值下产生的。这可以使用 ulimit 命令来改变。ulimit -c unlimited 帮助确保您获得应用程序的完整核心文件。

   虽然核心文件当前仅提供多线程应用程序中的有限的值,不过 2.5 版的开发内核已开始处理这个问题。预计 2.6 版的内核中会提供一些理想的线程改进。


command description
file load program
core load core file
bt back trace
where same as back trace
i f frame information
up move up stack
down move down stack
frame jump to frame
disassem display function's machine code
i locals display local variable values

图 2

   图 2 突出显示了一系列便利的 post mortem 命令。


(gdb) file simple
reading symbols from simple...done.
(gdb) core core
core was generated by `./simple'.
program terminated with signal 11, segmentation fault.
reading symbols from /lib/libc.so.6...done.
loaded symbols for /lib/libc.so.6
reading symbols from /lib/ld.so.1...done.
loaded symbols for /lib/ld.so.1
#0 0x400ab738 in memcpy () from /lib/libc.so.6
(gdb) where
#0 0x400ab738 in memcpy () from /lib/libc.so.6
#1 0x40066e in main () at simple.c:34
#2 0x40041eb8 in __libc_start_main () from /lib/libc.so.6
#3 0x4004ac in _start ()
(gdb) i f
stack level 0, frame at 0x7ffff7a0:
pswa = 0x400ab738 in memcpy; saved pswa 0x0
(frameless), called by frame at 0x7ffff7a0
arglist at 0x7ffff7a0, args:
locals at 0x7ffff7a0, previous frame's sp is 0x0
(gdb) up
#1 0x40066e in main () at simple.c:34
34 memcpy (doink.boik, boink.boik, sizeof(boink.boik));
(gdb) i locals
doink = {boik = 0x4019a0}
boink = {boik = 0x0}
(gdb) ptype boink.boik
type = int *
(gdb) print *boink.boik
cannot access memory at address 0x0
(gdb) print *doink.boik
$1 = 4

图 3

   图 3 简要显示了一个核心程序的完整运行过程。同样,我们使用了 simple 程序。 但不是手动加载程序和核心文件,而是从命令行调入:


gdb simple core

   在加载符号之后,gdb 将指出程序在何处终止。注意当前帧 #0 包含前一节中计算的地址。gdb 将在 31 位系统上截去高位,仅显示指令地址。 还要注意帧 #1 包含 gpr14 中的返回地址。

   接着往下看,i f 提供了关于当前堆栈帧的信息。在堆栈帧中往上移到 main,这就是我们离开该帧的地方(即调用 memcpy 的地方)。简单的 i locals 提供了传递给 memcpy 的变量的值,其中一个变量 boink.boik 的值为 0x0。使用 ptype 来检查变量类型,这样将确认它是一个整型指针,并且如果目的是为了拷贝内容到其中,它就不应该是 0x0。最后一个选项是使用 print,通过一个星号(*)来解除指针引用,以便接收值。

处理优化过的代码

   先前,我曾提到当您在源代码级调试优化过的代码时,gdb 可能变得有点棘手。编译器优化一些代码的执行顺序以最大化性能。图 4 显示了这样一个例子。您可以看到行号如何从 32 切换到 30 然后又切换回 32。


(gdb) break main
breakpoint 1 at 0x800007a8: file simple.c, line 32.
(gdb) r
starting program: /home/grundym/foo/simple
breakpoint 1, main () at simple.c:32
32 do_one_thing(&doink);
(gdb) s
30 doink.boik = &r1;
(gdb)
32 do_one_thing(&doink);
(gdb)
do_one_thing (pnum_times=0x1fffffff690) at simple.c:47
47 for (i = 0; i < 4; i++) {

图 4

   如何处理这种情况呢?使用 si 和 ni(next instruction;它类似 si,但是会跳过子例程调用)将非常有帮助。 在这个层次上,很好理解 zarchitecture 是有所帮助的。


(gdb) break *main
breakpoint 1 at 0x80000794: file simple.c, line 27.
(gdb) display /i &#36;pswa
(gdb) r
starting program: /home/grundym/foo/simple

breakpoint 1, main () at simple.c:27
27 {
1: x/i &#36;pswa 0x80000794 : eb af f0 50 00 24 stmg %r10,%r15,80(%r15)
(gdb) si
0x8000079a 27 {
1: x/i &#36;pswa 0x8000079a : b9 04 00 1f lgr %r1,%r15
(gdb)
0x8000079e 27 {
1: x/i &#36;pswa 0x8000079e : a7 fb ff 58 aghi %r15,-168
(gdb)
0x800007a2 in main () at simple.c:27
27 {
1: x/i &#36;pswa 0x800007a2 : e3 10 f0 00 00 24 stg %r1,0(%r15)
(gdb)
32 do_one_thing(&doink);
1: x/i &#36;pswa 0x800007a8 : 41 c0 f0 a0 la %r12,160(%r15)
(gdb)
30 doink.boik = &r1;
1: x/i &#36;pswa 0x800007ac : c0 10 00 00 08 c2 larl %r1,0x80001930
(gdb)
0x800007b2 30 doink.boik = &r1;
1: x/i &#36;pswa 0x800007b2 : e3 10 f0 a0 00 24 stg %r1,160(%r15)
(gdb)
32 do_one_thing(&doink);
1: x/i &#36;pswa 0x800007b8 : b9 04 00 2c lgr %r2,%r12
(gdb)
0x800007bc 32 do_one_thing(&doink);
1: x/i &#36;pswa 0x800007bc : c0 e5 ff ff ff 68 brasl %r14,0x8000068c
(gdb)
do_one_thing (pnum_times=0x1fffffff7f8) at simple.c:44
44 {
1: x/i &#36;pswa 0x8000068c : eb bf f0 58 00 24 stmg %r11,%r15,88(%r15)
(gdb)

图 5

   图 5 显示了为调试而对程序进行的设置。首先在 main()的地址处设置一个断点,然后设置一个 display。display 是一个表达式,它在每次代码停止执行时打印有关信息。在此例中,display 被设置为显示当前指令地址处的指令。/i 是打印为反汇编代码的格式,而当前指令指针在值/寄存器(value/register)&#36;pswa 中。

   单步调试代码,可以明显看出每条机器指令都与一行 c 代码相关联。 前四行与第 27 行(即函数 main 的开头)相关联。前四行是典型的函数引入操作,它们保存寄存器、堆栈指针并调整堆栈。当关联的行号变为 32 时,我们就设置好了对 do_one_thing() 的函数调用。

   当 display 在工作时,它显示 x /i 作为实际数据显示之前的命令。x 是检查内存的命令。/i 是以指令格式来格式化;/x 将以 16 进制格式来格式化;而 /a 将以 16 进制来格式化。然而,您应该在尽可能的地方把该值看作是地址,并解析符号名称。


display /i &#36;pswa
display /x &#36;pswm
display /a &#36;r0
display /a &#36;r1
display /a &#36;r2
display /a &#36;r3
display /a &#36;r4
display /a &#36;r5
display /a &#36;r6
display /a &#36;r7
display /a &#36;r8
display /a &#36;r9
display /a &#36;r10
display /a &#36;r11
display /a &#36;r12
display /a &#36;r13
display /a &#36;r12
display /a &#36;r14
display /a &#36;r15
display /10i &#36;pswa

图 6

   当在指令级工作时,设置一些显示可能是有所帮助的。您可以将所有 display 命令放在一个文件中,并在命令行上使用 -x 选项来指定它。图 6 包含了工作在汇编程序级时通常使用的 display 命令。


breakpoint 1, main () at simple.c:27
27 {
20: x/10i &#36;pswa
0x80000794 : eb af f0 50 00 24 stmg %r10,%r15,80(%r15)
0x8000079a : b9 04 00 1f lgr %r1,%r15
0x8000079e : a7 fb ff 58 aghi %r15,-168
0x800007a2 : e3 10 f0 00 00 24 stg %r1,0(%r15)
0x800007a8 : 41 c0 f0 a0 la %r12,160(%r15)
0x800007ac : c0 10 00 00 08 c2 larl %r1,0x80001930
0x800007b2 : e3 10 f0 a0 00 24 stg %r1,160(%r15)
0x800007b8 : b9 04 00 2c lgr %r2,%r12
0x800007bc : c0 e5 ff ff ff 68 brasl %r14,0x8000068c
0x800007c2 : e3 10 f0 a0 00 04 lg %r1,160(%r15)
19: /a &#36;r15 = 0x1fffffff698
18: /a &#36;r14 = 0x10000057b04 <__libc_start_main+260>
17: /a &#36;r12 = 0x10000019108 <__curbrk+280>
16: /a &#36;r13 = 0x8006c9be
15: /a &#36;r12 = 0x10000019108 <__curbrk+280>
14: /a &#36;r11 = 0x1fffffff7f8
13: /a &#36;r10 = 0x80000418 <_init>
12: /a &#36;r9 = 0x100000198f8 <_dl_debug_mask>
11: /a &#36;r8 = 0x1000017bee0
10: /a &#36;r7 = 0x1
9: /a &#36;r6 = 0x2
8: /a &#36;r5 = 0x100001803d8
7: /a &#36;r4 = 0x1fffffff808
6: /a &#36;r3 = 0x1fffffff7f8
5: /a &#36;r2 = 0x1
4: /a &#36;r1 = 0x80000794
3: /a &#36;r0 = 0x1ff00000000
2: /x &#36;pswm = 0x705c00180000000
1: x/i &#36;pswa 0x80000794 : eb af f0 50 00 24 stmg %r10,%r15,80(%r15)
(gdb)

图 7

   这个命令打印全部 psw 值、所有通用寄存器和从当前指令地址开始的下 10 行机器代码。图 7 显示了当我们在 main() 处中断时的结果。可以看到,在其中一些寄存器所指向的地方,/a 格式解析是如何使得理解正在发生的事情更加容易的。
结束语
   对于一些可用于 linux 应用程序调试的基本工具以及调试过程本身,本文中的信息应该为您提供了有用的入门信息。

  


 


关键字 本文所属关键字

相关 与本文相关文章

分类 所有文章关键字导航

源码编程相关

Java   Asp   PHP   .Net   XML   C/C++   CGI   VB   Jsp   J2ee   J2se   J2me   EJB   Servlet   Tomcat   Resin   Struts   Weblogic   Eclipse   ANT   GUI   JMS   Web servise   IDEA   Webphere   Hibernate   Spring   Jboss   Applet   Swing   Socket   Javamail   Perl   Ajax   P2P   安全   模式   框架   测试   开源   游戏

SQL数据库相关

My-SQL   Ms-SQL   Access   DB2   Oracle   Sybase   SQLserver   索引   存储过程   加密   数据库   分页   视图  

手机无线相关

3G   Wap   CDMA   GRPS   GSM   IVR   彩信   短信   无线   增值业务

网页设计制作相关

HTML   CSS   网页配色   网页特效   Javascript   VBscript   Dreamweaver   Frontpage   JS   Web   网站设计

网站建设推广相关

建站经验   网站优化   网站排名   推广   Alexa

操作系统/服务器相关

Windows XP   Windows 2000   Windows 2003   Windows Me   Windows 9.x   Linux   UNIX   注册表   操作系统   服务器   应用服务器

图形图像多媒体相关

Photoshop   Fireworks   Flash   Coreldraw   Illustrator   Freehand   Photoimpact   多媒体   图形图像

标准 网站致力的规范

Valid CSS!

无不良内容,无不良广告,无恶意代码

Valid XHTML 1.0 Transitional

creativecommons