扫一扫
分享文章到微信
扫一扫
关注官方公众号
至顶头条
图 2 显示了readelf -l simple的结果(记住“simple”是我们的测试程序的名称)。在Program Headers部分,第一个 LOAD 行提供了关于程序从哪里加载的信息。在 Flg 列,该段被标记为 R(read)E(executable)。VirtAddr是程序开始加载的地址。MemSiz是正在被加载到这个段中的代码长度。把它加到VirtAddr上,这个程序的基本地址范围就是0x400000-0x400990。程序发生崩溃的指令地址为0x400618,在程序的加载范围之内。现在我们知道了问题直接发生在代码中。
如果可执行文件包括调试符号,那么确定哪一行代码导致了问题是可以做到的。对该地址和可执行文件使用addr2line 程序,如下所示:
|
将返回:
|
要研究该问题,可以检查第 34 行。
对于图1中原始的程序崩溃,PSW 为070dc000 c00ab738。要获得指令地址,可减去0x80000000。结果为0x400ab738。这个地址并不准确地落在我们的小程序之内。那么,它是什么呢?是来自共享库的代码。如果对可执行文件运行ldd 命令(ldd simple),将会返回程序运行所需的共享对象的列表,以及该库在那里可用的地址。
|
该指令地址对应于加载libc.so.6的地址。在我们的简单测试案例中,只需要两个共享对象。其他应用程序可能需要更多共享对象,这使得ldd的输出更加复杂。我们将以perl作为例子。 输入:
|
将得到:
|
所需要的一切都在那里了,但是我发现对于这个进程,下面的内容读起来更快一点:
|
现在我们来确定崩溃发生在libc中的何处。假设libc.so.6的加载地址是0x40021000,从指令地址 0x400ab738减去它,结果为0x8a738。这是进入libc.so.6 的偏移。使用nm命令,从libc.so.6转储符号,然后尝试确定该地址位于哪个函数中。对于libc.so.6,nm将生成7,000多行输出。通过对计算得出的偏移部分执行 grep(正则表达式查找程序)可以削减必须检查的数据量。输入:
|
将返回 66 行,在该输出的中间,我们会发现:
|
该偏移落在memcpy中的某个位置。在此例中,一个空指针被当作目标地址传递给了memcpy。我们在何处调用的memcpy呢?问得好。我们可以通过检查输出在日志文件中的寄存器转储来确定目标区域。寄存器14包含执行某个函数调用时的返回地址。根据图1,R14是0x8040066e,它在截去高位之后产生一个地址 0x40066e。这个地址落在我们的程序范围之内,因此可以运行addr2line来确定该地址在何处。输入:
|
将返回:
|
这是我们调用memcpy之后的那一行。关于addr2line的一点补充:如果可执行文件中没有包括调试符号,您将获得??:0 作为响应。
如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。
现场直击|2021世界人工智能大会
直击5G创新地带,就在2021MWC上海
5G已至 转型当时——服务提供商如何把握转型的绝佳时机
寻找自己的Flag
华为开发者大会2020(Cloud)- 科技行者