fix(linux): thread a va_list* through _Printf (x86-64 SysV ABI) (#22)

* fix(linux): thread a va_list* through _Printf (x86-64 SysV ABI)

On the x86-64 System V ABI `va_list` is an array type, so a by-value
`va_list` parameter decays to a pointer. `_Printf()` was passing `&args`
(the address of that local pointer, not the argument list) to `_Putfld()`,
which does `va_arg(*args, ...)` — so `_Putfld` walked garbage and
segfaulted. Latent at `-Og`, fatal at `-O2` (PR #9), which is why the
Linux release build crashed at boot:

  bossInitMainthreadData (boss.c:247, the D121 per-level -m token sprintf)
    -> sprintf("-level_%c%c %s", ...) -> _Putfld -> SIGSEGV @ xprintf.c

Same UB class as D187. Windows x64's `va_list` is a scalar (`char*`), so
`&args` happened to work there — the Windows build is unaffected.

Fix: under `#ifdef PORT`, `_Printf` takes `va_list *` and passes it
straight to `_Putfld` (which already expects `va_list *`); `src/sprintf.c`
(the only PC caller — `rmon.c` is excluded from the build) passes `&args`
of a real `va_list` object. N64 build untouched (`#ifdef PORT`, and it
byte-matches the ROM).

Verified:
- Windows: build links, `-level_09` framediff 3/3 within golden threshold.
- Linux -O2: boot crash gone, now advances past bossInitMainthreadData
  into stage load (uncovers a separate pre-existing stan.c stack overflow
  during BUNKER1 prop setup — tracked separately).

* docs: D188 (xprintf va_list ABI) + D189 (stan.c stack-smash) findings + porting-notes D7
This commit is contained in:
James Dansereau authored and GitHub committed 2026-09-03 20:12:56 -04:00
1 parent bde5e0bdfa
commit 1261389a5c
4 files changed
+55

No files matched your search

+25
View File
@@ -606,6 +606,31 @@ silently disappears.
(the missing primitive is often small). Needs an `-O2` build + an eyeball
or a targeted crop diff.
## D7. `va_list` is an ARRAY type on x86-64 SysV — never take `&` of a by-value `va_list` param
On Windows x64 `va_list` is a scalar (`char *`), so passing a `va_list`
parameter by value and later taking its address (`&args`) to hand a helper a
"pointer to the list" happens to work. On the **x86-64 System V ABI**
(Linux/macOS) `va_list` is `__va_list_tag[1]` — an array — so a by-value
`va_list` parameter has already decayed to `__va_list_tag *`. `&args` is then
the address of the *local pointer slot*, not the argument list, and a helper
doing `va_arg(*args, …)` walks garbage → segfault. Latent at `-Og`, fatal at
`-O2`.
- Instance: **D188** — the IDO printf engine `_Printf`
(`src/libultrare/libc/xprintf.c`) passed `&args` to `_Putfld`; crashed at
boot on Linux `-O2` via `bossInitMainthreadData` → `sprintf`. Fix: under
`#ifdef PORT`, thread a real `va_list *` from the top (`sprintf.c` passes
`&args` of an actual `va_list` object; `_Printf` takes `va_list *` and
passes it straight through). `#else` keeps the ROM-matching body.
- Rule: to share one arg-walk between functions, pass `va_list *` explicitly
from the outermost `va_start` scope. Never `&` a `va_list` that arrived as
a parameter. `va_copy` into a fresh local is the other portable option when
the callee must not disturb the caller's position.
- Same "works on MinGW, UB on SysV, exposed at `-O2`" shape as D6 — suspect
this class for any Linux-only crash whose backtrace runs through
`xprintf`/`_Putfld`/`vsnprintf`-alikes.
## E. Process / method notes
- Investigation loop is: reproduce → env-gated capped probe → root-cause