skip to content

What are dSYM files in a React Native iOS archive, and why can native crash reports stay unsymbolicated without them?

level: middleimportance: nice to knowfreq 25%

answer

  1. symbols stripped from the shipped binary
  2. matched by build UUID
  3. saved inside the .xcarchive
  4. native frames only, not JS
  5. prebuilt core needs RCT_SYMBOLICATE_PREBUILT_FRAMEWORKS

basics

~20 s

A dSYM holds the debug symbols stripped from the shipped binary, matched to it by UUID. Crash reports map native addresses back to function names only with the matching dSYM from the archive; JavaScript frames need source maps instead.

solid answer

~40 s

A **dSYM** (debug symbol file) is produced when a build uses `DEBUG_INFORMATION_FORMAT = dwarf-with-dsym`, Xcode's default for Release. The shipped binary is stripped, and the dSYM keeps the mapping from addresses to functions, files and lines, tied to the binary by a **UUID**. **Product > Archive** saves dSYMs inside the `.xcarchive`, and only the dSYMs from that exact build symbolicate its crash reports. In a React Native app they cover **native** frames: your Swift and Objective-C, native libraries and React Native's own C++. JavaScript frames come from the bundle and need **source maps** instead. Two React Native specifics: since 0.84, React Native core comes as prebuilt `.xcframework`s by default, and their dSYMs are downloaded only if you set `RCT_SYMBOLICATE_PREBUILT_FRAMEWORKS=1` at `pod install`. And bitcode is gone, so the archive's dSYMs are final.

code

bash · 3 lines
bash
# Download dSYMs for the prebuilt React Native core frameworks (0.84+ default)
cd ios
RCT_SYMBOLICATE_PREBUILT_FRAMEWORKS=1 bundle exec pod install

go deeper

for a junior

Know that a dSYM holds the debug symbols for a native build, that the archive keeps them, and that they are needed to read native crash reports.

for a middle

Explain UUID matching, why rebuilds do not help, why JS frames need source maps instead, and why bitcode no longer changes which dSYMs are valid.

for a senior

Make sure the release process keeps every archive's dSYMs, fetches dSYMs for prebuilt React Native frameworks, and pairs them with JS source maps for complete crash reports.

for a principal

Treat symbol retention as part of the release contract: every shipped build must be diagnosable, native and JS, for as long as users run it.

## What a dSYM is When Xcode builds native code it can produce **debug symbols**: the map from machine-code addresses to function names, source files and line numbers. For a Release build those symbols are **stripped** from the app binary to keep it small, and written to a separate **dSYM** bundle instead. That happens when the target's `DEBUG_INFORMATION_FORMAT` is `dwarf-with-dsym`, which is Xcode's usual Release setting. Each binary and its dSYM share a **UUID**. A crash report records the UUIDs of the binaries that were loaded, and symbolication only works with the dSYM carrying the matching UUID, from that exact build. ## Where the dSYMs are - **Product > Archive** writes an `.xcarchive` that contains the app and a `dSYMs` folder with the symbol files for the app and its embedded frameworks. - Xcode's Organizer keeps archives, so the dSYMs stay available as long as the archive does. - Rebuilding the same commit later produces **different UUIDs**, so a dSYM from a rebuild does not symbolicate crashes from the original upload. Keep the archive, or its dSYMs, for every build you ship. ## Native frames vs JavaScript frames A React Native crash can surface in two worlds: | Crash frames | Where they come from | What symbolicates them | |---|---|---| | Swift, Objective-C, C++ | The app binary and native frameworks | dSYMs from the archive | | React Native core (C++, Objective-C) | Prebuilt `.xcframework`s by default | Their dSYMs, if downloaded | | JavaScript | `main.jsbundle` | Source maps from the bundling step | A dSYM does nothing for JavaScript; the bundling phase can emit a source map for that, and uploading dSYMs and source maps to a crash reporter is a separate topic. ## The React Native twist: prebuilt core frameworks Since React Native 0.84, `pod install` downloads **precompiled React Native core** as `.xcframework`s by default (set `RCT_USE_PREBUILT_RNCORE=0` to build from source). Because that code is not compiled in your build, its symbols are not in your archive's dSYMs automatically. React Native's CocoaPods scripts support `RCT_SYMBOLICATE_PREBUILT_FRAMEWORKS=1`, which downloads the dSYMs for the prebuilt frameworks and installs them next to the frameworks. Without it, crash frames inside React Native core stay as raw addresses. ``` RCT_SYMBOLICATE_PREBUILT_FRAMEWORKS=1 bundle exec pod install ``` ## Why bitcode no longer matters Before Xcode 14, apps could upload **bitcode**, an intermediate form that Apple recompiled on its side; the binary users ran was then not the one you built, and you had to download new dSYMs from App Store Connect. Bitcode was deprecated in Xcode 14 and App Store Connect no longer accepts bitcode submissions, and the React Native template's project file carries `ENABLE_BITCODE = NO`. The practical effect: **the dSYMs in your archive are the final ones**, so keeping the archive is enough. ## The scenario: a parking app's native crash After a release, a parking app sees crashes in its native map module. The crash reports show only addresses. The team finds that the CI job archives, uploads and then deletes the workspace, so the `.xcarchive` and its dSYMs are gone; rebuilding produces new UUIDs that do not match. They change the job to keep each archive's `dSYMs` folder, and add `RCT_SYMBOLICATE_PREBUILT_FRAMEWORKS=1` so frames inside React Native core resolve too. ## Common mistakes - **Deleting archives after upload**, often in CI, which throws away the only matching dSYMs. - **Assuming dSYMs cover JavaScript.** A JavaScript exception needs a source map, not a dSYM. - **Forgetting third-party frameworks.** Prebuilt binary frameworks from other libraries also need their own dSYMs to resolve their frames. - **Changing `DEBUG_INFORMATION_FORMAT` to speed up builds** in Release, which silently stops dSYM generation. - **Relying on bitcode-era advice** to fetch dSYMs from App Store Connect for new builds. ## Checklist 1. Confirm Release uses `dwarf-with-dsym`. 2. Keep every shipped `.xcarchive`, or at least its `dSYMs` folder. 3. Set `RCT_SYMBOLICATE_PREBUILT_FRAMEWORKS=1` when using prebuilt React Native core. 4. Produce source maps for the JS side as well.

  • Why can't a React Native team rebuild an old commit to get the dSYM for a crash from a shipped build?
    Symbolication matches a crash to a dSYM by the binary's UUID, and a rebuild produces a new binary with a different UUID, even from the same source. Only the dSYM produced alongside the shipped binary matches. That is why the archive, or its `dSYMs` folder, must be kept for every build uploaded to App Store Connect.
  • Since bitcode was removed, where do a React Native iOS app's final dSYMs come from?
    From your own archive. With bitcode, Apple recompiled the app, so the dSYMs had to be downloaded from App Store Connect. Bitcode is deprecated and no longer accepted, so the binary users run is the one you archived and its dSYMs sit in the `.xcarchive`.

saying these in an interview costs you the question

  • dSYMs symbolicate the JavaScript frames of a React Native crash.
  • A dSYM from a rebuild of the same commit will match the shipped build.
  • React Native core frames are always symbolicated by the app's own dSYMs.
  • You still need to download dSYMs from App Store Connect because of bitcode.
  • dSYMs are embedded in the app binary that users download.