Skip to content

Commit ac16cab

Browse files
committedApr 21, 2025
Annotate naked unwind example and add resources
We demonstrate how to correctly unwind from a naked function. The assembler directives and boilerplate for this may be unfamiliar to people, so let's annotate this example heavily and add links to further resources. We'll use the `sysv64-unwind` ABI rather than `C-unwind` just to make things a bit more unambiguous.
1 parent d64c438 commit ac16cab

File tree

1 file changed

+51
-8
lines changed

1 file changed

+51
-8
lines changed
 

‎src/inline-assembly.md

+51-8
Original file line numberDiff line numberDiff line change
@@ -1413,31 +1413,74 @@ r[asm.naked-rules.unwind]
14131413
```rust
14141414
# #[cfg(target_arch = "x86_64")] {
14151415
#[unsafe(naked)]
1416-
extern "C-unwind" fn naked_function() {
1416+
extern "sysv64-unwind" fn unwinding_naked() {
14171417
core::arch::naked_asm!(
1418+
// "CFI" here stands for "call frame information".
14181419
".cfi_startproc",
1420+
// The CFA (canonical frame address) is the value of `rsp`
1421+
// before the `call`, i.e. before the return address, `rip`,
1422+
// was pushed to `rsp`, so it's eight bytes higher in memory
1423+
// than `rsp` upon function entry (after `rip` has been
1424+
// pushed).
1425+
//
1426+
// This is the default, so we don't have to write it.
1427+
//".cfi_def_cfa rsp, 8",
1428+
//
1429+
// The traditional thing to do is to preserve the base
1430+
// pointer, so we'll do that.
14191431
"push rbp",
1420-
".cfi_def_cfa_offset 16",
1432+
// Since we've now extended the stack downward by 8 bytes in
1433+
// memory, we need to adjust the offset to the CFA from `rsp`
1434+
// by another 8 bytes.
1435+
".cfi_adjust_cfa_offset 8",
1436+
// We also then annotate where we've stored the caller's value
1437+
// of `rbp`, relative to the CFA, so that when unwinding into
1438+
// the caller we can find it, in case we need it to calculate
1439+
// the caller's CFA relative to it.
1440+
//
1441+
// Here, we've stored the caller's `rbp` starting 16 bytes
1442+
// below the CFA. I.e., starting from the CFA, there's first
1443+
// the `rip` (which starts 8 bytes below the CFA and continues
1444+
// up to it), then there's the caller's `rbp` that we just
1445+
// pushed.
14211446
".cfi_offset rbp, -16",
1447+
// As is traditional, we set the base pointer to the value of
1448+
// the stack pointer. This way, the base pointer stays the
1449+
// same throughout the function body.
14221450
"mov rbp, rsp",
1451+
// We can now track the offset to the CFA from the base
1452+
// pointer. This means we don't need to make any further
1453+
// adjustments until the end, as we don't change `rbp`.
14231454
".cfi_def_cfa_register rbp",
1424-
"",
1425-
"call {function}",
1426-
"",
1455+
// We can now call a function that may panic.
1456+
"call {f}",
1457+
// Upon return, we restore `rbp` in preparation for returning
1458+
// ourselves.
14271459
"pop rbp",
1460+
// Now that we've restored `rbp`, we must specify the offset
1461+
// to the CFA again in terms of `rsp`.
14281462
".cfi_def_cfa rsp, 8",
1463+
// Now we can return.
14291464
"ret",
14301465
".cfi_endproc",
1431-
function = sym function_that_panics,
1466+
f = sym may_panic,
14321467
)
14331468
}
14341469

1435-
extern "C-unwind" fn function_that_panics() {
1436-
panic!("unwind!");
1470+
extern "sysv64-unwind" fn may_panic() {
1471+
panic!("unwind");
14371472
}
14381473
# }
14391474
```
14401475

1476+
> [!NOTE]
1477+
>
1478+
> For more information on the `cfi` assembler directives above, see these resources:
1479+
>
1480+
> - [Using `as` - CFI directives](https://sourceware.org/binutils/docs/as/CFI-directives.html)
1481+
> - [DWARF Debugging Information Format Version 5](https://dwarfstd.org/doc/DWARF5.pdf)
1482+
> - [ImperialViolet - CFI directives in assembly files](https://www.imperialviolet.org/2017/01/18/cfi.html)
1483+
14411484
r[asm.validity]
14421485
### Correctness and Validity
14431486

0 commit comments

Comments
 (0)
Please sign in to comment.