Quick answer: IL2CPP converts C# code to C++ source code, which is then compiled to native machine code. This process mangles method names into C++-style identifiers with hash suffixes, removes C# namespaces, and flattens generics into concrete type names.

Learning how to decode obfuscated stack traces is a common challenge for game developers. Release builds of games often go through code transformation pipelines that make stack traces unreadable. IL2CPP mangles C# names into C++ identifiers. ProGuard renames Java classes to single letters. Code stripping removes entire methods from the binary. This guide covers how each obfuscation method affects your stack traces and the tools you need to decode them back to readable source locations.

IL2CPP: Unity's Native Compilation Pipeline

IL2CPP is Unity's default scripting backend for iOS, consoles, and WebGL. It converts C# intermediate language (IL) to C++ source code, then compiles that to native machine code. The conversion process transforms method names into C++-safe identifiers with hash suffixes to avoid naming conflicts.

A crash in an IL2CPP build produces a stack trace with mangled names:

# IL2CPP mangled stack trace
PlayerInventory_AddItem_m3F2A7B1C + 0x4f
LootDrop_CollectLoot_mA8C1D2E3 + 0x38
UnityEngine_Events_InvokableCall_Invoke_m1234ABCD + 0x22

The naming convention follows the pattern ClassName_MethodName_mHASH. You can often guess the original method from the mangled name, but you need the mapping files for exact identification, especially when generic methods or overloads are involved.

Decoding IL2CPP with Mapping Files

During the IL2CPP build, Unity generates several mapping files in the Il2CppOutputProject directory:

MethodMap.tsv maps mangled C++ function names back to their original C# method signatures. Each row contains the mangled name and the full C# namespace, class, method name, and parameter types.

LineNumberMappings.json maps offsets within the generated C++ code back to line numbers in your original C# source files. This is essential for getting line-level accuracy in symbolicated stack traces.

# MethodMap.tsv example entries
PlayerInventory_AddItem_m3F2A7B1C    MyGame.Inventory.PlayerInventory::AddItem(Item)
LootDrop_CollectLoot_mA8C1D2E3       MyGame.Loot.LootDrop::CollectLoot()

# To decode a stack trace, look up each mangled name in the TSV
grep "PlayerInventory_AddItem_m3F2A7B1C" MethodMap.tsv

For automated decoding, write a script that reads the crash trace, extracts each mangled function name, looks it up in the MethodMap, and produces a clean output with original C# names. Some crash reporting tools, including Bugnet, accept the mapping files as a build artifact and perform this decoding automatically.

ProGuard and R8: Android Java Obfuscation

Android games written in Java or Kotlin (or using Java-based plugins) often use ProGuard or R8 to shrink and obfuscate the bytecode. These tools rename classes, methods, and fields to short single-letter names:

# Obfuscated ProGuard stack trace
java.lang.NullPointerException
    at a.b.c.d(Unknown Source:12)
    at a.b.e.f(Unknown Source:34)
    at a.a.a.a(Unknown Source:7)

Without the mapping file, this is completely unreadable. Each build generates a unique mapping.txt file that records the renaming. The Android SDK includes retrace, a command-line tool that reverses the obfuscation:

# Decode an obfuscated stack trace with retrace
retrace mapping.txt obfuscated_stacktrace.txt

# Output: readable stack trace with original names
java.lang.NullPointerException
    at com.mygame.inventory.PlayerInventory.addItem(PlayerInventory.java:47)
    at com.mygame.loot.LootSystem.collectLoot(LootSystem.java:31)
    at com.mygame.core.GameLoop.update(GameLoop.java:89)

Code Stripping and Its Effects

Code stripping removes unused methods and classes from the build to reduce binary size. Unity's Managed Stripping Level controls how aggressively this happens. At the "High" setting, entire classes can be removed if the linker determines they are unreachable.

The problem arises when code that the linker thinks is unused is actually called via reflection, serialization, or dynamic dispatch. A crash in stripped code produces a stack trace with missing frames or a MethodNotFound exception that references a method that no longer exists in the binary.

To preserve methods from stripping, use the [Preserve] attribute in Unity or add entries to a link.xml file:

<!-- link.xml: preserve classes from managed stripping -->
<linker>
    <assembly fullname="Assembly-CSharp">
        <type fullname="MyGame.Inventory.PlayerInventory" preserve="all"/>
        <type fullname="MyGame.SaveSystem.SaveData" preserve="all"/>
    </assembly>
</linker>

Archiving Mapping Files

The single most important rule for obfuscated builds: archive the mapping file for every version you ship. Without the matching mapping file, an obfuscated stack trace is permanently unreadable. Each build generates unique mappings, and a file from a different build will produce incorrect decodings.

Add mapping file archival to your CI pipeline. For Unity IL2CPP builds, archive the Il2CppOutputProject directory or at minimum the MethodMap.tsv and LineNumberMappings.json. For Android ProGuard/R8 builds, archive the mapping.txt file. Tag each archive with the build number and git commit hash so you can match it to any version in the field.

"Obfuscation protects your code from reverse engineering, but it also protects your bugs from being found. The mapping file is the key to both."

Automated Decoding with Bugnet

Bugnet accepts IL2CPP mapping files, ProGuard mapping files, and platform-specific debug symbols as build artifacts. Upload them as a post-build step in your CI pipeline, and all incoming crash reports are automatically decoded and symbolicated. The dashboard shows fully readable C# method names and line numbers, even for IL2CPP builds on iOS and Android, eliminating the need to manually run retrace or grep through MethodMap files.

Related Issues

For a deeper look at symbolicating native crash reports across platforms, see capturing and symbolicating crash dumps from player devices. For Unity-specific stack trace interpretation, read understanding stack traces in Unity crash logs. To set up end-to-end crash reporting, see how to set up crash reporting for indie games.

Treat your mapping files like your source code: version them, archive them, and never delete them.