How do you inspect the binary you are about to ship: the minimum OS, platform and SDK, architectures, linked frameworks and the UUID that ties it to a dSYM?
Predict first
A release archive is inspected, one source line is changed and it is archived again. What do the checks show?
// === run.sh ===
cp -R "$FIXTURE"/. .
build() { xcodebuild archive -project Demo.xcodeproj -scheme Demo -sdk iphoneos -configuration Release \
-archivePath "$1" CODE_SIGNING_ALLOWED=NO -quiet >/dev/null 2>&1; }
build out/a.xcarchive
APP=out/a.xcarchive/Products/Applications/Demo.app
echo "$(vtool -show-build $APP/Demo | grep -E 'platform|minos' | tr -s ' ' | tr '\n' ' ')"
echo "Info.plist MinimumOSVersion: $(plutil -extract MinimumOSVersion raw $APP/Info.plist)"
echo "$(lipo -info $APP/Demo | sed 's/.*is architecture/architecture/')"
echo "links SwiftUI: $(otool -L $APP/Demo | grep -c SwiftUI.framework)"
B=$(dwarfdump --uuid $APP/Demo | awk '{print $2}')
D=$(dwarfdump --uuid out/a.xcarchive/dSYMs/Demo.app.dSYM | awk '{print $2}')
[ "$B" = "$D" ] && echo "binary and dSYM UUID match"
sed -i '' 's/Text("Demo")/Text("Demo v2")/' Demo/DemoApp.swift
build out/b.xcarchive
C=$(dwarfdump --uuid out/b.xcarchive/Products/Applications/Demo.app/Demo | awk '{print $2}')
[ "$B" != "$C" ] && echo "changed source gives a different UUID"
[ "$C" != "$D" ] && echo "old dSYM does not match the new binary"
- minos 17.0 equals MinimumOSVersion, one arm64 slice, UUIDs of binary and dSYM match, and a source change gives a new UUID
- The UUID never changes between builds
- The dSYM UUID differs from its own binary
Mental model. The UUID is the serial number on a lock; a dSYM is only the key for the lock with that exact serial.
The binary is evidence, so read it. vtool -show-build prints the LC_BUILD_VERSION load command: the platform, minos (the deployment target the linker recorded) and sdk (the SDK it was built against). lipo -info lists architectures, otool -L lists linked dylibs and frameworks (weak links are marked), and dwarfdump --uuid prints the UUID that the linker generates for each build. The check archives the sample app and shows minos 17.0 matching the Info.plist’s MinimumOSVersion (Q489), a single arm64 slice, SwiftUI linked, and the binary’s UUID equal to its dSYM’s. Then it changes one line of source, archives again and gets a different UUID, so the first build’s dSYM does not describe the second binary.
That UUID rule is why crash reports sometimes cannot be symbolicated even though “a dSYM exists”: symbolication matches the crash’s image UUID against the dSYM’s UUID (Q461), so a rebuilt-from-the-same-commit binary may not match the dSYM you archived, and an upload step that picks up the wrong folder silently fails. Put the checks in the pipeline: assert the deployment target and platform, assert the architectures, assert that every dSYM UUID is present for the binary and each embedded framework before uploading to the crash reporter, and store the UUID with the build record. Because the linker output was inspected here with a tiny app, behaviour for bitcode-era builds, fat frameworks or build settings that strip symbols was not covered.
The UUID identifies a link result, and a dSYM only matches the binary it came from.
Common traps
- Assuming the dSYM from the last build fits this binary. Any relink changes the UUID; keep the dSYM from the archive you shipped.
- Trusting a build setting over the binary. Read
minoswithvtool; transitive settings and frameworks can differ from what you intended.
They might follow up with
- Where else does the UUID show up? In the crash report’s “Binary Images” section, which is how symbolication picks the dSYM.
- Does a debug build have a dSYM? Not by default: debug builds keep DWARF in object files; release uses
dwarf-with-dsym.
Related questions
- 23.5.1 How do you read a crash report, what do the exception types and Swift runtime messages mean, and how does…
- 23.5.2 How do you build a production diagnostics pipeline: collection, symbolication, grouping, prioritisation…
- 25.5.2 What do the deployment target, availability checks and required device capabilities decide, and how do you…
- 21.7.2 Where does binary size come from, how do you measure and shrink it, and what do optimisation level, stripping…
- 16.3.1 How do static and dynamic linking differ for an app, what do they cost at launch and in size, and what are…
← What is inside an .xcarchive and an .ipa, what does exportArchive… How do you make Swift Package dependencies reproducible on CI: what… →