mirror of
https://github.com/SoPat712/YTLitePlus.git
synced 2025-10-30 12:23:58 -04:00
198 lines
7.8 KiB
Objective-C
198 lines
7.8 KiB
Objective-C
//
|
|
// FLEXHeapEnumerator.m
|
|
// Flipboard
|
|
//
|
|
// Created by Ryan Olson on 5/28/14.
|
|
// Copyright (c) 2020 FLEX Team. All rights reserved.
|
|
//
|
|
|
|
#import "FLEXHeapEnumerator.h"
|
|
#import "FLEXObjcInternal.h"
|
|
#import "FLEXObjectRef.h"
|
|
#import "NSObject+FLEX_Reflection.h"
|
|
#import "NSString+FLEX.h"
|
|
#import <malloc/malloc.h>
|
|
#import <mach/mach.h>
|
|
#import <objc/runtime.h>
|
|
|
|
static CFMutableSetRef registeredClasses;
|
|
|
|
// Mimics the objective-c object structure for checking if a range of memory is an object.
|
|
typedef struct {
|
|
Class isa;
|
|
} flex_maybe_object_t;
|
|
|
|
@implementation FLEXHeapEnumerator
|
|
|
|
static void range_callback(task_t task, void *context, unsigned type, vm_range_t *ranges, unsigned rangeCount) {
|
|
if (!context) {
|
|
return;
|
|
}
|
|
|
|
for (unsigned int i = 0; i < rangeCount; i++) {
|
|
vm_range_t range = ranges[i];
|
|
flex_maybe_object_t *tryObject = (flex_maybe_object_t *)range.address;
|
|
Class tryClass = NULL;
|
|
#ifdef __arm64__
|
|
// See http://www.sealiesoftware.com/blog/archive/2013/09/24/objc_explain_Non-pointer_isa.html
|
|
extern uint64_t objc_debug_isa_class_mask WEAK_IMPORT_ATTRIBUTE;
|
|
tryClass = (__bridge Class)((void *)((uint64_t)tryObject->isa & objc_debug_isa_class_mask));
|
|
#else
|
|
tryClass = tryObject->isa;
|
|
#endif
|
|
// If the class pointer matches one in our set of class pointers from the runtime, then we should have an object.
|
|
if (CFSetContainsValue(registeredClasses, (__bridge const void *)(tryClass))) {
|
|
(*(flex_object_enumeration_block_t __unsafe_unretained *)context)((__bridge id)tryObject, tryClass);
|
|
}
|
|
}
|
|
}
|
|
|
|
static kern_return_t reader(__unused task_t remote_task, vm_address_t remote_address, __unused vm_size_t size, void **local_memory) {
|
|
*local_memory = (void *)remote_address;
|
|
return KERN_SUCCESS;
|
|
}
|
|
|
|
+ (void)enumerateLiveObjectsUsingBlock:(flex_object_enumeration_block_t)block {
|
|
if (!block) {
|
|
return;
|
|
}
|
|
|
|
// Refresh the class list on every call in case classes are added to the runtime.
|
|
[self updateRegisteredClasses];
|
|
|
|
// Inspired by:
|
|
// https://llvm.org/svn/llvm-project/lldb/tags/RELEASE_34/final/examples/darwin/heap_find/heap/heap_find.cpp
|
|
// https://gist.github.com/samdmarshall/17f4e66b5e2e579fd396
|
|
|
|
vm_address_t *zones = NULL;
|
|
unsigned int zoneCount = 0;
|
|
kern_return_t result = malloc_get_all_zones(TASK_NULL, reader, &zones, &zoneCount);
|
|
|
|
if (result == KERN_SUCCESS) {
|
|
for (unsigned int i = 0; i < zoneCount; i++) {
|
|
malloc_zone_t *zone = (malloc_zone_t *)zones[i];
|
|
malloc_introspection_t *introspection = zone->introspect;
|
|
|
|
// This may explain why some zone functions are
|
|
// sometimes invalid; perhaps not all zones support them?
|
|
if (!introspection) {
|
|
continue;
|
|
}
|
|
|
|
void (*lock_zone)(malloc_zone_t *zone) = introspection->force_lock;
|
|
void (*unlock_zone)(malloc_zone_t *zone) = introspection->force_unlock;
|
|
|
|
// Callback has to unlock the zone so we freely allocate memory inside the given block
|
|
flex_object_enumeration_block_t callback = ^(__unsafe_unretained id object, __unsafe_unretained Class actualClass) {
|
|
unlock_zone(zone);
|
|
block(object, actualClass);
|
|
lock_zone(zone);
|
|
};
|
|
|
|
BOOL lockZoneValid = FLEXPointerIsReadable(lock_zone);
|
|
BOOL unlockZoneValid = FLEXPointerIsReadable(unlock_zone);
|
|
|
|
// There is little documentation on when and why
|
|
// any of these function pointers might be NULL
|
|
// or garbage, so we resort to checking for NULL
|
|
// and whether the pointer is readable
|
|
if (introspection->enumerator && lockZoneValid && unlockZoneValid) {
|
|
lock_zone(zone);
|
|
introspection->enumerator(TASK_NULL, (void *)&callback, MALLOC_PTR_IN_USE_RANGE_TYPE, (vm_address_t)zone, reader, &range_callback);
|
|
unlock_zone(zone);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
+ (void)updateRegisteredClasses {
|
|
if (!registeredClasses) {
|
|
registeredClasses = CFSetCreateMutable(NULL, 0, NULL);
|
|
} else {
|
|
CFSetRemoveAllValues(registeredClasses);
|
|
}
|
|
unsigned int count = 0;
|
|
Class *classes = objc_copyClassList(&count);
|
|
for (unsigned int i = 0; i < count; i++) {
|
|
CFSetAddValue(registeredClasses, (__bridge const void *)(classes[i]));
|
|
}
|
|
free(classes);
|
|
}
|
|
|
|
+ (NSArray<FLEXObjectRef *> *)instancesOfClassWithName:(NSString *)className retained:(BOOL)retain {
|
|
const char *classNameCString = className.UTF8String;
|
|
NSMutableArray *instances = [NSMutableArray new];
|
|
[FLEXHeapEnumerator enumerateLiveObjectsUsingBlock:^(__unsafe_unretained id object, __unsafe_unretained Class actualClass) {
|
|
if (strcmp(classNameCString, class_getName(actualClass)) == 0) {
|
|
// Note: objects of certain classes crash when retain is called.
|
|
// It is up to the user to avoid tapping into instance lists for these classes.
|
|
// Ex. OS_dispatch_queue_specific_queue
|
|
// In the future, we could provide some kind of warning for classes that are known to be problematic.
|
|
if (malloc_size((__bridge const void *)(object)) > 0) {
|
|
[instances addObject:object];
|
|
}
|
|
}
|
|
}];
|
|
|
|
NSArray<FLEXObjectRef *> *references = [FLEXObjectRef referencingAll:instances retained:retain];
|
|
return references;
|
|
}
|
|
|
|
+ (NSArray<FLEXObjectRef *> *)subclassesOfClassWithName:(NSString *)className {
|
|
NSArray<Class> *classes = FLEXGetAllSubclasses(NSClassFromString(className), NO);
|
|
NSArray<FLEXObjectRef *> *references = [FLEXObjectRef referencingClasses:classes];
|
|
return references;
|
|
}
|
|
|
|
+ (NSArray<FLEXObjectRef *> *)objectsWithReferencesToObject:(id)object retained:(BOOL)retain {
|
|
static Class SwiftObjectClass = nil;
|
|
static dispatch_once_t onceToken;
|
|
dispatch_once(&onceToken, ^{
|
|
SwiftObjectClass = NSClassFromString(@"SwiftObject");
|
|
if (!SwiftObjectClass) {
|
|
SwiftObjectClass = NSClassFromString(@"Swift._SwiftObject");
|
|
}
|
|
});
|
|
|
|
NSMutableArray<FLEXObjectRef *> *instances = [NSMutableArray new];
|
|
[FLEXHeapEnumerator enumerateLiveObjectsUsingBlock:^(__unsafe_unretained id tryObject, __unsafe_unretained Class actualClass) {
|
|
// Skip known-invalid objects
|
|
if (!FLEXPointerIsValidObjcObject((__bridge void *)tryObject)) {
|
|
return;
|
|
}
|
|
|
|
// Get all the ivars on the object. Start with the class and and travel up the
|
|
// inheritance chain. Once we find a match, record it and move on to the next object.
|
|
// There's no reason to find multiple matches within the same object.
|
|
Class tryClass = actualClass;
|
|
while (tryClass) {
|
|
unsigned int ivarCount = 0;
|
|
Ivar *ivars = class_copyIvarList(tryClass, &ivarCount);
|
|
|
|
for (unsigned int ivarIndex = 0; ivarIndex < ivarCount; ivarIndex++) {
|
|
Ivar ivar = ivars[ivarIndex];
|
|
NSString *typeEncoding = @(ivar_getTypeEncoding(ivar) ?: "");
|
|
|
|
if (typeEncoding.flex_typeIsObjectOrClass) {
|
|
ptrdiff_t offset = ivar_getOffset(ivar);
|
|
uintptr_t *fieldPointer = (__bridge void *)tryObject + offset;
|
|
|
|
if (*fieldPointer == (uintptr_t)(__bridge void *)object) {
|
|
NSString *ivarName = @(ivar_getName(ivar) ?: "???");
|
|
id ref = [FLEXObjectRef referencing:tryObject ivar:ivarName retained:retain];
|
|
[instances addObject:ref];
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
free(ivars);
|
|
tryClass = class_getSuperclass(tryClass);
|
|
}
|
|
}];
|
|
|
|
return instances;
|
|
}
|
|
|
|
@end
|