From fe3d543647f183cfc7b526c72d3a1932e3f2db2f Mon Sep 17 00:00:00 2001 From: James Dansereau <106631954+jkdansereau@users.noreply.github.com> Date: Wed, 2 Sep 2026 22:50:56 -0400 Subject: [PATCH] linux-runtime: BUNKER1 boots + renders on Linux First runtime bring-up on Linux (Ubuntu, gcc-14). -level_09 now renders past frame 1200 crash-free with a clean shutdown, matching Windows. - port/src/romdata.c: no-PIE loads the image at 0x400000, so the D34 'clear low 32 bits of a symbol address' gives 0 -> false 'image base not 4GiB-aligned' abort. The ANIM_DATA_* lvalues are only ever address-taken (never dereferenced), so fall back to a fixed 4GiB-aligned sentinel (0x100000000) for g_pc_animdata_base. - port/src/main.c: the 'image must load at 0x140000000' assertion is PE-specific (.refptr base relocations); ELF no-PIE resolves dram_syms.s absolute symbols literally regardless of load address. Guard it to PLATFORM_WINDOWS. - port/src/libultra.c: mmap game-thread stacks with MAP_32BIT so they sit in the low 2GiB. The decomp truncates stack-buffer pointers to u32 (e.g. in texLoad); glibc pthread stacks live above 4GiB and those truncations corrupt. One fix covers the whole idiom class. Windows pthread stacks are already low. - src/game/image.c: belt-and-braces #ifdef PORT -- align compbuffer at uintptr_t width (identical result on N64). - CMakeLists.txt: drop the -Ttext-segment experiment (needs -mcmodel=large; breaks R_X86_64_32 relocs) -- comment only. Windows: build 240/241, -level_09 crash-free past frame 1440. --- CMakeLists.txt | 4 ++++ port/src/libultra.c | 31 +++++++++++++++++++++++++++++++ port/src/main.c | 6 ++++++ port/src/romdata.c | 9 +++++---- src/game/image.c | 7 +++++++ 5 files changed, 53 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ebf19ccd..b9a5e02a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -283,6 +283,10 @@ elseif(UNIX AND NOT APPLE) # and link no-PIE so the image keeps its preferred load address. add_compile_options(-fno-pie) set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -no-pie") + # (No -Ttext-segment: pushing the image above 4GiB needs -mcmodel=large and + # breaks the R_X86_64_32 relocs. romdataInit handles the low no-PIE base + # 0x400000 by using a fixed 4GiB-aligned g_pc_animdata_base sentinel — the + # ANIM_DATA_* lvalues are only ever address-taken, never dereferenced.) endif() # port/shim first: PC-only shadow headers (bondtypes.h, bondconstants.h, diff --git a/port/src/libultra.c b/port/src/libultra.c index f9658e17..b8ce0fd5 100644 --- a/port/src/libultra.c +++ b/port/src/libultra.c @@ -334,6 +334,29 @@ void osCreateThread(OSThread *t, OSId id, void (*entry)(void *), void *arg, t->state = OS_STATE_STOPPED; } +#if !defined(_WIN32) +#include +/* The decomp aligns/compares stack-buffer pointers by truncating to u32 + * (e.g. `(u32)compbuffer` in image.c texLoad) — an N64 assumption. glibc + * puts pthread stacks above 4 GiB, so those truncations corrupt. Force each + * game-thread stack into the low 2 GiB with MAP_32BIT so every such idiom + * works exactly as on the console. Windows pthread stacks are already low. */ +static void *portAllocLowStack(size_t sz) +{ +#if defined(MAP_32BIT) + void *p = mmap(NULL, sz, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS | MAP_32BIT +#if defined(MAP_STACK) + | MAP_STACK +#endif + , -1, 0); + if (p != MAP_FAILED) + return p; +#endif + return NULL; +} +#endif + void osStartThread(OSThread *t) { PortThread *pt = portFind(t); @@ -344,7 +367,15 @@ void osStartThread(OSThread *t) pthread_attr_t attr; int rc; pthread_attr_init(&attr); +#if !defined(_WIN32) + void *lowStack = portAllocLowStack(PORT_THREAD_STACK); + if (lowStack) + pthread_attr_setstack(&attr, lowStack, PORT_THREAD_STACK); + else + pthread_attr_setstacksize(&attr, PORT_THREAD_STACK); +#else pthread_attr_setstacksize(&attr, PORT_THREAD_STACK); +#endif rc = pthread_create(&pt->th, &attr, portThreadWrapper, pt); pthread_attr_destroy(&attr); if (rc != 0) { diff --git a/port/src/main.c b/port/src/main.c index 878a947c..4b92576e 100644 --- a/port/src/main.c +++ b/port/src/main.c @@ -115,6 +115,7 @@ int main(int argc, char **argv) * loader loads at 0x140000000 and those relocations are no-ops; if we * ever got relocated, every such slot would be silently corrupted. * Fail loudly instead. */ +#if defined(PLATFORM_WINDOWS) if (sysImageBase() != 0x140000000ul) { sysLogPrintf(LOG_ERROR, "image loaded at %p, expected preferred base 0x140000000; " @@ -122,6 +123,11 @@ int main(int argc, char **argv) (void *)sysImageBase()); return 1; } +#else + /* Linux/ELF no-PIE: dram_syms.s absolute symbols resolve to their literal + * values independent of the image base (no .refptr indirection), so the + * load address is not constrained. sysImageBase() is a stub here anyway. */ +#endif /* 2b. Reserve the N64-DRAM region: s32-safe view @ 0x70000000 (cfb_16, * mempools) + KSEG0 mirror @ 0x80000000 (see port/src/dram.c). */ diff --git a/port/src/romdata.c b/port/src/romdata.c index 3b9eb82f..b1be80bb 100644 --- a/port/src/romdata.c +++ b/port/src/romdata.c @@ -157,10 +157,11 @@ int romdataInit(void) uintptr_t p = (uintptr_t)&probe; uintptr_t hi = p & ~(uintptr_t)0xFFFFFFFFu; if (hi == 0) { - sysLogPrintf(LOG_ERROR, "romdataInit: image base 0x%llX is not " - "4GiB-aligned; ANIM_DATA_* offsets would be wrong", - (unsigned long long)p); - return -1; + /* Low image base (Linux no-PIE loads at 0x400000). The ANIM_DATA_* + * lvalues are only ever address-taken — `(s32)&ANIM_DATA_x` yields + * the offset, never a dereference — so g_pc_animdata_base just has + * to be some 4GiB-aligned address whose low 32 bits are zero. */ + hi = (uintptr_t)0x100000000ull; } g_pc_animdata_base = (u8 *)hi; } diff --git a/src/game/image.c b/src/game/image.c index 86cca08e..56daa0d9 100644 --- a/src/game/image.c +++ b/src/game/image.c @@ -2435,7 +2435,14 @@ void texLoad(s32 *updateword, struct texpool *pool) if (tex == NULL) { +#ifdef PORT + /* compbuffer is a stack array; (u32) truncates the 64-bit host stack + * address (glibc pthread stacks live above 4GiB). Align at full + * pointer width -- same result on N64, correct on the port. */ + alignedcompbuffer = (u8 *) (((uintptr_t)compbuffer + 0xF) & ~(uintptr_t)0xF); +#else alignedcompbuffer = (u8 *) (((u32)compbuffer + 0xF) >> 4 << 4); +#endif if (alignedcompbuffer); if (tex);