From 5fe2c734a51113410cad16e28394bf84b019f8dc Mon Sep 17 00:00:00 2001 From: Matthew Stanley <1379tech@gmail.com> Date: Tue, 5 May 2026 21:47:09 -0700 Subject: [PATCH] recomp: SectionAbsolute guard + reloc filter + runtime-fragment decl MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three engine fixes uncovered by Stadium fragment dispatch: 1. recompilation.cpp: SectionAbsolute guard in print_func_call_by_address. Stadium's .fragmentN sections JAL into SHN_ABS symbols (e.g. osGbpakReadWrite); resolve_jal indexed context.sections[] at 65534 and segfaulted on first dispatch. Skip reloc resolution when reloc_section >= context.sections.size(). 2. main.cpp (overlay table emit): filter unsupported MIPS reloc types before indexing reloc_names[]. Stadium's .rel.fragmentN includes R_MIPS_PC16 (type 10) which the recompiler doesn't model; the OOB read embedded a NUL byte in the .type field and broke the C compile. 3. main.cpp: bounds-check inversion in the static-funcs scan (read section_funcs[size] before checking i < size). Latent bug exposed by .fragment1's larger CreateStatic surface. 4. recomp.h: forward-declare recomp_register_runtime_fragment so funcs files can call it from inlined hook text generated by [[patches.hook]] on Memmap_RelocateFragment. (NOTE: original local commit de76241 also added a recomp_unhandled_* forward-decl family; those declarations are dropped from this PR — they violate the no-stubs principle and depend on a runtime API not yet in upstream N64ModernRuntime.) Co-Authored-By: Claude Opus 4.7 (1M context) --- include/recomp.h | 10 ++++++++++ src/main.cpp | 19 ++++++++++++++++++- src/recompilation.cpp | 12 ++++++++++-- 3 files changed, 38 insertions(+), 3 deletions(-) diff --git a/include/recomp.h b/include/recomp.h index 73f3e73..2775713 100644 --- a/include/recomp.h +++ b/include/recomp.h @@ -468,6 +468,16 @@ void recomp_syscall_handler(uint8_t* rdram, recomp_context* ctx, int32_t instruc void pause_self(uint8_t *rdram); +/* Runtime fragment registrar — invoked from a Memmap_RelocateFragment + * hook (see Stadium's game.toml). Translates Stadium's encoded fragment + * id to the matching section in the recompiled section_table and + * registers all of that section's FuncEntry rows in func_map at + * (fragment_ptr + offset). Also runs the trampoline scanner over the + * fragment's textbin region. Implemented in + * librecomp/src/overlays.cpp. */ +void recomp_register_runtime_fragment(uint8_t *rdram, uint32_t id, + int32_t fragment_ptr); + #ifdef __cplusplus } #endif diff --git a/src/main.cpp b/src/main.cpp index f79580f..bd563b7 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -792,7 +792,12 @@ int main(int argc, char** argv) { // Determine the end of this static function uint32_t cur_func_end = static_cast(section.size + section.ram_addr); - // Search for the closest function + // Search for the closest function. The bounds check must come + // first — the previous order read section_funcs[size] before + // exiting, which is OOB and segfaults for static funcs whose + // address lies past the last known function in the section + // (observed in Stadium's .fragment1 once it was marked + // relocatable, which exposes more CreateStatic targets). size_t closest_func_index = 0; while (closest_func_index < section_funcs.size() && section_funcs[closest_func_index] < static_func_addr) { closest_func_index++; @@ -951,6 +956,18 @@ int main(int argc, char** argv) { for (const N64Recomp::Reloc& reloc : section_relocs) { bool emit_reloc = false; uint16_t target_section = reloc.target_section; + // Skip relocs whose type the runtime doesn't understand + // (e.g. R_MIPS_PC16, R_MIPS_GOT16) — these come through + // the ELF parser as raw cast values and would index + // reloc_names out of bounds, producing a NUL byte in + // the .type field of the emitted RelocEntry. Stadium's + // .rel.fragment* sections include R_MIPS_PC16 (type + // 10) for branches; the recompiler resolves those + // statically already, so dropping them here is safe. + size_t type_idx = static_cast(reloc.type); + if (type_idx >= reloc_names.size()) { + continue; + } // In reference symbol mode, only emit relocations into the table that point to // non-absolute reference symbols, events, or manual patch symbols. if (reference_symbol_mode) { diff --git a/src/recompilation.cpp b/src/recompilation.cpp index c228433..514078f 100644 --- a/src/recompilation.cpp +++ b/src/recompilation.cpp @@ -297,8 +297,16 @@ bool process_instruction(GeneratorType& generator, const N64Recomp::Context& con } else { uint32_t target_section = func.section_index; - // If this instruction has a reloc and the target section is a normal section, use the section of the reloc when searching for a matching target function. - if (has_reloc && reloc_section < 65500) { + // Only override target_section when the reloc points at a real + // section. SectionAbsolute / SectionImport / SectionEvent are + // special sentinels (negative when interpreted signed) and + // would index context.sections out of bounds — observed when + // marking Stadium's .fragment1 relocatable, which surfaces + // R_MIPS_26 relocs whose ELF symbol lives in SHN_ABS (the + // absolute address is already encoded in the JAL immediate, + // so the in-section function lookup against the caller's + // section is the right fallback). + if (has_reloc && reloc_section < context.sections.size()) { target_section = reloc_section; } JalResolutionResult jal_result = resolve_jal(context, target_section, target_func_vram, matched_func_index);