Quick answer: Unity iOS games compile C# to native code through IL2CPP, so App Store crashes arrive as native signals like SIGSEGV with addresses, not C# stack traces. To read them you need the matching dSYM to symbolicate. Capture both managed exceptions and native crash logs in-game, attach the build and device, and upload dSYMs so each crash resolves to a real method instead of a hex address.

Shipping a Unity game to the App Store means your C# code is converted to C++ by IL2CPP and compiled to a native iOS binary, which is great for performance and rough for debugging. A crash that would show a clean C# stack in the editor arrives from a real iPhone as a native crash log full of memory addresses and a signal like SIGSEGV or SIGABRT. Without the matching dSYM to symbolicate it, that log is unreadable. This post covers how Unity iOS crashes are structured, how managed and native faults differ, and how to capture and symbolicate them so each one points at a real method.

What IL2CPP does to your crashes

IL2CPP transpiles your C# to C++ and compiles it into the app binary, so at runtime there is no managed stack of the kind you see in the editor. When something goes wrong at the native level, an out-of-bounds pointer or a null native handle, iOS delivers a Mach signal such as SIGSEGV or SIGABRT, and the crash log records the call stack as memory addresses inside your binary. Those addresses mean nothing until they are mapped back to function names using the debug symbols.

Managed exceptions are different. A C# exception thrown in your game logic, a null reference or an index out of range, still propagates as a managed exception that Unity can surface with a readable stack before it ever becomes a native fault. The two classes need different handling: managed exceptions you can catch in C# and report directly, while native signals require an install-time handler and later symbolication. Telling them apart at capture time saves enormous triage effort.

Why dSYMs are non-negotiable

A dSYM is the debug symbol bundle Xcode produces for each build, mapping the binary's addresses to source functions and lines. When a native crash log comes in from a player's device, it is symbolicated by combining it with the dSYM that matches that exact build's UUID. If the UUIDs do not match, symbolication fails and you are left staring at raw offsets. This is why every release build's dSYM must be archived and findable later, not discarded after upload.

Unity and Xcode can complicate this. With bitcode or App Store recompilation in past workflows, the dSYM Apple holds may differ from your local one, so you sometimes need to download the App Store dSYMs from the organizer or App Store Connect. Whatever your pipeline, the rule is the same: keep the dSYM for every shipped build keyed by its UUID, because a native crash you cannot symbolicate is a crash you effectively cannot fix.

Capturing both classes in-game

For managed exceptions, register Application.logMessageReceived and inspect for Exception and Error types, capturing the condition and the stack trace string. This catches unhandled C# exceptions with their readable managed stack, which is often all you need for game-logic bugs. Report these immediately with the scene, the player state, and your custom fields, since they already resolve to real method names without any symbolication step.

For native signals you need a signal handler installed at startup that writes a minimal crash record, the signal, the faulting addresses, and a breadcrumb trail, to disk before the process dies. On the next launch the game finds that record and submits it for symbolication. Keep the handler async-signal-safe and minimal, because doing anything heavy or allocation-heavy inside a signal handler on a crashing process is unreliable and can lose the very report you are trying to save.

The device context an App Store crash needs

Every iOS crash report should carry the device model, the iOS version, the available and total memory, the thermal state, and whether the device was low on storage, since memory pressure and thermal throttling cause a surprising share of field crashes. Add your app's build number and version, the IL2CPP and Unity versions, and the exact binary UUID so the report can be matched to the right dSYM automatically rather than by hand.

Then add game state: current scene, level, session length, and any custom player attributes you track. A native SIGSEGV that only appears on older devices under low memory after long sessions practically diagnoses itself as a memory leak or an asset that is too large for that hardware. Without the device and memory context, the same crash log is just an unexplained signal, and you would spend days guessing at what the numbers make obvious.

Setting it up with Bugnet

Bugnet provides a Unity SDK and an in-game report button that fit an iOS title. Hook the SDK into your logMessageReceived handler for managed exceptions and into your native crash record for signals, attaching the device, memory, binary UUID, and scene state. Upload each release build's dSYM to Bugnet, and incoming native crash logs are symbolicated against the matching UUID, so a wall of hex addresses becomes a readable stack pointing at the actual method that faulted.

Occurrence grouping turns a flood of identical signals into one prioritized issue with a count, which matters on the App Store where a single bad build can crash for thousands of players overnight. Custom fields like device model, iOS version, and free memory become filters, so you can confirm a crash is isolated to one older device under memory pressure and respond with a targeted asset budget or a guard, rather than guessing from an aggregate crash rate with no detail.

Closing the loop before and after release

Before submitting a build, archive its dSYM and confirm your reporting handles a deliberately thrown C# exception and a forced native fault on a real device, not just the simulator, since IL2CPP and signal behavior differ from editor and simulator runs. Verify the binary UUID in your report matches the dSYM you archived, because a mismatch discovered after launch means a release worth of crashes you cannot read.

After release, watch the symbolicated occurrence list and fix the top crashes first, confirming the count falls in the next version. App Store review and slow rollout make fast, accurate crash data especially valuable, because you cannot patch as freely as on the web. A pipeline that captures both managed and native crashes and symbolicates reliably is what lets a small team ship confidently to millions of iPhones they will never hold.

Unity iOS crashes split into catchable managed exceptions and native signals that need dSYM symbolication, so capture both and archive every build's dSYM by UUID.