DMD堆栈跟踪中的地址是什么意思?

rub*_*ion 4 assembly d dmd

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

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

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   
Run Code Online (Sandbox Code Playgroud)

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

Ada*_*ppe 5

好的,我刚刚找到源代码中的一部分,可以从注释中证明我的直觉。

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

the,提交消息不是超级有用的信息,但是代码本身以及我的记忆使我非常确信。

因此,这是文件core/runtime.ddruntime库。在撰写本文时,它恰好位于第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;
Run Code Online (Sandbox Code Playgroud)

请注意,callstack抛出异常时,那里的变量会复制当前调用。跟踪打印机在被要求将其实际写出时,将查看该阵列以确定要写的内容。(请参阅,查找调试信息以打印文件/行号和函数名的确很慢,因此,只有在必须这样做时才能这样做,以保持正常的异常使用-稍后将其抛出并捕获-更快。)

无论如何,我记得当回溯线用于打印错误的行时。它将打印包含下一条指令的代码行-可能在源中与实际的assert / throw语句有一段距离,从而使打印不那么有用。如果您查看该git blame链接,将会看到用于从字面上直接复制地址的旧代码。

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

新的代码回退该地址以使其返回到调用指令本身,从而将打印的函数放在它所属的行上。但是,在x86上,有几个不同的调用指令,我什至不知道它是可以正确地倒带-你只能通过看操作码确定指令的实际大小,你只知道那里的操作码是如果您知道指令的大小,还是像cpu本身一样按正序读取代码。而且,在其他处理器体系结构上,大小将有所不同。

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

因此,仅假设大小为1,就可以极大地简化实现过程-足以使其回到该边界。

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

druntime打印代码还可以在打印之前+1返回它,以隐藏此内部实现hack ...但是,嗯。返回地址也不是实际发生调用的地方,无论如何,您都需要在反汇编程序中查找上方。甚至gdb实际上也不会显示调用的地址(至少不是我的旧版本,也许是新版本)。但是,如果它是反汇编中grepping的值,那么可能会很好,无论如何...如果要对druntime进行PR,我会在那方面为您提供支持(请注意,我没有权限,但可以提供意见)。

但这至少可以明确地说明现状。