C 纤维在 printf 上崩溃

wil*_*ptr 3 c assembly x86-64 abi fibers

我正在用 C 创建纤维线程系统,遵循https://graphitemaster.github.io/ Fibers/ 。我有一个设置和恢复上下文的函数,我想要完成的是将函数作为具有自己的堆栈的 Fiber 启动。Linux、x86_64 SysV ABI。

\n
extern void restore_context(struct fiber_context*);\nextern void create_context(struct fiber_context*);\n\nvoid foo_fiber()\n{\n    printf("Called as a fiber");\n    exit(0);\n}\n\nint main()\n{\n    const uint32_t stack_size = 4096 * 16;\n    const uint32_t red_zone_abi = 128;\n\n    char* stack = aligned_alloc(16, stack_size);\n    char* sp = stack + stack_size - red_zone_abi;\n\n    struct fiber_context c = {0};\n    c.rip = (void*)foo_fiber;\n    c.rsp = (void*)sp;\n\n    restore_context(&c);\n}\n
Run Code Online (Sandbox Code Playgroud)\n

其中restore_context代码如下:

\n
.type restore_context, @function\n.global restore_context\nrestore_context:\n  movq 8*0(%rdi), %r8\n\n  # Load new stack pointer.\n  movq 8*1(%rdi), %rsp\n\n  # Load preserved registers.\n  movq 8*2(%rdi), %rbx\n  movq 8*3(%rdi), %rbp\n  movq 8*4(%rdi), %r12\n  movq 8*5(%rdi), %r13\n  movq 8*6(%rdi), %r14\n  movq 8*7(%rdi), %r15\n\n  # Push RIP to stack for RET.\n  pushq %r8\n\n  xorl %eax, %eax\n  ret\n
Run Code Online (Sandbox Code Playgroud)\n

所以基本上我在堆上创建一个新的堆栈,并且由于堆栈向下增长,我采用结束地址 - 128 字节的红色区域(这在 ABI 中是必需的)。Restore_context 所做的只是将 %rsp 交换到我的新堆栈,并将 foo_optical 的地址压入其中,然后 ret\'s 跳转到 foo_optical 中。(它还从 Fiber_context 结构加载一些寄存器,但现在应该不重要)。

\n

从我在 GDB 中看到的情况来看,程序成功地正确跳转到 foo_ Fiber 并进入 printf,然后_vprintf_internalmovaps %xmm1, 0x10(%rsp).

\n
|  0x7ffff7e2f389 <__vfprintf_internal+153>        movdqu (%rax),%xmm1                                                                                                                                                    \xe2\x94\x82\n\xe2\x94\x82  0x7ffff7e2f38d <__vfprintf_internal+157>        movups %xmm1,0x128(%rsp)                                                                                                                                               \xe2\x94\x82\n\xe2\x94\x82  0x7ffff7e2f395 <__vfprintf_internal+165>        mov    0x10(%rax),%rax                                                                                                                                                 \xe2\x94\x82\n\xe2\x94\x82  >0x7ffff7e2f399 <__vfprintf_internal+169>       movaps %xmm1,0x10(%rsp)  \n
Run Code Online (Sandbox Code Playgroud)\n

我发现这非常奇怪,因为它管理的movups %xmm1, 0x128(%rsp)堆栈指针的偏移量要高得多。那里发生了什么事?

\n

如果我更改 foo_ Fiber 的代码来做其他事情,例如分配并随机填充 char[100],它就可以工作。

\n

我对正在发生的事情有点不知所措。起初我以为我可能有对齐问题,因为向量 xmm 函数崩溃了,所以我将 malloc 更改为aligned_alloc。我遇到的崩溃是 SIGSEGV,但是 0x10

\n

Nat*_*dge 5

同意评论:您的堆栈对齐不正确。

确实,堆栈必须对齐到 16 字节。然而,问题是何时? 通常的规则是,在调用 ABI 兼容函数的调用指令处,堆栈指针必须是 16 的倍数。

好吧,您不使用 call 指令,但这真正意味着在进入 ABI 兼容函数时,堆栈指针必须比16 的倍数小 8,或者换句话说,是 8 的奇数倍,因为它假设它是用一条call推送 8 字节返回地址的指令调用的。这与您的代码所做的恰恰相反,因此堆栈与程序的其余部分未对齐,这printf在尝试使用对齐的移动指令时会导致崩溃。

sp您可以从C 代码中的计算结果中减去 8 。

或者,我不太确定为什么您要麻烦地将目标地址加载到寄存器中,然后按下 和ret,而间接跳转或调用就可以了。(除非你故意试图欺骗间接分支预测器?)间接调用也会通过推送返回地址(即使它永远不会被使用)来杀死堆栈对齐鸟。因此,您可以保留其余代码,并将所有 r8/ret 内容替换restore_context

callq *(8*0)(%rdi)
Run Code Online (Sandbox Code Playgroud)