Debug files & native symbolication
A release build of a mobile or native app doesn't ship its names. An iOS or Android NDK crash
reports instruction addresses; an R8-minified Android app reports a.b.c.d. Debug files
are what turn those back into your code — the native counterpart of
source maps.
Errora applies them at ingest, before grouping, so issues group on real function names that stay stable across builds.
What gets resolved
- Native frames (iOS crashes, Android NDK) → function name, file and line. C++ and Swift names are demangled, and inlined frames are expanded into frames of their own.
- Java/Kotlin frames from an R8/ProGuard build → original class, method, file and line, with inlined methods expanded. Obfuscated exception class names are restored too.
- Profiles: iOS profile frames are symbolicated with dSYMs, and Android profile method names are remapped through the ProGuard mapping — see Profiling.
Supported formats
| Format | Platform | Where it comes from |
|---|---|---|
| dSYM (Mach-O DWARF) | iOS, macOS | Xcode, with Debug Information Format = DWARF with dSYM File |
ELF (.so, unstripped) | Android NDK, Linux | The NDK build's intermediate, unstripped libraries |
Breakpad (.sym) | Any native | dump_syms |
| PDB | Windows | The MSVC linker |
ProGuard/R8 mapping.txt | Android (Java/Kotlin) | app/build/outputs/mapping/<variant>/mapping.txt |
How a file is matched
Every file is identified by a debug id, never by name or release:
- Native files carry one inside the binary (the Mach-O UUID, the ELF build id, the PDB signature, the Breakpad module id). The SDK reports the debug id of every image loaded in the crashed process, and each frame's address is resolved against the file with that id.
- ProGuard mappings are matched by a UUID that the Gradle plugin stamps into the app and the SDK reports with each event.
So the file must come from the exact build that crashed. Rebuilding the same commit gives new debug ids, and the old crashes won't match the new files.
Uploading
Use the official Sentry tooling, unchanged — Errora implements the upload endpoints
sentry-cli talks to. Point it at Errora with four settings:
| Setting | Value |
|---|---|
SENTRY_URL | https://<your-errora-host> |
SENTRY_AUTH_TOKEN | An Errora personal access token (errora_pat_…) |
SENTRY_ORG | The organization slug (from /organizations/<slug>/…) |
SENTRY_PROJECT | The project slug |
The same values go in a .sentryclirc (url, org, project, token) or the Gradle
sentry {} block. The token's owner needs the member role or higher in the organization.
sentry-cli
# dSYMs, ELF, Breakpad, PDB — point it at a file or a directory
sentry-cli debug-files upload path/to/build/
# ProGuard/R8 mapping
sentry-cli upload-proguard --uuid <uuid> app/build/outputs/mapping/release/mapping.txt
Per platform
- Android — the Sentry Android Gradle plugin uploads the mapping and NDK symbols on every release build and stamps the ProGuard UUID for you. See Android.
- iOS — an Xcode Run Script build phase running
sentry-cli debug-files upload, or fastlane'ssentry_debug_files_upload. See iOS.
From the browser
Settings → Debug files uploads a file by hand, and lists, downloads and deletes what's there. Native files carry their own debug id. For a ProGuard mapping, fill in the optional debug id field with the UUID your app reports, so the mapping matches its events.
Reading an issue with missing files
A frame whose debug file is missing is kept, not dropped, and flagged as unsymbolicated. The issue page lists the missing debug ids — with the image or mapping each belongs to — so you know exactly which file to upload.
Debug files only apply to events that arrive after the upload: upload the file, then wait for the next occurrence.
Self-hosting
| Setting | Default | Meaning |
|---|---|---|
DEBUG_FILES_STORAGE | filesystem | filesystem keeps files on disk under DEBUG_FILES_ROOT; s3 keeps them in an S3-compatible bucket |
DEBUG_FILES_ROOT | backend/var/debug-files | Where files live with filesystem storage. Must be a shared volume when ingest workers run on several hosts — or use s3 |
DEBUG_FILES_S3_BUCKET, DEBUG_FILES_S3_ENDPOINT_URL, DEBUG_FILES_S3_REGION | — | The bucket for s3 storage (any S3-compatible service) |
DEBUG_FILES_S3_ACCESS_KEY, DEBUG_FILES_S3_SECRET_KEY | — | Its credentials — keep them in a secret |
DEBUG_FILES_S3_PREFIX | debug-files | Key prefix inside the bucket |
DEBUG_FILES_CACHE_DIR | system temp dir | With s3, each worker keeps the files it symbolicates with here (no volume needed) |
DEBUG_FILES_CACHE_MAX_BYTES | 2 GB | Size of that cache; least recently used files go first |
DEBUG_FILE_UPLOAD_MAX_BYTES | 500 MB | Cap on a single browser upload |
With s3 the workers are stateless: nothing needs to be mounted, and a replaced pod
re-downloads what it needs on first use.
Upload chunks left behind by sentry-cli are trimmed nightly.
Limitations
- Minidumps (Breakpad, Crashpad, sentry-native) are stack-walked on the server, as Sentry
does, with rust-minidump's
minidump-stackwalk(in the Docker image; setMINIDUMP_STACKWALKfor a custom build). Upload the binaries' debug files so the walk can use their unwind information; without them it falls back to frame pointers and stack scanning, and each frame shows how it was found (context,cfi,fp,scan). - Swift names are demangled with the Swift runtime library (
libswiftCore), which the Docker image includes; a self-built image without it shows Swift symbols mangled ($s7MyApp…). C++ is always demangled. - Frames without a line number (Android profiles) are resolved the way Sentry does: by the method's parameter types when the frame carries its signature, otherwise by name — so an obfuscated name that maps to several methods with the same parameters stays as-is.
Troubleshooting
| Symptom | Cause / fix |
|---|---|
Frames still show addresses / a.b.c | The file for that exact build wasn't uploaded — compare the missing debug id on the issue with Debug files |
| A file is listed but still doesn't match | It's from a different build (same version, rebuilt) — upload from the job that produced the shipped binary |
| NDK frames unresolved | You uploaded stripped .so files — upload the unstripped ones from the build intermediates |
| ProGuard mapping uploaded by hand never matches | Its debug id isn't the UUID the app reports |
sentry-cli uploads to sentry.io | SENTRY_URL / url isn't set |
403 on upload | The token's owner isn't a member of that organization |