Building apps all goes well in the beginning. You’re developing an app from scratch; it’s stable and runs perfectly fine. After releasing the first version, you get your first insights into crashes, from which one marks an EXC_BAD_ACCESS error. At this point, it’s time to start your journey into solving the crash.
The first challenge is to reproduce the crash, which can be tricky. Once you can do that, it’s still a challenge to find out the cause and the fix.
What does EXC_BAD_ACCESS mean?
EXC_BAD_ACCESS is an exception raised as a result of accessing bad memory. We’re constantly working with pointers to memory in Swift that link to a specific memory address. An application will crash whenever we try to access a pointer that is invalid or no longer exists. Such a pointer is also known as a “dangling pointer.”
The root cause of such bad memory can differ. The referencing memory could be deleted or deallocated while the value of the pointer never gets updated. The pointer still points to the memory location of the deallocated memory and becomes a dangling pointer. Another cause could be writing to read-only memory or jumping to an instruction at an invalid address.
Breaking down the error name
When breaking down the error name, we have two components:
- EXC: The kernel sends an ‘exception’
- BAD ACESS: Your app is trying to access a block of memory which it can’t
Types of EXC_BAD_ACCESS crashes
EXC_BAD_ACCESS crashes come with several kinds of Unix signals from which SIGSEGV and SIGBUS are most common. An example crash report could look as follows:
Exception Type: EXC_BAD_ACCESS (SIGSEGV)
Exception Subtype: KERN_INVALID_ADDRESS at 0x0000000000000000
The first line in the crash report tells us that we’re dealing with a bad access exception. Within the parenthesis, you can find the SIGSEGV subtype. The second line indicates the type of bad access together with the memory address.
Occasionally, you could encounter crash logs without the EXC_BAD_ACCESS key identifier, which is most common on macOS and looks as follows:
Exception Type: SIGSEGV
Exception Codes: SEGV_MAPERR at 0x34e5sffc2ad3
It’s best to look at other details of the crash logs to get an insight into where the crash occurred.
The SIGSEGV signal is a segmentation fault related to virtual memory addresses, and SIGBUS is a bus fault related to physical addresses. In most cases, you don’t need these details to solve the actual crash. Therefore, I won’t go into much detail about these Unix signals.
How to solve bad access exceptions
An EXC_BAD_ACCESS error in Swift is often causing frustration as it might not directly be clear what the cause of the crash is. However, if you know the possible reasons, it’s pretty doable to quickly get to the root cause of the bad access exception.
If you don’t know how to reproduce the crash
A crash is pretty hard to fix if you don’t know how to reproduce it as you’re unable to verify that you’ve solved the issue. You often have a particular idea of where the crash occurs, but you cannot find the same crash in your app.
Make sure to run the same configuration as the user that experienced the crash. Install the correct app version on the same OS and device if possible. Doing so at least excludes that the crash is not reproducible due to mismatching configuration.
If you’re still unable to reproduce the crash, try out one of the following things:
- Look at the crash breadcrumbs, for example, in Firebase. They allow you to follow the exact same path as the user did to produce the crash.
- Contact one of your users that had the crash and ask for a Diagnostics report. Read more about it in my blog post 4 Tips to make it easier to fix crashes and bugs
- Watch the Understanding Crashes and Crash Logs session from WWDC 2018 and figure out the cause
Using the Address Sanitizer
Once you can reproduce the crash locally, it’s time to run a few checks to verify that your code does not contain any memory issues.
The Address Sanitizer, also known as ASan, finds memory corruptions and other memory errors at runtime. Apple even recommends using the ASan continuously during development:
Memory issues are frequently the cause of security vulnerabilities. You are strongly encouraged to incorporate the Address Sanitizer as part of your software qualification process.
You can enable the Address Sanitizer in the scheme settings of your app under diagnostics:
Once enabled, you can start reproducing the crash again. The Address Sanitizer will pause the app whenever it detects a memory issue. See whether it finds any memory issues, solve them, and verify whether the EXC_BAD_ACCESS is still occurring.
Bad access due to race conditions
Data races could be another cause of bad memory access. Therefore, it’s worth verifying that your app is not containing any threading issues. You can use the Thread Sanitizer for this, which I explain in detail in my blog post Thread Sanitizer explained: Data Races in Swift.
How about Zombie Objects?
Developers that have experience with Objective-C and bad access exceptions might think of Zombie objects to solve the memory issue. You can enable Zombie Objects from the same diagnostics settings as we’ve used before to enable the Address Sanitizer. It keeps deallocated objects alive, which can help find the root cause of a memory issue related to accessing deallocated instances.
Swift, compared to Objective-C, does a lot more to prevent memory issues from occurring. It’s more likely that you run into crashes like “Attempted to read an unowned reference” than into a bad access exception like you would in Objective-C for accessing deallocated instances. For those cases, you would enable Zombie Objects so you could still read the memory address of the deallocated memory that your code accessed.
If you’re using unmanaged and unsafe pointers in Swift, it’s more likely that you’ll run into memory-related crashes. Interfering with Objective-C frameworks could also cause these crashes to occur more often. If so, try to reproduce the crash again with Zombie Objects enabled and see whether it gives you more information to fix the crash.
Although the EXC_BAD_ACCESS is occurring a lot less in Swift than in Objective-C, it’s still a common crash. The exception is related to bad accessing memory and comes with several kinds of subtypes and Unix signals helping you identify the root cause. Hopefully, you’re able to solve those issues a lot easier now after reading this blog post.
If you like to improve your Swift knowledge even more, check out the Swift category page. Feel free to contact me or tweet me on Twitter if you have any additional tips or feedback.