I'm essentially looking for a way to detect when/what third party libraries swizzle. I recently ran into a situation where an ad library used an oddball fork of AFNetworking. AFNetworking swizzles NSURLSessionTask, and the two swizzles didn't play nicely under certain circumstances. I'd really like to be able to detect and sanity check this kind of thing, and ideally even keep a versioned dump of every swizzled method in the app so we have some visibility into who's monkey patching what and what the risks are. Google and stack overflow search have turned up nothing but a bunch of tutorial on how to swizzle. Anybody run into this issue or have a solution? It looks like I might be able to code something up using objc/runtime.h but I can't imagine I'm the first person to need this.
Here's the closest I was able to get, with a few hours of tinkering. It involves using a fork of mach_override, a couple of DYLD quirks regarding load order, and a stomach for crazy hacks.
It will only work on the simulator, but that should suffice for the use case you seem to have (I certainly hope you don't have device-only dependencies).
The meat of the code looks something like this:
#include <objc/runtime.h>
#include <mach_override/mach_override.h>
// It is extremely important that we have DYLD run this constructor as soon as the binary loads. If we were to hook
// this at any other point (for example, another category on NSObject, in the main application), what could potentially
// happen is other `+load` implementations get invoked before we have a chance to hook `method_exchangeImplementation`,
// and we don't get to see those swizzles.
// It is also extremely important that this exists inside its own dylib, which will be loaded by DYLD before _main() is
// initialized. You must also make sure that this gets loaded BEFORE any other userland dylibs, which can be enforced by
// looking at the order of the 'link binary with libraries' phase.
__attribute__((constructor))
static void _hook_objc_runtime() {
kern_return_t err;
MACH_OVERRIDE(void, method_exchangeImplementations, (Method m1, Method m2), &err) {
printf("Exchanging implementations for method %s and %s.\n", sel_getName(method_getName(m1)), sel_getName(method_getName(m2)));
method_exchangeImplementations_reenter(m1, m2);
}
END_MACH_OVERRIDE(method_exchangeImplementations);
MACH_OVERRIDE(void, method_setImplementation, (Method method, IMP imp), &err) {
printf("Setting new implementation for method %s.\n", sel_getName(method_getName(method)));
method_setImplementation_reenter(method, imp);
}
END_MACH_OVERRIDE(method_setImplementation);
}
Which is surprisingly simple, and produces output like this:
Exchanging implementations for method description and custom_description.
There is no good way (without using a breakpoint and looking through the stack trace) to figure out which class is being swizzled, but for most things, just taking a peek at the selectors should give you a hint about where to go from there.
It appears to work with a couple of categories that I've created that swizzle during +load
, and from my reading of mach_override
and DYLD's internals, as long as you have your library load order properly setup, you can expect this to be initialized before any other user-land code, if you put it in it's own dynamic library.
Now, I can't vouch for safety of this, but it seems useful to keep around as a debugging tool, so I've published my example to github:
https://github.com/richardjrossiii/mach_override_example
It's MIT licensed, so feel free to use as you see fit.
+load
(barring dynamic swizzling libraries like OCMock and similar), it probably would be simpler to just use backtrace()
combined with dladdr()
to simply grab the class & category that invoked method_exchangeImplementation
:) –
Theophylline © 2022 - 2024 — McMap. All rights reserved.
method_exchangeImplementation
/method_setImplementation
, although you'd need to ensure that you don't use that code path during release builds. – Theophylline