kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

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:
Msrc/arch/mc.c | 17++++++++++++-----
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) {