我正在尝试做一件相当利基的事情,它本质上是破坏CFI
(DWARF EH 信息中的调用帧信息)和rbp
帧rsp
之间的链接。主要原因是,在线程控制流的某个点之后,我想做一个调用延续,这基本上是一个单向尾调用与一个应该清理堆栈然后返回到堆栈顶部的 yield准备在延续点再次执行。
这是原则上的想法,只要我将与堆栈混淆的行注释掉,它就可以工作:
/*
* x86_64 SysV:
* rdi, rsi, rds, rcx, r8, r9, xmm0-xmm7
*/
__asm {
mov rax, TCB
mov rax, qword ptr [rax] OSThreadControlBlock.StartFn;
call rax;
mov rax, 0;
// end of stack
//push rax;
//push rax;
//push rbx;
// last "real" frame
//push rbp;
//mov rbp, rsp;
//push rbx;
// make the call
mov rdi, RL;
lea rax, qword ptr __OS_RUNLOOP_START__;
call rax;
// trap if it returns
//int 3;
}
我知道 SP/BP 寄存器背后的一般原则,我专门使用-fno-omit-frame-pointer
. 我的问题是,在花了几个小时试图让它工作之后,我错过了什么?似乎对堆栈布局的任何更改,即使像在调用前推送并保持对齐一样简单,都会导致从类似这样的事情开始的雪球崩溃(自定义信号处理程序):
Received fatal signal: Segmentation fault (11) [thread: 10298 ctl-thrd]
* Unknown error at address 0x0 Regs:
%rip=0x00000000003E2D91 %rbp=0x00007F820A547EA8 %rsp=0x00007F820A547DE8 %rax=0x00007F820A547DE8 %rbx=0x00007F820A547F38
%rdi=0x00000000002121E1 %rsi=0x000000000000007B %rcx=0x000000000000000A %r8=0x0000000000000900 %r9=0x00007F820A5490C0
有问题的 ABI 是libc++
/libc++abi
在x86_64
Linux 上,具有基于 LLVM/Clang 6.0.X 的工具链。我几乎尝试了所有方法,我知道上面看起来很奇怪,但它是内联汇编的 MS 扩展,我在反汇编中多次检查它会生成完全正常的代码。据我了解,这是 CFI 和基于帧指针的东西之间的一些奇怪的冲突,但我并不是那么擅长,x86_64
所以我不确定我错过了什么。我知道展开过程是由哨兵(最后一帧上的空 SP/FP)终止的,但此时我真的迷路了,因为即使调试器也被完全抛弃了。
如果有人有任何非常感谢的建议,我尝试了各种方法,但核心问题是相同的,一旦我触摸堆栈,即使我将其恢复正常,一切都会变得混乱。超出 asm 块的破坏并不重要,因为最后一次调用通常不意味着返回。我确实注意到的一件事是,这似乎与 TLV 有某种关系,但我不确定 NPTL 是如何配置的。
任何帮助或建议我都会非常感激。
编辑:
看起来 Valgrind 的这条评论可以解释正在发生的事情:
/* NB 9 Sept 07. There is a nasty kludge here in all these CALL_FN_
macros. In order not to trash the stack redzone, we need to drop
%rsp by 128 before the hidden call, and restore afterwards. The
nastyness is that it is only by luck that the stack still appears
to be unwindable during the hidden call - since then the behaviour
of any routine using this macro does not match what the CFI data
says. Sigh.
Why is this important? Imagine that a wrapper has a stack
allocated local, and passes to the hidden call, a pointer to it.
Because gcc does not know about the hidden call, it may allocate
that local in the redzone. Unfortunately the hidden call may then
trash it before it comes to use it. So we must step clear of the
redzone, for the duration of the hidden call, to make it safe.
Probably the same problem afflicts the other redzone-style ABIs too
(ppc64-linux, ppc32-aix5, ppc64-aix5); but for those, the stack is
self describing (none of this CFI nonsense) so at least messing
with the stack pointer doesn't give a danger of non-unwindable
stack. */