Horse Drawn Games is a small team of developers committed to creating exceptional digital games and interactive experiences. In addition to producing our own original content, we also offer services for those building their own digital products.

Wow it’s been a while since I updated the blog! I think we’re due for a web site refresh too. But first let’s talk about call stacks!

Let’s say your iOS game crashes outside of the debugger. Assuming your device is plugged into your mac, you can get the device log from Xcode by bringing up the “Devices” window via Window -> Devices. You should see a live updated log and you might be able to find your crash details in among the spammy output. But even better – if you click on “View Device Logs” you’ll see a list of apps that have crashed.

In this example, Resynth (a game I’ve been working on for my new company Polyphonic LP) has crashed a couple of times, and I’ve selected one of the crashes.

Why did it crash? Luckily we have the full call stack. A call stack is simply a list of functions that are currently being executed by the CPU. In this case, the call stack looks like this:

There isn’t much symbol information; the only thing we can really see is that the NSLog function was running. But what called NSLog and what caused it to crash?

Fortunately there are several tools we can use to decode this. I’m going to cover one of them today: atos. atos converts memory addresses to symbol names, and it comes with macOS and lives in /usr/bin so it should already be in your path. It takes a couple of parameters:

We need to supply the architecture of our binary, the load address (the base address in memory where the executable was loaded), the path to a version of our binary with full debug information present, and the list of call stack addresses that we wish to translate into symbols.

If you have archived your game from Xcode, the full debug executable can be found inside the archive, in dSYMs/resynth.app.dSYM/Contents/Resources/DWARF.

Our architecture is arm64 (unless you’re running arm7 which is unlikely these days).

For this crash our load address is 0x10004c000. The load address could be anything and won’t always be the same. Sometimes the load address might not be present, and the call stack lines might look something like this:

9 resynth 0x000000010056ac28 resynth + 5368872

This can happen if you get your crash information from the device log view in Xcode instead of from the specific application crash view.

Here 0x000000010056ac28 is the real memory address where this particular function was loaded, and 5368872 is the offset of the function from the load address. We can therefore easily calculate the load address; it’s just 0x000000010056ac28 - 5368872 which is 0x10004C000.

Editing live on the device has never been easier, for fast iteration time and the ultimate in “what you see is what you get”. For more info check out this video, the forum thread or just read some of the other entries on this blog!