MobileSubstrate and Fishhook based objc_msgSend hook for debugging/inspection purposes.
Based on itrace by emeau, AspectiveC by saurik, and Subjective-C by kennytm.
Logs output to /var/mobile/Documents/InspectiveC or /var/mobile/Containers/Data/Application/<App-Hex>/Documents/InspectiveC (sandbox). Inside the InspectiveC folder, you'll find <exe>/<pid>_<tid>.log.
You can download the deb from the stable_debs folder or from my repo.
Description:
This is an inspection tool that you can use to log Objective-C message hierarchies. It can currently watch specific objects, all objects of a given class, and specific selectors. It is indeed compatible with arm64 - in fact, it is more full-featured on arm64 as arm32 has obj_msgSend[st|fp]ret which are currently not hooked.
Note that due to limitations with MobileSubstrate on iOS 10 and 11, you must use Fishhook to
interpose objc_msgSend instead. To do this, build with USE_FISHHOOK=1
, i.e.
make package USE_FISHHOOK=1 FOR_RELEASE=1 install
.
Features:
- arm64 support (and arm32)
- Watch specific objects
- Watch instances of a specific class
- Watch specific selectors
- Prints arguments
Hopeful Features (in no particular order):
- Support logging blocks/replaced C functions
- Print retvals
- Optimizations
- Better multithreading performance
Example Output:
***-|SpringBoard@<0x15455d320> _run|***
+|NSAutoreleasePool alloc|
+|NSAutoreleasePool allocWithZone:| NULL
-|NSAutoreleasePool@<0x170442a00> init|
-|SpringBoard@<0x15455d320> _accessibilityInit|
-|SpringBoard@<0x15455d320> performSelector:withObject:afterDelay:| @selector(_accessibilitySetUpQuickSpeak) nil 1.5
+|NSArray arrayWithObject:| @"kCFRunLoopDefaultMode"
-|SpringBoard@<0x15455d320> performSelector:withObject:afterDelay:inModes:| @selector(_accessibilitySetUpQuickSpeak) nil 1.5 <__NSArrayI@0x174233560>
-|SpringBoard@<0x15455d320> _updateAccessibilitySettingsLoader|
+|NSBundle mainBundle|
-|NSBundle@<0x17009f310> bundleIdentifier|
-|__NSCFString@<0x1740557b0> isEqualToString:| @"com.apple.PreBoard"
-|__NSStackBlock__@<0x16fdb7608> copy|
+|CFPrefsSearchListSource withSearchListForIdentifier:container:perform:| 0x19819f3b0 NULL <__NSStackBlock__@0x16fdb7570>
+|NSNumber class|
-|__NSCFBoolean@<0x194d4ab70> isKindOfClass:| [NSNumber class]
-|__NSCFBoolean@<0x194d4ab70> boolValue|
-|__NSCFBoolean@<0x194d4ab70> release|
-|SpringBoard@<0x15455d320> _updateApplicationAccessibility|
+|NSBundle mainBundle|
-|NSBundle@<0x17009f310> bundleIdentifier|
-|__NSCFString@<0x1740557b0> isEqualToString:| @"com.apple.PreBoard"
-|__NSStackBlock__@<0x16fdb75f8> copy|
+|CFPrefsSearchListSource withSearchListForIdentifier:container:perform:| 0x19819f3b0 NULL <__NSStackBlock__@0x16fdb7560>
+|NSNumber class|
-|__NSCFNumber@<0xb000000000000003> isKindOfClass:| [NSNumber class]
-|__NSCFNumber@<0xb000000000000003> boolValue|
-|__NSCFNumber@<0xb000000000000003> release|
-|SpringBoard@<0x15455d320> _updateLargeTextNotification|...
Usage:
Properly install theos and grab yourself a copy of the iOS SDK. You may have to modify the Makefile (i.e. ARCHS or TARGET) and/or InspectiveC.mm. I compile this on my Mac with Clang - if you use anything different you may have some issues with the assembly code.
When you install the deb, you will find libinspectivec.dylib in /usr/lib. Copy this dylib into $THEOS/lib and then copy InspectiveC.h into $THEOS/include.
Option 0: Use InspectiveC with Cycript for maximum efficiency
Use Cycript to inject into a process, then paste a single line to load InspectiveC. The command is a compiled version of the InspectiveC.cy file - found in this repo in cycript/InspectiveC.compiled.cy.
Be sure to install Cycript on Cydia and replace "SpringBoard" in the first command with the name of the process that you want to inject into. Also, don't forget to respring/kill the app when you no longer want InspectiveC loaded.
// You can replace SpringBoard with whatever process name you want.
root# cycript -p SpringBoard
cy# intFunc=new Type("v").functionWith(int);objFunc=new Type("v").functionWith(id);classFunc=new Type("v").functionWith(Class);selFunc=new Type("v").functionWith(SEL);voidFunc=new Type("v").functionWith(new Type("v"));objSelFunc=new Type("v").functionWith(id,SEL);classSelFunc=new Type("v").functionWith(Class,SEL);handle=dlopen("/usr/lib/libinspectivec.dylib",RTLD_NOW);setMaximumRelativeLoggingDepth=intFunc(dlsym(handle,"InspectiveC_setMaximumRelativeLoggingDepth"));watchObject=objFunc(dlsym(handle,"InspectiveC_watchObject"));unwatchObject=objFunc(dlsym(handle,"InspectiveC_unwatchObject"));watchSelectorOnObject=objSelFunc(dlsym(handle,"InspectiveC_watchSelectorOnObject"));unwatchSelectorOnObject=objSelFunc(dlsym(handle,"InspectiveC_unwatchSelectorOnObject"));watchClass=classFunc(dlsym(handle,"InspectiveC_watchInstancesOfClass"));unwatchClass=classFunc(dlsym(handle,"InspectiveC_unwatchInstancesOfClass"));watchSelectorOnClass=classSelFunc(dlsym(handle,"InspectiveC_watchSelectorOnInstancesOfClass"));unwatchSelectorOnClass=classSelFunc(dlsym(handle,"InspectiveC_unwatchSelectorOnInstancesOfClass"));watchSelector=selFunc(dlsym(handle,"InspectiveC_watchSelector"));unwatchSelector=selFunc(dlsym(handle,"InspectiveC_unwatchSelector"));enableLogging=voidFunc(dlsym(handle,"InspectiveC_enableLogging"));disableLogging=voidFunc(dlsym(handle,"InspectiveC_disableLogging"));enableCompleteLogging=voidFunc(dlsym(handle,"InspectiveC_enableCompleteLogging"));disableCompleteLogging=voidFunc(dlsym(handle,"InspectiveC_disableCompleteLogging"))
// Now use your InspectiveC commands as if they were the ones in InspCWrapper.
// Use this command to limit the recursion when logging.
cy# setMaximumRelativeLoggingDepth(5)
cy# watchObject(choose(SBUIController)[0])
cy# unwatchObject(choose(SBUIController)[0])
cy# watchSelector(@selector(anySelectorYouWant))
cy# watchClass([AnyClassYouWant class])
Option 1: Use the InspectiveC Wrapper
Include InspCWrapper.m in your Tweak file. You should probably use a DEBUG guard.
#if INSPECTIVEC_DEBUG
#include "InspCWrapper.m"
#endif
Then use the following API:
// Set the maximum logging depth after a hit.
void setMaximumRelativeLoggingDepth(int depth);
// Watches/unwatches the specified object (all selectors).
// Objects will be automatically unwatched when they receive a -|dealloc| message.
void watchObject(id obj);
void unwatchObject(id obj);
// Watches/unwatches the specified selector on the object.
// Objects will be automatically unwatched when they receive a -|dealloc| message.
void watchSelectorOnObject(id obj, SEL _cmd);
void unwatchSelectorOnObject(id obj, SEL _cmd);
// Watches/unwatches instances of the specified class ONLY - will not watch subclass instances.
void watchClass(Class clazz);
void unwatchClass(Class clazz);
// Watches/unwatches the specified selector on instances of the specified class ONLY - will not
// watch subclass instances.
void watchSelectorOnClass(Class clazz, SEL _cmd);
void unwatchSelectorOnClass(Class clazz, SEL _cmd);
// Watches/unwatches the specified selector.
void watchSelector(SEL _cmd);
void unwatchSelector(SEL _cmd);
// Enables/disables logging for the current thread.
void enableLogging();
void disableLogging();
// Enables/disables logging every message for the current thread.
void enableCompleteLogging();
void disableCompleteLogging();
Option 2: Link directly against InspectiveC
Add the following line to your makefile:
<YOUR_TWEAK_NAME>_LIBRARIES = inspectivec
This will automatically load InspectiveC in your tweak (whatever process your tweak injects into). Then include InspectiveC.h in your tweak and use those functions.
InspectiveC.h headlines the following API:
// Set the maximum logging depth after a hit.
void InspectiveC_setMaximumRelativeLoggingDepth(int depth);
// Watches/unwatches the specified object (all selectors).
// Objects will be automatically unwatched when they receive a -|dealloc| message.
void InspectiveC_watchObject(id obj);
void InspectiveC_unwatchObject(id obj);
// Watches/unwatches the specified selector on the object.
// Objects will be automatically unwatched when they receive a -|dealloc| message.
void InspectiveC_watchSelectorOnObject(id obj, SEL _cmd);
void InspectiveC_unwatchSelectorOnObject(id obj, SEL _cmd);
// Watches/unwatches instances of the specified class ONLY - will not watch subclass instances.
void InspectiveC_watchInstancesOfClass(Class clazz);
void InspectiveC_unwatchInstancesOfClass(Class clazz);
// Watches/unwatches the specified selector on instances of the specified class ONLY - will not
// watch subclass instances.
void InspectiveC_watchSelectorOnInstancesOfClass(Class clazz, SEL _cmd);
void InspectiveC_unwatchSelectorOnInstancesOfClass(Class clazz, SEL _cmd);
// Watches/unwatches the specified selector.
void InspectiveC_watchSelector(SEL _cmd);
void InspectiveC_unwatchSelector(SEL _cmd);
// Enables/disables logging for the current thread.
void InspectiveC_enableLogging();
void InspectiveC_disableLogging();
// Enables/disables logging every message for the current thread.
void InspectiveC_enableCompleteLogging();
void InspectiveC_disableCompleteLogging();