Correct my own decode bug: there is a caller, and r2 is a return value
349b498 reported that 0x3a7c30 has no jal callers. That was wrong and the bug was
mine: the scan built J-type targets as ((instr & 0x03ffffff) << 2) | 0x0c000000
without masking to the upper nibble, adding 0x0c000000 to every target so nothing
matched.
Corrected: exactly one jal caller, at 0x3a3d84. Still no j tail-calls, still not a
vtbl handler, still not a pointer in any level-0 data file.
Tracing back, r2 is never written between the call and the use:
0x3a3ce4 lw r3, 0x1500(r2)
0x3a3d70 jal 0x2ee178
0x3a3d84 jal 0x3a7c30
so r2 is the return value of 0x2ee178, passed through as the first argument. The
chain is now 0x2ee178 returns a pointer -> 0x3a7c30 -> 0x3a8060 -> addiu r17, r18,
0xc0.
Next question is one function: does 0x2ee178 return core_data + moby_classes.offset?
If so the reading is confirmed and the loader is located; if not, the 0xc0 in
0x3a8060 is an unrelated struct member.
Also recorded in docs: a negative from a hand-rolled decoder is worth nothing
until checked against a known case. jal 0x3a8060 decoded correctly under a
different constant in the same probe and I did not notice.
284 passing with the disc present, clippy clean.
This commit is contained in:
@@ -2268,3 +2268,49 @@ Not distinguished. The immediate question for anyone continuing is whether
|
||||
`? -> 0x3a7c30 (no static caller) -> 0x3a8060 -> addiu r17, r18, 0xc0`, with `r2`
|
||||
supplied by whatever reaches `0x3a7c30`. Every remaining question is about
|
||||
control flow and register provenance, not about counts.
|
||||
|
||||
### Correction to 349b498: there is a caller, and I had a decode bug
|
||||
|
||||
`349b498` reported "0x3a7c30 has no `jal` callers anywhere in level-0 `.text`".
|
||||
**That was wrong, and the bug was mine.** The scan built the branch target as
|
||||
|
||||
((instr & 0x03ff_ffff) << 2) | 0x0c00_0000
|
||||
|
||||
without masking to the upper four bits. J-type targets take their top nibble from
|
||||
the opcode field, which is zero here, so the correct form is just
|
||||
`(instr & 0x03ffffff) << 2`. The un-masked constant added `0x0c000000` to every
|
||||
target, so the comparison never matched anything.
|
||||
|
||||
Corrected scan: **`0x3a7c30` has exactly one `jal` caller, at `0x3a3d84`.** No
|
||||
`j` tail-calls, and the function is still not a `lvl.vtbl` handler and still not a
|
||||
pointer in any level-0 data file.
|
||||
|
||||
### r2 is a return value, not a caller argument
|
||||
|
||||
Tracing backwards from `0x3a3d84` inside the calling function:
|
||||
|
||||
0x3a3ce4 lw r3, 0x1500(r2) r2 already live, used as a base
|
||||
0x3a3d70 jal 0x2ee178
|
||||
0x3a3d84 jal 0x3a7c30 r2 is v0, i.e. the *result* of the call above
|
||||
|
||||
No instruction between the two writes `r2`. It is simply the return value of the
|
||||
function at `0x2ee178`, passed straight through as the first argument to
|
||||
`0x3a7c30`.
|
||||
|
||||
So the chain is
|
||||
|
||||
0x2ee178 --returns a pointer--> 0x3a7c30 --passes it in r2-->
|
||||
0x3a8060 --addiu r17, r18, 0xc0--> 16-byte-stride walk
|
||||
|
||||
**The next question is now one function:** does `0x2ee178` return
|
||||
`core_data + moby_classes.offset`? It is a small, self-contained function and it is
|
||||
the only thing between the program and the constant. If it returns a pointer built
|
||||
with `addiu r2, base, 0xc0`, the reading is confirmed and the moby class loader
|
||||
has been located. If it returns something else, the `0xc0` in `0x3a8060` is an
|
||||
unrelated struct member.
|
||||
|
||||
**Recorded as the lesson it is:** a negative result from a hand-rolled decoder is
|
||||
worth nothing until the decoder is checked against a case where the answer is
|
||||
already known. `jal 0x3a8060` decoded correctly under a different constant in the
|
||||
same probe and I did not notice, because one probe reporting a plausible number is
|
||||
as convincing as one reporting zero.
|
||||
|
||||
Reference in New Issue
Block a user