C 纤维在 printf 上崩溃

C fibers crashing on printf

我正在按照 https://graphitemaster.github.io/fibers/ 用 C 创建一个光纤线程系统。我有一个设置和恢复上下文的功能,我想要完成的是启动一个功能作为具有自己堆栈的光纤。 Linux、x86_64 SysV ABI。

extern void restore_context(struct fiber_context*);
extern void create_context(struct fiber_context*);

void foo_fiber()
{
    printf("Called as a fiber");
    exit(0);
}

int main()
{
    const uint32_t stack_size = 4096 * 16;
    const uint32_t red_zone_abi = 128;

    char* stack = aligned_alloc(16, stack_size);
    char* sp = stack + stack_size - red_zone_abi;

    struct fiber_context c = {0};
    c.rip = (void*)foo_fiber;
    c.rsp = (void*)sp;

    restore_context(&c);
}

其中restore_context代码如下:

.type restore_context, @function
.global restore_context
restore_context:
  movq 8*0(%rdi), %r8

  # Load new stack pointer.
  movq 8*1(%rdi), %rsp

  # Load preserved registers.
  movq 8*2(%rdi), %rbx
  movq 8*3(%rdi), %rbp
  movq 8*4(%rdi), %r12
  movq 8*5(%rdi), %r13
  movq 8*6(%rdi), %r14
  movq 8*7(%rdi), %r15

  # Push RIP to stack for RET.
  pushq %r8

  xorl %eax, %eax
  ret

所以基本上我在堆上创建了一个新堆栈,并且由于堆栈向下增长,我采用结束地址 - 128 字节的红色区域(这在 ABI 中是必需的)。 restore_context 所做的只是将 %rsp 交换到我的新堆栈,并将 foo_fiber 的地址压入它,然后返回跳转到 foo_fiber。 (它还会从 fiber_context 结构中加载一些寄存器,但现在应该无关紧要了)。

从我在 GDB 中看到的情况来看,该程序设法正确跳转到 foo_fiber 并进入 printf,然后它在 movaps %xmm1, 0x10(%rsp) 上的 _vprintf_internal 中崩溃。

|  0x7ffff7e2f389 <__vfprintf_internal+153>        movdqu (%rax),%xmm1                                                                                                                                                    │
│  0x7ffff7e2f38d <__vfprintf_internal+157>        movups %xmm1,0x128(%rsp)                                                                                                                                               │
│  0x7ffff7e2f395 <__vfprintf_internal+165>        mov    0x10(%rax),%rax                                                                                                                                                 │
│  >0x7ffff7e2f399 <__vfprintf_internal+169>       movaps %xmm1,0x10(%rsp)  

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

如果我更改 foo_fiber 的代码来做其他事情,例如分配和随机填充 char[100],它会起作用。

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

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

确实堆栈必须对齐到16字节。但是,问题是什么时候? 通常的规则是,在调用ABI-compliant 函数的调用指令处,堆栈指针必须是16 的倍数。

好吧,你没有使用调用指令,但这真正意味着在进入 ABI-compliant 函数时,堆栈指针必须 8 小于 8 的倍数16,或者换句话说是 8 的奇数倍数,因为它假设它是用 call 指令调用的,该指令推送了一个 8 字节的 return 地址。这与您的代码所做的正好相反,因此堆栈对于您的程序的其余部分未对齐,这使得 printf 在尝试使用对齐的移动指令时崩溃。

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

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

callq *(8*0)(%rdi)