recomp: SectionAbsolute guard + reloc filter + runtime-fragment decl

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) <noreply@anthropic.com>
This commit is contained in:
Matthew Stanley 2026-05-05 21:47:09 -07:00
parent 81213c1831
commit 5fe2c734a5
3 changed files with 38 additions and 3 deletions

View File

@ -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

View File

@ -792,7 +792,12 @@ int main(int argc, char** argv) {
// Determine the end of this static function
uint32_t cur_func_end = static_cast<uint32_t>(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<size_t>(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) {

View File

@ -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);