Linux:一起使用 backtrace()、/proc/self/maps 和 addr2line 会导致无效结果
Linux: using backtrace(), /proc/self/maps and addr2line together results in invalid result
我正在尝试实现一种方法来将我的程序的调用堆栈记录到一个文件中,然后稍后显示它。
以下是步骤:
- 将/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
,然后 returns 更正文件和行号。
问题是如果地址在共享对象中,那么绝对地址将不起作用,而减去的偏移量将起作用。
为什么主可执行文件和共享对象的地址映射方式如此不同?或者这可能是 backtrace
实现特定的,这样它总是 returns 主可执行文件中函数的绝对地址?
Why is there such a difference in the way the addresses are mapped for the main executable and the shared objects?
共享库通常链接到地址 0 并重新定位。非位置可执行文件通常链接在 x86_64
Linux 上的地址 0x400000 并且必须 not 重新定位(否则它不会工作)。
要找出给定的 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
正是您需要减去的 重定位。
我正在尝试实现一种方法来将我的程序的调用堆栈记录到一个文件中,然后稍后显示它。 以下是步骤:
- 将/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
,然后 returns 更正文件和行号。
问题是如果地址在共享对象中,那么绝对地址将不起作用,而减去的偏移量将起作用。
为什么主可执行文件和共享对象的地址映射方式如此不同?或者这可能是 backtrace
实现特定的,这样它总是 returns 主可执行文件中函数的绝对地址?
Why is there such a difference in the way the addresses are mapped for the main executable and the shared objects?
共享库通常链接到地址 0 并重新定位。非位置可执行文件通常链接在 x86_64
Linux 上的地址 0x400000 并且必须 not 重新定位(否则它不会工作)。
要找出给定的 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
正是您需要减去的 重定位。