├── .gitignore ├── Makefile ├── README.md ├── objc_instance_finder.h ├── objc_instance_finder.m └── test ├── Makefile └── test.m /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | obj/ 3 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | export TARGET=macosx:clang 2 | ARCHS = x86_64 3 | 4 | # Disable dpkg 5 | override PACKAGE_FORMAT := none 6 | include $(THEOS)/makefiles/common.mk 7 | 8 | LIBRARY_NAME = objc_instance_finder 9 | objc_instance_finder_FILES = objc_instance_finder.m 10 | 11 | # This requires a modified version of theos 12 | objc_instance_finder_LINKAGE = static 13 | 14 | include $(THEOS_MAKE_PATH)/library.mk 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | objc_instance_finder 2 | ==================== 3 | 4 | Find instances of objc classes at runtime 5 | 6 | Provides one function with the following declaration: 7 | ``` 8 | NSHashTable *find_instances_of_class(Class class, BOOL include_subclasses); 9 | ``` 10 | 11 | First parameter should be an Objective-C class for which you would like to find all instances of. 12 | 13 | The second parameter specifies wether you want to include recursively subclasses of the class. 14 | 15 | Returns an NSHashTable instance with weak references to all the found instances. 16 | 17 | Example usage 18 | ------------- 19 | 20 | ``` 21 | @implemenation TestClass : NSObject @end 22 | @interface TestClass @end 23 | ``` 24 | 25 | ``` 26 | TestClass *obj1 = [TestClass new]; 27 | NSHashTable *instances = find_instances_of_class([TestClass class], NO); 28 | TestClass *obj2 = [TestClass new]; 29 | 30 | NSLog(@"%d, %d", [instances containsObject:obj1], [instances containsObject:obj2]); // 1, 0 31 | ``` 32 | 33 | Making 34 | ------ 35 | 36 | Requires a [modified version of theos](https://github.com/Tyilo/theos) to build a static library. 37 | The environment variable `THEOS` must be set to the path of theos. 38 | -------------------------------------------------------------------------------- /objc_instance_finder.h: -------------------------------------------------------------------------------- 1 | NSHashTable *find_instances_of_class(Class class, BOOL include_subclasses); 2 | -------------------------------------------------------------------------------- /objc_instance_finder.m: -------------------------------------------------------------------------------- 1 | #import 2 | #include 3 | #include 4 | #include 5 | 6 | Class *get_objc_class_list(int *count) { 7 | static Class *cache = NULL; 8 | static int cache_size = 0; 9 | 10 | *count = objc_getClassList(NULL, 0); 11 | if(cache) { 12 | if(cache_size == *count) { 13 | return cache; 14 | } else { 15 | free(cache); 16 | } 17 | } 18 | 19 | cache = (Class *)malloc(sizeof(Class) * *count); 20 | objc_getClassList(cache, *count); 21 | return cache; 22 | } 23 | 24 | bool is_objc_class(const void *address) { 25 | if(!address) { 26 | return false; 27 | } 28 | 29 | int class_count; 30 | Class *classes = get_objc_class_list(&class_count); 31 | for(int i = 0; i < class_count; i++) { 32 | void *class = classes[i]; 33 | if(address == class) { 34 | return true; 35 | } 36 | void *meta_class = *(void **)class; 37 | if(address == meta_class) { 38 | return true; 39 | } 40 | } 41 | 42 | return false; 43 | } 44 | 45 | bool is_objc_object(const void *address) { 46 | if(!address) { 47 | return false; 48 | } 49 | 50 | if(is_objc_class(address)) { 51 | return true; 52 | } 53 | 54 | // We already know that the input is a pointer to a class 55 | // so we can safely de-reference it 56 | void *class = *(void **)address; 57 | if(!is_objc_class(class)) { 58 | return false; 59 | } 60 | 61 | size_t msize = malloc_size(address); 62 | size_t isize = class_getInstanceSize(class); 63 | 64 | return msize >= isize; 65 | } 66 | 67 | BOOL is_class_recursive_subclass(Class class, Class superclass) { 68 | do { 69 | if(class == superclass) { 70 | return YES; 71 | } 72 | } while((class = class_getSuperclass(class))); 73 | 74 | return NO; 75 | } 76 | 77 | void malloc_enumerator(task_t task, void *data, unsigned type, vm_range_t *ranges, unsigned count) { 78 | NSArray *array = (NSArray *)data; 79 | NSArray *classes = array[0]; 80 | NSHashTable *instances = array[1]; 81 | 82 | for(unsigned i = 0; i < count; i++) { 83 | vm_range_t range = ranges[i]; 84 | void *obj = (void *)range.address; 85 | void *class = *(void **)obj; 86 | if([classes indexOfObjectIdenticalTo:class] != NSNotFound && is_objc_object(obj)) { 87 | [instances addObject:obj]; 88 | } 89 | } 90 | } 91 | 92 | kern_return_t memory_reader(task_t remote_task, vm_address_t remote_address, vm_size_t size, void **local_memory) { 93 | *local_memory = (void *)remote_address; 94 | return KERN_SUCCESS; 95 | } 96 | 97 | NSHashTable *find_instances_of_class_helper(NSArray *classes) { 98 | NSHashTable *instances = [NSHashTable hashTableWithOptions:NSPointerFunctionsWeakMemory | NSPointerFunctionsOpaquePersonality]; 99 | 100 | task_t task = mach_task_self(); 101 | 102 | vm_address_t *malloc_zone_addresses; 103 | unsigned malloc_zone_count; 104 | kern_return_t ret = malloc_get_all_zones(task, memory_reader, &malloc_zone_addresses, &malloc_zone_count); 105 | if(ret != KERN_SUCCESS) { 106 | return instances; 107 | } 108 | 109 | NSArray *array = @[classes, instances]; 110 | 111 | for(int i = 0; i < malloc_zone_count; i++) { 112 | malloc_zone_t *zone = (malloc_zone_t *)malloc_zone_addresses[i]; 113 | if(zone && zone->introspect && zone->introspect->enumerator) { 114 | zone->introspect->enumerator(task, array, MALLOC_PTR_IN_USE_RANGE_TYPE, (vm_address_t)zone, memory_reader, malloc_enumerator); 115 | } 116 | } 117 | 118 | return instances; 119 | } 120 | 121 | NSHashTable *find_instances_of_class(Class class, BOOL include_subclasses) { 122 | if(!is_objc_class(class)) { 123 | return [NSHashTable new]; 124 | } 125 | 126 | NSHashTable *instances; 127 | NSMutableArray *possible_classes = [NSMutableArray new]; 128 | 129 | @autoreleasepool { 130 | [possible_classes addObject:class]; 131 | 132 | if(include_subclasses) { 133 | int class_count; 134 | Class *classes = get_objc_class_list(&class_count); 135 | for(int i = 0; i < class_count; i++) { 136 | Class c = classes[i]; 137 | if(is_class_recursive_subclass(c, class)) { 138 | [possible_classes addObject:c]; 139 | } 140 | } 141 | } 142 | 143 | instances = [find_instances_of_class_helper(possible_classes) retain]; 144 | } 145 | 146 | return instances; 147 | } 148 | -------------------------------------------------------------------------------- /test/Makefile: -------------------------------------------------------------------------------- 1 | export TARGET=macosx:clang 2 | 3 | include $(THEOS)/makefiles/common.mk 4 | 5 | TOOL_NAME = test 6 | test_FILES = test.m ../objc_instance_finder.m 7 | 8 | include $(THEOS_MAKE_PATH)/tool.mk 9 | -------------------------------------------------------------------------------- /test/test.m: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "../objc_instance_finder.h" 4 | 5 | @interface TestClass : NSObject 6 | @end 7 | @implementation TestClass 8 | @end 9 | 10 | @interface TestSubClass : TestClass 11 | @end 12 | @implementation TestSubClass 13 | @end 14 | 15 | #define NSLog(args...) puts([[NSString stringWithFormat:args] UTF8String]) 16 | 17 | extern bool is_objc_object(const void *address); 18 | 19 | void test(Class class, BOOL include_subclasses, NSArray *knownInstances) { 20 | NSHashTable *instances = find_instances_of_class(class, include_subclasses); 21 | 22 | for(id obj in instances) { 23 | if(include_subclasses) { 24 | assert([obj isKindOfClass:class]); 25 | } else { 26 | assert([obj class] == class); 27 | } 28 | } 29 | 30 | NSHashTable *knownInstancesTable = [[NSHashTable alloc] initWithOptions: NSPointerFunctionsOpaqueMemory | NSPointerFunctionsOpaquePersonality capacity:knownInstances.count]; 31 | for(id obj in knownInstances) { 32 | [knownInstancesTable addObject:obj]; 33 | } 34 | 35 | assert([knownInstancesTable isSubsetOfHashTable:instances]); 36 | 37 | [instances minusHashTable:knownInstancesTable]; 38 | 39 | if(instances.count != 0) { 40 | NSLog(@"Found %lu unknown instances of %@%@ at runtime:", (unsigned long)instances.count, class, include_subclasses? @"": @" or subclasses"); 41 | for(id obj in instances) { 42 | NSLog(@"\t%@", obj); 43 | } 44 | } 45 | 46 | [knownInstancesTable release]; 47 | } 48 | 49 | int main(int argc, const char *argv[]) { 50 | @autoreleasepool { 51 | NSMutableArray *instances1 = [NSMutableArray new]; 52 | for(int i = 0; i < 3; i++) { 53 | TestClass *obj = [TestClass new]; 54 | [instances1 addObject:obj]; 55 | assert(is_objc_object(obj)); 56 | [obj release]; 57 | } 58 | 59 | NSMutableArray *instances2 = [NSMutableArray new]; 60 | for(int i = 0; i < 3; i++) { 61 | TestSubClass *obj = [TestSubClass new]; 62 | [instances2 addObject:obj]; 63 | assert(is_objc_object(obj)); 64 | [obj release]; 65 | } 66 | 67 | test([TestClass class], NO, instances1); 68 | test([TestSubClass class], NO, instances2); 69 | test([TestClass class], YES, [instances1 arrayByAddingObjectsFromArray:instances2]); 70 | 71 | NSHashTable *strings = find_instances_of_class([NSString class], YES); 72 | NSLog(@"Found %lu strings:", (unsigned long)strings.count); 73 | for(NSString *s in strings) { 74 | @try { 75 | NSLog(@"\t%@", s); 76 | } @catch(NSException *e) { 77 | NSLog(@"\t"); 78 | } 79 | } 80 | 81 | [instances1 release]; 82 | [instances2 release]; 83 | } 84 | 85 | return 0; 86 | } 87 | --------------------------------------------------------------------------------