Linux:同时使用backtrace()、/proc/self/maps和addr2line会导致无效结果

Linux:同时使用backtrace()、/proc/self/maps和addr2line会导致无效结果,c,linux,elf,backtrace,C,Linux,Elf,Backtrace,我正在尝试实现一种方法,将程序的调用堆栈记录到一个文件中,然后在以后显示它。 以下是步骤: 将/proc/self/maps的内容写入日志文件。 在本例中,/proc/self/maps的内容是: 00400000-05cdc000 r-xp 00000000:51 12974779926 helloworld 这意味着helloworld程序的基址为0x400000 在程序中,每当一个有趣的代码需要记录其调用堆栈时,我都会使用函数backtrace()获取调用堆栈的地址,然后写入日志文件

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

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

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

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

共享库通常在地址0处链接并重新定位。非位置可执行文件通常链接在
x86_64
Linux上的地址0x400000处,并且不能重新定位(否则它将无法工作)

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

请注意,存在位置独立的可执行文件,对于它们,您需要执行减法运算

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

实际上,您需要做的是在运行时从linked at address中减去load address以获得重定位(非饼图可执行文件为0,共享库为非0),然后从
backtrace
记录的程序计数器中减去重定位以获得符号值


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

addr2line仅识别VMA地址。正如您已经发现的,对于共享lib,VMA地址从@0开始,但是对于exe,它们可以从@0x400000开始。这里有另一个解释和一张图。