How to Manually Symbolicate a Crash Log from a macOS App

By Dave Ceddia

Is one of your Mac apps crashing? If you got a crash report from your users, or an unsymbolicated crash log from Sentry, I’ll walk you through how to decode it into something you can use to debug the crash.

For my own app Recut, I’ve added the Sentry library to capture crash reports. For whatever reason, Sentry does not symbolicate my crash reports. I’ve uploaded the dSYMs and it just seems to ignore them. So: I’ll just symbolicate them myself!

Here’s how to do it manually. I also put together a Ruby script to symbolicate the whole crash report too.

1. Download the unsymbolicated crash report from Sentry.

On the error report, select Raw and Unsymbolicated and click Download. The Download button won’t appear until you select Raw. Don’t leave it as the default Symbolicated or else the downloaded log will have a bunch of <redacted> where the memory addresses should be.

Symbolication options in Sentry

2. Make a new empty folder

Then open a Terminal, and cd to your new folder. We’ll be copying the necessary files here, because they all need to be in one place to symbolicate properly.

3. Open Xcode and the Organizer

In Xcode, under the Window menu, click Organizer to open it up.

This contains all of the archived builds of your app.

4. Find the correct release

Match up the info from the crash log (or the info in Sentry) with your list of releases in Xcode’s Organizer, and pick the one that matches.

Symbolication won’t work right (or maybe at all?) unless all the versions match up.

In my example I’m using release 2.0.1, build 552.

Xcode Organizer window showing available releases

5. Copy files out of the release

Right-click the release, and choose Show in Finder.

That’ll open a Finder window with a single .xcarchive file in it.

Right-click that xcarchive, and choose Show Package Contents.

You should see a bunch of folders like this:

An example of a .xcarchive folder

The files we need are in 2 places:

  • from dSYMs: copy everything into the folder you created.
  • from Products/Applications: copy your app into the same folder.

Make sure that you copy these files! Do not move them.

This xcarchive is your key to the kingdom… if you lose these files, you won’t be able to symbolicate crash reports anymore. So leave the original files alone, just make copies.

6. Run the symbolicator tool

You should have a preinstalled command line tool called atos. From its man page, its job is to “convert numeric addresses to symbols of binary images or processes”.

Now in your folder you should have at least these 3 things:

  • the crash report file (a text file)
  • YourApp.app.dSYM
  • YourApp.app

You can manually symbolicate a single line by plugging a couple memory addresses into atos. For example, here are the first few lines of a crash in my app:

Thread 0 Crashed:
0   Recut                           0x204302872         0x204296000 + 444530
1   Recut                           0x204308b5c         0x204296000 + 469852
2   Recut                           0x204308afe         0x204296000 + 469758

To look up the function and file where this crash occurred, I’ll take the first 2 memory addresses and run atos;

atos -o Recut.app/Contents/MacOS/Recut -arch x86_64 -l 0x204296000 0x204302872

That prints out the location of the crash and I can go look it up.

closure #1 in WaveDataManager.samplesForResolution(_:) (in Recut) (WaveDataManager.swift:150)

7. Run a script to automatically symbolicate everything

I wrote a Ruby script to run the atos command on every relevant line and symbolicate the whole crash report in one go. It’s in this GitHub gist.

To use:

  1. Download it
  2. Make it executable chmod +x ./symbolicate.rb
  3. Run it on your files:
symbolicate.rb 53c91214f29a42f1a0d19f86b7236e70.crash x86_64 Recut.app Recut.app.dSYM

This will print out the crash report, but with your own app’s calls symbolicated:

Thread 0 Crashed:
0   Recut                           0x204302872         closure #1 in WaveDataManager.samplesForResolution(_:) (in Recut) (WaveDataManager.swift:150)
1   Recut                           0x204308b5c         thunk for @callee_guaranteed () -> () (in Recut) (<compiler-generated>:0)
2   Recut                           0x204308afe         thunk for @escaping @callee_guaranteed () -> () (in Recut) (<compiler-generated>:0)
...

You can pass the -g to colorize the output, to highlight the lines that correspond to your app’s code.

By default it hides threads that don’t include any calls into your app code. It also hides the big list of binaries at the end of the crash report. You can turn those off with flags.

Here’s the full usage info:

Usage: symbolicate -g -b -t [crash_log] [arch] [app_bundle] [dsym]

The crash log, app, and dSYM should all be in the current working directory.

  In Xcode: Window > Organizer
  right-click the release, Show in Finder
  right-click the xcarchive, Show Package Contents
  copy files from `dSYMs` and `Products/Applications` into a new empty folder
  copy the crash log to the same folder

-g          Colorize the output to highlight this app's lines
-b          Show the 'Binary Images' section (by default this is omitted for brevity)
-t          Show all threads, including ones that have no calls to your app
crash_log   text file from Sentry
arch        x86_64 or arm64 (get this from Sentry)
app_bundle  TheApp.app (in current directory)
dsym        TheApp.app.dSYM (in current directory)

Now Go Debug!

I hope this helps you with your own app debugging journey!