Skip to main content
Version: 2.1.x

Release Symbolication & De-obfuscation for KMP

When Kotlin Multiplatform applications are compiled for production release, code is minified, obfuscated, and stripped of debug symbols:

  • Android: R8/ProGuard shrinks and obfuscates bytecode, renaming classes, fields, and methods to short non-descriptive tokens (e.g. a.b.c()).
  • iOS (Kotlin/Native): The Kotlin compiler compiles shared Kotlin code into native Mach-O binaries (ComposeApp.framework or shared.framework). Debug symbols are stripped into separate .dSYM bundles.

Without the matching symbol files (mapping.txt for Android and .dSYM bundles for iOS), stack traces captured in Appxiom cannot be resolved to original source files and line numbers.


Part 1: Android ProGuard / R8 De-obfuscation

Every Release Build Produces a Unique mapping.txt If mapping.txt is not archived before running ./gradlew clean or building a new version, production crashes from that version can never be de-obfuscated.

Step 1: Ensure Minification & Line Numbers are Preserved​

In composeApp/proguard-rules.pro:

-keepattributes SourceFile,LineNumberTable
-renamesourcefileattribute SourceFile

Step 2: Automated Gradle Backup Task​

Add an automated task to your Android module's build.gradle.kts (e.g. composeApp/build.gradle.kts) to copy mapping.txt to a timestamped backup directory after every release build:

tasks.register<Copy>("backupReleaseMapping") {
val mappingDir = layout.buildDirectory.dir("outputs/mapping/release")
from(mappingDir)
into(rootProject.layout.projectDirectory.dir("symbol-backups/android"))
include("mapping.txt")
rename { "mapping-${android.defaultConfig.versionName ?: "release"}-${System.currentTimeMillis()}.txt" }

doLast {
println("Appxiom: Archived release mapping.txt successfully.")
}
}

tasks.named("assembleRelease") {
finalizedBy("backupReleaseMapping")
}

Step 3: De-obfuscate Using Android retrace CLI​

To translate an obfuscated stack trace locally using the Android SDK's retrace tool:

# Syntax: retrace <mapping-file> <obfuscated-stack-trace>
$ANDROID_HOME/cmdline-tools/latest/bin/retrace mapping.txt obfuscated_stack.txt

Part 2: iOS Kotlin/Native Framework Symbolication

Step 1: Enable Kotlin/Native Debug Info in Release​

In your shared module's build.gradle.kts (e.g. composeApp/build.gradle.kts), enable light debug symbols for native release binaries:

kotlin {
targets.withType<org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget> {
binaries.withType<org.jetbrains.kotlin.gradle.plugin.mpp.Framework> {
freeCompilerArgs += listOf("-Xadd-light-debug=enable")
}
}
}

Step 2: Configure Xcode Release Build Settings​

In Xcode (iosApp.xcodeproj):

  1. Select your target → Build Settings.
  2. Debug Information Format: Set Release to DWARF with dSYM File (DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym").
  3. Strip Debug Symbols During Copy: Set Release to YES.
  4. Strip Linked Product: Set Release to YES.

Step 3: Archive & Locate .dSYM Bundles​

When you create an Xcode Archive (Product → Archive):

  1. Open Xcode Organizer (Window → Organizer).
  2. Right-click the archive → Show in Finder.
  3. Right-click the .xcarchive → Show Package Contents.
  4. Navigate to the dSYMs/ folder:
    • ComposeApp.framework.dSYM (Shared Kotlin/Native code)
    • iosApp.app.dSYM (Native Swift/UIKit code)
  5. Copy the .dSYM folders into your project's permanent symbol-backups/ios/ directory.

Step 4: Verify Slice UUID with dwarfdump​

Check the UUID of your archived .dSYM:

dwarfdump --uuid symbol-backups/ios/ComposeApp.framework.dSYM/Contents/Resources/DWARF/ComposeApp

Verify that the output UUID matches the Binary Images UUID reported in the Appxiom crash occurrence.

Step 5: Symbolicate Memory Addresses with atos​

Translate raw memory addresses back to Kotlin/Native source files and lines:

# Syntax: atos -o <dwarf-binary> -arch arm64 -l <load-address> <crash-address>
atos -o ComposeApp.framework.dSYM/Contents/Resources/DWARF/ComposeApp \
-arch arm64 \
-l 0x104500000 0x104523a10

Output:

kfun:tech.appxiom.kmp.example.CheckoutViewModel.submitPayment(kotlin.String) (CheckoutViewModel.kt:42)

Next Steps​

Connect your AI coding assistants to the Appxiom diagnostic engine with MCP Server Integration.