/* * Copyright (c) 2009 Apple Inc. All rights reserved. * * @APPLE_APACHE_LICENSE_HEADER_START@ * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * @APPLE_APACHE_LICENSE_HEADER_END@ */ // // dump.m // gctests // // Created by Blaine Garst on 10/22/08. // Copyright 2008 __MyCompanyName__. All rights reserved. // #import #import #import #import #import #include #if 0 extern void auto_zone_dump(auto_zone_t *zone, void (^stack_dump)(const void *base, unsigned long byte_size), void (^register_dump)(const void *base, unsigned long byte_size), void (^thread_local_node_dump)(const void *address, unsigned long size, unsigned int layout, unsigned long refcount), void (^root_dump)(const void **address), void (^global_node_dump)(const void *address, unsigned long size, unsigned int layout, unsigned long refcount), void (^weak_dump)(const void **address, const void *item) ); #endif __weak id Y = nil; /* * Simple zone printer */ void printLiveDump(auto_zone_t *zone) { auto_zone_dump(zone, ^(const void *base, unsigned long byte_size) { printf("stack base %p, length %lu\n", base, byte_size);}, ^(const void *base, unsigned long byte_size) { printf("register base %p, length %lu\n", base, byte_size);}, ^(const void *address, unsigned long size, unsigned int layout, unsigned long refcount) { printf("thread local %p, size %ld, layout %x, refcount %ld\n", address, size, layout, refcount); }, ^(const void **address) { printf("global root at %p, value there %p\n", address, *address); }, ^(const void *address, unsigned long size, unsigned int layout, unsigned long refcount) { printf("non-local %p, size %ld, layout %x, refcount %ld\n", address, size, layout, refcount); }, ^(const void **address, const void *item) { printf("weak ref located at %p referencing %p\n", address, item); } ); } #if 0 /* * Raw file format definitions */ // must be unique in first letter... // RAW FORMAT #define HEADER "dumpster" #define THREAD 't' #define LOCAL 'l' #define NODE 'n' #define REGISTER 'r' #define ROOT 'g' #define WEAK 'w' #define CLASS 'c' #define END 'e' #define SixtyFour 1 #define Little 2 /* Raw format, not that anyone should really care. *
:= 'd' 'u' 'm' 'p' 's' 't' 'e' 'r' ; the HEADER string := SixtyFour? + Little? ; architecture := | | | | := * ; the triple := 'r' longLength [bytes] ; the register bank := 't' longLength [bytes] ; the stack := 'l' [long] ; a thread local node := 'g' longAddress longValue := 'n' longAddress longSize intLayout longRefcount longIsa? := 'w' longAddress longValue := 'c' longAddress := intLength [bytes] ; no null byte := intLength [bytes] ; including 0 byte at end := intLength [bytes] ; including 0 byte at end := 'e' */ #endif // COOKED FORMAT #define HEADER2 "dumpfile" /* * Utilities */ static char myType() { char type = 0; if (sizeof(void *) == 8) type |= SixtyFour; #if __LITTLE_ENDIAN__ type |= Little; #endif return type; } #if 0 /* * Sigh, a mutable set. */ typedef struct { long *items; long count; long capacity; } pointer_set_t; static pointer_set_t *new_pointer_set() { pointer_set_t *result = malloc(sizeof(pointer_set_t)); result->items = calloc(64, sizeof(long)); result->count = 0; result->capacity = 63; // last valid ptr, also mask return result; } static void pointer_set_grow(pointer_set_t *set); static void pointer_set_add(pointer_set_t *set, long ptr) { long hash = ptr & set->capacity; while (true) { if (!set->items[hash]) { set->items[hash] = ptr; ++set->count; if (set->count*3 > set->capacity*2) pointer_set_grow(set); return; } if (set->items[hash] == ptr) return; hash = (hash + 1) & set->capacity; } } static void pointer_set_grow(pointer_set_t *set) { long oldCapacity = set->capacity; long *oldItems = set->items; set->count = 0; set->capacity = 2*(oldCapacity+1)-1; set->items = calloc(2*(oldCapacity+1), sizeof(long)); for (long i = 0; i < oldCapacity; ++i) if (oldItems[i]) pointer_set_add(set, oldItems[i]); free(oldItems); } static void pointer_set_iterate(pointer_set_t *set, void (^block)(long item)) { for (long i = 0; i < set->capacity; ++i) if (set->items[i]) block(set->items[i]); } static pointer_set_dispose(pointer_set_t *set) { free(set->items); free(set); } /* Quickly dump heap to a named file in a pretty raw format. */ void auto_zone_create_dump_file(auto_zone_t *zone, const char *filename) { // just write interesting info to disk FILE *fp = fopen(filename, "w"); if (fp == NULL) { printf("couldn't open test file: %s", filename); return; } fwrite(HEADER, strlen(HEADER), 1, fp); char type = myType(); fwrite(&type, 1, 1, fp); // for each thread... // do registers first void (^dump_registers)(const void *, unsigned long) = ^(const void *base, unsigned long byte_size) { char type = REGISTER; fwrite(&type, 1, 1, fp); //fwrite(REGISTER, strlen(REGISTER), 1, fp); fwrite(&byte_size, sizeof(byte_size), 1, fp); fwrite(base, byte_size, 1, fp); }; // then stacks void (^dump_stack)(const void *, unsigned long) = ^(const void *base, unsigned long byte_size) { char type = THREAD; fwrite(&type, 1, 1, fp); //fwrite(THREAD, strlen(THREAD), 1, fp); fwrite(&byte_size, sizeof(byte_size), 1, fp); fwrite(base, byte_size, 1, fp); }; // then locals void (^dump_local)(const void *, unsigned long, unsigned int, unsigned long) = ^(const void *address, unsigned long size, unsigned int layout, unsigned long refcount) { // just write the value - rely on it showing up again as a node later char type = LOCAL; fwrite(&type, 1, 1, fp); fwrite(&address, sizeof(address), 1, fp); }; // roots void (^dump_root)(const void **) = ^(const void **address) { char type = ROOT; fwrite(&type, 1, 1, fp); // write the address so that we can catch misregistered globals fwrite(&address, sizeof(address), 1, fp); // write content, even (?) if zero fwrite(address, sizeof(*address), 1, fp); }; // the nodes pointer_set_t *classes = new_pointer_set(); void (^dump_node)(const void *, unsigned long, unsigned int, unsigned long) = ^(const void *address, unsigned long size, unsigned int layout, unsigned long refcount) { char type = NODE; fwrite(&type, 1, 1, fp); fwrite(&address, sizeof(address), 1, fp); fwrite(&size, sizeof(size), 1, fp); fwrite(&layout, sizeof(layout), 1, fp); fwrite(&refcount, sizeof(refcount), 1, fp); if ((layout & AUTO_UNSCANNED) != AUTO_UNSCANNED) { // now the nodes unfiltered content fwrite(address, size, 1, fp); } if ((layout & AUTO_OBJECT) == AUTO_OBJECT) { #if 0 id theClass = *(id *)address; if (theClass) [classes addObject:theClass]; #else long theClass = *(long *)address; if (theClass) pointer_set_add(classes, theClass); #endif } }; // weak void (^dump_weak)(const void **, const void *) = ^(const void **address, const void *item) { char type = WEAK; fwrite(&type, 1, 1, fp); fwrite(&address, sizeof(address), 1, fp); fwrite(&item, sizeof(item), 1, fp); }; auto_zone_dump(zone, dump_stack, dump_registers, dump_local, dump_root, dump_node, dump_weak); //for (Class class in classes) { pointer_set_iterate(classes, ^(long class) { char type = CLASS; fwrite(&type, 1, 1, fp); fwrite(&class, sizeof(class), 1, fp); // write address so that we can map it from node isa's // classname (for grins) const char *className = class_getName(class); unsigned int length = strlen(className); fwrite(&length, sizeof(length), 1, fp); // n fwrite(className, length, 1, fp); // n bytes // strong layout const char *layout = class_getIvarLayout(class); length = layout ? strlen(layout)+1 : 0; // format is ending with <0><0> fwrite(&length, sizeof(length), 1, fp); // n fwrite(layout, length, 1, fp); // n bytes // weak layout layout = class_getWeakIvarLayout(class); length = layout ? strlen(layout)+1 : 0; // format is ending with <0><0> fwrite(&length, sizeof(length), 1, fp); // n fwrite(layout, length, 1, fp); // n bytes }); { // end char type = END; fwrite(&type, 1, 1, fp); fclose(fp); } } #endif /* * Primitives for reading raw file */ unsigned long readLong(FILE *fp) { unsigned long result; size_t len = fread(&result, sizeof(result), 1, fp); if (len != 1) { printf("error reading long\n"); exit(1); } return result; } void *readPointer(FILE *fp) { void *result; size_t len = fread(&result, sizeof(result), 1, fp); if (len != 1) { printf("error reading pointer\n"); exit(1); } return result; } int readInt(FILE *fp) { int result; size_t len = fread(&result, sizeof(result), 1, fp); if (len != 1) { printf("error reading int\n"); exit(1); } return result; } __strong char *readString(FILE *fp) { int length; size_t len = fread(&length, sizeof(length), 1, fp); if (len != 1) { printf("error reading length\n"); exit(1); } if (length == 0) return NULL; char buffer[length+1]; len = fread(buffer, length, 1, fp); if (len != 1) { printf("error reading string content\n"); exit(1); } char *result = NSAllocateCollectable(length+1, 0); memmove(result, buffer, length); result[length] = 0; return result; } __strong char *readStringSize(FILE *fp, uint32_t length) { if (length == 0) return NULL; char buffer[length+1]; int len = fread(buffer, length, 1, fp); if (len != 1) { printf("error reading string content\n"); exit(1); } char *result = NSAllocateCollectable(length+1, 0); memmove(result, buffer, length); result[length] = 0; return result; } FILE *openDump(const char *filename) { FILE *fp = fopen(filename, "r"); if (!fp) { printf("Whoops, can't open %s for reading\n", filename); return NULL; } char header[strlen(HEADER)+1]; fread(header, strlen(HEADER), 1, fp); if (memcmp(header, HEADER, strlen(HEADER))) { header[strlen(HEADER)] = 0; printf("Whoops, header not recognized: %s\n", header); return NULL; } fgetc(fp); // skip type for now return fp; } FILE *openCooked(const char *filename) { FILE *fp = fopen(filename, "r"); if (!fp) { printf("Whoops, can't open %s for reading\n", filename); return NULL; } char header[strlen(HEADER2)+1]; fread(header, strlen(HEADER2), 1, fp); if (memcmp(header, HEADER2, strlen(HEADER2))) { header[strlen(HEADER2)] = 0; printf("Whoops, header not recognized: %s\n", header); return NULL; } return fp; } /* Read raw dump and print out its contents without much processing. Just a utility printer - or base for better efforts */ void printRawDumpFile(const char *filename) { FILE *fp = openDump(filename); if (!fp) { printf("Whoops, can't open %s for reading\n", filename); return; } while (true) { int ch = fgetc(fp); switch (ch) { case EOF: { printf("unexpected end of input\n"); return; } case THREAD: { unsigned long byte_size = readLong(fp); printf("thread of size %ld bytes\n", byte_size); fseek(fp, byte_size, SEEK_CUR); break; } case REGISTER: { unsigned long byte_size = readLong(fp); printf("registers of size %ld bytes\n", byte_size); fseek(fp, byte_size, SEEK_CUR); break; } case LOCAL: { void *address = readPointer(fp); printf("local node at %p\n", address); break; } case ROOT: { void *address = readPointer(fp); void *value = readPointer(fp); printf("root at %p value %p\n", address, value); break; } case NODE: { void *address = readPointer(fp); unsigned long size = readLong(fp); int layout = readInt(fp); unsigned long refcount = readLong(fp); printf("node at %p, size %ld, layout %x, refcount %ld\n", address, size, layout, refcount); if ((layout & AUTO_UNSCANNED) != AUTO_UNSCANNED) { fseek(fp, size, SEEK_CUR); } break; } case WEAK: { void *address = readPointer(fp); void *value = readPointer(fp); printf("weak at %p value %p\n", address, value); break; } case CLASS: { void *address = readPointer(fp); char *name = readString(fp); printf("class %s at %p", name, address); char *layout = readString(fp); if (layout) printf(" hasStrong"); layout = readString(fp); if (layout) printf(" hasWeak"); printf("\n"); break; } case END: { fclose(fp); return; } default: printf("unrecognized key: %x, '%c'\n", ch, ch); exit(1); } } } #pragma mark dump crunching /* Define structures for dealing with processed data. */ typedef struct { uint32_t class_index; uint32_t nameOffset, nameLen; uint32_t strongOffset, strongLen; uint32_t weakOffset, weakLen; } class_descriptor_t; typedef struct { void *original; uint32_t size, layout, refcount; uint32_t nitems; struct { uint32_t offset; // in words uint32_t index; } items[]; } node_descriptor_t; typedef struct { uint32_t nitems; struct { uint32_t offset; // in words uint32_t index; } items[]; } stack_descriptor_t; typedef struct thread_descriptor { stack_descriptor_t *__strong stack_descriptor; uint32_t nLocals; uint32_t *locals; uint32_t nRegisters; uint32_t *registers; } thread_descriptor_t; node_descriptor_t *getNodeDescriptor(char *buffer, uint32_t size, char *nibbles, NSMapTable *nodeIndex) { char *nibbler = nibbles; id *fields = (id *)buffer; uint32_t limit = size/sizeof(id); uint32_t counter = 0; uint32_t nitems = 0; // allocate worst case. node_descriptor_t *descriptor = NSAllocateCollectable(sizeof(node_descriptor_t)+limit*2*sizeof(uint32_t), 0); while (counter < limit) { if (nibbler && *nibbler) { int skip = ((*nibbler) >> 4 & 0xf); counter += skip; int use = (*nibbler) & 0xf; while (use) { long index = (long)[nodeIndex objectForKey:(id)fields[counter]]; if (index) { descriptor->items[nitems].offset = counter; descriptor->items[nitems].index = index - 1; ++nitems; } --use; ++counter; } nibbler++; } else { long index = (long)[nodeIndex objectForKey:(id)fields[counter]]; if (index) { descriptor->items[nitems].offset = counter; descriptor->items[nitems].index = index - 1; ++nitems; } ++counter; } } descriptor->nitems = nitems; return descriptor; } /* * raw file reader * cooked file writer */ @interface Dumpster : NSObject { NSPointerArray *classArray; NSPointerArray *nodeArray; NSPointerArray *rootArray; NSPointerArray *stackArray; } - initWithRawFilename:(const char *)filename verbose:(bool) verbose; - (void)writeCookedToFilename:(const char *)filename; @end @implementation Dumpster /* Read raw format from disk & canonicalize the class & node references. We assume less than 4G nodes so we use uint_32 indexes to represent them. Ergo #classes too. Thread stacks are represented as { int32_t skip, int32_t index }. This does not preserve stale references to free nodes. */ - initWithRawFilename:(const char *)filename verbose:(bool) verbose { FILE *fp = openDump(filename); if (!fp) { printf("Whoops, can't open %s for reading\n", filename); return nil; } // set up class list & mapping tables // first, a structure thing... NSPointerFunctions *structDescriptorFunctions = [NSPointerFunctions pointerFunctionsWithOptions: NSPointerFunctionsStrongMemory |NSPointerFunctionsStructPersonality]; // the array... classArray = [NSPointerArray pointerArrayWithPointerFunctions:structDescriptorFunctions]; // now the pointer keys... NSPointerFunctions *pointerFunctions = [NSPointerFunctions pointerFunctionsWithOptions: NSPointerFunctionsOpaqueMemory |NSPointerFunctionsOpaquePersonality]; // now the maptable... NSMapTable *classMap = [[NSMapTable alloc] initWithKeyPointerFunctions:pointerFunctions valuePointerFunctions:structDescriptorFunctions capacity:32]; // set up mapping of nodes to their next life index NSMapTable *nodeIndex = [[NSMapTable alloc] initWithKeyPointerFunctions:pointerFunctions valuePointerFunctions:pointerFunctions capacity:32]; // set up a hashTable (set) of local nodes NSHashTable *locals = [[NSHashTable alloc] initWithPointerFunctions:pointerFunctions capacity:32]; nodeArray = [NSPointerArray pointerArrayWithPointerFunctions:structDescriptorFunctions]; rootArray = [NSPointerArray pointerArrayWithPointerFunctions:pointerFunctions]; stackArray = [NSPointerArray pointerArrayWithPointerFunctions:structDescriptorFunctions]; // pass one, establish nodes in heap, set up class table // pass two, process node references + everything else int pass = 1; int nthreads = 0; thread_descriptor_t *thread_descriptor = NULL; while (true) { int ch = fgetc(fp); switch (ch) { case EOF: { printf("unexpected end of input\n"); exit(1); } case REGISTER: { // REGISTER unsigned long byte_size = readLong(fp); if (pass == 1) { fseek(fp, byte_size, SEEK_CUR); thread_descriptor = NSAllocateCollectable(sizeof(thread_descriptor_t), NSScannedOption); [stackArray addPointer:thread_descriptor]; break; } thread_descriptor = (thread_descriptor_t *)[stackArray pointerAtIndex:nthreads++]; uint32_t nitems = byte_size/sizeof(void *); thread_descriptor->nRegisters = nitems; thread_descriptor->registers = (uint32_t *)NSAllocateCollectable(nitems*sizeof(uint32_t), 0); for (uint32_t i = 0; i < nitems; ++i) { void *pointer = (void *)readLong(fp); long index = (long)[nodeIndex objectForKey:pointer]; thread_descriptor->registers[i] = index; // +1 for sanity } break; } case THREAD: { // XXX not complete thread description - need register state also unsigned long byte_size = readLong(fp); if (pass == 1) { fseek(fp, byte_size, SEEK_CUR); //thread_descriptor = NSAllocateCollectable(sizeof(thread_descriptor_t), NSScannedOption); //[stackArray addPointer:thread_descriptor]; break; } printf("initWithRaw: thread of size %ld bytes\n", byte_size); long *stack = (long *)malloc(byte_size); fread(stack, byte_size, 1, fp); // pass one, fill each "long" with index+1 of node it references, if any uint32_t limit = byte_size/sizeof(long); uint32_t nitems = 0; for (int i = 0; i < limit; ++i) { bool isLocal = [locals member:(id)stack[i]]; stack[i] = (long)[nodeIndex objectForKey:(id)stack[i]]; if (isLocal) stack[i] = -stack[i]; // negate index for locals if (stack[i]) ++nitems; } printf("thread size %ld (%ld items) there were %d references found\n", byte_size, byte_size/sizeof(long), nitems); // now allocate an appropriately sized stack descriptor unsigned long size = nitems*2*sizeof(uint32_t) + sizeof(stack_descriptor_t); stack_descriptor_t *descriptor = (stack_descriptor_t *)NSAllocateCollectable(size, 0); // and fill it up descriptor->nitems = nitems; int stackWalker = 0; int descriptorWalker = 0; for (; stackWalker < limit; ++stackWalker) { if (stack[stackWalker]) { descriptor->items[descriptorWalker].offset = stackWalker; descriptor->items[descriptorWalker].index = stack[stackWalker] - 1; ++descriptorWalker; } } if (descriptorWalker != nitems) { printf("whoops, we set up %d descriptors but should have had %d\n", descriptorWalker, nitems); return nil; } free(stack); if (verbose) printf("stack descriptor %p (%ld) has %d items\n", descriptor, [stackArray count], nitems); //thread_descriptor = (thread_descriptor_t *)[stackArray pointerAtIndex:nthreads++]; thread_descriptor->stack_descriptor = descriptor; break; } case LOCAL: { void *address = readPointer(fp); printf("initWithRaw: local node at %p\n", address); if (pass == 1) { [locals addObject:(id)address]; thread_descriptor->nLocals++; break; } if (!thread_descriptor->locals) { thread_descriptor->locals = NSAllocateCollectable(thread_descriptor->nLocals*sizeof(uint32_t), 0); thread_descriptor->nLocals = 0; } long index = (long)[nodeIndex objectForKey:(id)address]; thread_descriptor->locals[thread_descriptor->nLocals++] = index; break; } case ROOT: { void *address = readPointer(fp); void *value = readPointer(fp); if (pass == 1) break; // wait till pass 2 long index = (long)[nodeIndex objectForKey:(id)value]; if (verbose > 1) printf("root at %p value %p, index %ld\n", address, value, index); if (index == 0) { printf("Hmm, root points to non-node ??\n"); } else { [rootArray addPointer:(void *)(index-1)]; } break; } case NODE: { void *address = readPointer(fp); unsigned long size = readLong(fp); int layout = readInt(fp); unsigned long refcount = readLong(fp); if (pass == 1) { // use +1 so that node 0 shows up; this is internal only, won't go to file that way [nodeIndex setObject:(id)(1 + [nodeIndex count]) forKey:(id)address]; //printf("node at %p, size %ld, layout %x, refcount %ld\n", address, size, layout, refcount); if ((layout & AUTO_UNSCANNED) != AUTO_UNSCANNED) { fseek(fp, size, SEEK_CUR); } } else { if (verbose > 1) printf("node at %p, size %ld, layout %x, refcount %ld\n", address, size, layout, refcount); node_descriptor_t *nodeDescriptor = NULL; if ((layout & AUTO_UNSCANNED) != AUTO_UNSCANNED) { char buffer[size]; fread(buffer, size, 1, fp); char *nibbles = NULL; if ((layout & AUTO_OBJECT) == AUTO_OBJECT) { id class; memmove(&class, buffer, sizeof(class)); class_descriptor_t *descriptor = (class_descriptor_t *)[classMap objectForKey:class]; layout |= (descriptor->class_index << 8); if (!descriptor) { printf("Hmm, object doesn't have a known class\n"); } else if (descriptor->strongLen) { nibbles = ((char *)descriptor) + descriptor->strongOffset; // XXX must write null byte } } nodeDescriptor = getNodeDescriptor(buffer, size, nibbles, nodeIndex); } else { nodeDescriptor = NSAllocateCollectable(sizeof(node_descriptor_t), 0); nodeDescriptor->nitems = 0; } nodeDescriptor->original = address; nodeDescriptor->size = size; if ([locals member:(id)address]) { printf("**** found %p as a local\n", address); layout |= 128; // mark it as a local } nodeDescriptor->layout = layout; nodeDescriptor->refcount = refcount; //printf("adding node descriptor %p\n", nodeDescriptor); [nodeArray addPointer:nodeDescriptor]; } break; } case WEAK: { void *address = readPointer(fp); void *value = readPointer(fp); if (verbose) printf("weak at %p value %p\n", address, value); break; } case CLASS: { void *address = readPointer(fp); char *name = readString(fp); char *slayout = readString(fp); char *wlayout = readString(fp); if (pass > 1) break; if (verbose) printf("class %s at %p", name, address); uint32_t dlen = sizeof(class_descriptor_t) + strlen(name); if (slayout) if (verbose) printf(" hasStrong"); if (slayout) dlen += strlen(slayout); if (wlayout) if (verbose) printf(" hasWeak"); if (wlayout) dlen += strlen(wlayout); if (verbose) printf("\n"); class_descriptor_t *descriptor = NSAllocateCollectable(sizeof(class_descriptor_t)+dlen, 0); descriptor->class_index = [classMap count]; // used in node_descriptor descriptor->nameOffset = sizeof(class_descriptor_t); descriptor->nameLen = strlen(name)+1; descriptor->strongOffset = descriptor->nameOffset + descriptor->nameLen; descriptor->strongLen = slayout ? (strlen(slayout)+1) : 0; descriptor->weakOffset = descriptor->strongOffset + descriptor->strongLen; descriptor->weakLen = wlayout ? (strlen(wlayout)+1) : 0; memmove(((char *)descriptor) + descriptor->nameOffset, name, descriptor->nameLen); if (slayout) memmove(((char *)descriptor) + descriptor->strongOffset, slayout, descriptor->strongLen); if (wlayout) memmove(((char *)descriptor) + descriptor->weakOffset, wlayout, descriptor->weakLen); [classArray addPointer:(id)descriptor]; [classMap setObject:(id)descriptor forKey:address]; break; } case END: { if (pass == 1) { fclose(fp); fp = openDump(filename); if (!fp) { printf("Whoops, can't reopen %s for reading\n", filename); return nil; } ++pass; if (verbose) printf("!!!! done with first pass!!!!\n"); } else { if (verbose) printf("!!!! done with second pass!!!!\n"); fclose(fp); return self; } break; } default: { printf("unrecognized key: %x, '%c'\n", ch, ch); exit(1); } } } } - (void)writeCookedToFilename:(const char *)filename { FILE *fp = fopen(filename, "w"); if (!fp) { printf("could not open %s for writing\n", filename); exit(1); return; } // write header fwrite(HEADER2, strlen(HEADER2), 1, fp); // write ObjC class info // first.. count! uint32_t count = [classArray count]; fwrite(&count, sizeof(count), 1, fp); // and now 'count' of them for (unsigned int index = 0; index < [classArray count]; ++index) { class_descriptor_t *descriptor = (class_descriptor_t *)[classArray pointerAtIndex:index]; uint32_t len = sizeof(class_descriptor_t) + descriptor->nameLen + descriptor->strongLen + descriptor->weakLen; fwrite(descriptor, len, 1, fp); } // write node info // first the count count = [nodeArray count]; fwrite(&count, sizeof(count), 1, fp); // and now the nodes for (unsigned int index = 0; index < [nodeArray count]; ++index) { node_descriptor_t *descriptor = (node_descriptor_t *)[nodeArray pointerAtIndex:index]; //uint32_t len = sizeof(node_descriptor_t) + descriptor->nitems*2*sizeof(uint32_t); uint32_t len = sizeof(node_descriptor_t); //printf("writing cooked [%d] orig %p layout %x size %d\n", index, descriptor->original, descriptor->layout, descriptor->size); fwrite(descriptor, len, 1, fp); } // write node links info for (unsigned int index = 0; index < [nodeArray count]; ++index) { node_descriptor_t *descriptor = (node_descriptor_t *)[nodeArray pointerAtIndex:index]; uint32_t len = descriptor->nitems*2*sizeof(uint32_t); fwrite(&descriptor->items[0], len, 1, fp); } // write roots info // first the count count = [rootArray count]; fwrite(&count, sizeof(count), 1, fp); // and now the nodes for (unsigned int counter = 0; counter < [rootArray count]; ++counter) { long index = (long)[rootArray pointerAtIndex:counter]; uint32_t intIndex = index; fwrite(&intIndex, sizeof(intIndex), 1, fp); } // write thread info count = [stackArray count]; fwrite(&count, sizeof(count), 1, fp); // and now the stacks for (unsigned int index = 0; index < [stackArray count]; ++index) { thread_descriptor_t *thread_descriptor = (thread_descriptor_t *)[stackArray pointerAtIndex:index]; stack_descriptor_t *descriptor = thread_descriptor->stack_descriptor; uint32_t len = sizeof(stack_descriptor_t) + descriptor->nitems*2*sizeof(uint32_t); //printf("descriptor %p says %d items for thread %d\n", descriptor, descriptor->nitems, index); fwrite(descriptor, len, 1, fp); // now the locals len = thread_descriptor->nLocals; fwrite(&len, sizeof(uint32_t), 1, fp); fwrite(thread_descriptor->locals, len*sizeof(uint32_t), 1, fp); // now the registers len = thread_descriptor->nRegisters; fwrite(&len, sizeof(uint32_t), 1, fp); fwrite(thread_descriptor->registers, len*sizeof(uint32_t), 1, fp); } fclose(fp); } @end /* * cooked file reader */ struct class_map_entry { char *className; class_descriptor_t diskClass; }; @interface Heap : NSObject { int nClasses; class_descriptor_t *__strong *__strong classes; int nNodes; node_descriptor_t *__strong *__strong node_descriptors; int nRoots; uint32_t *roots; int nThreads; thread_descriptor_t *__strong thread_descriptors; // weak // associations auto_zone_t *collector; } @end @implementation Heap - initWithCookedFilename:(const char *)cookedFile { FILE *fp = openCooked(cookedFile); if (!fp) { printf("Couldn't open cooked file: %s\n", cookedFile); exit(1); } /* * Classes first */ nClasses = readInt(fp); // allocate space for the pointers printf("Cooked: %d classes to read\n", nClasses); classes = (class_descriptor_t **)NSAllocateCollectable(nClasses*sizeof(struct class_descriptor_t *), NSScannedOption); // now read in size & then details for each class for (uint32_t i = 0; i < nClasses; ++i) { class_descriptor_t fixed; fread(&fixed, sizeof(class_descriptor_t), 1, fp); uint32_t stringsize = fixed.nameLen + fixed.strongLen + fixed.weakLen; uint32_t totalsize = sizeof(class_descriptor_t) + stringsize; classes[i] = (class_descriptor_t *)NSAllocateCollectable(totalsize, 0); memcpy(classes[i], &fixed, sizeof(class_descriptor_t)); // now pack the 3 strings in right afterwards fread(((char *)classes[i])+fixed.nameOffset, stringsize, 1, fp); //printf("read class %s\n", ((char *)classes[i])+fixed.nameOffset); } /* * Nodes next */ nNodes = readInt(fp); printf("Cooked: %d nodes to read\n", nNodes); node_descriptors = (node_descriptor_t **)NSAllocateCollectable(nNodes*sizeof(node_descriptor_t *), NSScannedOption); for (uint32_t i = 0; i < nNodes; ++i) { node_descriptor_t fixed; fread(&fixed, sizeof(node_descriptor_t), 1, fp); uint32_t totalsize = sizeof(node_descriptor_t) + fixed.nitems * 2 * sizeof(uint32_t); node_descriptors[i] = (node_descriptor_t *)NSAllocateCollectable(totalsize, 0); memcpy(node_descriptors[i], &fixed, sizeof(node_descriptor_t)); } for (uint32_t i = 0; i < nNodes; ++i) { uint32_t nitems = node_descriptors[i]->nitems; uint32_t size = nitems * 2 * sizeof(uint32_t); //printf("reading %d links\n", nitems); if (nitems) fread(((char *)node_descriptors[i])+sizeof(node_descriptor_t), size, 1, fp); } /* * roots */ nRoots = readInt(fp); roots = (uint32_t *)NSAllocateCollectable(nRoots*sizeof(uint32_t), 0); printf("Cooked: reading %d roots\n", nRoots); fread(roots, nRoots, sizeof(uint32_t), fp); /* * threads */ nThreads = readInt(fp); thread_descriptors = (thread_descriptor_t *)NSAllocateCollectable(nThreads*sizeof(thread_descriptor_t), NSScannedOption); for (uint32_t i = 0; i < nThreads; ++i) { /* do stack descriptor part only */ stack_descriptor_t stack_descriptor, *descriptorp; fread(&stack_descriptor, sizeof(stack_descriptor), 1, fp); uint32_t variablesize = stack_descriptor.nitems*2*sizeof(uint32_t); uint32_t totalsize = sizeof(stack_descriptor_t) + variablesize; descriptorp = (stack_descriptor_t *)NSAllocateCollectable(totalsize, 0); memcpy(descriptorp, &stack_descriptor, sizeof(stack_descriptor)); fread(((char *)descriptorp)+sizeof(stack_descriptor_t), variablesize, 1, fp); thread_descriptors[i].stack_descriptor = descriptorp; /* now the locals */ thread_descriptors[i].nLocals = readInt(fp); uint32_t size = thread_descriptors[i].nLocals*sizeof(uint32_t); thread_descriptors[i].locals = NSAllocateCollectable(size, 0); fread(thread_descriptors[i].locals, size, 1, fp); /* now the registers */ thread_descriptors[i].nRegisters = readInt(fp); size = thread_descriptors[i].nRegisters*sizeof(uint32_t); thread_descriptors[i].registers = NSAllocateCollectable(size, 0); fread(thread_descriptors[i].registers, size, 1, fp); } } @end void printCookedFilename(const char *filename) { FILE *fp = openCooked(filename); if (!fp) { printf("Couldn't open cooked file: %s\n", filename); exit(1); } uint32_t count = readInt(fp); printf("%d classes to read...\n", count); struct class_map_entry *classMap = NSAllocateCollectable(count*sizeof(struct class_map_entry), NSScannedOption); for (uint32_t i = 0; i < count; ++i) { // if we memory mapped the file and put the string info in its own section // after the constant sized class info we could simply patch the offset // with the base pointer, or something, thinking about longs vs uint32_t.. fread(&classMap[i].diskClass, sizeof(class_descriptor_t), 1, fp); classMap[i].className = readStringSize(fp, classMap[i].diskClass.nameLen); readStringSize(fp, classMap[i].diskClass.strongLen); readStringSize(fp, classMap[i].diskClass.weakLen); printf("read class %s\n", classMap[i].className); } // now do the nodes... count = readInt(fp); printf("%d nodes to read...\n", count); node_descriptor_t *node_descriptors = (node_descriptor_t *)NSAllocateCollectable(count*sizeof(node_descriptor_t), 1); for (uint32_t i = 0; i < count; ++i) { node_descriptor_t *node_descriptor = &node_descriptors[i]; fread(node_descriptor, sizeof(node_descriptor_t), 1, fp); //printf("node[%d] %p (%p) size %d, layout %x, nitems %d\n", i, node_descriptor, node_descriptor->original, node_descriptor->size, node_descriptor->layout, node_descriptor->nitems); } for (uint32_t i = 0; i < count; ++i) { node_descriptor_t *node_descriptor = &node_descriptors[i]; printf("node[%d] (%p) size %d, layout %x, nitems %d", i, node_descriptor->original, node_descriptor->size, node_descriptor->layout, node_descriptor->nitems); uint32_t classIndex = node_descriptor->layout >> 8; if ((node_descriptor->layout & AUTO_OBJECT) == AUTO_OBJECT) { printf(", class: %s", classMap[classIndex].className); } if ((node_descriptor->layout & 128) == 128) { printf(", local"); } printf("\n"); uint32_t nitems = node_descriptors[i].nitems; struct { uint32_t offset; uint32_t index; } items[nitems]; if (nitems) fread(&items[0], sizeof(items), 1, fp); for (uint32_t j = 0; j < nitems; ++j) { printf(" [%d] -> node[%d]\n", items[j].offset, items[j].index); } } // now do the roots... count = readInt(fp); printf("%d roots to read...\n", count); uint32_t roots[count]; fread(roots, sizeof(roots), 1, fp); for (uint32_t i = 0; i < count; ++i) { printf("root [%d]\n", roots[i]); } // now do the stacks... count = readInt(fp); printf("%d stacks to read...\n", count); for (uint32_t i = 0; i < count; ++i) { stack_descriptor_t stack_descriptor; fread(&stack_descriptor, sizeof(stack_descriptor), 1, fp); printf("stack %d has %d items\n", i, stack_descriptor.nitems); struct { uint32_t offset; uint32_t index; } *items = NSAllocateCollectable(stack_descriptor.nitems*2*sizeof(uint32_t), 0); fread(&items[0], stack_descriptor.nitems*2*sizeof(uint32_t), 1, fp); for (uint32_t j = 0; j < stack_descriptor.nitems; ++j) { // need to use absolute value - 1 int32_t index = items[j].index; bool isLocal = index < 0; if (isLocal) index = -index; printf(" [%d] -> node[%d] %s\n", items[j].offset, index, isLocal ? "isLocal" : ""); } printf("Locals\n"); uint32_t nitems = readInt(fp); uint32_t *memory = malloc(nitems*sizeof(uint32_t)); fread(memory, nitems, sizeof(uint32_t), fp); for (uint32_t j = 0; j < nitems; ++j) { // -1 bias? printf("local node[%d]\n", memory[j]); } free(memory); printf("Registers\n"); nitems = readInt(fp); memory = malloc(nitems*sizeof(uint32_t)); fread(memory, nitems, sizeof(uint32_t), fp); for (uint32_t j = 0; j < nitems; ++j) { // adjust for -1 bias if (memory[j]) printf("register node[%d]\n", memory[j]-1); } free(memory); } } char RawName[1024]; void takeRawSnapshot() { static id X = nil; if (!X) X = [NSObject new]; printf("Should see %p as a global\n", &X); //static __weak id Y = nil; Y = X; printf("Should see %p as a weak -> %p\n", &Y, X); id local = [NSObject new]; printf("Should see %p as a local\n", local); //printLiveDump((auto_zone_t *)NSDefaultMallocZone()); //auto_zone_create_dump_file((auto_zone_t *)NSDefaultMallocZone(), rawName); objc_dumpHeap(RawName, sizeof(RawName)); //printRawDumpFile("/private/tmp/test.dumpster"); } void cook(const char *rawName, const char *cookedName) { Dumpster *dumpster = [[Dumpster alloc] initWithRawFilename:rawName verbose:false]; [dumpster writeCookedToFilename:cookedName]; } void enliven(const char *cookedName) { } int main(int argc, char *argv[]) { //printLiveDump(); char cookedName[1024]; if (0) { takeRawSnapshot(); printf("raw file written to %s\n", RawName); sprintf(cookedName, "%s.cooked", RawName); } else { strcpy(RawName, argv[1]); sprintf(cookedName, "%s.cooked", RawName); } cook(RawName, cookedName); //printCookedFilename(cookedName); Heap *heap = [[Heap alloc] initWithCookedFilename:cookedName]; return 0; }