Debugging a runtime crash can be a painful process. When an iOS app crashes in the wild, the device only has information about the memory addresses and offsets of the running process at the time of the crash. This information is presented as a stack trace, a list of method calls in order of recent execution pointing to where the crash occurred. Stack traces can have varying levels of detail, and mechanisms are in place to help developers get the most out of these stack traces. 

However, a common misconception among security-focused engineers is that a hardened application breaks commonly used field debugging tools (e.g., Firebase Crashlytics), making understanding app crashes even more difficult. That doesn’t have to be the case; an application can have both strong security and actionable, relevant crash logs. So, how do we pull this off? 

To help get readable crash logs for iOS apps in the wild, developers can utilize dSYM files. dSYM files contain symbol information for debugging and can be generated by Xcode during the build process. We’ll review the contents of these files, why they’re useful, and how Digital.ai Application Security products integrate with this mechanism to make debugging your secure application possible. 

Let’s work through an example using our open-source Job Dispatcher iOS app.  

What’s Inside a dSYM Bundle? 

A dSYM (short for debug SYMbols) bundle contains symbols and source code line information about your iOS/tvOS/macOS application. These are generated by Xcode when ‘Debug Information Format’ is set to “DWARF with dSYM File” for the build configuration you’re using:

Now you might be asking: Why would we want to have all our symbols in a separate file instead of inside the app itself? The main reason is portability with debugging. You can build an app and debug it on another machine without needing the object files used to build it. dSYM also enables you to debug release versions of your application, which can be a huge time saver.  

Now that we’ve introduced what dSYM is–and a bit on why it’s important–let’s open the .xcarchive we just built and take a look at the ‘Job Dispatcher.app.dSYM’ bundle below.

Job Dispatcher has only one bundle for the main app, but more can be generated if your project builds additional libraries or frameworks during the build process. 

So, what’s actually inside the dSYM bundle? If we right-click the bundle and choose ‘Show Package Contents,’ we can see the following:

The ‘Info.plist’ file just contains metadata about the bundle itself and isn’t very useful. Both the ‘Job Dispatcher.yml’ file and the ‘Job Dispatcher’ MachO debug file contain symbol names and locations and are what we care about. Let’s first open up the ‘Job Dispatcher.yml’ file (picture truncated for brevity):

Here, we can see a list of properties for each symbol in the binary. For example, ‘size’ correlates to the size of the pointer (all of these are 64-bit symbols), ‘symName’ corresponds to the name of the symbol, ‘symBinAddr’ is where the symbol exists in the binary itself, and ‘symSize’ is the size of the function or data object. This file focuses on relocation information, which is a big part of the AArch64 architecture, but we won’t be diving into that here.  

Let’s now look at the file in the DWARF folder called ‘Job Dispatcher.’ This binary file contains detailed debug information for all the symbols in the application we built (as you can see, the name of the DWARF file and the app are the same). We can run the ‘dwarfdump’ command on it to get a readable dump of information within this file. Let’s look at the authenticate symbol:

A couple of notable properties are the DW_AT_decl_file and DW_AT_decl_line, which tell us that the authenticate function was defined in the ‘LoginFunc.m’ file on line 13. Another property is the DW_AT_type, which gives us the type of the function (which is a bool in this case). Each symbol has a section like this in the DWARF file with info explaining what it is and where it was defined, among other things. 

While the contents of the dSYM bundle can be pretty difficult to parse, it’s not something everybody needs to know to actually utilize them. We’re now going to look at what dSYM actually does for us when trying to debug a crash in real time. 

dSYM in Action 

Let’s watch the power of dSYM in action by forcing a crash in Job Dispatcher.  We can do that by assigning a value to a null pointer on lines 14 and 15 in the authenticate function:

When we run the application without an associated dSYM, the app crashes, and we get a stack trace like this:

Now, it’s certainly possible to debug an application using this information. Each stack frame has information on the address of the crash and the offsets from the (rather unhelpful) symbol name where the crash occurs. However, understanding this stack trace requires deep analysis of the binary itself and provides almost no insight into what part of the source code is causing the issue. Compare this result to a crash log with dSYM information available:

Each stack frame still contains the address where the app crashed, but the symbol information is much more complete. We can see the crash happened in the _authenticate function on line 15 of ‘LoginFunc.m’ (exactly where we induced the crash in our source code). Recreating this readable stack trace is the goal of every crash reporting tool (Firebase Crashlytics, BugSnag, Instabug, etc.), and that is why those tools require you to upload a dSYM bundle to achieve this. 

Where Does App Security Fit With All This? 

Everything we’ve discussed about dSYM so far has relied on the fact that the application we built with Xcode has remained completely unmodified. What happens if we want to protect our app from bad actors before we submit it to the app store? 

Well, during protection, the binary gets shuffled around such that the newly obfuscated symbol locations no longer match up with the info found in the dSYM bundle. If we try to symbolicate a crash on our protected app with the dSYM bundle of the unprotected application, the results won’t make any sense. Is there anything we can do to fix this? 

Post-Build vs. In-Build Protections 

Our App Protection for Mobile: ARM product is a post-build protection solution that can be used without any configuration at all. This makes securing an application from threat actors easier, and developers can have a better feedback loop when testing their protected app because it only needs to be built once. However, as mentioned before, this comes with the caveat that the original dSYM bundle is incompatible with the protected application. Developers must reauthor the dSYM files after protection to account for code changes and symbolicate potential crashes. 

On the other hand, we can protect an application without needing to reauthor the dSYM files that Xcode produces. Our App Protection for Apple Native product has this capability because it is an in-build protection solution. The app is compiled and linked with various security features enabled alongside the original source code. This makes symbolication of a protected app possible since the generated dSYM files are structured to account for everything specified in the build. The downside is that build-time protections require more configuration on the developer’s part, which can get more challenging as their application gets larger and more complicated. 

Wrapping Things Up 

Hopefully, this gave some insight into the importance of dSYM bundles, what’s inside, how it’s used, and the solutions that exist to ensure that security-focused engineers do not have to make any sacrifices to utilize tools that make debugging iOS applications easy. 

demo placeholder jungle

Author

Dan Shugrue, Troy Clendenen, and Michael Crabill

See Digital.ai Application Security in Action

Explore

What's New In The World of Digital.ai

February 11, 2025

Better Together: Unlocking Endless Possibilities For Our Customers

This Valentine’s Day, join us in celebrating the unique stories that make our Digital.ai customers special!

Learn More
January 6, 2025

Guide to Threat Monitoring: Protect Apps Against Threats

Discover the essentials of threat monitoring, from key components to advanced techniques. Stay ahead of cyber threats with our comprehensive guide.

Learn More
December 17, 2024

Guide to Android Application Security

Gain a comprehensive understanding of Android app security, including common threats, best practices, and essential tools to protect your mobile applications.

Learn More