gpt4 book ai didi

assembly - DMD 堆栈跟踪中的地址有何含义?

转载 作者:行者123 更新时间:2023-12-03 06:32:49 25 4
gpt4 key购买 nike

我编译文件 stacktrace.d:void main(){assert(false);} 关闭 ASLR,运行时我得到:

core.exception.AssertError@stacktrace.d(2): Assertion failure
----------------
??:? _d_assertp [0x55586ed8]
??:? _Dmain [0x55586e20]

objdump -t stacktrace|grep _Dmain 给出

0000000000032e0c w F .text 0000000000000019 _Dmain

如果我运行gdb -q -nx -ex start -ex 'disas/rs _Dmain' -ex q stacktrace:

...
Dump of assembler code for function _Dmain:
0x0000555555586e0c <+0>: 55 push %rbp
0x0000555555586e0d <+1>: 48 8b ec mov %rsp,%rbp
=> 0x0000555555586e10 <+4>: be 02 00 00 00 mov $0x2,%esi
0x0000555555586e15 <+9>: 48 8d 3d 44 c0 02 00 lea 0x2c044(%rip),%rdi # 0x5555555b2e60 <_TMP0>
0x0000555555586e1c <+16>: e8 47 00 00 00 callq 0x555555586e68 <_d_assertp>
0x0000555555586e21 <+21>: 31 c0 xor %eax,%eax
0x0000555555586e23 <+23>: 5d pop %rbp
0x0000555555586e24 <+24>: c3 retq

因此,即使前两个 0x55 字节被截断,堆栈跟踪中给出的 0x...86e20 也与指令的开头不匹配。

最佳答案

好的,我刚刚从评论中找到了证明我直觉的源代码部分。

这是添加时的 git 责任:https://github.com/dlang/druntime/blame/bc940316b4cd7cf6a76e34b7396de2003867fbef/src/core/runtime.d#L756

唉,提交消息并不是非常丰富,但代码本身,加上我的内存,让我非常确信。

这就是 druntime 库中的文件 core/runtime.d。截至撰写本文时,它恰好位于第 756 行

enum CALL_INSTRUCTION_SIZE = 1; // it may not be 1 but it is good enough to get
// in CALL instruction address range for backtrace
callstack[numframes++] = *(stackPtr + 1) - CALL_INSTRUCTION_SIZE;

请注意,抛出异常时,callstack 变量会生成当前调用的副本。当请求实际将其写出时,跟踪打印机将查看该数组以确定要写入的内容。 (看,查找调试信息以打印文件/行号和函数名称真的很慢,所以它只在必须时才这样做,以保持正常的异常使用 - 当它被抛出并稍后捕获时 - 更快。)

无论如何,我记得回溯曾经打印过错误的行。它将打印包含下一条指令的代码行 - 这可能在源代码中距实际的断言/抛出语句相当远,使得打印的帮助不大。如果您查看 git Blame 链接,您会看到旧代码用于直接从堆栈中复制地址。

call指令的工作原理是将返回地址压入堆栈,然后跳转到子例程地址。返回地址紧接在调用指令之后,因此当CPU返回那里时,它不会再次运行调用。这就是为什么旧代码会显示错误的行号,错误地将责任归咎于以下指令。

新代码会稍微倒回该地址,使其返回到调用指令本身 - 从而将打印的函数放在它所属的行上。但是,在 x86 上,有一些不同的调用指令,我什至不确定是否可以正确倒回 - 您只能通过查看操作码来确定指令的实际大小,只有当您知道指令的大小,或者像CPU本身一样按正向顺序读取代码时,您才知道操作码在哪里。此外,在其他处理器架构上,大小也会有所不同。

就像该行中的评论所说,我们实际上不必做到完美。此回溯的目标是让用户查看正确的位置。调试信息使用一种边界框 - 如果您位于该函数或源代码行的起始地址或之后,但尚未到达下一个函数/行的起始地址,则它认为您在那里。它不知道也不关心代码的小数行。

因此,只需假设大小为 1 - 足以使其回到该边界,就大大简化了实现。

我敢打赌,gdb 在内部做了类似的事情,只是它的打印机隐藏了这一点,直接在其回溯中显示堆栈的返回地址。 (顺便说一句,有趣的提示:在 gdb 中运行程序时,将 --DRT-trapExceptions=no 传递给程序的命令行参数。然后,它会在程序仍在运行时捕获抛出点,而不是打印消息并说程序退出并显示代码 1!)

druntime 打印代码也可以在打印之前返回 +1 以隐藏这个内部实现黑客......但是,嗯。返回地址也不是实际发生调用的位置,无论如何,您都需要在反汇编程序中查看上面的内容。甚至 gdb 实际上也不显示调用的地址(至少不是我的旧版本,也许新版本会显示)。但如果它是 grep 反汇编中的一个值,那可能会很好,不管怎样......如果你想向 druntime 做一个 PR,我会支持你(注意我在那里没有权限,但可以帮助评论)。

但这至少明确地解释了现状。

关于assembly - DMD 堆栈跟踪中的地址有何含义?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54550999/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com