Skip to content

Commit 2bc1bf6

Browse files
authored
Merge pull request #197 from facebook/findinstances
Add findinstances, and new support framework in Chisel.xcodeproj
2 parents 1b3e34d + 54f010a commit 2bc1bf6

File tree

15 files changed

+1083
-0
lines changed

15 files changed

+1083
-0
lines changed

Chisel/Chisel.xcodeproj/project.pbxproj

Lines changed: 439 additions & 0 deletions
Large diffs are not rendered by default.

Chisel/Chisel.xcodeproj/project.xcworkspace/contents.xcworkspacedata

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Chisel/Chisel/CHLAllocations.c

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// Copyright 2004-present Facebook. All Rights Reserved.
2+
3+
#include "CHLAllocations.h"
4+
5+
static kern_return_t reader(__unused task_t remote_task, vm_address_t remote_address, __unused vm_size_t size, void **local_memory)
6+
{
7+
*local_memory = (void *)remote_address;
8+
return KERN_SUCCESS;
9+
}
10+
11+
typedef struct {
12+
CHLRangeHandler handler;
13+
void *context;
14+
} RangeEnumeratorArgs;
15+
16+
static void rangeEnumerator(__unused task_t task, void *context, __unused unsigned type, vm_range_t *ranges, unsigned int count)
17+
{
18+
const RangeEnumeratorArgs *args = (RangeEnumeratorArgs *)context;
19+
for (unsigned int i = 0; i < count; ++i) {
20+
args->handler(ranges[i], args->context);
21+
}
22+
}
23+
24+
void CHLScanAllocations(CHLRangeHandler handler, void *context, const malloc_zone_t *sideZone)
25+
{
26+
vm_address_t *zones;
27+
unsigned int count;
28+
malloc_get_all_zones(TASK_NULL, &reader, &zones, &count);
29+
30+
RangeEnumeratorArgs args = {handler, context};
31+
32+
for (unsigned int i = 0; i < count; ++i) {
33+
malloc_zone_t *zone = (malloc_zone_t *)zones[i];
34+
if (zone != sideZone) {
35+
zone->introspect->enumerator(TASK_NULL, &args, MALLOC_PTR_IN_USE_RANGE_TYPE, (vm_address_t)zone, reader, rangeEnumerator);
36+
}
37+
}
38+
}

Chisel/Chisel/CHLAllocations.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Copyright 2004-present Facebook. All Rights Reserved.
2+
3+
#include <malloc/malloc.h>
4+
5+
#if defined(__cplusplus)
6+
extern "C" {
7+
#endif
8+
9+
typedef void (*CHLRangeHandler)(vm_range_t range, void *context);
10+
11+
// Enumerate live allocations in all malloc zones. If callers allocate memory in the handler, those
12+
// allocations should be within the given `sideZone`.
13+
void CHLScanAllocations(CHLRangeHandler handler, void *context, const malloc_zone_t *sideZone);
14+
15+
#if defined(__cplusplus)
16+
}
17+
#endif
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Copyright 2004-present Facebook. All Rights Reserved.
2+
3+
@class NSPredicate;
4+
5+
#if defined(__cplusplus)
6+
extern "C" {
7+
#endif
8+
9+
// Debugger interface for finding and printing instances of a type, with an optional predicate.
10+
// The predicate format is anything supported by NSPredicate.
11+
void PrintInstances(const char *type, const char *pred);
12+
13+
#if defined(__cplusplus)
14+
}
15+
#endif
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
// Copyright 2004-present Facebook. All Rights Reserved.
2+
3+
#import "CHLObjcInstanceCommands.h"
4+
5+
#include <objc/runtime.h>
6+
#include <vector>
7+
8+
#import <CoreFoundation/CoreFoundation.h>
9+
#import <Foundation/Foundation.h>
10+
11+
#import "CHLObjcInstances.h"
12+
#import "CHLPredicateTools.h"
13+
#include "zone_allocator.h"
14+
15+
#if __has_feature(objc_arc)
16+
#error Disable ARC for this file
17+
#endif
18+
19+
struct IsValidArgs {
20+
const std::unordered_set<Class> &classSet;
21+
bool isValid = true;
22+
};
23+
24+
static void isValidObject(const void *value, void *context)
25+
{
26+
const auto args = reinterpret_cast<IsValidArgs *>(context);
27+
if (!args->isValid) {
28+
return;
29+
}
30+
31+
vm_range_t range = {(vm_address_t)value, malloc_size(value)};
32+
if (CHLViableObjcInstance(range, args->classSet) == nil) {
33+
args->isValid = false;
34+
}
35+
}
36+
37+
static void isValidKeyValue(const void *key, const void *value, void *context)
38+
{
39+
const auto args = reinterpret_cast<IsValidArgs *>(context);
40+
isValidObject(key, context);
41+
if (args->isValid) {
42+
isValidObject(value, context);
43+
}
44+
}
45+
46+
static bool predicatePrecheck(id obj, const std::unordered_set<Class> &classSet)
47+
{
48+
IsValidArgs args{classSet};
49+
50+
if ([obj isKindOfClass:objc_getClass("__NSCFDictionary")]) {
51+
CFDictionaryApplyFunction((CFDictionaryRef)obj, &isValidKeyValue, &args);
52+
} else if ([obj isKindOfClass:objc_getClass("__NSCFSet")]) {
53+
CFSetApplyFunction((CFSetRef)obj, &isValidObject, &args);
54+
} else {
55+
// Skip classes containing NSPlaceholder.
56+
// TODO: Figure out better way to ignore invalid instances.
57+
char *name = (char *)object_getClassName(obj);
58+
while (*name == '_') ++name;
59+
if (strncmp(name, "NSPlaceholder", sizeof("NSPlaceholder") - 1) == 0) {
60+
args.isValid = false;
61+
}
62+
}
63+
64+
if (!args.isValid && getenv("FINDINSTANCES_DEBUG")) {
65+
printf("%p has class %s but contains non objc data\n", obj, object_getClassName(obj));
66+
}
67+
68+
return args.isValid;
69+
}
70+
71+
static void printObject(id obj, NSSet *keyPaths) {
72+
printf("<%s: %p", object_getClassName(obj), obj);
73+
for (NSString *keyPath in keyPaths) {
74+
printf("; %s = %s", keyPath.UTF8String, [[obj valueForKeyPath:keyPath] description].UTF8String);
75+
}
76+
printf(">\n");
77+
}
78+
79+
static bool objectIsMatch(NSPredicate *predicate, id obj, const std::unordered_set<Class> &classSet)
80+
{
81+
if (!predicate) {
82+
return true;
83+
}
84+
85+
bool debug = getenv("FINDINSTANCES_DEBUG");
86+
87+
if (!predicatePrecheck(obj, classSet)) {
88+
if (debug) {
89+
printf("%p has class %s but has non objc contents\n", obj, object_getClassName(obj));
90+
}
91+
return false;
92+
}
93+
94+
@try {
95+
return [predicate evaluateWithObject:obj];
96+
} @catch (...) {
97+
if (debug) {
98+
printf("%p has class %s but failed predicate evaluation\n", obj, object_getClassName(obj));
99+
}
100+
return false;
101+
}
102+
}
103+
104+
// Function reimplementation of +[NSObject isSubclassOf:] to avoid the objc runtime side
105+
// effects that can happen when calling methods, like realizing classes, +initialize, etc.
106+
static bool isSubclassOf(Class base, Class target)
107+
{
108+
for (auto cls = base; cls != Nil; cls = class_getSuperclass(cls)) {
109+
if (cls == target) {
110+
return true;
111+
}
112+
}
113+
return false;
114+
}
115+
116+
// Function reimplementation of +[NSObject conformsToProtocol:] to avoid the objc runtime side
117+
// effects that can happen when calling methods, like realizing classes, +initialize, etc.
118+
static bool conformsToProtocol(Class base, Protocol *protocol)
119+
{
120+
for (auto cls = base; cls != Nil; cls = class_getSuperclass(cls)) {
121+
if (class_conformsToProtocol(cls, protocol)) {
122+
return true;
123+
}
124+
}
125+
return false;
126+
}
127+
128+
void PrintInstances(const char *type, const char *pred)
129+
{
130+
NSPredicate *predicate = nil;
131+
if (pred != nullptr && *pred != '\0') {
132+
predicate = [NSPredicate predicateWithFormat:@(pred)];
133+
}
134+
135+
const std::unordered_set<Class> objcClasses = CHLObjcClassSet();
136+
std::unordered_set<Class> matchClasses;
137+
138+
Protocol *protocol = objc_getProtocol(type);
139+
if (protocol != nullptr && strcmp("NSObject", type) != 0) {
140+
for (auto cls : objcClasses) {
141+
if (conformsToProtocol(cls, protocol)) {
142+
matchClasses.insert(cls);
143+
}
144+
}
145+
}
146+
147+
if (type[0] == '*') {
148+
++type;
149+
Class cls = objc_getClass(type);
150+
if (cls != nullptr) {
151+
matchClasses.insert(cls);
152+
}
153+
} else if (Class kind = objc_getClass(type)) {
154+
// This could be optimized for type == "NSObject", but it won't be a typical search.
155+
for (auto cls : objcClasses) {
156+
if (isSubclassOf(cls, kind)) {
157+
matchClasses.insert(cls);
158+
}
159+
}
160+
}
161+
162+
if (matchClasses.empty()) {
163+
// TODO: Accept name of library/module, and list instances of classes defined there.
164+
printf("Unknown type: %s\n", type);
165+
return;
166+
}
167+
168+
NSSet *keyPaths = CHLVariableKeyPaths(predicate);
169+
170+
std::vector<id, zone_allocator<id>> instances = CHLScanObjcInstances(matchClasses);
171+
unsigned int matches = 0;
172+
173+
for (id obj : instances) {
174+
if (objectIsMatch(predicate, obj, objcClasses)) {
175+
++matches;
176+
printObject(obj, keyPaths);
177+
}
178+
}
179+
180+
if (matches > 1) {
181+
printf("%d matches\n", matches);
182+
}
183+
}

Chisel/Chisel/CHLObjcInstances.h

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Copyright 2004-present Facebook. All Rights Reserved.
2+
3+
#if defined(__cplusplus)
4+
5+
#include <malloc/malloc.h>
6+
#include <unordered_set>
7+
#include <vector>
8+
9+
#include "zone_allocator.h"
10+
11+
// Create a set containing all known Classes.
12+
std::unordered_set<Class> CHLObjcClassSet();
13+
14+
// Enumerates the heap and returns all objects that appear to be legitimate.
15+
std::vector<id, zone_allocator<id>> CHLScanObjcInstances(const std::unordered_set<Class> &classSet);
16+
17+
// Performs a number of heuristic checks on the memory range, to determine if the memory appears to
18+
// be a viable Objective-C object.
19+
id CHLViableObjcInstance(vm_range_t range, const std::unordered_set<Class> &classSet);
20+
21+
#endif

0 commit comments

Comments
 (0)