phase6: register the leaf/gp batch and record the framed-function blocker
This commit is contained in:
+5
-1
@@ -10,7 +10,11 @@
|
||||
# the build is the all-payload data baseline and contains no C.
|
||||
#
|
||||
# Matched so far:
|
||||
0x80012780 0x8001278C src/func_80012780.c
|
||||
0x80017AD4 0x80017AE8 src/func_80017AD4.c
|
||||
0x80017AE8 0x80017AF8 src/func_80017AE8.c
|
||||
0x80026264 0x80026274 src/func_80026264.c
|
||||
0x800262E0 0x800262EC src/func_800262E0.c
|
||||
0x800262EC 0x800262F8 src/func_800262E0.c
|
||||
0x8002D2A0 0x8002D2BC src/func_8002D2A0.c
|
||||
0x8002D2BC 0x8002D2D4 src/func_8002D2BC.c
|
||||
0x80012780 0x8001278C src/func_80012780.c
|
||||
|
||||
|
@@ -16,3 +16,5 @@ D_8012E2C8 0x8012E2C8
|
||||
D_8010F354 0x8010F354
|
||||
_gp 0x80121938
|
||||
D_80121974 0x80121974 gp
|
||||
D_80121B18 0x80121B18 gp
|
||||
D_80121B14 0x80121B14 gp
|
||||
|
||||
|
@@ -13,6 +13,11 @@ project; each claim was observed here first.
|
||||
| Assembler | `ASPSX` 2.81 (Sony) | SDK banner |
|
||||
| Working invocation | `cc1 -quiet -O2 -G0 -mno-split-addresses`, then GNU `as -march=r3000 -G0` | Byte-identical ranges; `make gate` |
|
||||
|
||||
> **Under review (Phase 6).** This identification does not explain the whole executable. Framed
|
||||
> functions use a reorder-mode epilogue that `egcs-2.91.66` + maspsx does not produce, and two
|
||||
> verified functions require opposite assembler scheduling. See finding 11 and
|
||||
> [PHASE6_FRAMED_BLOCKER.md](PHASE6_FRAMED_BLOCKER.md).
|
||||
|
||||
## Findings
|
||||
|
||||
### 1. `-mno-split-addresses` is mandatory
|
||||
@@ -128,8 +133,27 @@ over 1,286 distinct addresses. See [PHASE6_SMALL_DATA.md](PHASE6_SMALL_DATA.md).
|
||||
*Limit:* the numeric `-G` threshold is **not recoverable** from the code (object sizes are unknown);
|
||||
`-mgpopt`/`-mno-gpopt` produce identical `cc1` output for these functions.
|
||||
|
||||
### 11. Framed epilogues use reorder-mode scheduling — unresolved
|
||||
|
||||
- **322** framed functions end `lw ra,off(sp)` / `addiu sp,sp,N` / `jr ra` / `nop` (shape A).
|
||||
- `egcs-2.91.66` + `-mno-split-addresses` + maspsx produces `lw ra,off(sp)` / `nop` / `jr ra` /
|
||||
`addiu sp,sp,N` (shape B). The **real** PsyQ 4.5 `CC1PSX` and the **real** `ASPSX` 2.81 also
|
||||
produce shape B.
|
||||
- The `gcc-2.6.0-psx` / `2.6.3-psx` / `2.7.2-psx` / `2.7.2-cdk` builds emit the epilogue in reorder
|
||||
mode and reproduce shape A byte-for-byte (test case `0x80024C14`).
|
||||
- Separately, `q_sym_store` (`0x800F3160`) and `func_8002D2BC` (`0x8002D2BC`) require **opposite**
|
||||
assembler scheduling, so no single assembler/maspsx configuration reproduces both.
|
||||
|
||||
*Basis:* the epilogue census (322 vs 13), the `0x80024C14` compiler matrix, and real-`ASPSX`
|
||||
assembly of the cc1 output.
|
||||
*Limit:* **open** — the compiler identity and the assembler model need revision before framed
|
||||
functions can be matched. See [PHASE6_FRAMED_BLOCKER.md](PHASE6_FRAMED_BLOCKER.md).
|
||||
|
||||
## Open questions
|
||||
|
||||
- **Framed-function epilogue and assembler scheduling are unresolved** (finding 11): the original
|
||||
compiler is a 2.6/2.7-family build, or egcs-2.91.66 with an unidentified flag; and
|
||||
`q_sym_store`/`func_8002D2BC` conflict under every tested assembler configuration.
|
||||
- The exact `-G` small-data threshold is not recoverable from the code; the per-symbol `gp` form is
|
||||
reconstructed from the original's accesses instead (finding 10).
|
||||
- Whether `-mgpopt` was passed is not observable: it does not change `cc1` output for the cases
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
# Phase 6 — Framed-Function and Assembler-Scheduling Blocker
|
||||
|
||||
**Scope:** P6-T6 (the matching batch). **Status: OPEN — recorded, not resolved.** This blocks the
|
||||
"call with a frame" shape and any framed function, and it puts the Phase 5 compiler identification
|
||||
back in question. It does **not** affect the 8 already-registered matches.
|
||||
|
||||
## 1. The framed epilogue
|
||||
|
||||
A census of `jr ra` sites shows two epilogue shapes for functions with a stack frame:
|
||||
|
||||
| Shape | Instructions | Count |
|
||||
|---|---|---|
|
||||
| **A** (game code) | `lw ra,off(sp)` / `addiu sp,sp,N` / `jr ra` / `nop` | **322** |
|
||||
| **B** (CRT/library region `0x800FBxxx`–`0x80100xxx`) | `lw ra,off(sp)` / `nop` / `jr ra` / `addiu sp,sp,N` | 13 |
|
||||
|
||||
The original uses shape **A** almost everywhere. Our pipeline produces shape **B**:
|
||||
|
||||
- The open `gcc-2.91.66-psx` and the **real** PsyQ 4.5 `CC1PSX.EXE` produce the *same* cc1 output,
|
||||
which puts the stack restore in the jump delay slot under `.set noreorder`.
|
||||
- The **real** `ASPSX.EXE` 2.81, run on that cc1 output, also produces shape **B** (verified: the
|
||||
object's `.text` is `lw ra,16(sp)` / `nop` / `jr ra` / `addiu sp,sp,24`).
|
||||
- The `gcc-2.6.0-psx`, `gcc-2.6.3-psx`, `gcc-2.7.2-psx` and `gcc-2.7.2-cdk` builds emit the epilogue
|
||||
in **reorder** mode (no `.set noreorder`). With maspsx + GNU `as` they produce shape **A** and
|
||||
match the original byte-for-byte.
|
||||
|
||||
Test case (`0x80024C14..0x80024C34`, a trivial `void f(void){ g(); }`):
|
||||
|
||||
| Compiler | Result |
|
||||
|---|---|
|
||||
| `gcc-2.91.66-psx` (`-mno-split-addresses`) | `DIFF` at `0x80024C28` |
|
||||
| real `CC1PSX.EXE` 4.5 | same as 2.91.66 |
|
||||
| `gcc-2.6.0-psx`, `gcc-2.6.3-psx`, `gcc-2.7.2-psx`, `gcc-2.7.2-cdk` | **`MATCH`** |
|
||||
|
||||
**Implication:** the original compiler is in the **2.6/2.7 family**, or egcs-2.91.66 emits shape A
|
||||
under an unidentified flag (none found: `-O0/-O1/-O2/-O3/-Os`, `-fno-delayed-branch`,
|
||||
`-fno-schedule-insns(2)`, `-mcpu=r3000`, `-mips1`, `-fno-omit-frame-pointer`, and others were tried).
|
||||
|
||||
## 2. The conflicting assembler evidence
|
||||
|
||||
Two functions that are both byte-verified force **opposite** assembler scheduling:
|
||||
|
||||
| Function | Original bytes | Requires |
|
||||
|---|---|---|
|
||||
| `q_sym_store` `0x800F3160` (12 B) | `lui at,0x8014` / `jr ra` / `sw a0,off(at)` | `$at` macro form **and** the store in the jump delay slot (GNU `as` reorder behaviour) |
|
||||
| `func_8002D2BC` `0x8002D2BC` (24 B) | `lui v0` / `addiu v0` / `lui at` / `sw v0,off(at)` / `jr ra` / `nop` | ASPSX `addiu` for `la` **and** the store before the jump (noreorder behaviour) |
|
||||
|
||||
Measured results for the same cc1 output:
|
||||
|
||||
| Pipeline | `q_sym_store` | `func_8002D2BC` |
|
||||
|---|---|---|
|
||||
| `as` reorder, assemble-time `--defsym`, no maspsx | **MATCH** (12 B) | `LENGTH-MISMATCH` (20 B) |
|
||||
| maspsx + link-time symbols (current, P6-T3) | `LENGTH-MISMATCH` (16 B) | **MATCH** (24 B) |
|
||||
| real `ASPSX.EXE` 2.81 | `LENGTH-MISMATCH` (16 B) | **MATCH** (24 B) |
|
||||
|
||||
No single assembler, flag set, or maspsx configuration reproduces both. Either the original compiler
|
||||
emitted per-function `.set` scheduling that we have not reproduced, or the toolchain/assembler model
|
||||
is wrong.
|
||||
|
||||
**Regression:** P6-T3's maspsx stage (needed for `func_8002D2BC`'s `la`) changes `q_sym_store` from
|
||||
`MATCH` to `LENGTH-MISMATCH`. `q_sym_store` was a Phase 5 oracle but was never registered, so the
|
||||
tracked gate is unaffected.
|
||||
|
||||
## 3. Consequences
|
||||
|
||||
- **No framed function can be matched** until the epilogue scheduling is resolved; the "call with a
|
||||
frame" shape required by P6-T6 is blocked.
|
||||
- The `0x8005DEF8` re-attempt is **pre-empted**: it is a framed function, so its epilogue would
|
||||
mismatch regardless of the constant-multiply reconstruction.
|
||||
- The Phase 5 compiler identification (`egcs-2.91.66`, PsyQ 4.5) is **not sufficient** to explain the
|
||||
executable. The loader's `PsyQ Version = 4.5.0` may describe the runtime library, not the compiler.
|
||||
|
||||
## 4. What is unaffected
|
||||
|
||||
- The 8 registered regions still pass `sf3_match range` and `make gate`
|
||||
(`c_regions=8`, 0 differing bytes, SHA-1 `e173426c157384ebf1b6caf8c6fea18a85a14af9`).
|
||||
- The four toolchain-gap closures (P6-T2..T4) remain byte-proven for the functions they were tested
|
||||
on.
|
||||
|
||||
## 5. Questions for the developer
|
||||
|
||||
1. Re-identify the compiler within the 2.6/2.7 family (2.6.0, 2.6.3, 2.7.2-psx, 2.7.2-cdk) — more
|
||||
discriminating functions are needed.
|
||||
2. Decide the assembler model (ASPSX vs GNU `as` reorder vs a per-function hybrid).
|
||||
3. Decide how to handle maspsx: it is required for `la` but breaks the `sw`-delay-slot case.
|
||||
@@ -12,7 +12,7 @@
|
||||
- [x] **P6-T4 — `-G` small-data threshold from byte evidence** (complete)
|
||||
- [x] **Rules check** — re-read `AGENTS.md` mandatory behavior after P6-T4 and stated the required continuation notice.
|
||||
- [x] **P6-T5 — Evidence-graded function-boundary inventory** (complete)
|
||||
- [ ] P6-T6 — First matching batch, with duplicate sharing
|
||||
- [~] **P6-T6 — First matching batch, with duplicate sharing** — **partial**: 8 regions registered and duplicate sharing demonstrated; the "call with a frame" shape is blocked by `docs/PHASE6_FRAMED_BLOCKER.md`
|
||||
- [ ] P6-T7 — Cookbook, conventions, verification record, and phase gate
|
||||
|
||||
## P6-T1 — Baseline revalidation (2026-09-23)
|
||||
@@ -193,3 +193,29 @@ code in this single linked image.
|
||||
|
||||
**Limit:** no candidate is promoted to a match on the strength of the inventory alone; every match
|
||||
still needs `sf3_match range` and `make gate`.
|
||||
|
||||
## P6-T6 — Matching batch (partial, 2026-09-23)
|
||||
|
||||
**Delivered:**
|
||||
|
||||
- Registered three more matches and one duplicate row, taking the registry to **8 regions**:
|
||||
`func_80017AE8` (three struct stores), `func_80026264` (gp getter), and `func_800262E0` /
|
||||
`func_800262EC` (an identical 12-byte gp setter body registered twice against one source).
|
||||
- **Duplicate sharing demonstrated on a real shared body:** the 12-byte body occurs exactly twice in
|
||||
the payload (`0x800262E0`, `0x800262EC`); both rows compile from `src/func_800262E0.c`.
|
||||
- `make gate`: `c_regions=8`, 0 differing bytes, SHA-1 `e173426c…`.
|
||||
|
||||
**Blocked:** the "call with a frame" shape, and the `0x8005DEF8` re-attempt (itself a framed
|
||||
function). The blocker is recorded in `docs/PHASE6_FRAMED_BLOCKER.md`:
|
||||
|
||||
- 322 framed functions end `lw ra,off(sp)` / `addiu sp,sp,N` / `jr ra` / `nop` (shape A); our
|
||||
`egcs-2.91.66` + maspsx pipeline produces `lw ra` / `nop` / `jr ra` / `addiu sp,sp,N` (shape B).
|
||||
- The **real** PsyQ 4.5 `CC1PSX` and the **real** `ASPSX` 2.81 also produce shape B, so the original
|
||||
compiler is not egcs-2.91.66 for this class; the `gcc-2.6.0/2.6.3/2.7.2-psx/2.7.2-cdk` builds
|
||||
produce shape A and match the test case byte-for-byte.
|
||||
- `q_sym_store` (`0x800F3160`) and `func_8002D2BC` (`0x8002D2BC`) require **opposite** assembler
|
||||
scheduling, so no single assembler/maspsx configuration reproduces both. P6-T3's maspsx fixes
|
||||
`la` but regresses `q_sym_store`.
|
||||
|
||||
**Not claimed:** no framed function is matched; the Phase 5 compiler identification is recorded as
|
||||
insufficient pending developer input.
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* func_80017AE8 — 16 bytes at 0x80017AE8..0x80017AF8
|
||||
*
|
||||
* Byte-identical reconstruction of a leaf that stores three arguments into
|
||||
* consecutive struct fields, with the last store scheduled into the `jr ra`
|
||||
* delay slot.
|
||||
*
|
||||
* The observed instructions are:
|
||||
* sw a1,12(a0)
|
||||
* sw a2,16(a0)
|
||||
* jr ra
|
||||
* sw a3,20(a0) (delay slot)
|
||||
*
|
||||
* LIMITS: the function name, the struct name and every field name are
|
||||
* hypotheses reconstructed from the disassembly. Only the compiled bytes are
|
||||
* evidence. Fields before offset 0x0C and after 0x14 are not touched here and
|
||||
* are not modelled.
|
||||
*/
|
||||
|
||||
typedef struct {
|
||||
char pad[12]; /* offsets 0x00..0x0B — not touched */
|
||||
int f3; /* offset 0x0C */
|
||||
int f4; /* offset 0x10 */
|
||||
int f5; /* offset 0x14 */
|
||||
} func_80017AE8_args;
|
||||
|
||||
void func_80017AE8(func_80017AE8_args *p, int a, int b, int c) {
|
||||
p->f3 = a;
|
||||
p->f4 = b;
|
||||
p->f5 = c;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* func_80026264 — 16 bytes at 0x80026264..0x80026274
|
||||
*
|
||||
* Byte-identical reconstruction of a leaf getter that returns the difference of
|
||||
* two gp-relative globals.
|
||||
*
|
||||
* The observed instructions are:
|
||||
* lw v1,480(gp) v1 = D_80121B18
|
||||
* lw v0,476(gp) v0 = D_80121B14
|
||||
* jr ra
|
||||
* subu v0,v1,v0 (delay slot)
|
||||
*
|
||||
* `gp` is 0x80121938, so the two globals are 0x80121B18 and 0x80121B14; both
|
||||
* are marked `gp` in `config/symbols.tsv` and accessed with `%gp_rel`.
|
||||
*
|
||||
* LIMITS: the function name and both global names/types are hypotheses
|
||||
* reconstructed from the disassembly. Only the compiled bytes are evidence.
|
||||
*/
|
||||
|
||||
extern int D_80121B18;
|
||||
extern int D_80121B14;
|
||||
|
||||
int func_80026264(void) {
|
||||
return D_80121B18 - D_80121B14;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* func_800262E0 / func_800262EC — 12 bytes each
|
||||
*
|
||||
* Byte-identical reconstruction of a leaf setter. The identical 12-byte body
|
||||
* occurs at both 0x800262E0..0x800262EC and 0x800262EC..0x800262F8, so it is
|
||||
* matched once and registered twice against this source (the documented
|
||||
* N-rows-to-one-source duplicate-sharing mechanism).
|
||||
*
|
||||
* The observed instructions are:
|
||||
* sw a0,480(gp) D_80121B18 = x
|
||||
* jr ra
|
||||
* nop
|
||||
*
|
||||
* `gp` is 0x80121938, so the target is 0x80121B18; it is marked `gp` in
|
||||
* `config/symbols.tsv` and accessed with `%gp_rel`.
|
||||
*
|
||||
* LIMITS: the function name and the global's name/type are hypotheses
|
||||
* reconstructed from the disassembly. Only the compiled bytes are evidence.
|
||||
* The two addresses are treated as two function entries because each ends in
|
||||
* its own `jr ra` and each is a distinct code location; this is an evidence
|
||||
* judgement, not a proven original symbol table.
|
||||
*/
|
||||
|
||||
extern int D_80121B18;
|
||||
|
||||
void func_800262E0(int x) {
|
||||
D_80121B18 = x;
|
||||
}
|
||||
Reference in New Issue
Block a user