Setting Up dSYM Upload for iOS Crash Deobfuscation
Firebase Crashlytics shows 0x000000010034a5c8 instead of PaymentViewController.swift:142 — and a developer spends an hour trying to figure out what actually crashed. dSYM files (debug symbol maps) solve this problem, but their upload to crash reporting regularly breaks for several non-obvious reasons.
Why dSYMs Don't Make It to Crashlytics
The most common scenario: Bitcode was enabled, Apple recompiled the binary on their servers — and the dSYM for that specific build now lives not in Xcode Organizer, but in App Store Connect. Crashlytics gets outdated symbols from the local build and can't match addresses. Result: all production crashes come through undeobfuscated.
A second problem: automatic upload via Run Script doesn't work when building on CI. The ${PODS_ROOT}/FirebaseCrashlytics/run script runs in Build Phase, but on an agent without a keychain, Firebase CLI can't authenticate. Crashes start accumulating with undeobfuscated data from the first release.
How We Set Up dSYM Upload
Basic Setup via Run Script
For projects without Bitcode and manual CI, a properly configured Build Phase is sufficient:
# Build Phase: Run Script
"${PODS_ROOT}/FirebaseCrashlytics/run"
In the Input Files field, you must specify:
${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Resources/DWARF/${TARGET_NAME}
$(SRCROOT)/$(BUILT_PRODUCTS_DIR)/$(INFOPLIST_PATH)
Without Input Files, Xcode skips the script during incremental builds — Crashlytics doesn't receive new symbols.
Downloading dSYMs from App Store Connect
When Bitcode is enabled (or for App Clips), dSYMs must be downloaded separately:
# Fastlane: download_dsyms + upload_symbols_to_crashlytics
lane :refresh_dsyms do
download_dsyms(
app_identifier: "com.example.app",
version: "2.1.0",
build_number: "210"
)
upload_symbols_to_crashlytics(
dsym_paths: Actions.lane_context[SharedValues::DSYM_PATHS]
)
clean_build_artifacts
end
This lane can be scheduled via CI (for example, once a day after a new build goes to the App Store) or as a post-deploy step.
Manual Upload via Firebase CLI
If Fastlane isn't being used:
firebase crashlytics:symbols:upload \
--app=1:123456789:ios:abcdef \
MyApp.app.dSYM.zip
The dSYM file is located in ~/Library/Developer/Xcode/Archives/ after archiving, or can be downloaded from Xcode Organizer → Archives → Download Debug Symbols.
Verifying Symbol Correctness
After upload, verify via Firebase Console: Crashlytics → select a crash → confirm that the stack trace shows method names and code lines. If it still shows addresses — the dSYM UUID doesn't match the binary UUID.
Check UUIDs:
# Binary UUID
dwarfdump --uuid MyApp.app/MyApp
# dSYM UUID
dwarfdump --uuid MyApp.app.dSYM
Both should match. A mismatch means the dSYM from a different build was uploaded.
Integration with Sentry
For projects using Sentry instead of Firebase, the process is similar but via sentry-cli:
sentry-cli upload-dif \
--org my-org \
--project ios-app \
--include-sources \
MyApp.app.dSYM
The --include-sources flag allows displaying source code directly in Sentry — convenient for teams without access to the local repository during on-call duty.
Setting Up on CI/CD
On Bitrise, GitHub Actions, or GitLab CI, dSYM upload is integrated into the pipeline after the archiving step:
# GitHub Actions example
- name: Upload dSYM to Crashlytics
env:
FIREBASE_TOKEN: ${{ secrets.FIREBASE_TOKEN }}
run: |
fastlane run upload_symbols_to_crashlytics \
dsym_path:"./MyApp.app.dSYM.zip" \
gsp_path:"./GoogleService-Info.plist"
FIREBASE_TOKEN is obtained via firebase login:ci — the token doesn't expire and is safe to store in CI secrets.
Workflow
Audit current setup: check Build Phase, presence of Input Files, upload history in Firebase Console.
Determine dSYM source: local build or App Store Connect (depends on Bitcode/App Clip).
Configure automatic upload: Fastlane lane or CI step after each release build.
Verify: create a test crash via fatalError() in a Debug build, check symbolization in Console.
Timeline Estimates
Setup for a project without Bitcode with working CI — 2–4 hours. If you need to set up regular downloads from App Store Connect and integration with multiple crash reporting services — 1 business day.







