// unwindStubProbe.cpp — Phase 5.1 smoke for the `_Unwind_*` stub. // // Exercises the Itanium `_Unwind_*` surface from libunwindStub.o. These // entry points are what third-party C++ libraries reference from their // own exception-handling paths (abseil, fmt, libcxx itself); confirming // they link AND that the cleanup callback fires at runtime proves the // stub is functional end-to-end. // // Why no `throw` / `catch` in this runtime probe: SJLJ-prepared C++ // exception code is documented to crash MAME's apple2gs CPU emulation // intermittently (smokeTest.sh:4906-4912 notes the same and runs only a // link check there). This probe stays on the pure-C surface so we get // a green runtime marker. The companion link check // (scripts/smokeTest.sh) already validates that clang++ + libunwindStub // produces a linkable C++ binary that uses throw / catch. // // Markers (16-bit, bank-2): // $025000 = 0xC0DE reached main() // $025002 = 0xBEEF _Unwind_DeleteException cleanup callback fired // $025004 = 0x900D end of main() extern "C" { #include } // Itanium ABI shapes — duplicated locally so the probe is // self-contained (no shim in our tree). Layout must match // libunwindStub.c's _Unwind_Exception. typedef enum { URC_NO_REASON = 0, URC_FOREIGN_EXCEPTION_CAUGHT = 1 } UnwindReasonE; struct _Unwind_Exception; typedef void (*UnwindExceptionCleanupFn)(UnwindReasonE, _Unwind_Exception *); struct _Unwind_Exception { uint64_t exception_class; UnwindExceptionCleanupFn exception_cleanup; uintptr_t private_1; uintptr_t private_2; }; extern "C" void _Unwind_DeleteException(_Unwind_Exception *exc); static volatile uint16_t gCleanupFired = 0; static void onCleanup(UnwindReasonE reason, _Unwind_Exception *exc) { (void)reason; (void)exc; gCleanupFired = 0xBEEF; } int main(void) { *(volatile uint16_t *)0x025000UL = 0xC0DE; // Stack-allocate a _Unwind_Exception, register a cleanup callback, // hand it to _Unwind_DeleteException, and confirm the callback // fired. This is the surface third-party code reaches when it // owns the exception storage itself rather than going through // __cxa_throw. _Unwind_Exception localExc; localExc.exception_class = 0; localExc.exception_cleanup = &onCleanup; localExc.private_1 = 0; localExc.private_2 = 0; _Unwind_DeleteException(&localExc); *(volatile uint16_t *)0x025002UL = gCleanupFired; *(volatile uint16_t *)0x025004UL = 0x900D; // GNO commands return to gsh after main(). return 0; }