You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

134 lines
4.9 KiB

  1. //
  2. // FLEXHeapEnumerator.m
  3. // Flipboard
  4. //
  5. // Created by Ryan Olson on 5/28/14.
  6. // Copyright (c) 2014 Flipboard. All rights reserved.
  7. //
  8. #import "FLEXHeapEnumerator.h"
  9. #import <malloc/malloc.h>
  10. #import <mach/mach.h>
  11. #import <objc/runtime.h>
  12. static CFMutableSetRef registeredClasses;
  13. // Mimics the objective-c object structure for checking if a range of memory is an object.
  14. typedef struct {
  15. Class isa;
  16. } flex_maybe_object_t;
  17. @implementation FLEXHeapEnumerator
  18. static void range_callback(task_t task, void *context, unsigned type, vm_range_t *ranges, unsigned rangeCount)
  19. {
  20. if (!context) {
  21. return;
  22. }
  23. for (unsigned int i = 0; i < rangeCount; i++) {
  24. vm_range_t range = ranges[i];
  25. flex_maybe_object_t *tryObject = (flex_maybe_object_t *)range.address;
  26. Class tryClass = NULL;
  27. #ifdef __arm64__
  28. // See http://www.sealiesoftware.com/blog/archive/2013/09/24/objc_explain_Non-pointer_isa.html
  29. extern uint64_t objc_debug_isa_class_mask WEAK_IMPORT_ATTRIBUTE;
  30. tryClass = (__bridge Class)((void *)((uint64_t)tryObject->isa & objc_debug_isa_class_mask));
  31. #else
  32. tryClass = tryObject->isa;
  33. #endif
  34. // If the class pointer matches one in our set of class pointers from the runtime, then we should have an object.
  35. if (CFSetContainsValue(registeredClasses, (__bridge const void *)(tryClass))) {
  36. (*(flex_object_enumeration_block_t __unsafe_unretained *)context)((__bridge id)tryObject, tryClass);
  37. }
  38. }
  39. }
  40. static kern_return_t reader(__unused task_t remote_task, vm_address_t remote_address, __unused vm_size_t size, void **local_memory)
  41. {
  42. *local_memory = (void *)remote_address;
  43. return KERN_SUCCESS;
  44. }
  45. + (void)enumerateLiveObjectsUsingBlock:(flex_object_enumeration_block_t)block
  46. {
  47. if (!block) {
  48. return;
  49. }
  50. // Refresh the class list on every call in case classes are added to the runtime.
  51. [self updateRegisteredClasses];
  52. // Inspired by:
  53. // https://llvm.org/svn/llvm-project/lldb/tags/RELEASE_34/final/examples/darwin/heap_find/heap/heap_find.cpp
  54. // https://gist.github.com/samdmarshall/17f4e66b5e2e579fd396
  55. vm_address_t *zones = NULL;
  56. unsigned int zoneCount = 0;
  57. kern_return_t result = malloc_get_all_zones(TASK_NULL, reader, &zones, &zoneCount);
  58. if (result == KERN_SUCCESS) {
  59. for (unsigned int i = 0; i < zoneCount; i++) {
  60. malloc_zone_t *zone = (malloc_zone_t *)zones[i];
  61. malloc_introspection_t *introspection = zone->introspect;
  62. // This may explain why some zone functions are
  63. // sometimes invalid; perhaps not all zones support them?
  64. if (!introspection) {
  65. continue;
  66. }
  67. void (*lock_zone)(malloc_zone_t *zone) = introspection->force_lock;
  68. void (*unlock_zone)(malloc_zone_t *zone) = introspection->force_unlock;
  69. // Callback has to unlock the zone so we freely allocate memory inside the given block
  70. flex_object_enumeration_block_t callback = ^(__unsafe_unretained id object, __unsafe_unretained Class actualClass) {
  71. unlock_zone(zone);
  72. block(object, actualClass);
  73. lock_zone(zone);
  74. };
  75. // The largest realistic memory address varies by platform.
  76. // Only 48 bits are used by 64 bit machines while
  77. // 32 bit machines use all bits.
  78. //
  79. // __LP64__ is defined as 1 for both arm64 and x86_64
  80. // via: clang -dM -arch [arm64|x86_64] -E -x c /dev/null | grep LP
  81. #if __LP64__
  82. static uintptr_t MAX_REALISTIC_ADDRESS = 0x0000FFFFFFFFFFFF;
  83. BOOL lockZoneValid = lock_zone != nil && (uintptr_t)lock_zone < MAX_REALISTIC_ADDRESS;
  84. BOOL unlockZoneValid = unlock_zone != nil && (uintptr_t)unlock_zone < MAX_REALISTIC_ADDRESS;
  85. #else
  86. BOOL lockZoneValid = lock_zone != nil;
  87. BOOL unlockZoneValid = unlock_zone != nil;
  88. #endif
  89. // There is little documentation on when and why
  90. // any of these function pointers might be NULL
  91. // or garbage, so we resort to checking for NULL
  92. // and impossible memory addresses at least
  93. if (introspection->enumerator && lockZoneValid && unlockZoneValid) {
  94. lock_zone(zone);
  95. introspection->enumerator(TASK_NULL, (void *)&callback, MALLOC_PTR_IN_USE_RANGE_TYPE, (vm_address_t)zone, reader, &range_callback);
  96. unlock_zone(zone);
  97. }
  98. }
  99. }
  100. }
  101. + (void)updateRegisteredClasses
  102. {
  103. if (!registeredClasses) {
  104. registeredClasses = CFSetCreateMutable(NULL, 0, NULL);
  105. } else {
  106. CFSetRemoveAllValues(registeredClasses);
  107. }
  108. unsigned int count = 0;
  109. Class *classes = objc_copyClassList(&count);
  110. for (unsigned int i = 0; i < count; i++) {
  111. CFSetAddValue(registeredClasses, (__bridge const void *)(classes[i]));
  112. }
  113. free(classes);
  114. }
  115. @end