级别: 初级 Mike GrundyIBM
2004 年 2 月 01 日 这是由两部分组成的关于调试 zSeries* 上的 Linux 应用程序的系列文章中的第 2 部分。请参阅
第 1 部分。
最后,set args 为程序设置命令行参数。您也可以在执行 run 时指定命令行参数,但是 set args 将使参数在 run 的多次执行中都有效。
gdb Post Mortem
当程序意外地终止时,内核会尝试产生一个核心文件,以图判断发生了什么错误。然而,核心文件通常不是在默认设置值下产生的。这可以使用 ulimit 命令来改变。ulimit -c unlimited 帮助确保您获得应用程序的完整核心文件。
虽然核心文件当前仅提供多线程应用程序中的有限的值,不过 2.5 版的开发内核已开始处理这个问题。预计 2.6 版的内核中会提供一些理想的线程改进。
图 2突出显示了一系列便利的 post mortem 命令。
图 3简要显示了一个核心程序的完整运行过程。同样,我们使用了 simple 程序。 但不是手动加载程序和核心文件,而是从命令行调入:
在加载符号之后,gdb 将指出程序在何处终止。注意当前帧 #0 包含前一节中计算的地址。gdb 将在 31 位系统上截去高位,仅显示指令地址。 还要注意帧 #1 包含 gpr14 中的返回地址。
接着往下看,i f 提供了关于当前堆栈帧的信息。在堆栈帧中往上移到 main,这就是我们离开该帧的地方(即调用 memcpy 的地方)。简单的 i locals 提供了传递给 memcpy 的变量的值,其中一个变量 boink.boik 的值为 0x0。使用 ptype 来检查变量类型,这样将确认它是一个整型指针,并且如果目的是为了拷贝内容到其中,它就不应该是 0x0。最后一个选项是使用 print,通过一个星号(*)来解除指针引用,以便接收值。
处理优化过的代码
先前,我曾提到当您在源代码级调试优化过的代码时,gdb 可能变得有点棘手。编译器优化一些代码的执行顺序以最大化性能。
图
4显示了这样一个例子。您可以看到行号如何从 32 切换到 30 然后又切换回 32。
如何处理这种情况呢?使用 si 和 ni(next instruction;它类似 si,但是会跳过子例程调用)将非常有帮助。 在这个层次上,很好理解 zArchitecture 是有所帮助的。
图 5显示了为调试而对程序进行的设置。首先在 main()的地址处设置一个断点,然后设置一个 display。display 是一个表达式,它在每次代码停止执行时打印有关信息。在此例中,display 被设置为显示当前指令地址处的指令。/i 是打印为反汇编代码的格式,而当前指令指针在值/寄存器(value/register)$pswa 中。
单步调试代码,可以明显看出每条机器指令都与一行 c 代码相关联。 前四行与第 27 行(即函数 main 的开头)相关联。 前四行是典型的函数引入操作,它们保存寄存器、堆栈指针并调整堆栈。当关联的行号变为
32 时,我们就设置好了对 do_one_thing() 的函数调用。
当 display 在工作时,它显示 x /i 作为实际数据显示之前的命令。x 是检查内存的命令。/i 是以指令格式来格式化;/x 将以 16 进制格式来格式化;而 /a 将以 16 进制来格式化。然而,您应该在尽可能的地方把该值看作是地址,并解析符号名称。
当在指令级工作时,设置一些显示可能是有所帮助的。您可以将所有 display 命令放在一个文件中,并在命令行上使用 -x 选项来指定它。
图 6包含了工作在汇编程序级时通常使用的 display 命令。
这个命令打印全部 PSW 值、所有通用寄存器和从当前指令地址开始的下 10 行机器代码。
图 7显示了当我们在 main() 处中断时的结果。可以看到,在其中一些寄存器所指向的地方,/a 格式解析是如何使得理解正在发生的事情更加容易的。
结束语
对于一些可用于 Linux 应用程序调试的基本工具以及调试过程本身,本文中的信息应该为您提供了有用的入门信息。
关于作者
对本文的评价
|