Skip to content

Commit

Permalink
Merge pull request #197 from facebook/findinstances
Browse files Browse the repository at this point in the history
Add findinstances, and new support framework in Chisel.xcodeproj
  • Loading branch information
kastiglione committed Nov 17, 2017
2 parents 1b3e34d + 54f010a commit 2bc1bf6
Show file tree
Hide file tree
Showing 15 changed files with 1,083 additions and 0 deletions.
439 changes: 439 additions & 0 deletions Chisel/Chisel.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

38 changes: 38 additions & 0 deletions Chisel/Chisel/CHLAllocations.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Copyright 2004-present Facebook. All Rights Reserved.

#include "CHLAllocations.h"

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;
}

typedef struct {
CHLRangeHandler handler;
void *context;
} RangeEnumeratorArgs;

static void rangeEnumerator(__unused task_t task, void *context, __unused unsigned type, vm_range_t *ranges, unsigned int count)
{
const RangeEnumeratorArgs *args = (RangeEnumeratorArgs *)context;
for (unsigned int i = 0; i < count; ++i) {
args->handler(ranges[i], args->context);
}
}

void CHLScanAllocations(CHLRangeHandler handler, void *context, const malloc_zone_t *sideZone)
{
vm_address_t *zones;
unsigned int count;
malloc_get_all_zones(TASK_NULL, &reader, &zones, &count);

RangeEnumeratorArgs args = {handler, context};

for (unsigned int i = 0; i < count; ++i) {
malloc_zone_t *zone = (malloc_zone_t *)zones[i];
if (zone != sideZone) {
zone->introspect->enumerator(TASK_NULL, &args, MALLOC_PTR_IN_USE_RANGE_TYPE, (vm_address_t)zone, reader, rangeEnumerator);
}
}
}
17 changes: 17 additions & 0 deletions Chisel/Chisel/CHLAllocations.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Copyright 2004-present Facebook. All Rights Reserved.

#include <malloc/malloc.h>

#if defined(__cplusplus)
extern "C" {
#endif

typedef void (*CHLRangeHandler)(vm_range_t range, void *context);

// Enumerate live allocations in all malloc zones. If callers allocate memory in the handler, those
// allocations should be within the given `sideZone`.
void CHLScanAllocations(CHLRangeHandler handler, void *context, const malloc_zone_t *sideZone);

#if defined(__cplusplus)
}
#endif
15 changes: 15 additions & 0 deletions Chisel/Chisel/CHLObjcInstanceCommands.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright 2004-present Facebook. All Rights Reserved.

@class NSPredicate;

#if defined(__cplusplus)
extern "C" {
#endif

// Debugger interface for finding and printing instances of a type, with an optional predicate.
// The predicate format is anything supported by NSPredicate.
void PrintInstances(const char *type, const char *pred);

#if defined(__cplusplus)
}
#endif
183 changes: 183 additions & 0 deletions Chisel/Chisel/CHLObjcInstanceCommands.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
// Copyright 2004-present Facebook. All Rights Reserved.

#import "CHLObjcInstanceCommands.h"

#include <objc/runtime.h>
#include <vector>

#import <CoreFoundation/CoreFoundation.h>
#import <Foundation/Foundation.h>

#import "CHLObjcInstances.h"
#import "CHLPredicateTools.h"
#include "zone_allocator.h"

#if __has_feature(objc_arc)
#error Disable ARC for this file
#endif

struct IsValidArgs {
const std::unordered_set<Class> &classSet;
bool isValid = true;
};

static void isValidObject(const void *value, void *context)
{
const auto args = reinterpret_cast<IsValidArgs *>(context);
if (!args->isValid) {
return;
}

vm_range_t range = {(vm_address_t)value, malloc_size(value)};
if (CHLViableObjcInstance(range, args->classSet) == nil) {
args->isValid = false;
}
}

static void isValidKeyValue(const void *key, const void *value, void *context)
{
const auto args = reinterpret_cast<IsValidArgs *>(context);
isValidObject(key, context);
if (args->isValid) {
isValidObject(value, context);
}
}

static bool predicatePrecheck(id obj, const std::unordered_set<Class> &classSet)
{
IsValidArgs args{classSet};

if ([obj isKindOfClass:objc_getClass("__NSCFDictionary")]) {
CFDictionaryApplyFunction((CFDictionaryRef)obj, &isValidKeyValue, &args);
} else if ([obj isKindOfClass:objc_getClass("__NSCFSet")]) {
CFSetApplyFunction((CFSetRef)obj, &isValidObject, &args);
} else {
// Skip classes containing NSPlaceholder.
// TODO: Figure out better way to ignore invalid instances.
char *name = (char *)object_getClassName(obj);
while (*name == '_') ++name;
if (strncmp(name, "NSPlaceholder", sizeof("NSPlaceholder") - 1) == 0) {
args.isValid = false;
}
}

if (!args.isValid && getenv("FINDINSTANCES_DEBUG")) {
printf("%p has class %s but contains non objc data\n", obj, object_getClassName(obj));
}

return args.isValid;
}

static void printObject(id obj, NSSet *keyPaths) {
printf("<%s: %p", object_getClassName(obj), obj);
for (NSString *keyPath in keyPaths) {
printf("; %s = %s", keyPath.UTF8String, [[obj valueForKeyPath:keyPath] description].UTF8String);
}
printf(">\n");
}

static bool objectIsMatch(NSPredicate *predicate, id obj, const std::unordered_set<Class> &classSet)
{
if (!predicate) {
return true;
}

bool debug = getenv("FINDINSTANCES_DEBUG");

if (!predicatePrecheck(obj, classSet)) {
if (debug) {
printf("%p has class %s but has non objc contents\n", obj, object_getClassName(obj));
}
return false;
}

@try {
return [predicate evaluateWithObject:obj];
} @catch (...) {
if (debug) {
printf("%p has class %s but failed predicate evaluation\n", obj, object_getClassName(obj));
}
return false;
}
}

// Function reimplementation of +[NSObject isSubclassOf:] to avoid the objc runtime side
// effects that can happen when calling methods, like realizing classes, +initialize, etc.
static bool isSubclassOf(Class base, Class target)
{
for (auto cls = base; cls != Nil; cls = class_getSuperclass(cls)) {
if (cls == target) {
return true;
}
}
return false;
}

// Function reimplementation of +[NSObject conformsToProtocol:] to avoid the objc runtime side
// effects that can happen when calling methods, like realizing classes, +initialize, etc.
static bool conformsToProtocol(Class base, Protocol *protocol)
{
for (auto cls = base; cls != Nil; cls = class_getSuperclass(cls)) {
if (class_conformsToProtocol(cls, protocol)) {
return true;
}
}
return false;
}

void PrintInstances(const char *type, const char *pred)
{
NSPredicate *predicate = nil;
if (pred != nullptr && *pred != '\0') {
predicate = [NSPredicate predicateWithFormat:@(pred)];
}

const std::unordered_set<Class> objcClasses = CHLObjcClassSet();
std::unordered_set<Class> matchClasses;

Protocol *protocol = objc_getProtocol(type);
if (protocol != nullptr && strcmp("NSObject", type) != 0) {
for (auto cls : objcClasses) {
if (conformsToProtocol(cls, protocol)) {
matchClasses.insert(cls);
}
}
}

if (type[0] == '*') {
++type;
Class cls = objc_getClass(type);
if (cls != nullptr) {
matchClasses.insert(cls);
}
} else if (Class kind = objc_getClass(type)) {
// This could be optimized for type == "NSObject", but it won't be a typical search.
for (auto cls : objcClasses) {
if (isSubclassOf(cls, kind)) {
matchClasses.insert(cls);
}
}
}

if (matchClasses.empty()) {
// TODO: Accept name of library/module, and list instances of classes defined there.
printf("Unknown type: %s\n", type);
return;
}

NSSet *keyPaths = CHLVariableKeyPaths(predicate);

std::vector<id, zone_allocator<id>> instances = CHLScanObjcInstances(matchClasses);
unsigned int matches = 0;

for (id obj : instances) {
if (objectIsMatch(predicate, obj, objcClasses)) {
++matches;
printObject(obj, keyPaths);
}
}

if (matches > 1) {
printf("%d matches\n", matches);
}
}
21 changes: 21 additions & 0 deletions Chisel/Chisel/CHLObjcInstances.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright 2004-present Facebook. All Rights Reserved.

#if defined(__cplusplus)

#include <malloc/malloc.h>
#include <unordered_set>
#include <vector>

#include "zone_allocator.h"

// Create a set containing all known Classes.
std::unordered_set<Class> CHLObjcClassSet();

// Enumerates the heap and returns all objects that appear to be legitimate.
std::vector<id, zone_allocator<id>> CHLScanObjcInstances(const std::unordered_set<Class> &classSet);

// Performs a number of heuristic checks on the memory range, to determine if the memory appears to
// be a viable Objective-C object.
id CHLViableObjcInstance(vm_range_t range, const std::unordered_set<Class> &classSet);

#endif
Loading

0 comments on commit 2bc1bf6

Please sign in to comment.