How do I track crash reports with readable stack traces when using KMP + Compose on iOS?

2026-07-13

Stack Overflow: View Question

Tags: ios, debugging, crash-reports, kmp, compose

Score: 0 | Views: 57

The asker is shipping an iOS app built with Kotlin Multiplatform + Compose Multiplatform. They've wired up NSExceptionKt, Firebase Crashlytics, and Xcode's native crash tooling, yet: (a) many crashes never surface at all, and (b) the ones that do arrive with stack traces that are effectively unreadable — often just hex addresses or generic Kotlin/Native internals.

Why this is genuinely hard. A KMP iOS binary is Kotlin compiled through Kotlin/Native (LLVM) into an Objective-C framework. That means a crash can originate in three different worlds — Kotlin, Swift/ObjC, or the C runtime — and each has its own unwinding and symbolication story:

A direction toward a fix.

  1. Upload the KMP framework's dSYM to Crashlytics. The Gradle output at build/bin/iosArm64/releaseFramework/shared.framework.dSYM must be uploaded via upload-symbols (or the run-script phase) alongside your app's own dSYM. This alone fixes ~80% of "unreadable" traces.
  2. Install a Kotlin uncaught exception hook. Use setUnhandledExceptionHook { throwable -> ... } in commonMain (or iOS-side) to forward the throwable to Crashlytics before the runtime aborts. Log throwable.getStackTrace() as a non-fatal so Firebase captures the Kotlin frames.
  3. Keep NSExceptionKt but understand its limit. It converts Kotlin exceptions crossing the ObjC boundary into NSExceptions with symbolicated traces — but it only helps for exceptions that actually cross the bridge. Pure-Kotlin crashes still need the hook above.
  4. Disable stack trace stripping in release. In your framework config: freeCompilerArgs += ["-Xg0"] or ensure debug info is preserved (-Xadd-source-mapping).

Gotchas. Bitcode is deprecated but if legacy settings strip symbols during App Store processing, dSYMs must come from App Store Connect, not your local build. Also, background-thread Kotlin crashes need the hook installed on that thread's context — the new memory model made this easier but it's still per-worker.

The challenge: Crashes in KMP/iOS cross three runtimes (Kotlin/Native, ObjC, C) and each needs its own dSYM, exception hook, and symbolication path — miss any one and traces silently degrade to hex.

All newsletters