Manually symbolicating crash reports

June 04, 2022 3 minute read

You may know that as part of my principles behind PHP Monitor, I do not collect personal information. This means that I do not have access to automated crash log uploads. Instead, most of the time it is folks who see the app crash who end up reporting the crash.

Sometimes, it’s just faulty behaviour that pops up, but every now and then I get a hard crash report. Unfortunately, a crash report sent to me as a text file (.ips) means that I need to figure out what went wrong.

This process is called crash symbolication, and normally Xcode can do this by itself, as Apple notes:

When an app crashes, the operating system collects diagnostic information about what the app was doing at the time of crash. One of the most important parts of the crash report are the thread backtraces, reported as hexadecimal addresses. You translate these thread backtraces into readable function names and line numbers in source code, a process called symbolication, then use that information to understand why your app crashed. In many cases, the Crashes organizer in Xcode automatically symbolicate the crash reports for you.

Sadly, since this is a macOS app that I distribute myself, I need to do some of this symbolication myself. To do so, I need to identify where the crash occured. Here’s an example of a thread that crashed.

Thread 2 Crashed::  Dispatch queue: com.apple.root.user-initiated-qos
0   libswiftDispatch.dylib        	    0x7ff82aa3ab8c static OS_dispatch_source.makeProcessSource(identifier:eventMask:queue:) + 28
1   PHP Monitor                   	       0x1096907d8 0x10965e000 + 206808
                                                |            |
                                             address      load address
2   PHP Monitor                   	       0x1096903ac 0x10965e000 + 205740
3   PHP Monitor                   	       0x10968f88b 0x10965e000 + 202891

After this, I retrieve the DSYM from the archive. I need to know what version is crashing, but fortunately that information is at the top of the log, assuming you open the .ips file with Console.app.

For example, here’s a crash report I received earlier this year:

-------------------------------------
Translated Report (Full Report Below)
-------------------------------------

Process:               PHP Monitor [25232]
Path:                  /Applications/PHP Monitor.app/Contents/MacOS/PHP Monitor
Identifier:            com.nicoverbruggen.phpmon
Version:               4.1 (135)
Code Type:             ARM-64 (Native)
Parent Process:        launchd [1]
User ID:               501

Once I know what build is affected, I can retrieve the DSYM (I get it from the archive that corresponds to the build that crashed) and symbolicate to find out what exactly went wrong.

Once I have the DSYM in a known location, I reference it when I use atos:

$ atos -arch x86_64 -o '/path/to/PHP Monitor.app.dSYM/Contents/Resources/DWARF/PHP Monitor' -l 0x10965e000 0x1096907d8
             |                                           |                                       |              |
             architecture                                path to DSYM                         load address    address

The output of the command above in question was:

FSWatcher.startMonitoring(_:behaviour:) (in PHP Monitor) (PhpConfigWatcher.swift:95)

As you can see, this particular crash was caused by the PHP config watcher. This bug was caused by a threading issue, which I ended up resolving after figuring out how to symbolicate this crash.

I’ve also documented how to manually symbolicate crashes in the project’s DEVELOPER.md file.

Tagged as: Programming