Skip to main content
A release build of your app carries no function names. When it crashes, the report holds addresses: for each frame, a binary image and an offset into it. To show CheckoutViewModel.submit() in CheckoutViewModel.swift:214 instead, Apsio needs the debug symbols of that exact build.

dSYMs and UUIDs

On Apple platforms the symbols are in dSYM bundles, one per binary: the app, each extension and each framework you build. Xcode writes them when Debug Information Format is “DWARF with dSYM File”, and an archive keeps them in its dSYMs folder. Every binary has a UUID per architecture, and it changes with every build. A crash report lists the UUID of each binary image in the stack, so Apsio matches a crash to the dSYM with the same UUID. A dSYM from another build, even of the same version, does not match.

What happens on upload

The apsio CLI uploads one file per UUID. Apsio checks that each file is a Mach-O with debug information and that its UUID is the one it claims, then keeps it for your project only: one project never uses another project’s symbols. Crashes that arrived before their symbols are symbolicated when the symbols land, and they are grouped again with function names instead of addresses.

What is symbolicated

  • Frames in binaries you uploaded: function, file and line.
  • Frames in Apple’s system libraries are not symbolicated; they keep the library name and offset.
  • Frames in a binary whose dSYM is missing keep the image name and offset until it is uploaded.

Android and R8 mappings

An Android crash in Kotlin or Java code carries the stack as the runtime prints it, with class and method names instead of addresses. When R8 shrinks the build, those names are R8’s (a.b.b), and Apsio needs the build’s mapping (mapping.txt) to retrace them. Nothing in an app can read a build’s identity at run time, so the build gets one: a build id (a UUID) written into the manifest as io.apsio.build_id and reported with every crash as app.build_id. The mapping is uploaded under the same id, and kept for your project only.
  • Retraced: classes, methods, source files and lines, with the frames R8 inlined into one call site expanded, and the exception’s class. The causes (Caused by:) are retraced too.
  • In your app: frames of classes under your application id’s package and its subpackages, outside the Android platform, the Java and Kotlin libraries, AndroidX and common libraries such as OkHttp, Retrofit, Gson, Firebase and coroutines. When no frame is under it, its parent package counts instead (com.example.shop for com.example.shop.debug); when none is under that either, for example an application id unlike your code’s package, the classes the mapping lists. Grouping uses these frames; a crash with none of them groups by its platform frames. A list you can set per project comes later.
  • Before the mapping: the crash is grouped by R8’s names, and grouped again with the original names once the mapping arrives. A frame without a line number whose renamed method matches several methods shows the first, marked as ambiguous.
  • Builds without R8 need no mapping: their stacks already have the original names, and they never show a missing mapping.
  • Exceptions listed under Suppressed: are not causes and are left out. JavaScript stacks from React Native apps on Android are not retraced with R8.

Android native code (NDK)

A native crash on Android 12 and later carries the frames of the system’s crash report: for each frame, the library, the offset into it and the library’s GNU build id, which the linker writes into every library. Apsio matches the frames to the debug files uploaded with the same build id, kept for your project only.
  • Symbolicated: functions, source files and lines in your libraries, with inlined frames expanded.
  • In your app: the libraries loaded from your app’s own install (/data/app/...), and any library you uploaded symbols for. Libraries of the system (/system, /apex, /vendor and the other partitions, such as libc.so and libart.so) never are: they keep the function name the device gives, and are never listed as missing.
  • Before the upload: the crash is grouped by library and offset, and grouped again by function names once the debug files arrive. A frame is keyed by the library’s name, not its path, which changes with every install.

React Native JavaScript

The React Native SDK reports a JavaScript error or crash with its stack as Hermes prints it: for each frame, the function, the bundle and an offset into its bytecode. Apsio matches the frames to the source map uploaded for that bundle and build (the app’s app.build_id), or, for an over-the-air bundle the app named, to that bundle’s map only.
  • Symbolicated: your original function names, files and lines.
  • In your app: your own code, outside node_modules. React Native, Babel’s helpers and other libraries are not.
  • Before the map: the crash is grouped by the function names Hermes kept, and grouped again once the map arrives.

Missing symbols

The read API lists, per release, the images of your app that events used and that have no symbols in the project, with their debug id (the UUID), name, architecture and build: GET /v1/projects/{projectId}/releases/{release}/missing-symbols. Upload the dSYMs with those UUIDs. For an Android build whose stacks show R8’s names, the list holds its build id under the app’s package: upload the build’s mapping under that id. For an Android native crash, it holds your libraries without debug files, by the debug id of their build id: upload them with apsio upload ndk. For a React Native bundle, it holds the bundle’s file name (with @<bundle id> for an over-the-air bundle): upload its map with apsio upload sourcemaps. An issue’s latest occurrence also lists, in missing_debug_ids, every image of its stack without symbols, Apple’s system frameworks included; those you cannot upload. Common reasons a crash stays unsymbolicated:
  • Debug Information Format is “DWARF” for that configuration, so no dSYM was written. Debug builds use it by default.
  • The dSYMs of that build were never uploaded, for example a build made on a laptop.
  • A third-party framework shipped as a binary without its dSYM. Ask its vendor for the dSYM, then upload it with apsio upload dsyms.
Upload from the Xcode build phase or from CI so every shipped build has its symbols.