【问题标题】:Linux: using backtrace(), /proc/self/maps and addr2line together results in invalid resultLinux:同时使用 backtrace()、/proc/self/maps 和 addr2line 会导致无效结果
【发布时间】:2020-03-12 06:23:21
【问题描述】:

我正在尝试实现一种方法,将我的程序的调用堆栈记录到一个文件中,然后再显示它。 步骤如下:

  • 将 /proc/self/maps 的内容写入日志文件。
    • 在本例中,/proc/self/maps 的内容为:
    • 00400000-05cdc000 r-xp 00000000 00:51 12974779926 helloworld
    • 也就是说helloworld程序的基地址是0x400000。
  • 在程序中,每当感兴趣的代码需要记录其调用堆栈时,我使用函数backtrace() 获取调用堆栈的地址,然后写入日志文件。假设这个例子中的调用栈是:
    • 0x400001
    • 0x400003
  • 稍后,在一个单独的日志查看器程序中,日志文件被打开并解析。调用堆栈中的地址将被程序的基地址减去。在这种情况下:
    • 0x400001 - 0x400000 = 1
  • 然后我使用 addr2line 程序使用这个扣除的偏移量来获取行号:
    • addr2line -fCe hellowork 0x1
    • 但是这会产生??? 结果,即无效的偏移量。
  • 但是如果我不扣除调用堆栈的地址,而是将实际值传递给 add2line 命令:
    • addr2line -fCe hellowork 0x400001,则返回正确的文件和行号。

问题是如果地址在共享对象中,那么绝对地址将不起作用,而扣除的偏移量将起作用。

为什么主可执行文件和共享对象的地址映射方式存在如此大的差异?或者这可能是 backtrace 实现特定的,这样它总是返回主可执行文件中函数的绝对地址?

【问题讨论】:

  • addr2line 仅识别 VMA 地址。就像您已经发现的那样,对于共享库,VMA 地址从 @ 0 开始,但对于 EXE,它们可以从 @ 0x400000 开始。这是另一种带有图纸的解释。 stackoverflow.com/questions/4636456/…

标签: c linux elf backtrace


【解决方案1】:

为什么主可执行文件和共享对象的地址映射方式存在如此大的差异?

共享库通常链接在地址 0 并重新定位。非位置可执行文件通常链接到x86_64 Linux 上的地址 0x400000,并且必须重新定位(否则它将不起作用)。

要找出给定 ELF 二进制文件的链接位置,请查看第一个 PT_LOAD 段的 p_vaddr 地址(readelf -Wl foo 会告诉你)。此外,只能重定位 ET_DYN ELF 二进制文件,而不能重定位 ET_EXEC 二进制文件。

请注意,存在与位置无关的可执行文件,您需要对它们进行减法。

请注意,共享库通常链接在地址 0(因此减法有效),但它们并非必须。在共享库上运行 prelink 将导致共享库链接到非 0 地址,然后您使用的减法将不起作用

真的,您需要做的是从链接地址中减去运行时加载地址以获得重定位(对于非 PIE 可执行文件,该地址为 0,对于共享库,则为非 0),然后减去该重定位从backtrace记录的程序计数器中获取符号值。

最后,如果您使用 dl_iterate_phdr 遍历所有加载的 ELF 图像,它提供的 dlpi_addr 正是您需要减去的重定位。

【讨论】:

    猜你喜欢
    • 2010-11-26
    • 1970-01-01
    • 2015-01-10
    • 2011-09-06
    • 2018-04-07
    • 2016-04-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多