Building Your Own Crash Report in Swift. Think Twice Before Doing It.

Gabriel Marson
The Startup
Published in
5 min readDec 1, 2020
Photo by Iva Rajović on Unsplash

I am writing this article in the hope to help someone convince their bosses that building a crash report for iOS is far from trivial. It’s also important to state that the following lines are entirely my opinion about this subject, so if at the end of this text you still want to do it, go for it 🤘!

Although it appears to be simple and straightforward, building a crash report will require a lot of knowledge in subjects that are not quite easy to grasp, such as threading, concurrency and memory management. Also, you, almost certainly, will have to code at low level to have access to system functions, which means, C or C++.

But before writing about these topics let’s begin with the simple stuff.

Intercepting NSExceptions

This is the one thing that I have found to be simple when the subject is crash report in iOS. If the goal is to only log exceptions in runtime and there is no appropriate catch for an exception, this code will do the job. Paste it inside the didFinishLaunchingWithOptions method located in AppDelegate.swift file.

NSSetUncaughtExceptionHandler { exception in   print(exception)}

For some developers, logging these exceptions will be enough. Unfortunately, it wasn't the case for me… 😢

Now it gets complicated

So what is the problem with the code above? The thing is that Swift doesn't use exceptions(objective-c does), it use Errors. And because of that, our code won’t be capable to handle any kind of the thrown errors. So it will log basically nothing.

That was the moment I realized that to build a more capable crash report, we must sweep throughout the stack trace, find the information we want and translate them to something that developers would actually understand. That’s because the data contained in the stack trace will often tell us what happened but not why it happened. And this brings us to our problems:

Your code will run on a crashed process

It is extremely unsafe and unreliable to run code in this scenario because the process is in an undefined state. Some risks could be minimized by making your code tied to system implementation details. Even so, it’s not 100% guaranteed that your code will run without problems because, if Apple decides to change something in its OS, then the crash reporter, or at least some part of it, would have to be rewritten.

Handling signals

At some point of the code, we must implement a way to handle the signals in the stack trace to catch the crash. I've found out that there are two ways of doing that:

  • UNIX Signals: Since there are more developers that are more familiar to Unix Signals, you will find more stuff googling about this. I suggest starting from this one, but be aware that there are some events of crash in swift that are not directly translated to UNIX Signals.
  • Mach Exception Handler: This is the default error handling mechanism used by Apple crash reporter. You might even think that using this is a good idea, but this mechanism is quite complex to understand.

We also have to be very careful about how we scan memory addresses by looking for these signals.

Reading Memory

As well pointed by this thread, we might scan a memory address that we shouldn't and end in an undefined state that will cause unexpected behavior. It’s reasonable to assume(for the sake of minimizing the risks), that our signal handler can safely access the following types of memory.

  • Its code
  • Its stack
  • Its arguments
  • Immutable global state

This caution about memory reading is important because in case something unpredicted happens, it not only will affect our own code but will also, most likely, prevent apple crash reporter from working properly.

Apple Crash Report

Like I said, it is very crucial that, by building a reading memory algorithm, we pay attention to not break Apple's own crash report. If we scan a bad memory address caused by reading a corrupted section of memory, then we must exit() our code and let the default crash reporter do the job.

And this is just one problem regarding the crash reporter. Let's not even mention that we might end up in significant privacy implications if we do not follow apple guidelines about privacy data.

Symbolication and Stack Unwinding

Let’s imagine that we managed to successfully handle all of the problems listed above. After a sweep through the stack trace we will find information that can't be understood. And why this happens? Because we will find the addresses related to crash and not a string describing what happened.

In order to get the actual function calls we need to apply Symbolication to the addresses. I forgot to mention that there are other stuff besides functions calls on the stack trace, so some filtering would have to be done. This whole process is called stack unwinding. The following image taken from this lecture illustrates quite well the hole process.

And these are not all of the problems 😞

I've listed just the ones that I thought to be more relevant. If you keep digging, it’s guaranteed that you will find more problems.

What's the verdict? Should I build my own crash report?

I am not saying it is impossible to build your own crash report. My personal opinion is, if you are a single person trying to achieve this, it does not worth the effort. You would be better off with some libraries that already do this job and are backed up by a lot of developers.

Anyway, I hope this article, at least, filled your curiosity about the challenges of building a capable crash report system. Most of the information contained here was gathered from these links so you should definitely take a look at them:

I will also leave some pieces of code that I’ve found across the internet and might come in handy if you want to study more about this subject.

--

--