Why iOS 18 Crashes When exit() Is Called and How to Fix It
This article analyzes a driver‑side iOS 18 crash triggered by BackBoardServices calling exit, explains the underlying XPC and C++ destructor chain, explores failed hook and compiler‑flag workarounds, and presents a reliable atexit‑based solution that eliminates the crash on iOS 18 and later.
Background
Our driver‑side App started crashing on iOS 18 when a method in the BackBoardServices library triggered an exit call. After exit executes, a C++ global object is destroyed, leading to a crash.
Crash stacks (two patterns)
Pattern 1: -[BKSHIDEventObserver init] + 0 Pattern 2:
-[BKSHIDEventDeliveryManager _initForTestingWithService:] + 0The issue appears only on iOS 18 and higher, typically after the app has been in the background for a while.
Root Cause Analysis
When the user touches the iPhone, the system daemon backboardd detects the event and uses BackBoardServices to package the data into an IOHIDEvent. This event is sent via BoardServices over IPC to the foreground daemon SpringBoard, which finally forwards it to the app.
The exit path involves: libsystem_c.dylib ___cxa_finalize_ranges + 480 – runs global destructors and atexit callbacks. libsystem_c.dylib _exit + 32 – calls the low‑level exit routine.
During this process, BackBoardServices detects an invalid parameter and calls exit. The C++ global object PosBridgeImpl is then destroyed, which in turn destroys GPSHelper. GPSHelper 's destructor calls TimerEventRunnerObserverImp::detachTimer(), which accesses a freed target object, causing an EXC_BAD_ACCESS (SIGSEGV) at <+36>: ldr x8, [x8, #0x18]. The likely cause is a multithread race in a third‑party library.
The leading underscore in symbols like _exit is an ABI‑mandated name‑mangling that isolates system symbols and aids debugging.
Solution Attempts
Attempt 1 – Hooking
We tried hooking exit in libsystem_c.dylib and two BackBoardServices methods ( -[BKSHIDEventObserver init] and -[BKSHIDEventDeliveryManager _initForTestingWithService:]). The hook would detect a BackBoardServices call and replace exit(0) with _exit(0). This worked in debug and release builds signed with a development certificate (which have get-task-allow), but failed in App Store builds because iOS 14+ introduces Pointer Authentication Codes (PAC). PAC rejects the modified function pointers, causing an instruction‑level abort.
Consequences of PAC failure include:
CPU throws EXC_BAD_ACCESS with code 0x8badf00d.
The offending thread is suspended.
The kernel sends SIGKILL, marking the process as “contaminated”.
The app crashes instantly without a crash log.
Attempt 2 – Suppressing Destructors
We explored two compiler‑level options: [[clang::no_destroy]] – disables destructor generation for specific globals. Not usable because the problematic objects reside in a third‑party framework without source. -fno-c++-static-destructors – disables all static destructors. The flag cannot affect the pre‑compiled third‑party framework, so the crash persisted.
Attempt 3 – Atexit‑Based Fix (Successful)
We registered a custom atexit handler that runs **before** the C++ static destructors (by registering after the framework’s own atexit entries). The handler inspects the current thread’s stack; if it contains BackBoardServices, it calls _exit(0) directly, bypassing the C++ destructor chain.
This approach works for iOS 18 and later, and has been deployed without introducing latency, freezes, or other stability regressions.
Result
After releasing the atexit‑based fix, the crash disappeared completely in the new version, and no new performance or stability issues were observed.
The ideal long‑term solution remains a fix from the third‑party library vendor.
Summary
The iOS 18 crash originates from a system‑level exit triggered by BackBoardServices, which leads to a C++ global destructor accessing a freed object. Hooking fails on App Store builds due to PAC, and compiler flags cannot affect third‑party binaries. Registering a higher‑priority atexit handler that replaces exit with _exit(0) successfully prevents the destructor from running and eliminates the crash.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
