No code we write is flawless, but when bugs in Apple’s code leads to our app crashing, we typically cannot do much other than file a radar. However, Sash Zats demonstrates in this talk how we, developers with no access to Apple’s code, can use swizzling and patching to fix bugs in those private frameworks using Swift.
Since most frameworks we use are still in Objective-C, we can swizzle methods or add to their original implementation to fix issues. Sash goes over how to find bugs when there’s no access to the source code, demonstrates handy tools to reverse-engineer others’ code and add our own code to it, and even explains how to patch C functions using function pointers with Swift.
Introduction (0:00)
Hi! I’m Sash and I’d like to discuss how to deal with proprietary codebases and how to fix bugs and crashes in third-party code.
A bit about me: for the past two years, I had very long hair until last weekend before this talk was given when I shaved my hair and face - a traumatic experience. Five years ago, I moved to Israel from Russia, and about five months ago I moved to the States. As you might have guessed, I do like changes, and I enjoy trying out new things.
Swizzling With Swift (0:55)
About a year ago upon Swift’s release, I was excited to play with Apple’s new language. It has many amazing features and new things to experiment with along with new tools, but let’s focus on Swift’s background first. If we look closely, we’ll find that it’s somewhat familiar, as it deals with Cocoa and Cocoa Touch, depending on the platform. It means that we have to learn how to deal with these Objective-C frameworks while using Swift.
If we ask ourselves how many first-party frameworks are written in Swift by Apple, I wrote a simple Ruby script for that:
#!/usr/bin/ruby
developers_dir = `xcode-select -p`.chomp
frameworks_dir = "#{developers_dir}/Platforms/iPhoneSimulator.platform"
frameworks_dir += "/Developer/SDKs/iPhoneSimulator.sdk/System/Library/Frameworks/*"
swift_frameworks_count = 0
Dir[frameworks_dir].each { |file|
framework = "#{file}/#{File.basename(file, ".*")}"
next unless File.exist?(framework)
puts framework unless `otool -l #{framework} | grep -i swift`.empty?
}
If we run the script against the system frameworks folder, we’ll see the staggering grand total of… Zero, absolute zero. We therefore have to learn how to coexist with Objective-C frameworks when using Swift. Why would we care about that? Because our users don’t. When your app crashes, your users really don’t care who’s code was responsible for that, whether it’s Apple’s fault or yours; they don’t know how to file radars. Therefore, to assure good quality, we have to figure out how to work around issues in proprietary code, as I will go over.
Swizzling Objective-C Methods (Demo) (2:35)
I will demo an application called UIKitty to explain this point. The app is similar to Instagram, but it’s for sharing just one picture of a cat with your friends.
Side-note: I wrote an Xcode plugin that allows you to increase the font size using ⌘ +
.
In the demo app, the “Print” iOS Share Sheet appears incorrectly because its view is not sized right. My patch fixes this, and the method I used could be applied to more useful bug fixes.
Let me walk through the solution and how we achieved it. Basically, it might look a bit scary, but I will just walk through it line by line.
if let cls = NSClassFromString("UIPrinterSearchingView") {
let block: @objc_block (AspectInfo) -> Void = { (aspectInfo) in
if let view = aspectInfo.instance() as? UIView {
view.frame.size.height = view.superview!.frame.height - 44
}
}
let blockObject = unsafeBitCast(block, AnyObject.self)
cls.aspect_hookSelector(
Selector("layoutSubviews"),
withOptions: .PositionAfter,
usingBlock: blockObject,
error: nil
)
}
First thing, with visual debugger, we can understand which class is actually behind this misaligned view: UIPrinterSearchingView
. Here in the first line, I get this class at runtime. After that I’m basically using a technology called swizzling. Swizzling allows you, the developer without direct access to the framework’s code, to replace methods at runtime with your custom code. In this case, we define that we want this particular block of code to be called every single time layoutSubviews()
is triggered.
As far as the framework itself is concerned, you didn’t change anything at all. UIKit
would call layoutSubviews()
the same way as it used to call it in the layout pass. But in this particular case, after it’s done executing, it would also invoke our methods. Again, in this particular case, it is a very dumb fix because they didn’t account for the height of the status bar, so I just decided to hardcode it to 44 points.
That actually did the trick. Depending on your particular problem, you might have more involved fixes. Also, sometimes you may not even want to call to the original implementation, but replace it altogether.
To swizzle layoutSubviews()
, I use a library called Aspects. The difference between this library and vanilla swizzling is that this one allows you to apply particular methods across all the classes. This comes in handy when you would want to add analytics to all your viewWillAppear()
methods in the entire project, for instance, including Apple’s views.
To do so, you just add one line of code, and define when you want it to happen. In our example, viewWillAppear()
is going to be called, and right before the original implementation would be called, you get to execute your code. Then you could log the screen name and any interesting information.
Swizzling Notes (8:34)
In our UIKitty
demo, swizzling is still possible in Swift despite the optimization that Swift employs. This is because it doesn’t optimize code that is running inside of pre-compiled frameworks. Because all the frameworks that we’re working with, all of UIKit
, is still Objective-C, we can do these kinds of things and get away with them. A framework calling into itself is not going to be optimized out. Therefore, we can replace this implementation at runtime and be safe for now.
Furthermore, you should set up the applicable versions of iOS, OS X that you want to apply your patch for because there are some pretty dramatic transitions that we already witnessed in iOS 6 to iOS 7 that broke a lot of code. You probably want to make sure that you stick to only to minor versions and not apply your swizzled patch to a future version of the OS.
You should also test your patch on devices, as many, many frameworks are not only compiled differently, but even written differently between simulator and device.
Often times patches fixing simulator problems do not fix or are not applicable to the device, so you should not attempt testing swizzled methods only on the simulator.
Lastly, aspect-oriented programing is a concept for you to explore if you’re curious about it.
Patching C Functions (10:12)
Another example is found in an application my friend wrote called Organizer. The app addresses the problem of iPhoto stripping metadata information or messing with the dates in which photos were taken when importing many photos from an old camera.
Organizer allows you to tweak the meta information for the pictures. As he has been told, Apple might even use it for their internal work. It uses Photos.framework, maybe you’ve heard of it. It was introduced one version ago, and is a designated, convenient way to work with photo library.
A neat feature of this framework is that it allows observation of changes of the photo library, and it feeds you animated changes for your UI. For example, if a picture was removed outside of your app, you want to animate this change in your grid of pictures. This framework allows you to do that, much more nicely than just calling reloadData()
.
In this demo, I show a sample project downloaded directly from Apple’s website. It’s a simple app that that shows the photo library. The only modification I made to the original code is that it observes the changes to my library and animates them in and out. When I actually tap on any of the pictures, the app changes the modification date of the photo to the current time, and triggers the sorting animation.
I can then see the changes happening on demand and not when someone is going to change the picture in the background. However, when we tap the picture, the app just crashes. This is the exact sample code that you can find on Apple’s website, except for the tapping part. And I promise that there are no bugs in tapping, that part works as intended.
Locating a Crash (Demo) (13:04)
Let’s try tracking this issue and fix it ourselves.
[Demo] - In the stack trace after the crash, the crash appears in a method that doesn’t not belong to us. Even though the top of the stack shows objc_msgSend
, the problem is almost never there but in the steps before it; objc_msgSend
is just the final breadcrumb.
However, beneath it are two unknown symbols that come from somewhere else, Apple’s code, which we will need to track down and fix.
Tip: to get a nice stack trace of the crash compared to what you can get sometimes with only console text, you want to enable the “All Exceptions” breakpoint. This will make sure that you stop not after the exception was thrown and nothing catches it, but at the moment the exception occurs. Then you will be able to go through the stack to see the state of your variables.
To track down the crashing symbols, we first attempt searching their names in Quick Open and hope that they will appear in public Apple headers. Here it was just pointing to a particular class inside of the Photos.framework, a lucky situation.
However, if you get no results at all, I suggest searching on GitHub. There are many repositories (such as this one) that have dumped iOS private internal headers. You may then type the name of the class or method and it will show you where in Apple’s frameworks it comes from. This is a nicer way than doing what’s called strings by grab on all the frameworks. Definitely something to take into account.
Disassembling with Hopper (Demo) (16:15)
By now we know that this crash happened in Photos framework. What are we going to do with that? How are we going to figure out what actually crashed?
Hopper Disassembler is an incredibly handy tool for this case. If you’re just curious about how iOS or OS X work, fire up this tool and open the framework. It allows you to disassemble the compiled code, and it shows in a very nice way something close to the original code.
Using Hopper we can load the framework into it, find the infringing class, and search for the method that crashed. It displays assembly, but using the pseudocode button we can see a mixture of Objective-C and assembly and try to decipher the issue and trace it down. Sometimes this shows straightforward problems, but assembly knowledge helps. The actual problem, such as this one, could be tough to track down and understand.
The app apparently crashes on a static C function with 8 parameters, fun:
diffArrays(var_24, eax, edi->_changedItems, nil, nil, nil, nil, nil);
void diffArrays(NSArray <NSManagedObject *> *arg0,
NSArray <NSManagedObject *> *arg1,
NSArray <NSManagedObject *> *arg2,
NSIndexSet **arg3,
NSIndexSet **arg4,
NSIndexSet **arg5,
NSArray <NSManagedObject *> **arg6,
NSIndexSet **arg7);
It takes all the managed objects which represent your photos and index sets that represent the mutation such as insertion, deletion, and updates.
By the end of it of the function, it’s supposed to return to you what changed, and that’s exactly what I start with. That framework allows you to observe the changes and to animate them in your UI, and this function is at its core.
However, because we’re dealing with a static C function, there’s no way we can swizzle it. The solution that we had in the previous example simply will not work because there is no class to swizzle this function against.
Our patch below solves this in a different way. Basically, we have to reach into the internal implementations of Swift (which is a fragile solution in itself), and then access the address of the actual problematic function.
Fixing a Crash (19:14)
The easiest part is just to figure out how to work around these issues once we found them. The implementation of the function itself was so tough to follow that I gave up at a certain point. However, I noticed that what I’m getting back was a typical bad pointer to deallocated memory but someone was still trying to access it.
To patch this, I ended up doing over-retaining one of the parameters that was being returned. When my friend showed this solution to the Q&A engineer at Apple, he commented that it will leak 8 bytes. However, it’s not going to crash - a much better result.
Fixing these types of issues will always be a balance between trying to figure out how does it work, trying not to sacrifice too much memory, and keeping users happy as this is core functionality of the app. If app crashes every single time you change the date, no one will be happy.
Patching C with Swift (21:02)
Next we will review the details of the patch piece by piece.
// Internal structures
struct swift_func_wrapper {
var trampolinePtr: UnsafeMutablePointer<uintptr_t>
var functionObject: UnsafeMutablePointer<swift_func_object>
}
struct swift_func_object {
var original_type_ptr: UnsafeMutablePointer<uintptr_t>
var unknown: UnsafeMutablePointer<UInt64>
var address: uintptr_t
var selfPtr: UnsafeMutablePointer<uintptr_t>
}
This is how Swift represents functions internally up to this version (2.0). If you want to get to the guts of the functions inside of Swift, you have to cut this structure and to reach inside to get the address. This is the process necessary to access Swift functions from C and C functions from Swift, in which we access the address of the function.
Next, we will define a simple function example function to show how to call it and hook into the failing function. In the real world, this would implement the retaining of the argument and have 8 arguments corresponding to the C function. For the example, we’ll keep it simple with a string log:
// Method we want to call
func hello(world: String) -> Void {
print("Hello, \(world)")
}
typedef helloFn = (String) -> Void
We then get a pointer to the actual C function behind the Swift function:
// C function pointer
let fn = UnsafeMutablePointer<helloFn>.alloc(1)
fn.initialize(hello)
let fnWrapper = UnsafeMutablePointer<swift_func_wrapper>(fn)
let opaque = COpaquePointer(bitPattern: fnWrapper.memory.functionObject.memory.address)
let cFunction = CFunctionPointer<helloFn>(opaque)
Here we start with our Swift function, and then by reaching into the implementation details of the structures that represents Swift functions, we’ll get the address. Next we’ll actually get the C function pointer, which we can pass to the C side and call from there, and it’s just going to be fine.
Another issue I’ve yet to mention is how to call the original implementation. In this case, I had no desire to figure out how to re-implement the function, so we have to call the original implementation, and retain the returning object on top. This is a problem with Swift 1.2: you cannot call C functions just through their pointer. To be clear, you can call C functions from Swift, but you cannot obtain pointers to functions using other tools and call the function through its pointer like you would do in Objective-C.
As a result, this part has to be implemented in Objective-C. I am not proud about it, but that’s what we have to do for now.
Conclusion and Tips (24:30)
First, you have to figure out how to work around the bug, and then you have to apply your patch to it using the tools described.
The actual engine that does that patching once you wrote the solution is Fishhook. It’s the library that allows you to take any C function while you’re inside of the Objective-C runtime, and essentially swizzle it.
Once again, anyone who would call this function would get your implementation, and you can get the original implementation inside of your patch. That’s where Swift falls short. However, from what I understood in Swift 2.0, you have @convention(c)
which potentially might allow us to do that do that directly in Swift.
Fixing Problems in Others’ Frameworks (25:28)
If your problem is in Objective-C, swizzle away.
If your problem is with C functions, it gets a bit uglier because you have to add Objective-C code into your project to work around it. Swift is pretty bad at this, as we saw. Because of Swift’s optimization, you are not guaranteed to go through the same dynamic dispatch that you will go through when you have no optimization at all. Therefore you have no chance of replacing methods at runtime, which is a good thing.
It brings us all the speed and niceness of Swift that we like, but at the same time it prevents us from doing those kind of patching solutions.
Credits, Further Readings, and Resources (26:19)
-
Perceptual Debugging by Kendall Gelner: a recommended AltConf session that discusses the process of developing debugging intuition - the most important part. The rest of patching is just the technology, but you have to understand how to find the problem, and discover how to patch it. This takes time to learn and become proficient at.
-
Reverse Engineering by Samantha Marshall: an informative compilation of links on reverse-engineering of tools, technologies, and more.
-
Unsafe Swift: For Fun & Profit by Russ Bishop: thorough discussion of the interaction between C and Swift. It covers everything I briefly touched on in the subject, including the structures, safe pointers and more. I highly recommended watching this talk, it’s a lot of fun.
-
Peter Steinberger’s blog by the man himself: fantastic resource as he patches
UIKit
like crazy as part of his job working on PSPDFKit to perfect it. -
WWDC debugging sessions: they will make you a better human being. Just watch them.
-
Fishhook: a library that helps swizzle C methods.
-
Aspects: a great library against by Peter, which helps apply some sort of cross-section functionality across all your app using the methods in the first example.
Q&A (29:09)
Q: Back in the days there was a tool that could spit out load maps based on an address in the stack that points to it’s location in the code based on function offsets. What’s an iOS equivalent of that, using LLVM?
Sash: I know that you may pause using LLVM during runtime, but you only land somewhere in the run loop and have to investigate from there. I’m unfamiliar with the specific tool to achieve this more easily.
Audience member (@nevyn): This can be achieve by simply using otool
or nm
- I can’t recall which of those - to get the list of load commands for your binary.
Receive news and updates from Realm straight to your inbox