HINSPHeapStackInspector.m 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. //
  2. // RMHeapEnumerator.m
  3. // HeapInspectorExample
  4. //
  5. // Created by Christian Menschel on 22.08.14.
  6. // Copyright (c) 2014 tapwork. All rights reserved.
  7. //
  8. // Inspired by Flipboard's FLEX and HeapEnumerator
  9. // See more: https://github.com/Flipboard/FLEX/blob/master/Classes/Utility/FLEXHeapEnumerator.m
  10. //
  11. #import "HINSPHeapStackInspector.h"
  12. #import <malloc/malloc.h>
  13. #import <mach/mach.h>
  14. #import <objc/runtime.h>
  15. #import <NSObject+HeapInspector.h>
  16. static CFMutableSetRef classesLoadedInRuntime = NULL;
  17. static NSSet *heapShotOfLivingObjects = nil;
  18. // Mimics the objective-c object stucture for checking if a range of memory is an object.
  19. typedef struct {
  20. Class isa;
  21. } rm_maybe_object_t;
  22. @implementation HINSPHeapStackInspector
  23. static inline kern_return_t memory_reader(task_t task, vm_address_t remote_address, vm_size_t size, void **local_memory)
  24. {
  25. *local_memory = (void *)remote_address;
  26. return KERN_SUCCESS;
  27. }
  28. static inline void range_callback(task_t task,
  29. void *context,
  30. unsigned type,
  31. vm_range_t *ranges,
  32. unsigned rangeCount)
  33. {
  34. RMHeapEnumeratorBlock enumeratorBlock = (__bridge RMHeapEnumeratorBlock)context;
  35. if (!enumeratorBlock) {
  36. return;
  37. }
  38. BOOL stop = NO;
  39. for (unsigned int i = 0; i < rangeCount; i++) {
  40. vm_range_t range = ranges[i];
  41. rm_maybe_object_t *object = (rm_maybe_object_t *)range.address;
  42. Class tryClass = NULL;
  43. #ifdef __arm64__
  44. // See http://www.sealiesoftware.com/blog/archive/2013/09/24/objc_explain_Non-pointer_isa.html
  45. extern uint64_t objc_debug_isa_class_mask WEAK_IMPORT_ATTRIBUTE;
  46. tryClass = (__bridge Class)((void *)((uint64_t)object->isa & objc_debug_isa_class_mask));
  47. #else
  48. tryClass = object->isa;
  49. #endif
  50. if (tryClass &&
  51. CFSetContainsValue(classesLoadedInRuntime, (__bridge const void *)(tryClass)) &&
  52. canRecordObject((__bridge id)object)) {
  53. enumeratorBlock((__bridge id)object, &stop);
  54. if (stop) {
  55. break;
  56. }
  57. }
  58. }
  59. }
  60. + (void)enumerateLiveObjectsUsingBlock:(RMHeapEnumeratorBlock)completionBlock
  61. {
  62. if (!completionBlock) {
  63. return;
  64. }
  65. // Refresh the class list on every call in case classes are added to the runtime.
  66. [self updateRegisteredClasses];
  67. // For another exmple of enumerating through malloc ranges (which helped my understanding of the api) see:
  68. // http://llvm.org/svn/llvm-project/lldb/tags/RELEASE_34/final/examples/darwin/heap_find/heap/heap_find.cpp
  69. // Also https://gist.github.com/samdmarshall/17f4e66b5e2e579fd396
  70. // or http://www.opensource.apple.com/source/Libc/Libc-167/gen.subproj/malloc.c
  71. vm_address_t *zones = NULL;
  72. mach_port_t task = mach_task_self();
  73. unsigned int zoneCount = 0;
  74. kern_return_t result = malloc_get_all_zones(task, memory_reader, &zones, &zoneCount);
  75. BOOL __block stopEnumerator = NO;
  76. if (result == KERN_SUCCESS) {
  77. for (unsigned i = 0; i < zoneCount; i++) {
  78. malloc_zone_t *zone = (malloc_zone_t *)zones[i];
  79. if (zone != NULL
  80. && !zone->reserved1 && !zone->reserved2 // if reserved1 and reserved2 are not NULL, zone object is corrupted
  81. && zone->introspect != NULL && zone->introspect->enumerator != NULL) {
  82. RMHeapEnumeratorBlock enumeratorBlock = ^(__unsafe_unretained id object, BOOL *stop) {
  83. completionBlock(object, &stopEnumerator);
  84. if (stopEnumerator) {
  85. *stop = YES;
  86. }
  87. };
  88. if (!stopEnumerator) {
  89. zone->introspect->enumerator(task,
  90. (__bridge void *)(enumeratorBlock),
  91. MALLOC_PTR_IN_USE_RANGE_TYPE,
  92. (vm_address_t)zone,
  93. memory_reader,
  94. range_callback);
  95. } else {
  96. break;
  97. }
  98. }
  99. }
  100. }
  101. }
  102. + (void)updateRegisteredClasses
  103. {
  104. if (!classesLoadedInRuntime) {
  105. classesLoadedInRuntime = CFSetCreateMutable(NULL, 0, NULL);
  106. } else {
  107. CFSetRemoveAllValues(classesLoadedInRuntime);
  108. }
  109. unsigned int count = 0;
  110. Class *classes = objc_copyClassList(&count);
  111. for (unsigned int i = 0; i < count; i++) {
  112. CFSetAddValue(classesLoadedInRuntime, (__bridge const void *)(classes[i]));
  113. }
  114. free(classes);
  115. }
  116. #pragma mark - Public
  117. + (void)performHeapShot
  118. {
  119. heapShotOfLivingObjects = [[self class] heap];
  120. }
  121. + (void)reset
  122. {
  123. heapShotOfLivingObjects = nil;
  124. classesLoadedInRuntime = NULL;
  125. }
  126. + (NSSet *)recordedHeap
  127. {
  128. NSMutableSet *endLiveObjects = [[[self class] heap] mutableCopy];
  129. [endLiveObjects minusSet:heapShotOfLivingObjects];
  130. return [endLiveObjects copy];
  131. }
  132. + (NSSet *)heap
  133. {
  134. NSMutableSet *objects = [NSMutableSet set];
  135. [HINSPHeapStackInspector enumerateLiveObjectsUsingBlock:^(__unsafe_unretained id object, BOOL *stop) {
  136. // We cannot store the object itself - We want to avoid any retain calls.
  137. // We store the class name + pointer
  138. NSString *string = [NSString stringWithFormat:@"%s: %p",
  139. object_getClassName(object),
  140. object];
  141. [objects addObject:string];
  142. }];
  143. return objects;
  144. }
  145. + (id)objectForPointer:(NSString *)pointer
  146. {
  147. id __block foundObject = nil;
  148. [HINSPHeapStackInspector enumerateLiveObjectsUsingBlock:^(__unsafe_unretained id object, BOOL *stop) {
  149. if ([pointer isEqualToString:[NSString stringWithFormat:@"%p",object]]) {
  150. foundObject = object;
  151. *stop = YES;
  152. }
  153. }];
  154. return foundObject;
  155. }
  156. @end