Assembly DMD堆栈跟踪中的地址意味着什么?

Assembly DMD堆栈跟踪中的地址意味着什么?,assembly,d,dmd,Assembly,D,Dmd,我编译文件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

我编译文件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.文本000000000000019\u Dmain

如果我运行
gdb-q-nx-ex start-ex'disas/rs\u 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   
。。。
函数_Dmain的汇编程序代码转储:
0x0000555586E0C:55推送%rbp
0x0000555586E0D:488B ec mov%rsp,%rbp
=>0x0000555586E10:be 02 00 mov$0x2,%esi
0x000055555586E15:48 8d 3d 44 c0 02 00 lea 0x2c044(%rip),%rdi#0x55555B2E60
0x0000555586E1C:e8 47 00 00 00呼叫0x5555586E68
0x0000555586E21:31 c0异或%eax,%eax
0x0000555586E23:5d pop%rbp
0x0000555586E24:c3 retq

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

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

这是git在添加时的责任:

唉,提交消息的信息量不是很高,但代码本身,加上我的记忆,让我非常信服

这就是
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
变量会在引发异常时复制当前调用。当要求跟踪打印机实际写入时,跟踪打印机将查看该数组以确定要写入的内容。(请参阅,查找调试信息以打印文件/行号和函数名的速度非常慢,因此它只在必须时才这样做,以保持正常的异常使用(稍后抛出和捕获时)更快。)

不管怎样,我记得回溯打印错误行的时候。它将打印包含下一条指令的代码行,这可能在源代码中与实际的assert/throw语句有相当大的距离,从而使打印变得不那么有用。如果您查看git-bull链接,您将看到用于直接从堆栈中复制地址的旧代码

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

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

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

因此,只要假设大小为1,就可以极大地简化实现——这足以使它回到该边界

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

druntime打印代码也可以在打印前返回+1,以隐藏此内部实现黑客。。。但是我。返回地址也不是调用实际发生的位置,不管怎样,您都需要查看上面的反汇编程序。甚至gdb实际上也没有显示通话地址(至少不是我的旧版本,也许是新版本)。但是如果它在反汇编中是grepping的一个值,那就更好了。。。如果你想对druntime进行公关,我会支持你(注意,我在那里没有权限,但可以提供评论)


但这至少可以肯定地解释现状。

这可能是你的问题。返回地址已损坏1位。硬件内存故障?我认为运行时代码只是从它保留的值中减去一,以显示异常的源代码行号,而不是源代码的下一行(调用
指令推送下一条指令的地址,直接读取会给出错误的行号)。但是我不能证明这一点,我现在还没有在源代码中看到它,所以我还不想把它作为一个答案发布。谢谢你花时间,这是非常有用的!我昨天也提交了一份关于它的bug报告,好消息是看起来已经有一个公关在修复这个:)