Repository navigation
feat(analyzer): expose data-, instruction- and table-referenced function pointers as callable entry candidates - #399
Open
LeandroSSB wants to merge 3 commits into
Open
LeandroSSB wants to merge 3 commits into
LeandroSSB wants to merge 3 commits into
Conversation
…entry candidates Games reach real functions through data tables of function pointers (SDK printf output chains, driver callback registries, timer tables). The CFG walk from the ELF entry never sees those targets, and a symbol-less one never becomes an entry point, so the generated module has no callable function at that address. At runtime the indirect call is reported as a missing function and the calling guest thread is made dormant - Vanguard lost both of its threads this way (JALR from the printf output-callback walker at 0x356568 to 0x356998, referenced by the data word at 0x510a54, killed tid 1 and tid 3 and stalled the whole engine). AnalysisPasses::collectDataReferencedEntryCandidates scans every loadable section for 4-byte words that point into executable text at an address that is not a known function start and whose first instruction is a standard prologue (addiu/daddiu sp, sp or sd ra, sp). ElfAnalyzer emits those addresses as address-referenced entry points, the same channel the recompiler already uses to synthesize standalone guest entries. Regression test covers the prologue predicate and the candidate scan (known start, interior non-prologue, out-of-text and self pointers are rejected). Suite: 454/454. Co-Authored-By: Claude Code <noreply@anthropic.com>
…candidates Callback pointers are also formed in code as lui+addiu pairs and handed straight to registration syscalls - SetAlarm's handler argument never touches data memory, so the data-word scan cannot see it. Vanguard's alarm-sleep wake callback (0x355840, referenced only by 'lui s1, 0x35; addiu s1, s1, 0x5840' before SetAlarm) had no compiled entry: the runtime's setAlarm rejected the handler as unmapped, the game slept anyway, and no alarm ever woke it - every thread in the engine parked in alarm sleeps while the boot heartbeat timer froze. collectDataReferencedEntryCandidates now also walks executable sections for lui+addiu/daddiu same-register pairs forming a 4-aligned text address that is not a known function start and whose first instruction is a standard prologue. The candidate set stays small (22 additions on MOH5RDVD.ELF vs 33 from the data scan). Suite: 455/455. Co-Authored-By: Claude Code <noreply@anthropic.com>
…candidates Module dispatch tables hand out entries that are bare continuation fragments whose first instruction is a register load, not a prologue. Vanguard's module dispatcher died exactly this way: jalr into 0x10d6b8, pointed to by the .rodata words 0x544420/0x544590, with no dispatch row at the target, so the scheduler put the thread to sleep for good. Third tier of collectDataReferencedEntryCandidates: a data-section word that points into a code section at an address the runtime cannot already dispatch to - not a known function start, not a decoded direct branch label - becomes a callable entry candidate regardless of prologue shape. Function spans are deliberately not the oracle: discovery sweeps them linearly, so a span also covers holes the compiled dispatch table never fills. Pointer sources stay restricted to data sections so code words that merely look like text pointers (every jr ra encodes 0x03e00008, which lands inside text) are not treated as table registrations. On MOH5RDVD.ELF the tier emits 3203 new entry candidates beyond the two earlier tiers, including the 0x10d6b8 dispatcher target. Co-Authored-By: Claude Code <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Root cause
PS2 games reach real functions through pointers the CFG entry walk never sees:
.ctors: 4-byte words in a loadable section that point at aligned unknown text.lui+addiupairs formed in code and passed straight to registration syscalls (SetAlarm/handler, thread entry).lw a0, 0(a1)), not a prologue.With no symbol and no
jalthe function never becomes a compiled entry — and aJALRthrough the unregistered pointer kills the calling thread (makeDormant). On MOH:Vanguard this killed two threads at the first post-boot printf and froze every engine sleep; the class has ~6.8k targets in this ELF alone.Fix
AnalysisPasses::collectDataReferencedEntryCandidatesscans all loadable sections for (a) 4-byte words pointing at aligned unknown text with a standard prologue and (b)lui+addiuaddress-forming pairs; a third tier admits prologue-less data-word targets that are neither known starts nor decoded direct-branch labels. Candidates flow through the existingentry_pointschannel, so the recompiler synthesizes real entries for them.Three commits, one per tier (data / instruction-formed / prologue-less tables), each with its own tests.
Related: #286 resolves calls through registers at runtime — complementary (that is a runtime concern; this is the compile-time entry-set concern).
Test
elf_analyzer_tests: data-referenced (known start / interior / outside-text / self-pointer rejections; prologue unit test), instruction-formed (lui+addiupair), prologue-less table targets, direct branch targets feeding the served set.Measured (MOH:Vanguard)
+33 data and +22 instruction candidates immediately synthesized two missing entries; tier-3 added 3.203 candidates (55 → 3.258), 18 standalone entries; zero no-function inside text and zero dormant threads in the next run.
🤖 Generated with Claude Code
Suite evidence
Fresh configure+build of
ps2x_testson this branch (VS 2022, Release): 450/450 passed, 0 failed.