Why calling longjmp in a non-main stack causes the program to crash?

Why calling longjmp in a non-main stack causes the program to crash?
typescript
Ethan Jackson

The following code attempts to create a simple stackful coroutine. It allocates a stack frame in the heap space by setting the rsp register and then calling a function. Afterwards, it exits the coroutine via longjmp. However, using longjmp on the coroutine frame (the coro_test function) will cause a crash, regardless of whether the setjmp is located in the main stack or the coroutine stack.

So, my question is:

  • 1.In theory, setjmp just saves all registers, and longjmp just restores the values of all registers saved by setjmp. Therefore, they can be used for stack frame switching in coroutines. Is this point incorrect? If it's incorrect, please point it out.

  • 2.If using setjmp and longjmp is unsafe, how should I implement coroutine stack frame switching?

My compiler is GCC 14.2.0 x86_64-w64-mingw32.

#include <iostream> #include <functional> #include <cstdint> #include <csetjmp> #include <memory> #include <cstdint> struct coroutine_context { std::jmp_buf exit; std::size_t stack_memory_size = 0; std::unique_ptr<std::byte[]> stack_memory; std::function<void(coroutine_context&)> coroutine_body; int value_thoroughfare = 0; }; #define SET_RSP(new_rsp) \ asm volatile ( \ "mov %0, %%rsp" \ : \ : "r" (new_rsp) \ : "memory" \ ) std::uintptr_t get_stack_base(const coroutine_context& context) { const static bool towards_address_LSB = []() -> bool { volatile int a; volatile int b; return &a > &b; }(); if(towards_address_LSB) return (reinterpret_cast<std::uintptr_t>(context.stack_memory.get() + context.stack_memory_size) & (~0xF)); else return (reinterpret_cast<std::uintptr_t>(context.stack_memory.get()) & (~0xF)) + 0x10; } void coro_test(coroutine_context& context) { context.value_thoroughfare = 114514; std::longjmp(context.exit, 1); // The program crashes here. } void create_coroutine_stack_frame(coroutine_context& context) { if(setjmp(context.exit)) return; SET_RSP(get_stack_base(context)); coro_test(context); } int main() { coroutine_context context; context.stack_memory_size = 1024 * 16; context.stack_memory = std::make_unique<std::byte[]>(1024 * 16); context.coroutine_body = coro_test; create_coroutine_stack_frame(context); std::cout << context.value_thoroughfare; return 0; }

Answer

Answer to Q1:
You're right that setjmp saves registers and longjmp restores them, but they assume stack continuity. Calling longjmp from a fake, heap-allocated stack breaks this, causing a crash.

Answer to Q2:
Use a proper context switching mechanism (assembly, Boost.Context, Windows Fibers). Do not use setjmp/longjmp for coroutine stack switching — it’s unsafe and non-portable.

Related Articles