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

+22
View File
@@ -35,8 +35,23 @@ static char spaces[] = " ";
static char zeroes[] = "00000000000000000000000000000000";
static void _Putfld(printf_struct *pf, va_list *pap, char code, char *ac);
#ifdef PORT
/* x86-64 System V makes va_list an array type, so a by-value va_list
* parameter decays to a pointer: `&args` below would be the address of
* _Printf's local pointer slot, not the argument list, and _Putfld()'s
* `va_arg(*args, ...)` then walks garbage (crash seen at
* bossInitMainthreadData -> sprintf -> _Putfld; same UB class as D187,
* latent at -Og, fatal at -O2). Thread a real va_list* through instead.
* The only PC caller is src/sprintf.c. The N64 build is semantically
* unaffected (scalar va_list) but this file byte-matches the ROM, so the
* change is guarded. */
int _Printf(outfun prout, char *arg, const char *fmt, va_list *argsp)
{
#define args (*argsp)
#else
int _Printf(outfun prout, char *arg, const char *fmt, va_list args)
{
#endif
printf_struct x;
const char *s;
char c;
@@ -96,7 +111,11 @@ int _Printf(outfun prout, char *arg, const char *fmt, va_list args)
x.length = 'L';
++s;
}
#ifdef PORT
_Putfld(&x, argsp, *s, ac);
#else
_Putfld(&x, &args, *s, ac);
#endif
x.width -= x.n0 + x.num_leading_zeros + x.part2_len + x.num_mid_zeros + x.part3_len + x.num_trailing_zeros;
if (!(x.flags & FLAGS_MINUS))
PAD(spaces, x.width);
@@ -112,6 +131,9 @@ int _Printf(outfun prout, char *arg, const char *fmt, va_list args)
}
return 0;
}
#ifdef PORT
#undef args
#endif
static void _Putfld(printf_struct *x, va_list *args, char type, char *buff)
{
+6
View File
@@ -12,7 +12,13 @@ int sprintf(char *dst, const char *fmt, ...) {
s32 written;
va_list args;
va_start(args, fmt);
#ifdef PORT
/* xprintf.c's _Printf takes a va_list* under PORT — see the comment there
* (x86-64 SysV va_list-is-an-array ABI). */
written = _Printf(proutSprintf, dst, fmt, &args);
#else
written = _Printf(proutSprintf, dst, fmt, args);
#endif
if (written >= 0) {
dst[written] = 0;
}