Assembly DMD堆栈跟踪中的地址意味着什么?
我编译文件stacktrace.d: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
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报告,好消息是看起来已经有一个公关在修复这个:)