kit

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

commit 13677b4af13ca7c0336624e9f49d44c792de6201
parent 907a6891029bb98c2264ba18f327e045e4e68acd
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Wed, 10 Jun 2026 18:39:40 -0700

fix(macho): index x86_64 chained-fixups seg table by emitted LC order

The dyld_chained_starts_in_image seg_count / seg_info_offset[] array was
indexed by MCtx segment *slot* (PAGEZERO, TEXT, DATA_CONST, DATA, DWARF,
LINKEDIT = 6), but empty middle segments (__DATA_CONST / __DWARF with no
sections) are dropped from the emitted load commands. So a __DATA fixup
filed under slot 3 with seg_count 6 lands on the wrong segment once the
load-command order compacts (__DATA becomes emitted index 2, LINKEDIT
index 3). arm64 dyld tolerates this via the struct's segment_offset, but
x86_64's applyFixupsGeneric keys on the actual segment at the emitted
index -> a global-pointer rebase walked into __LINKEDIT and SIGBUS'd at
dyld load.

Add macho_emit_seg_indices() (mirrors the emit_load_command_segment skip
logic) and index seg_count + seg_info_offset[] by emitted LC order. Fixes
toy 15_cg_api_types_bytes_globals on x86_64-macos; arm64 unchanged.

Diffstat:
Msrc/obj/macho/link.c | 40++++++++++++++++++++++++++++++++++++----
1 file changed, 36 insertions(+), 4 deletions(-)

diff --git a/src/obj/macho/link.c b/src/obj/macho/link.c @@ -1555,6 +1555,28 @@ static int site_cmp_by_vaddr(const void* a, const void* b) { return 0; } +/* Map each MCtx segment slot to its index in the emitted LC_SEGMENT_64 + * sequence and return the emitted-segment count. Empty middle segments + * (__DATA_CONST / __DWARF with no sections) are omitted from the load + * commands (see emit_load_command_segment calls), so the emitted order is + * a compaction of the slot order: PAGEZERO, TEXT, [DATA_CONST], [DATA], + * [DWARF], LINKEDIT. Must mirror that emit logic exactly. dyld indexes + * dyld_chained_starts_in_image.seg_info_offset[] by this emitted order, so + * the chained-fixups seg table uses it rather than the raw slot index — + * indexing by slot leaves a non-emitted hole that x86_64 dyld's + * applyFixupsGeneric resolves to the wrong segment (SIGBUS at load). + * Slots that are not emitted get emit_idx[slot] = 0xff. */ +static u32 macho_emit_seg_indices(const MCtx* x, u8 emit_idx[MSEG_COUNT]) { + u32 n = 0; + for (u32 i = 0; i < x->nsegs; ++i) { + int emitted = (i == MSEG_PAGEZERO) || (i == MSEG_LINKEDIT) || + (x->segs[i].nsects > 0); + emit_idx[i] = emitted ? (u8)n : 0xffu; + if (emitted) ++n; + } + return n; +} + /* tiny insertion sort to avoid pulling qsort */ static void sort_sites(FixSite* a, u32 n) { for (u32 i = 1; i < n; ++i) { @@ -1627,10 +1649,15 @@ static void build_chained_fixups(MCtx* x, FixList* fl) { */ u32 starts_off = out->len; wr_u32_le(out->data + starts_offset_pos, starts_off); - objbb_u32(out, x->nsegs); + /* seg_count + seg_info_offset[] are indexed by emitted LC_SEGMENT_64 + * order, not MCtx slot — empty middle segments are dropped from the load + * commands. */ + u8 emit_seg[MSEG_COUNT]; + u32 emit_nsegs = macho_emit_seg_indices(x, emit_seg); + objbb_u32(out, emit_nsegs); /* Reserve seg_info_offset[]. */ u32 seg_info_offsets_pos = out->len; - for (u32 i = 0; i < x->nsegs; ++i) objbb_u32(out, 0); + for (u32 i = 0; i < emit_nsegs; ++i) objbb_u32(out, 0); /* Sort fixsites by vaddr globally. */ sort_sites(fl->a, fl->n); @@ -1646,11 +1673,16 @@ static void build_chained_fixups(MCtx* x, FixList* fl) { } } if (!count) continue; + if (emit_seg[si] == 0xffu) + compiler_panic(x->c, SRCLOC_NONE, + "link_macho: chained fixups in non-emitted segment %u", + (unsigned)si); /* Page-align this struct to 4. */ objbb_align(out, 4); u32 sis_off = out->len; - /* Patch seg_info_offset[si] to (sis_off - starts_off). */ - wr_u32_le(out->data + seg_info_offsets_pos + si * 4u, sis_off - starts_off); + /* Patch seg_info_offset[emitted index] to (sis_off - starts_off). */ + wr_u32_le(out->data + seg_info_offsets_pos + emit_seg[si] * 4u, + sis_off - starts_off); /* Compute page count for this segment. */ u64 seg_va = x->segs[si].vmaddr;