When the odds are stacked against you, your mind is overflowing, and you are ready to just pop, there’s always practical debugging tips to help you through a cloudy day.
In this post I’ll take you through a debugging session where I reproduce a crash, for which we were receiving a bunch of crash reports, but I was unable to reproduce by just using the application.
It will cover the following topics:
- Narrow down the breakpoint to the method invocation where the crash occurs.
- Locate the exact instruction that causes the crash.
- Look at the implementation of the method where the crash occurs.
- Simulate the crash.
The crash report
1 2 3 4 5 6 7 8 9 10 11 12 13 14
This crash report was shortened for clarity sake, you can find the full report here.
Now, this might not be the toughest nut to crack –if you’ve been doing UIKit development for a while, you may already
UIScrollView does not weakify it’s
delegate– but instead of just going by experience and making some
changes, let’s see if we can’t figure out exactly what’s happening, for the sake of reproducing and confidently making
the right fix.
The lines near the top of the stack trace tell me that it’s probably a message being sent to some garbage memory, i.e. a released object, so that’s where I want to be poking around.
Getting at often called locations
So I want to get at the 2nd frame in the stack, but that method and the one at the 3rd frame get invoked a lot while navigating to the view I want to debug. There’s many ways to do this, but the simple approach I often take in these cases is to set a breakpoint for the last frame in the stack that is unique to the location that I want to get at and then keep refining the breakpoints every time I hit one.
In this case that starts off with a breakpoint in our code:
With that set I then navigate to the view I want to get at and, once the breakpoint is hit, set the breakpoint for a frame that’s even closer to the location I want to get at:
1 2 3 4 5 6
And finally I repeat the process and set the breakpoint that I really want to get to:
Locating the instruction that crashes by looking at the real on-device framework (iPhoneOS SDK)
By this point, I’m left with the realization that I don’t have a device running iOS 8.1.x anymore –the above was all on the simulator– and thus jumping through the code in a debugger on a device is not going to be reliable. Instead, I’m going to take a look at the disassembly and (pseudo) decompiled code in Hopper –a tool I highly suggest you go and buy right now, it’s ridiculously cheap for the amount of time it will save you–.
To be able to do so, though, I first had to get a copy of UIKit for one of the devices of which we received crash logs.
Firmware decryption keys: keys for many variants are listed here. If the model you need is not listed you’ll have to manually figure out the key, which is beyond the scope of this article.
Download firmware: you can find links for all variants on this page. I chose iOS 8.1.2 for the 2nd revision of the iPhone 5 (iPhone 5,2), because the keys to decrypt that are known and it’s one of the devices and OS versions for which we had received crash reports.
Decrypt image: there are a bunch of tools that allow you to decrypt firmware images, which are listed here. I’m using xpwn’s ‘dmg’ tool) which you can get from planetbeing’s GitHub repo. Once you’ve got the key from here, or have otherwise manually figured it out, you can decrypt the disk image like so:
1 2 3
- Extract UIKit from shared DYLD cache: for performance reasons, Apple decided to create one big cache that contains all of the commonly used frameworks, including UIKit. To get just UIKit, you’ll need to use any of the tools listed here, I used dyld_decache:
1 2 3 4
With that out of the way, I can finally load that up in Hopper and look at the instruction. I can get the offset of
the instruction from the stack frame, specifically the ‘66’ in
-[UIScrollView _getDelegateZoomView] + 66. This means that the instruction’s address is that of the function it is
located in plus 66, which, as you can see in the below screenshot, is halfway through the