commit 0969be734c0414dd30b53f179e370bb68a56f408
parent 042da531c8520ad46630ba784365522a62347926
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Fri, 29 May 2026 14:04:31 -0700
mc: make the CFI prologue-PC override sticky until cfi_endproc
All three backends emit their entire CFI directive batch in func_end (after the
epilogue), calling cfi_set_next_pc_offset(post_prologue) once before
cfi_def_cfa. The override was one-shot — cleared after the first directive — so
only def_cfa got the post-prologue PC; the following cfi_offset rules for the
saved FP/LR/RA and callee-saves fell back to obj_pos() = the function END. The
FDE therefore advanced to func-end before those rules, so a mid-function
unwind (signal, async sample, or any PC between prologue and the last
instruction) saw the saved-register and return-address locations as undefined.
Make the override sticky until cfi_endproc (cleared there and at cfi_startproc
so it never crosses an FDE). All three backends set it exactly once before the
batch and emit no other directives at obj_pos within the proc, so no callsite
changes are needed.
Verified via llvm-dwarfdump on a callee-save-spilling function for all three
arches: the def_cfa and every offset rule now share the post-prologue PC
(aa64 0x8, x64 0xb, rv64 0x1c) with no advance-to-func-end in between, matching
the gcc/clang convention. test-debug (incl. cfi_unit) + test-dbg + smokes green.
Diffstat:
1 file changed, 12 insertions(+), 5 deletions(-)
diff --git a/src/arch/mc.c b/src/arch/mc.c
@@ -74,10 +74,11 @@ typedef struct MCLabelInfo {
*
* Each cfi_startproc opens a new FDE record; the per-arch backend then
* calls cfi_def_cfa / cfi_offset / cfi_restore as the prologue is laid
- * down. Each directive snapshots either the current section offset or a
- * one-shot override set by cfi_set_next_pc_offset (used by backends
- * that patch the prologue in func_end). The .eh_frame section is
- * synthesised at mc_emit_eh_frame() time. */
+ * down. Each directive snapshots either the current section offset or the
+ * override set by cfi_set_next_pc_offset — which is STICKY until cfi_endproc
+ * (used by backends that emit the whole CFI batch in func_end, after the
+ * epilogue, but want every rule pinned to the post-prologue PC). The
+ * .eh_frame section is synthesised at mc_emit_eh_frame() time. */
typedef enum CfiOpKind {
CFI_OP_DEF_CFA,
CFI_OP_DEF_CFA_REGISTER,
@@ -375,8 +376,12 @@ static void fde_push(MCImpl* mc, u8 kind, u32 reg, i32 imm) {
fde->dir_cap = new_cap;
}
if (mc->has_pc_override) {
+ /* Sticky until cfi_endproc: every directive in a func_end prologue batch
+ * shares the post-prologue PC set once before cfi_def_cfa. (A one-shot
+ * override only covered the first directive, so the saved-register and
+ * return-address offset rules fell back to obj_pos() — the function END —
+ * making mid-function unwind unable to recover them.) */
pc_off = mc->pc_override;
- mc->has_pc_override = 0;
} else {
pc_off = obj_pos(mc->base.obj, mc->base.section_id) - fde->func_start;
}
@@ -411,6 +416,7 @@ static void m_cfi_startproc(MCEmitter* m) {
mc->fdes_cap = new_cap;
}
mc->cur_fde = (i32)mc->nfdes;
+ mc->has_pc_override = 0; /* no override carries across an FDE boundary */
{
CfiFde* fde = &mc->fdes[mc->nfdes++];
fde->func_sym = m->cur_func_sym;
@@ -430,6 +436,7 @@ static void m_cfi_endproc(MCEmitter* m) {
fde = &mc->fdes[mc->cur_fde];
fde->func_end = obj_pos(m->obj, m->section_id);
mc->cur_fde = -1;
+ mc->has_pc_override = 0; /* the sticky prologue-PC override ends with the FDE */
}
static void m_cfi_def_cfa(MCEmitter* m, u32 r, i32 o) {