6969//! And this is the layout of the parent stack when a coroutine is running:
7070//!
7171//! ```text
72- //! | |
73- //! ~ ... ~
74- //! | |
75- //! +-------------+
76- //! | Saved RBX |
77- //! +-------------+
78- //! | Saved RIP | <- These 2 values form a valid entry in the frame pointer
79- //! +-------------+ | chain. The parent link itself is another entry in the
80- //! | Saved RBP | <- frame pointer chain since RBP points to it.
81- //! +-------------+ <- Parent link points here.
72+ //! | |
73+ //! ~ ... ~
74+ //! | |
75+ //! +----------------+
76+ //! | Saved RBX |
77+ //! +----------------+
78+ //! | Saved RIP | <- These 2 values form a valid entry in the frame pointer
79+ //! +----------------+ | chain. The parent link itself is another entry in the
80+ //! | Saved RBP | <- frame pointer chain since RBP points to it.
81+ //! +----------------+ <- Parent link points here.
82+ //! ```
83+ //!
84+ //! On UEFI targets, a secondary copy of the saved RIP is added below the saved
85+ //! RBX. This is needed because SEH unwind codes are not as flexible as DWARF
86+ //! CFI and the unwinder always pops a return address after processing all
87+ //! unwind opcodes.
88+ //!
89+ //! ```text
90+ //! | |
91+ //! ~ ... ~
92+ //! | |
93+ //! +----------------+
94+ //! | Secondary RIP | <- Only on UEFI; used by the SEH unwinder.
95+ //! +----------------+
96+ //! | Saved RBX |
97+ //! +----------------+
98+ //! | Saved RIP |
99+ //! +----------------+
100+ //! | Saved RBP |
101+ //! +----------------+ <- Parent link points here.
82102//! ```
83103//!
84104//! And finally, this is the stack layout of a coroutine that has just been
@@ -109,6 +129,32 @@ use crate::unwind::{
109129} ;
110130use crate :: util:: EncodedValue ;
111131
132+ // On UEFI targets, we emit SEH unwind information so that PE/COFF debuggers
133+ // (WinDbg, LLDB) can reconstruct backtraces across coroutine stack boundaries.
134+ // Unlike Windows, UEFI does not have a Thread Environment Block (TEB), so the
135+ // SEH annotations are simpler with adjusted stack offsets.
136+ //
137+ // The cfi!() and seh!() macros ensure that DWARF CFI and SEH directives are
138+ // mutually exclusive: UEFI uses SEH (.pdata/.xdata), all other platforms use
139+ // DWARF CFI (.eh_frame).
140+ cfg_if:: cfg_if! {
141+ if #[ cfg( target_os = "uefi" ) ] {
142+ macro_rules! seh {
143+ ( $asm: expr) => { $asm }
144+ }
145+ macro_rules! cfi {
146+ ( $asm: expr) => { "" }
147+ }
148+ } else {
149+ macro_rules! seh {
150+ ( $asm: expr) => { "" }
151+ }
152+ macro_rules! cfi {
153+ ( $asm: expr) => { $asm }
154+ }
155+ }
156+ }
157+
112158pub const STACK_ALIGNMENT : usize = 16 ;
113159pub const PARENT_STACK_OFFSET : usize = 0 ;
114160pub const PARENT_LINK_OFFSET : usize = 16 ;
@@ -118,15 +164,19 @@ pub type StackWord = u64;
118164// be the "base" function of all coroutines. This entrypoint is used in
119165// init_stack() to bootstrap the execution of a new coroutine.
120166//
121- // We also use this function as a persistent frame on the stack to emit dwarf
167+ // We also use this function as a persistent frame on the stack to emit unwind
122168// information to unwind into the caller. This allows us to unwind from the
123169// coroutines's stack back to the main stack that the coroutine was called from.
124- // We use special dwarf directives here to do so since this is a pretty
125- // nonstandard function.
170+ // We use special directives here to do so since this is a pretty nonstandard
171+ // function.
172+ //
173+ // On non-UEFI platforms we use DWARF CFI directives. On UEFI we use SEH
174+ // directives instead (see the seh!() and cfi!() macros above).
126175global_asm ! (
127176 ".balign 16" ,
128177 asm_function_begin!( "stack_init_trampoline" ) ,
129- ".cfi_startproc" ,
178+ cfi!( ".cfi_startproc" ) ,
179+ seh!( ".seh_proc stack_init_trampoline" ) ,
130180 // GDB has a hard-coded check that rejects backtraces where the frame
131181 // addresses do not monotonically increase. This can unfortunately trigger
132182 // when the stack of a coroutine is located at a higher address than its
@@ -142,7 +192,7 @@ global_asm!(
142192 // *after* the return address to search for unwind information. To avoid
143193 // issues, any asm! blocks containing a return address that may be unwound
144194 // into must not have that address at the end of the asm! block.
145- cfi_signal_frame!( ) ,
195+ cfi! ( cfi_signal_frame!( ) ) ,
146196 // This gets called by switch_and_link() the first time a coroutine is
147197 // resumed, due to the initial state set up by init_stack().
148198 //
@@ -171,9 +221,11 @@ global_asm!(
171221 // Set up the frame pointer to point at the parent link. This is needed for
172222 // the unwinding code below.
173223 "mov rbp, rsi" ,
174- // This sequence of magic numbers deserves some explanation. We need to tell
175- // the unwinder where to find the Canonical Frame Address (CFA) of the
176- // parent context.
224+ //
225+ // DWARF CFI unwind directives (non-UEFI platforms)
226+ //
227+ // We need to tell the unwinder where to find the Canonical Frame Address
228+ // (CFA) of the parent context.
177229 //
178230 // The CFA is normally defined as the stack pointer value in the caller just
179231 // before executing the call instruction. In our case, this is the stack
@@ -192,13 +244,34 @@ global_asm!(
192244 // 0x76 0x00: DW_OP_breg6 (rbp + 0) -- GDB doesn't like DW_OP_reg6
193245 // 0x06: DW_OP_deref
194246 // 0x23, 0x18: DW_OP_plus_uconst 24
195- ".cfi_escape 0x0f, 5, 0x76, 0x00, 0x06, 0x23, 0x18" ,
247+ cfi! ( ".cfi_escape 0x0f, 5, 0x76, 0x00, 0x06, 0x23, 0x18" ) ,
196248 // Now we can tell the unwinder how to restore the 3 registers that were
197249 // pushed on the parent stack. These are described as offsets from the CFA
198250 // that we just calculated.
199- ".cfi_offset rbx, -8" ,
200- ".cfi_offset rip, -16" ,
201- ".cfi_offset rbp, -24" ,
251+ cfi!( ".cfi_offset rbx, -8" ) ,
252+ cfi!( ".cfi_offset rip, -16" ) ,
253+ cfi!( ".cfi_offset rbp, -24" ) ,
254+ //
255+ // SEH unwind directives (UEFI only)
256+ //
257+ // These tell the SEH unwinder how to restore the register state to that of
258+ // the parent call frame. The SEH unwinder processes these in reverse order:
259+ // 1. .seh_setframe rbp, 0: Copy virtual RBP to virtual RSP.
260+ // 2. .seh_savereg rsp, 0: Read the parent link and place it in virtual RSP,
261+ // which now points to the top of the parent stack.
262+ // 3. .seh_pushreg rbp: Pop and restore RBP from the parent stack.
263+ // 4. .seh_stackalloc 8: Skip the saved RIP from the CALL instruction.
264+ // 5. .seh_pushreg rbx: Pop and restore RBX from the parent stack.
265+ //
266+ // After all these operations, the unwinder pops a return address off the
267+ // stack. This is the secondary copy of the return address created in
268+ // switch_and_link.
269+ seh!( ".seh_pushreg rbx" ) ,
270+ seh!( ".seh_stackalloc 8" ) ,
271+ seh!( ".seh_pushreg rbp" ) ,
272+ seh!( ".seh_savereg rsp, 0" ) ,
273+ seh!( ".seh_setframe rbp, 0" ) ,
274+ seh!( ".seh_endprologue" ) ,
202275 // Set up the 3rd argument to the initial function to point to the object
203276 // that init_stack() set up on the stack.
204277 "mov rdx, rsp" ,
@@ -234,7 +307,8 @@ global_asm!(
234307 // the bounds of the function. In any case, this instruction is never
235308 // executed since the function we are calling never returns.
236309 "int3" ,
237- ".cfi_endproc" ,
310+ cfi!( ".cfi_endproc" ) ,
311+ seh!( ".seh_endproc" ) ,
238312 asm_function_end!( "stack_init_trampoline" ) ,
239313) ;
240314
@@ -247,8 +321,9 @@ global_asm!(
247321 // used here.
248322 ".balign 16" ,
249323 asm_function_begin!( "stack_call_trampoline" ) ,
250- ".cfi_startproc" ,
251- cfi_signal_frame!( ) ,
324+ cfi!( ".cfi_startproc" ) ,
325+ seh!( ".seh_proc stack_call_trampoline" ) ,
326+ cfi!( cfi_signal_frame!( ) ) ,
252327 // At this point our register state contains the following:
253328 // - RSP points to the top of the parent stack.
254329 // - RBP holds its value from the parent context.
@@ -259,8 +334,13 @@ global_asm!(
259334 // Create a stack frame and point the frame pointer at it.
260335 "push rbp" ,
261336 "mov rbp, rsp" ,
262- ".cfi_def_cfa rbp, 16" ,
263- ".cfi_offset rbp, -16" ,
337+ // DWARF CFI (non-UEFI)
338+ cfi!( ".cfi_def_cfa rbp, 16" ) ,
339+ cfi!( ".cfi_offset rbp, -16" ) ,
340+ // SEH (UEFI only)
341+ seh!( ".seh_pushreg rbp" ) ,
342+ seh!( ".seh_setframe rbp, 0" ) ,
343+ seh!( ".seh_endprologue" ) ,
264344 // Switch to the new stack.
265345 "mov rsp, rsi" ,
266346 // Call the function pointer. The argument is already in the correct
@@ -271,7 +351,8 @@ global_asm!(
271351 "mov rsp, rbp" ,
272352 "pop rbp" ,
273353 "ret" ,
274- ".cfi_endproc" ,
354+ cfi!( ".cfi_endproc" ) ,
355+ seh!( ".seh_endproc" ) ,
275356 asm_function_end!( "stack_call_trampoline" ) ,
276357) ;
277358
@@ -331,6 +412,11 @@ pub unsafe fn switch_and_link(
331412 let ( ret_val, ret_sp) ;
332413
333414 asm_may_unwind_root ! (
415+ // Set up a secondary copy of the return address. This is only used by
416+ // the SEH unwinder on UEFI, not by actual returns.
417+ seh!( "lea rax, [rip + 2f]" ) ,
418+ seh!( "push rax" ) ,
419+
334420 // Save RBX. Ideally this would be done by specifying them as a clobber
335421 // but that is not possible since RBX is an LLVM reserved register.
336422 //
@@ -365,8 +451,12 @@ pub unsafe fn switch_and_link(
365451 // instruction. However this doesn't cause any issues in practice.
366452
367453 // Restore RBX.
454+ "2:" ,
368455 "pop rbx" ,
369456
457+ // Pop the secondary return address (UEFI only).
458+ seh!( "add rsp, 8" ) ,
459+
370460 // The RDI register is specifically chosen to hold the argument since
371461 // the ABI uses it for the first argument of a function call.
372462 //
@@ -554,6 +644,11 @@ pub unsafe fn switch_and_throw(
554644 let ( ret_val, ret_sp) ;
555645
556646 asm_may_unwind_root ! (
647+ // Set up a secondary copy of the return address for the SEH unwinder
648+ // (UEFI only), just like in switch_and_link().
649+ seh!( "lea rax, [rip + 2f]" ) ,
650+ seh!( "push rax" ) ,
651+
557652 // Save RBX just like the first half of switch_and_link().
558653 "push rbx" ,
559654
@@ -598,6 +693,9 @@ pub unsafe fn switch_and_throw(
598693 // Restore registers just like the second half of switch_and_link.
599694 "pop rbx" ,
600695
696+ // Pop the secondary return address (UEFI only).
697+ seh!( "add rsp, 8" ) ,
698+
601699 // Helper function to trigger stack unwinding.
602700 throw = sym throw,
603701
0 commit comments