Quick answer: PDB (Program Database) files are debug symbol files generated by the Microsoft Visual C++ compiler. They contain the mapping between compiled machine code addresses and your original source code, including function names, file paths, and line numbers.
Learning how to symbolicate crash reports from players is a common challenge for game developers. A crash report full of hexadecimal addresses is useless until you symbolicate it. Symbolication is the process of mapping raw memory addresses back to function names, file paths, and line numbers using debug symbol files. This guide covers the tools and workflows for symbolicating crash reports on Windows, Apple platforms, and Android.
Why Crash Reports Need Symbolication
Release builds strip debug information to reduce binary size and protect source code. When a player's game crashes, the crash report contains only the module name and a memory offset for each frame in the stack trace. Without symbolication, a crash looks like this:
# Unsymbolicated crash report
Thread 0:
0x00401c2f MyGame.exe + 0x1c2f
0x004087a4 MyGame.exe + 0x87a4
0x0040d088 MyGame.exe + 0xd088
After symbolication with the matching debug symbols, the same crash becomes actionable:
# Symbolicated crash report
Thread 0:
InventorySystem::RemoveItem() inventory.cpp:142
PlayerController::DropWeapon() player.cpp:389
GameLoop::ProcessInput() main.cpp:67
Windows: PDB Files
The Microsoft Visual C++ compiler generates PDB (Program Database) files alongside your executable. Each PDB contains a unique GUID and age value that must match the executable exactly. A PDB from a different build, even with identical source code but different compiler flags, will not work.
To symbolicate a Windows crash dump, open the .dmp file in Visual Studio and configure the symbol path to include your PDB archive:
# Visual Studio symbol path configuration
# Debug > Options > Symbols > Symbol file locations
srv*C:\LocalCache*\\buildserver\symbols
srv*C:\LocalCache*https://your-symbol-server.example.com/symbols
# Or in WinDbg
.sympath srv*C:\LocalCache*\\buildserver\symbols
.reload /f
!analyze -v
Microsoft's symstore.exe tool organizes PDB files into a structured directory layout that debuggers can query by GUID. Add a symstore step to your CI pipeline so that every build's symbols are indexed automatically.
Apple Platforms: dSYM Bundles
On macOS and iOS, the compiler generates dSYM (Debug Symbol) bundles containing DWARF debug information. Each dSYM has a UUID that corresponds to the UUID embedded in the binary. Xcode generates these automatically when you archive a build.
To symbolicate a .crash file from a player, use the symbolicatecrash tool or the atos command:
# Symbolicate an entire crash log
export DEVELOPER_DIR="/Applications/Xcode.app/Contents/Developer"
symbolicatecrash player_crash.crash MyGame.app.dSYM > symbolicated.crash
# Symbolicate a single address
atos -arch arm64 -o MyGame.app.dSYM/Contents/Resources/DWARF/MyGame \
-l 0x100000000 0x10004a4c2
The critical step is preserving dSYM files for every build you distribute. If you use Xcode's Archive feature, dSYMs are stored in the archive automatically. For command-line builds, add a post-build step that copies the dSYM bundle to your symbol archive.
Android: NDK Symbolication
Android NDK games produce native crash reports called tombstones. These appear in /data/tombstones/ on the device or in adb logcat output. To symbolicate them, you need the unstripped .so shared library files from your build.
# Using ndk-stack to symbolicate a tombstone
adb logcat -d | ndk-stack -sym /path/to/unstripped/libs/arm64-v8a/
# Using addr2line for a specific address
$NDK/toolchains/aarch64-linux-android-4.9/prebuilt/linux-x86_64/bin/\
aarch64-linux-android-addr2line -e libMyGame.so -f 0x4a4c2
Unity and Unreal generate a symbols.zip during Android builds that contains the unstripped libraries. Archive this file for every release. The Google Play Console also accepts symbol uploads for automatic crash symbolication in the Android Vitals dashboard.
Archiving Symbols Per Build
The most critical part of symbolication is having the right symbols available. If a player crashes on version 1.2.3 and you only have symbols for 1.2.4, the crash is permanently unreadable. Your CI pipeline must archive symbols for every build that reaches players.
A practical approach is to tag symbol archives with the git commit hash and build number. Store them in cloud storage (S3, GCS, or Azure Blob) with a consistent naming convention:
# Symbol archive directory structure
symbols/
v1.2.3-abc1234/
windows/
MyGame.pdb
macos/
MyGame.app.dSYM/
android/
arm64-v8a/
libMyGame.so
armeabi-v7a/
libMyGame.so
Automate this in your build script. The cost of storing a few hundred megabytes of symbols per build is negligible compared to the cost of being unable to diagnose a crash affecting thousands of players.
Automated Symbolication with Bugnet
Bugnet automates the entire symbolication pipeline. Upload your symbol files as a post-build step in CI using the Bugnet CLI, and all incoming crash reports are symbolicated automatically. The dashboard shows fully resolved stack traces grouped by crash signature, so you can see which function is crashing, how many players are affected, and whether your fix resolved the issue across all platforms.
"The crash report you cannot symbolicate is the crash you cannot fix. Treat symbol archival with the same seriousness as your source code backups."
Related Issues
For a deeper look at the crash dump collection process, see our guide on capturing and symbolicating crash dumps from player devices. For a general introduction to stack trace concepts, read reading game stack traces: a beginner's guide. To set up crash reporting from scratch, see how to set up crash reporting for indie games.
One missed symbol upload can mean thousands of unreadable crashes. Automate it in CI and never think about it again.