You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C++17 Windows x64实现栈式协程时抛异常致程序终止问题

Windows x64 C++17 栈式协程异常抛出崩溃问题

问题现象

在Windows x64平台基于C++17实现栈式协程时,协程内部抛出异常就会导致程序立刻以错误退出码终止,即使异常在抛出点所在函数内被立即捕获、完全不会逃逸出协程执行范围,也会触发该崩溃。

现有实现逻辑

协程栈分配

首先通过如下代码为新协程分配独立栈空间:

void* Allocate() {
  static constexpr std::size_t kStackSize{524'288};
  auto new_stack{::operator new(kStackSize)};
  return static_cast<std::byte *>(new_stack) + kStackSize;
}

跳板函数(Trampoline)初始化

使用MSVC编译器,通过MASM编写跳板初始化逻辑:

SetTrampoline PROC
    mov rax, rsp ; 保存当前栈指针
    mov rsp, [rcx] ; 切换到新栈指针

    sub rsp, 20h ; 预留shadow stack空间
    push rdx ; 保存传入的目标函数指针

    ; 预留非易失寄存器保存空间
    sub rsp, 0e0h

    mov [rcx], rsp ; 保存调整后的新栈指针
    mov rsp, rax ; 切回原栈指针
    ret
SetTrampoline ENDP

上下文切换逻辑

参考MSVC x64调用约定实现上下文切换,完成协程之间的执行流跳转:

SwitchContext PROC
    ; 将调用方的非易失寄存器保存到栈上
    push rbx
    push rbp
    push rdi
    push rsi
    push r12
    push r13
    push r14
    push r15

    sub rsp, 10h
    movdqu [rsp], xmm6

    ; 此处省略xmm7-xmm14的入栈保存逻辑

    sub rsp, 10h
    movdqu [rsp], xmm15

    mov [rdx], rsp ; 保存调用方栈指针

SwitchContextFinally PROC
    mov rsp, [rcx] ; 切换到被调用方的栈指针

    ; 恢复被调用方的寄存器值
    movdqu xmm15, [rsp]
    add rsp, 10h

    ; 此处省略xmm7-xmm14的出栈恢复逻辑    
  
    movdqu xmm6, [rsp]
    add rsp, 10h

    pop r15
    pop r14
    pop r13
    pop r12
    pop rsi
    pop rdi
    pop rbp
    pop rbx

    ret
SwitchContextFinally ENDP
SwitchContext ENDP

问题根因

崩溃核心原因有3个,和异常是否逃逸出协程范围无关:

  • SEH栈校验失败:MSVC的C++异常完全基于Windows原生结构化异常处理(SEH)实现,异常抛出时系统会首先校验当前RSP是否落在当前线程TEB(线程环境块)中记录的合法栈区间(StackBase/StackLimit)内,同时会遍历栈上的SEH注册节点完成栈展开。你直接通过operator new分配的内存是普通堆内存,既没有被记录到TEB的栈区间信息中,也没有初始化对应的SEH节点,异常抛出的瞬间系统判定栈非法,直接终止进程,根本不会执行后续的catch逻辑。
  • 栈对齐不符合调用约定:MSVC x64要求通过call指令调用目标函数前,RSP必须保持16字节对齐。你当前的栈偏移计算逻辑没有满足该要求,哪怕异常问题修复,调用包含SIMD指令的函数时也会出现随机崩溃。
  • shadow space预留位置错误:你确实需要为被调用函数预留32字节的shadow space(对应调用约定中RCX/RDX/R8/R9四个寄存器参数的保存空间),但当前预留位置和后续栈偏移计算没有配合对齐要求,属于无效预留。

修复方案

  • 不要直接用堆内存裸分配协程栈,优先使用系统提供的CreateFiberAPI完成协程栈的初始化。Fiber是Windows原生支持的用户态协程机制,会自动完成栈区间注册、SEH链初始化的全部工作,你只需要在Fiber的基础上封装自己的上下文切换逻辑即可,不需要手动处理TEB、SEH相关的底层细节,能避免90%以上的平台相关坑。
  • 重新计算栈偏移量,保证所有C++函数调用点的RSP满足16字节对齐要求。
  • 补全SwitchContext中省略的xmm7-xmm14寄存器保存/恢复逻辑,避免上下文切换时非易失寄存器值错乱。

内容的提问来源于stack exchange,提问作者Denis319199

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 07:24:36