phase6: reproduce a gp-relative function and record the small-data evidence
This commit is contained in:
@@ -13,3 +13,4 @@
|
||||
0x80017AD4 0x80017AE8 src/func_80017AD4.c
|
||||
0x8002D2A0 0x8002D2BC src/func_8002D2A0.c
|
||||
0x8002D2BC 0x8002D2D4 src/func_8002D2BC.c
|
||||
0x80012780 0x8001278C src/func_80012780.c
|
||||
|
||||
|
+8
-1
@@ -1,11 +1,18 @@
|
||||
# Symbol registry: absolute addresses for cross-references used by C regions.
|
||||
#
|
||||
# Columns: NAME<TAB>address (address is hex; 0x prefix optional)
|
||||
# Columns: NAME<TAB>address[<TAB>gp] (address is hex; 0x prefix optional)
|
||||
#
|
||||
# These name unmatched functions and globals so a C region can reference them
|
||||
# by name. The harness passes each row to the linker as
|
||||
# `--defsym NAME=0xADDR`. A name here is a placeholder for an address, not a
|
||||
# claim about the object's meaning; real names are earned from evidence.
|
||||
#
|
||||
# A `gp` marker means the original accessed the symbol gp-relative, so the
|
||||
# harness rewrites the macro access to explicit `%gp_rel`. `_gp` is the small
|
||||
# data pointer value crt0 loads at 0x800FB3E4 (`lui gp,0x8012` +
|
||||
# `addiu gp,gp,6456`), and is needed to resolve R_MIPS_GPREL16.
|
||||
g_80122354 0x80122354
|
||||
D_8012E2C8 0x8012E2C8
|
||||
D_8010F354 0x8010F354
|
||||
_gp 0x80121938
|
||||
D_80121974 0x80121974 gp
|
||||
|
||||
|
@@ -88,7 +88,11 @@ g_80122354 0x80122354
|
||||
|
||||
`make code` and `make gate` pass `config/symbols.tsv` automatically; `range` accepts `--symbols`
|
||||
(and `--defsym` for one-off names). A name in the registry is an address placeholder, not a claim
|
||||
about the object's meaning. `0x8002D2A0` and `0x8002D2BC` are registered and gated this way.
|
||||
about the object's meaning. `0x8002D2A0`, `0x8002D2BC` and `0x80012780` are registered and gated this way.
|
||||
|
||||
An optional third column `gp` marks a symbol the original accessed `gp`-relative (small data). The
|
||||
harness rewrites that symbol's macro accesses to explicit `%gp_rel(sym)($gp)`, and the linker resolves
|
||||
`R_MIPS_GPREL16` against the `_gp` row. See [PHASE6_SMALL_DATA.md](PHASE6_SMALL_DATA.md).
|
||||
|
||||
Symbols are resolved by the **linker**, not the assembler: the assembler leaves them undefined and
|
||||
emits `%hi`/`%lo` relocations, and the linker applies the HI16 carry adjustment. This is what makes a
|
||||
|
||||
@@ -112,9 +112,27 @@ this executable overwhelmingly `t0`.
|
||||
*Limit:* the register choice is context-dependent, so it cannot be used to identify the compiler; it
|
||||
only matters when reproducing a specific function.
|
||||
|
||||
### 10. `gp`-relative access is per symbol, forced with `%gp_rel`
|
||||
|
||||
- The `gp` base is **`0x80121938`**: crt0 does `lui gp,0x8012` + `addiu gp,gp,6456` at `0x800FB3E4`.
|
||||
- The original accesses some globals `gp`-relative and others absolutely **even within ±32 KB of
|
||||
`gp`**: `0x80122354` is absolute (`func_8002D2A0`) while `0x80121974` is `gp`-relative
|
||||
(`func_80012780`). The choice is the object's **size/section**, not its distance from `gp`.
|
||||
- GNU `as` only expands `lw`/`sw` macros to `%gp_rel` for an undefined symbol when `-G>0`, and it does
|
||||
not do so for `sw` at all without the symbol's section. The harness therefore marks `gp` symbols in
|
||||
the registry and rewrites their accesses to explicit `%gp_rel(sym)($gp)`; the linker resolves
|
||||
`R_MIPS_GPREL16` against `_gp`.
|
||||
|
||||
*Basis:* `func_80012780` (`sw a0,60(gp)`) byte-identical; a census of 3,976 `gp`-relative accesses
|
||||
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.
|
||||
|
||||
## Open questions
|
||||
|
||||
- The exact `-G` small-data threshold (the executable clearly uses `gp`-relative data in places).
|
||||
- Whether `-mgpopt` was passed.
|
||||
- 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
|
||||
tested.
|
||||
- One reconstructed function (`0x8005DEF8`) whose constant multiply the original emits as a real
|
||||
`mult` while every compiler tested synthesises it — the reconstruction is the likely cause.
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
# Phase 6 — Small Data (`gp`) Evidence
|
||||
|
||||
**Scope:** P6-T4 — determine the small-data threshold from the executable's `gp`-relative accesses and
|
||||
reproduce at least one `gp`-relative function byte-for-byte.
|
||||
**Status:** complete. One `gp`-relative function matched; the numeric threshold is recorded as **not
|
||||
uniquely recoverable** from the code, with the evidence and limits stated.
|
||||
|
||||
## The `gp` base
|
||||
|
||||
crt0 loads `gp` at `0x800FB3E4` from an absolute value:
|
||||
|
||||
| Address | Instruction | Value |
|
||||
|---|---|---|
|
||||
| `0x800FB3E4` | `lui gp,0x8012` | high half |
|
||||
| `0x800FB3E8` | `addiu gp,gp,6456` | `gp = 0x80121938` |
|
||||
|
||||
So the small-data pointer for this executable is **`0x80121938`**.
|
||||
|
||||
## Access census
|
||||
|
||||
Scanning every load/store with an immediate base register of `gp`:
|
||||
|
||||
- **3,976** such instructions.
|
||||
- **1,286** distinct target addresses, spanning `0x80119938..0x801298CF` (≈64 KB), with 257 gaps
|
||||
larger than 64 bytes — i.e. many separate small objects.
|
||||
- The most common offsets are 32–46 (addresses `0x80121958..0x80121966`), suggesting one or more
|
||||
small objects close to `gp`.
|
||||
- `gp`-relative **address** materialisation (`addiu rt,gp,imm`) also occurs, e.g. `addiu r4,gp,44`,
|
||||
so the original also took the address of small objects.
|
||||
|
||||
## Why the numeric threshold is not recoverable here
|
||||
|
||||
GNU `as` behaviour (measured): with `-G N` where `N > 0`, the symbol macros `lw rt,sym` / `sw rt,sym`
|
||||
for an **undefined** symbol expand to `%gp_rel`; with `-G0` they expand to `lui`/`%lo` absolute form.
|
||||
|
||||
But the original's choice is **per symbol**, not a single range test. The counter-example is decisive:
|
||||
|
||||
| Symbol | Address | Original access |
|
||||
|---|---|---|
|
||||
| `g_80122354` (`func_8002D2A0`) | `0x80122354` | **absolute** (`lui v0,0x8012` / `lw v0,9044(v0)`) |
|
||||
| `D_80121974` (`func_80012780`) | `0x80121974` | **`gp`-relative** (`sw a0,60(gp)`) |
|
||||
|
||||
`0x80122354` is inside ±32 KB of `gp` (offset `+0xA1C`), yet is accessed absolutely. The difference is
|
||||
the **size/section** of each object (`-G` puts objects ≤ N bytes in `.sdata`/`.sbss`), and object sizes
|
||||
are not observable from the code because the data is the untouched payload fallback.
|
||||
|
||||
**Conclusion:** the bytes determine *which symbols* were accessed `gp`-relative, not the numeric `-G`
|
||||
value. A lower bound is that the small-data region spans the full signed 16-bit offset range; the
|
||||
largest `gp`-relative object's size would bound `-G` from above, but that size is unknown.
|
||||
|
||||
## `-mgpopt` evidence
|
||||
|
||||
`cc1` output is **identical** for `-G0`, `-G4`, `-G8`, `-G16`, `-G32`, and identical with `-mgpopt`
|
||||
versus `-mno-gpopt`, for `extern int`/`char`/struct loads and stores:
|
||||
|
||||
```
|
||||
cc1 -quiet -O2 -G8 -mno-split-addresses [-mgpopt|-mno-gpopt] -> identical .s
|
||||
```
|
||||
|
||||
The `gp`-versus-absolute decision is therefore made by the assembler (from the symbol's section) and
|
||||
the linker, not by `cc1`. `-mgpopt` is **not observable** from these functions and is left unselected.
|
||||
|
||||
## The reproduction
|
||||
|
||||
`func_80012780` (`0x80012780..0x8001278C`, 12 bytes):
|
||||
|
||||
```
|
||||
sw a0,60(gp)
|
||||
jr ra
|
||||
nop
|
||||
```
|
||||
|
||||
Mechanism (all now in the harness):
|
||||
|
||||
1. `config/symbols.tsv` marks `D_80121974` with the `gp` marker and defines `_gp = 0x80121938`.
|
||||
2. The harness rewrites the maspsx output `sw $4,D_80121974` to
|
||||
`sw $4,%gp_rel(D_80121974)($gp)` — GNU `as` will not do this itself for a symbol whose section it
|
||||
cannot see.
|
||||
3. GNU `as -G0` emits `R_MIPS_GPREL16`; the linker resolves it against `--defsym _gp=0x80121938`.
|
||||
|
||||
Result: `sf3_match range` → 0 differing bytes; `make gate` → `c_regions=4`, 0 differing bytes, SHA-1
|
||||
`e173426c157384ebf1b6caf8c6fea18a85a14af9`.
|
||||
|
||||
Exact commands:
|
||||
|
||||
```bash
|
||||
./tools/sf3_match range \
|
||||
--exe 'extracted/SCUS_946.40;1' --source src/func_80012780.c \
|
||||
--start 0x80012780 --end 0x8001278C --work .run/p6-t4-gp \
|
||||
--symbols config/symbols.tsv
|
||||
# -> result=MATCH
|
||||
```
|
||||
|
||||
## Limits
|
||||
|
||||
- `gp` symbols are identified from the original's access form, not from a recovered symbol table; the
|
||||
registry marker is the reconstruction of that per-symbol property.
|
||||
- The `la`-of-a-`gp`-symbol rewrite (`addiu rt,$gp,%gp_rel(sym)`) is implemented but not yet exercised
|
||||
on a real function.
|
||||
- The numeric `-G` remains unresolved and is not needed by the current mechanism; it would matter only
|
||||
if a region defined its own small data.
|
||||
@@ -9,8 +9,8 @@
|
||||
- [x] **P6-T1 — Phase control records, baseline revalidation, and open-item triage** (complete)
|
||||
- [x] **P6-T2 — Registry symbol support and per-region flag overrides** (complete)
|
||||
- [x] **P6-T3 — `maspsx` integration and the ASPSX `la` verification** (complete)
|
||||
- [ ] P6-T4 — `-G` small-data threshold from byte evidence
|
||||
- [ ] Rules check
|
||||
- [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.
|
||||
- [ ] P6-T5 — Evidence-graded function-boundary inventory
|
||||
- [ ] P6-T6 — First matching batch, with duplicate sharing
|
||||
- [ ] P6-T7 — Cookbook, conventions, verification record, and phase gate
|
||||
@@ -129,3 +129,37 @@ next code). This confirms the Phase 5 conclusion that the entry is CRT startup,
|
||||
**Limits:** maspsx is exercised on `la`, symbol load/store and delay-slot scheduling; `-G`/gp-relative
|
||||
behaviour is not yet exercised (P6-T4). The clear-loop negative is bounded to the tested reconstruction
|
||||
and symbol names.
|
||||
|
||||
## P6-T4 — Small-data (`gp`) threshold from byte evidence (2026-09-23)
|
||||
|
||||
**Delivered:**
|
||||
|
||||
- `config/symbols.tsv` gained an optional `gp` marker and the `_gp` row (`0x80121938`, from crt0's
|
||||
`lui gp,0x8012` + `addiu gp,gp,6456` at `0x800FB3E4`).
|
||||
- The harness rewrites `gp`-marked symbols' macro accesses to explicit `%gp_rel(sym)($gp)`; the linker
|
||||
resolves `R_MIPS_GPREL16` against `_gp`.
|
||||
- Registered `0x80012780..0x8001278C` (`src/func_80012780.c`) — the first `gp`-relative match.
|
||||
- Full evidence and limits in `docs/PHASE6_SMALL_DATA.md`.
|
||||
|
||||
**Findings:**
|
||||
|
||||
| Question | Byte-evidenced answer |
|
||||
|---|---|
|
||||
| `gp` base | `0x80121938` (crt0, `0x800FB3E4`) |
|
||||
| How many `gp`-relative accesses | 3,976 over 1,286 distinct addresses, spanning ≈64 KB |
|
||||
| Is the threshold recoverable? | **No** — `0x80122354` is absolute and `0x80121974` is `gp`-relative, both within ±32 KB of `gp`; the choice is the object's size/section, which is unobservable |
|
||||
| Does `-mgpopt` matter? | Not observable: `-mgpopt`/`-mno-gpopt` (and `-G0..-G32`) give identical `cc1` output |
|
||||
|
||||
**Verification:**
|
||||
|
||||
| Check | Result |
|
||||
|---|---|
|
||||
| `range --symbols config/symbols.tsv` for `func_80012780` | 12 bytes, 0 differing, `result=MATCH` |
|
||||
| `make gate` | `c_regions=4`, 0 differing, SHA-1 `e173426c…`, exit 0 |
|
||||
| Synthetic suite | 71 tests pass (gp-marker parsing, unknown-marker rejection, end-to-end gp rewrite) |
|
||||
|
||||
**Limits:** `gp` symbols are identified from the original's access form, not a recovered symbol table;
|
||||
the numeric `-G` is unresolved and unnecessary under this mechanism; the `la`-of-a-`gp`-symbol rewrite
|
||||
is implemented but not yet exercised on a real function.
|
||||
|
||||
**Rules check — re-read complete. Continuing with P6-T5.**
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* func_80012780 — 12 bytes at 0x80012780..0x8001278C
|
||||
*
|
||||
* Byte-identical reconstruction of a leaf setter that stores its argument to a
|
||||
* **gp-relative** global. This is the project's first match whose bytes depend
|
||||
* on the small-data (`gp`) addressing form.
|
||||
*
|
||||
* The observed instructions are:
|
||||
* sw a0,60(gp) D_80121974 = x
|
||||
* jr ra
|
||||
* nop
|
||||
*
|
||||
* `gp` is 0x80121938: crt0 loads it at 0x800FB3E4 (`lui gp,0x8012` +
|
||||
* `addiu gp,gp,6456`). The target is therefore 0x80121974.
|
||||
*
|
||||
* GNU `as` only turns the `sw rt,sym` macro into `%gp_rel` when it can see the
|
||||
* symbol defined in `.sdata`/`.sbss`. Here the data is the untouched payload
|
||||
* fallback, so the harness rewrites the access to explicit `%gp_rel` for
|
||||
* symbols marked `gp` in `config/symbols.tsv`; the linker resolves
|
||||
* R_MIPS_GPREL16 against `_gp`.
|
||||
*
|
||||
* LIMITS: the function name and the global's name and type are hypotheses
|
||||
* reconstructed from the disassembly; only the compiled bytes are evidence.
|
||||
* The global's size is unknown, so this match does not by itself pin the
|
||||
* original `-G` threshold (see docs/PHASE6_SMALL_DATA.md).
|
||||
*/
|
||||
|
||||
extern int D_80121974;
|
||||
|
||||
void func_80012780(int x) {
|
||||
D_80121974 = x;
|
||||
}
|
||||
+75
-7
@@ -231,30 +231,48 @@ def parse_regions(text: str) -> list[Region]:
|
||||
return regions
|
||||
|
||||
|
||||
# A tracked symbol registry: `NAME<TAB>address` rows naming absolute addresses.
|
||||
# A tracked symbol registry: `NAME<TAB>address[<TAB>gp]` rows naming absolute
|
||||
# addresses. The optional `gp` marker forces a gp-relative access.
|
||||
_SYMBOL_NAME = re.compile(r"[A-Za-z_.][A-Za-z0-9_.]*\Z")
|
||||
|
||||
|
||||
def parse_symbols(text: str) -> list[str]:
|
||||
"""Parse a symbol registry into assembler `--defsym NAME=0xADDR` arguments."""
|
||||
@dataclass(frozen=True)
|
||||
class SymbolTable:
|
||||
defsyms: tuple[str, ...] = ()
|
||||
gp_names: frozenset[str] = frozenset()
|
||||
|
||||
|
||||
def parse_symbols(text: str) -> SymbolTable:
|
||||
"""Parse a symbol registry into linker `--defsym` args and gp markers.
|
||||
|
||||
Columns are `NAME<TAB>address[<TAB>gp]`. A `gp` marker means the original
|
||||
accessed the symbol `gp`-relative, so the harness rewrites its macro
|
||||
accesses to explicit `%gp_rel` (GNU `as` will not do this for a symbol whose
|
||||
section it cannot see).
|
||||
"""
|
||||
defsyms: list[str] = []
|
||||
gp_names: set[str] = set()
|
||||
seen: set[str] = set()
|
||||
for number, raw in enumerate(text.splitlines(), 1):
|
||||
line = raw.split("#", 1)[0].strip()
|
||||
if not line:
|
||||
continue
|
||||
fields = line.split()
|
||||
if len(fields) != 2:
|
||||
raise ToolError(f"symbols line {number}: expected 'NAME address'")
|
||||
if len(fields) not in (2, 3):
|
||||
raise ToolError(f"symbols line {number}: expected 'NAME address [gp]'")
|
||||
name = fields[0]
|
||||
if not _SYMBOL_NAME.match(name):
|
||||
raise ToolError(f"symbols line {number}: invalid symbol name {name!r}")
|
||||
if name in seen:
|
||||
raise ToolError(f"symbols line {number}: duplicate symbol {name!r}")
|
||||
address = parse_hex_address(fields[1], number)
|
||||
if len(fields) == 3:
|
||||
if fields[2] != "gp":
|
||||
raise ToolError(f"symbols line {number}: unknown marker {fields[2]!r}")
|
||||
gp_names.add(name)
|
||||
seen.add(name)
|
||||
defsyms.append(f"{name}=0x{address:X}")
|
||||
return defsyms
|
||||
return SymbolTable(tuple(defsyms), frozenset(gp_names))
|
||||
|
||||
|
||||
def validate_regions(regions: Sequence[Region], exe: PsxExe) -> None:
|
||||
@@ -390,10 +408,49 @@ class Toolchain:
|
||||
cc1_flags: Sequence[str]
|
||||
as_flags: Sequence[str]
|
||||
defsyms: Sequence[str]
|
||||
gp_symbols: frozenset[str] = frozenset()
|
||||
maspsx: Path | None = None
|
||||
aspsx_version: str = DEFAULT_ASPSX_VERSION
|
||||
|
||||
|
||||
# A symbol macro access that can be forced gp-relative.
|
||||
_GP_ACCESS = re.compile(
|
||||
r"^(?P<indent>\s*)(?P<op>lw|lh|lhu|lb|lbu|lwl|lwr|sw|sh|sb|swl|swr)"
|
||||
r"\s+(?P<rt>\$[A-Za-z0-9]+),\s*(?P<sym>[A-Za-z_.][A-Za-z0-9_.]*)"
|
||||
r"(?P<addend>[+-][0-9]+)?\s*$"
|
||||
)
|
||||
_GP_LA = re.compile(
|
||||
r"^(?P<indent>\s*)la\s+(?P<rt>\$[A-Za-z0-9]+),\s*"
|
||||
r"(?P<sym>[A-Za-z_.][A-Za-z0-9_.]*)(?P<addend>[+-][0-9]+)?\s*$"
|
||||
)
|
||||
|
||||
|
||||
def rewrite_gp_accesses(text: str, gp_symbols: frozenset[str]) -> str:
|
||||
"""Force `%gp_rel` access for symbols the original read through `gp`."""
|
||||
if not gp_symbols:
|
||||
return text
|
||||
lines: list[str] = []
|
||||
for line in text.splitlines():
|
||||
match = _GP_ACCESS.match(line)
|
||||
if match and match.group("sym") in gp_symbols:
|
||||
symbol = match.group("sym") + (match.group("addend") or "")
|
||||
lines.append(
|
||||
f"{match.group('indent')}{match.group('op')} "
|
||||
f"{match.group('rt')},%gp_rel({symbol})($gp)"
|
||||
)
|
||||
continue
|
||||
match = _GP_LA.match(line)
|
||||
if match and match.group("sym") in gp_symbols:
|
||||
symbol = match.group("sym") + (match.group("addend") or "")
|
||||
lines.append(
|
||||
f"{match.group('indent')}addiu {match.group('rt')},"
|
||||
f"$gp,%gp_rel({symbol})"
|
||||
)
|
||||
continue
|
||||
lines.append(line)
|
||||
return "\n".join(lines) + ("\n" if text.endswith("\n") else "")
|
||||
|
||||
|
||||
def compile_c(source: Path, out_object: Path, work: Path, tools: Toolchain) -> None:
|
||||
"""Preprocess, compile, ASPSX-emulate and assemble one C source.
|
||||
|
||||
@@ -415,6 +472,13 @@ def compile_c(source: Path, out_object: Path, work: Path, tools: Toolchain) -> N
|
||||
assembly, transformed,
|
||||
)
|
||||
assembly = transformed
|
||||
if tools.gp_symbols:
|
||||
rewritten = work / (out_object.stem + ".gp.s")
|
||||
rewritten.write_text(
|
||||
rewrite_gp_accesses(assembly.read_text(encoding="utf-8"), tools.gp_symbols),
|
||||
encoding="utf-8",
|
||||
)
|
||||
assembly = rewritten
|
||||
run([str(tools.assembler), *tools.as_flags, "-o", str(out_object), str(assembly)])
|
||||
|
||||
|
||||
@@ -680,9 +744,12 @@ def resolve_toolchain(args: argparse.Namespace) -> Toolchain:
|
||||
raise ToolError("no preprocessor found; pass --cpp")
|
||||
cpp = Path(located)
|
||||
defsyms = list(args.defsym)
|
||||
gp_names: set[str] = set()
|
||||
if args.symbols is not None:
|
||||
symbols_path = require_file(args.symbols, "symbol registry")
|
||||
defsyms = parse_symbols(symbols_path.read_text(encoding="utf-8")) + defsyms
|
||||
table = parse_symbols(symbols_path.read_text(encoding="utf-8"))
|
||||
defsyms = list(table.defsyms) + defsyms
|
||||
gp_names |= table.gp_names
|
||||
maspsx = None
|
||||
if not args.no_maspsx:
|
||||
maspsx = require_file(args.maspsx, "maspsx")
|
||||
@@ -696,6 +763,7 @@ def resolve_toolchain(args: argparse.Namespace) -> Toolchain:
|
||||
cc1_flags=args.cc1_flag or DEFAULT_CC1_FLAGS,
|
||||
as_flags=args.as_flag or DEFAULT_AS_FLAGS,
|
||||
defsyms=defsyms,
|
||||
gp_symbols=frozenset(gp_names),
|
||||
maspsx=maspsx,
|
||||
aspsx_version=args.aspsx_version,
|
||||
)
|
||||
|
||||
@@ -199,10 +199,20 @@ class RegionOptionTests(unittest.TestCase):
|
||||
|
||||
class SymbolTests(unittest.TestCase):
|
||||
def test_parses_names_comments_and_hex(self) -> None:
|
||||
defsyms = sf3_match.parse_symbols(
|
||||
table = sf3_match.parse_symbols(
|
||||
"# comment\n\ng_80122354\t0x80122354\nfunc_1\t80123456\n"
|
||||
)
|
||||
self.assertEqual(defsyms, ["g_80122354=0x80122354", "func_1=0x80123456"])
|
||||
self.assertEqual(list(table.defsyms),
|
||||
["g_80122354=0x80122354", "func_1=0x80123456"])
|
||||
self.assertEqual(table.gp_names, frozenset())
|
||||
|
||||
def test_parses_a_gp_marker(self) -> None:
|
||||
table = sf3_match.parse_symbols("g\t0x80121974\tgp\nother\t0x80122354\n")
|
||||
self.assertEqual(table.gp_names, frozenset({"g"}))
|
||||
|
||||
def test_rejects_an_unknown_marker(self) -> None:
|
||||
with self.assertRaises(sf3_match.ToolError):
|
||||
sf3_match.parse_symbols("g\t0x80121974\tbig\n")
|
||||
|
||||
def test_rejects_a_malformed_line(self) -> None:
|
||||
with self.assertRaises(sf3_match.ToolError):
|
||||
@@ -240,7 +250,7 @@ int synthetic_get(Synthetic *p) { return p->value; }
|
||||
class EndToEndTests(unittest.TestCase):
|
||||
"""Build a synthetic executable whose payload starts with real compiler output."""
|
||||
|
||||
def _toolchain(self, *, cc1_flags=(), as_flags=(), defsyms=()) -> object:
|
||||
def _toolchain(self, *, cc1_flags=(), as_flags=(), defsyms=(), symbols=None) -> object:
|
||||
args = type("Args", (), {})()
|
||||
args.cpp = None
|
||||
args.cc1 = sf3_match.DEFAULT_CC1
|
||||
@@ -251,7 +261,7 @@ class EndToEndTests(unittest.TestCase):
|
||||
args.cc1_flag = list(cc1_flags)
|
||||
args.as_flag = list(as_flags)
|
||||
args.defsym = list(defsyms)
|
||||
args.symbols = None
|
||||
args.symbols = symbols
|
||||
args.maspsx = sf3_match.DEFAULT_MASPSX
|
||||
args.no_maspsx = False
|
||||
args.aspsx_version = sf3_match.DEFAULT_ASPSX_VERSION
|
||||
@@ -259,13 +269,14 @@ class EndToEndTests(unittest.TestCase):
|
||||
|
||||
def _compile_payload(
|
||||
self, root: Path, *, source_text: str = SOURCE, cc1_flags=(),
|
||||
as_flags=(), defsyms=(), corrupt: bool = False,
|
||||
as_flags=(), defsyms=(), symbols=None, corrupt: bool = False,
|
||||
) -> tuple[Path, int]:
|
||||
source = root / "synthetic.c"
|
||||
source.write_text(source_text, encoding="ascii")
|
||||
work = root / "work"
|
||||
obj = root / "synthetic.o"
|
||||
tools = self._toolchain(cc1_flags=cc1_flags, as_flags=as_flags, defsyms=defsyms)
|
||||
tools = self._toolchain(cc1_flags=cc1_flags, as_flags=as_flags,
|
||||
defsyms=defsyms, symbols=symbols)
|
||||
sf3_match.compile_c(source, obj, work, tools)
|
||||
code = sf3_match.link_object_bytes(obj, PAYLOAD, work, tools)
|
||||
payload = bytearray(max(0x40, len(code) + 0x10))
|
||||
@@ -354,6 +365,26 @@ class EndToEndTests(unittest.TestCase):
|
||||
self.assertEqual(rc, 0)
|
||||
self.assertEqual((out / "scus_946_40.rebuilt").read_bytes(), exe.read_bytes())
|
||||
|
||||
def test_gp_marker_forces_a_gp_relative_access(self) -> None:
|
||||
source = "extern int g_gp;\nvoid synthetic_gp(int x) { g_gp = x; }\n"
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
root = Path(tmp)
|
||||
symbols = self._symbols(root, "g_gp\t0x80121974\tgp\n_gp\t0x80121938\n")
|
||||
exe, code_length = self._compile_payload(
|
||||
root, source_text=source, symbols=symbols
|
||||
)
|
||||
regions = self._registry(root, code_length)
|
||||
out = root / "out"
|
||||
rc = sf3_match.main(["gate", "--exe", str(exe), "--regions", str(regions),
|
||||
"--symbols", str(symbols), "--out", str(out)])
|
||||
self.assertEqual(rc, 0)
|
||||
self.assertEqual((out / "scus_946_40.rebuilt").read_bytes(), exe.read_bytes())
|
||||
# Without the gp marker the access is absolute and the gate fails.
|
||||
plain = self._symbols(root, "g_gp\t0x80121974\n_gp\t0x80121938\n")
|
||||
rc = sf3_match.main(["gate", "--exe", str(exe), "--regions", str(regions),
|
||||
"--symbols", str(plain), "--out", str(root / "out2")])
|
||||
self.assertEqual(rc, 1)
|
||||
|
||||
def test_refuses_an_existing_destination(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
root = Path(tmp)
|
||||
|
||||
Reference in New Issue
Block a user