/* * Copyright (c) 2004 Apple Computer, Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * Copyright (c) 2004 Apple Computer, Inc. All Rights Reserved. * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ /* * objc-auto.m * Copyright 2004 Apple Computer, Inc. */ #import "objc-auto.h" #import #import #import #import #import #import "objc-private.h" #import "objc-rtp.h" #import "maptable.h" // Types and prototypes from non-open-source auto_zone.h #include #include typedef malloc_zone_t auto_zone_t; typedef uint64_t auto_date_t; typedef struct { unsigned version; // reserved - 0 for now /* Memory usage */ unsigned long long num_allocs; // number of allocations performed volatile unsigned blocks_in_use;// number of pointers in use unsigned bytes_in_use; // sum of the sizes of all pointers in use unsigned max_bytes_in_use; // high water mark unsigned bytes_allocated; /* GC stats */ /* When there is an array, 0 stands for full collection, 1 for generational */ unsigned num_collections[2]; boolean_t last_collection_was_generational; unsigned bytes_in_use_after_last_collection[2]; unsigned bytes_allocated_after_last_collection[2]; unsigned bytes_freed_during_last_collection[2]; auto_date_t duration_last_collection[2]; auto_date_t duration_all_collections[2]; } auto_statistics_t; typedef enum { AUTO_COLLECTION_NO_COLLECTION = 0, AUTO_COLLECTION_GENERATIONAL_COLLECTION, AUTO_COLLECTION_FULL_COLLECTION } auto_collection_mode_t; typedef enum { AUTO_LOG_COLLECTIONS = (1 << 1), // log whenever a collection occurs AUTO_LOG_COLLECT_DECISION = (1 << 2), // logs when deciding whether to collect AUTO_LOG_GC_IMPL = (1 << 3), // logs to help debug GC AUTO_LOG_REGIONS = (1 << 4), // log whenever a new region is allocated AUTO_LOG_UNUSUAL = (1 << 5), // log unusual circumstances AUTO_LOG_WEAK = (1 << 6), // log weak reference manipulation AUTO_LOG_ALL = (~0u) } auto_log_mask_t; typedef struct auto_zone_cursor *auto_zone_cursor_t; typedef void (*auto_zone_foreach_object_t) (auto_zone_cursor_t cursor, void (*op) (void *ptr, void *data), void* data); typedef struct { unsigned version; // reserved - 0 for now boolean_t trace_stack_conservatively; boolean_t (*should_collect)(auto_zone_t *, const auto_statistics_t *stats, boolean_t about_to_create_a_new_region); // called back when a threshold is reached; must say whether to collect (and what type) // all locks are released when that call back is called // callee is free to call for statistics or reset the threshold unsigned ask_should_collect_frequency; // should_collect() is called each allocations or free, where is this field unsigned full_vs_gen_frequency; // ratio of generational vs. full GC for the frequency based ones int (*collection_should_interrupt)(void); // called during scan to see if garbage collection should be aborted void (*invalidate)(auto_zone_t *zone, void *ptr, void *collection_context); void (*batch_invalidate) (auto_zone_t *zone, auto_zone_foreach_object_t foreach, auto_zone_cursor_t cursor); // called back with an object that is unreferenced // callee is responsible for invalidating object state void (*resurrect) (auto_zone_t *zone, void *ptr); // convert the object into a safe-to-use, but otherwise "undead" object. no guarantees are made about the // contents of this object, other than its liveness. unsigned word0_mask; // mask for defining class void (*note_unknown_layout)(auto_zone_t *zone, unsigned class_field); // called once for each class encountered for which we don't know the layout // callee can decide to register class with auto_zone_register_layout(), or do nothing // Note that this function is called during GC and therefore should not do any auto-allocation char* (*name_for_address) (auto_zone_t *zone, vm_address_t base, vm_address_t offset); auto_log_mask_t log; // set to auto_log_mask_t bits as desired boolean_t disable_generational; // if true, ignores requests to do generational GC. boolean_t paranoid_generational; // if true, always compares generational GC result to full GC garbage list boolean_t malloc_stack_logging; // if true, uses malloc_zone_malloc() for stack logging. } auto_collection_control_t; typedef enum { AUTO_TYPE_UNKNOWN = -1, // this is an error value AUTO_UNSCANNED = 1, AUTO_OBJECT = 2, AUTO_MEMORY_SCANNED = 0, // holds conservatively scanned pointers AUTO_MEMORY_UNSCANNED = AUTO_UNSCANNED, // holds unscanned memory (bits) AUTO_OBJECT_SCANNED = AUTO_OBJECT, // first word is 'isa', may have 'exact' layout info elsewhere AUTO_OBJECT_UNSCANNED = AUTO_OBJECT | AUTO_UNSCANNED, // first word is 'isa', good for bits or auto_zone_retain'ed items } auto_memory_type_t; typedef struct { vm_address_t referent; vm_address_t referrer_base; intptr_t referrer_offset; } auto_reference_t; typedef void (*auto_reference_recorder_t)(auto_zone_t *zone, void *ctx, auto_reference_t reference); static void auto_collect(auto_zone_t *zone, auto_collection_mode_t mode, void *collection_context); static auto_collection_control_t *auto_collection_parameters(auto_zone_t *zone); static const auto_statistics_t *auto_collection_statistics(auto_zone_t *zone); static void auto_enumerate_references(auto_zone_t *zone, void *referent, auto_reference_recorder_t callback, void *stack_bottom, void *ctx); static void auto_enumerate_references_no_lock(auto_zone_t *zone, void *referent, auto_reference_recorder_t callback, void *stack_bottom, void *ctx); static auto_zone_t *auto_zone(void); static void auto_zone_add_root(auto_zone_t *zone, void *root, size_t size); static void* auto_zone_allocate_object(auto_zone_t *zone, size_t size, auto_memory_type_t type, boolean_t initial_refcount_to_one, boolean_t clear); static const void *auto_zone_base_pointer(auto_zone_t *zone, const void *ptr); static auto_memory_type_t auto_zone_get_layout_type(auto_zone_t *zone, void *ptr); static auto_memory_type_t auto_zone_get_layout_type_no_lock(auto_zone_t *zone, void *ptr); static boolean_t auto_zone_is_finalized(auto_zone_t *zone, const void *ptr); static boolean_t auto_zone_is_valid_pointer(auto_zone_t *zone, const void *ptr); static unsigned int auto_zone_release(auto_zone_t *zone, void *ptr); static void auto_zone_retain(auto_zone_t *zone, void *ptr); static unsigned int auto_zone_retain_count_no_lock(auto_zone_t *zone, const void *ptr); static void auto_zone_set_class_list(int (*get_class_list)(void **buffer, int count)); static size_t auto_zone_size_no_lock(auto_zone_t *zone, const void *ptr); static void auto_zone_start_monitor(boolean_t force); static void auto_zone_write_barrier(auto_zone_t *zone, void *recipient, const unsigned int offset_in_bytes, const void *new_value); static void *auto_zone_write_barrier_memmove(auto_zone_t *zone, void *dst, const void *src, size_t size); static void record_allocation(Class cls); static auto_zone_t *gc_zone_init(void); __private_extern__ BOOL UseGC NOBSS = NO; static BOOL RecordAllocations = NO; static int IsaStompBits = 0x0; static auto_zone_t *gc_zone = NULL; static BOOL gc_zone_finalizing = NO; static intptr_t gc_collection_threshold = 128 * 1024; static size_t gc_collection_ratio = 100, gc_collection_counter = 0; static NXMapTable *gc_finalization_safe_classes = NULL; static BOOL gc_roots_retained = YES; /*********************************************************************** * Internal utilities **********************************************************************/ #define ISAUTOOBJECT(x) (auto_zone_is_valid_pointer(gc_zone, (x))) // A should-collect callback that never allows collection. // Currently used to prevent on-demand collection. static boolean_t objc_never_collect(auto_zone_t *zone, const auto_statistics_t *stats, boolean_t about_to_create_a_new_region) { return false; } /*********************************************************************** * Utility exports * Called by various libraries. **********************************************************************/ void objc_collect(void) { if (UseGC) { auto_collect(gc_zone, AUTO_COLLECTION_FULL_COLLECTION, NULL); } } void objc_collect_if_needed(unsigned long options) { if (UseGC) { const auto_statistics_t *stats = auto_collection_statistics(gc_zone); if (options & OBJC_GENERATIONAL) { // use an absolute memory allocated threshold to decide when to generationally collect. intptr_t bytes_allocated_since_last_gc = stats->bytes_in_use - stats->bytes_in_use_after_last_collection[stats->last_collection_was_generational]; if (bytes_allocated_since_last_gc >= gc_collection_threshold) { // malloc_printf("bytes_allocated_since_last_gc = %ld\n", bytes_allocated_since_last_gc); // periodically run a full collection until to keep memory usage down, controlled by OBJC_COLLECTION_RATIO (100 to 1 is the default). auto_collection_mode_t mode = AUTO_COLLECTION_GENERATIONAL_COLLECTION; if (gc_collection_counter++ >= gc_collection_ratio) { mode = AUTO_COLLECTION_FULL_COLLECTION; gc_collection_counter = 0; } auto_collect(gc_zone, mode, NULL); } } else { // Run full collections until we no longer recover additional objects. We use two measurements // to determine whether or not the collector is being productive: the total number of blocks // must be shrinking, and the collector must itself be freeing bytes. Otherwise, another thread // could be responsible for reducing the block count. On the other hand, another thread could // be generating a lot of garbage, which would keep us collecting. This will need even more // tuning to prevent starvation, etc. unsigned blocks_in_use; do { blocks_in_use = stats->blocks_in_use; auto_collect(gc_zone, AUTO_COLLECTION_FULL_COLLECTION, NULL); // malloc_printf("bytes freed = %ld\n", stats->bytes_freed_during_last_collection[0]); } while (stats->bytes_freed_during_last_collection[0] > 0 && stats->blocks_in_use < blocks_in_use); gc_collection_counter = 0; } } } void objc_collect_generation(void) { if (UseGC) { auto_collect(gc_zone, AUTO_COLLECTION_GENERATIONAL_COLLECTION, NULL); } } unsigned int objc_numberAllocated(void) { const auto_statistics_t *stats = auto_collection_statistics(gc_zone); return stats->blocks_in_use; } BOOL objc_isAuto(id object) { return UseGC && ISAUTOOBJECT(object) != 0; } BOOL objc_collecting_enabled(void) { return UseGC; } /*********************************************************************** * Memory management. * Called by CF and Foundation. **********************************************************************/ // Allocate an object in the GC zone, with the given number of extra bytes. id objc_allocate_object(Class cls, int extra) { id result = (id)auto_zone_allocate_object(gc_zone, cls->instance_size + extra, AUTO_OBJECT_SCANNED, false, true); result->isa = cls; if (RecordAllocations) record_allocation(cls); return result; } /*********************************************************************** * Write barrier exports * Called by pretty much all GC-supporting code. **********************************************************************/ // Platform-independent write barriers // These contain the UseGC check that the platform-specific // runtime-rewritten implementations do not. id objc_assign_strongCast_generic(id value, id *dest) { if (UseGC) { return objc_assign_strongCast_gc(value, dest); } else { return (*dest = value); } } id objc_assign_global_generic(id value, id *dest) { if (UseGC) { return objc_assign_global_gc(value, dest); } else { return (*dest = value); } } id objc_assign_ivar_generic(id value, id dest, unsigned int offset) { if (UseGC) { return objc_assign_ivar_gc(value, dest, offset); } else { id *slot = (id*) ((char *)dest + offset); return (*slot = value); } } #if defined(__ppc__) // PPC write barriers are in objc-auto-ppc.s // write_barrier_init conditionally stomps those to jump to the _impl versions. #else // use generic implementation until time can be spent on optimizations id objc_assign_strongCast(id value, id *dest) { return objc_assign_strongCast_generic(value, dest); } id objc_assign_global(id value, id *dest) { return objc_assign_global_generic(value, dest); } id objc_assign_ivar(id value, id dest, unsigned int offset) { return objc_assign_ivar_generic(value, dest, offset); } // not defined(__ppc__) #endif void *objc_memmove_collectable(void *dst, const void *src, size_t size) { if (UseGC) { return auto_zone_write_barrier_memmove(gc_zone, dst, src, size); } else { return memmove(dst, src, size); } } /*********************************************************************** * Testing tools * Used to isolate resurrection of garbage objects during finalization. **********************************************************************/ BOOL objc_is_finalized(void *ptr) { return ptr != NULL && auto_zone_is_finalized(gc_zone, ptr); } /*********************************************************************** * CF-only write barrier exports * Called by CF only. * The gc_zone guards are not thought to be necessary **********************************************************************/ // Exported as very private SPI to Foundation to tell CF about void* objc_assign_ivar_address_CF(void *value, void *base, void **slot) { if (value && gc_zone) { if (auto_zone_is_valid_pointer(gc_zone, base)) { unsigned int offset = (((char *)slot)-(char *)base); auto_zone_write_barrier(gc_zone, base, offset, value); } } return (*slot = value); } // Same as objc_assign_strongCast_gc, should tell Foundation to use _gc version instead // exported as very private SPI to Foundation to tell CF about void* objc_assign_strongCast_CF(void* value, void **slot) { if (value && gc_zone) { void *base = (void *)auto_zone_base_pointer(gc_zone, (void*)slot); if (base) { unsigned int offset = (((char *)slot)-(char *)base); auto_zone_write_barrier(gc_zone, base, offset, value); } } return (*slot = value); } /*********************************************************************** * Write barrier implementations, optimized for when GC is known to be on * Called by the write barrier exports only. * These implementations assume GC is on. The exported function must * either perform the check itself or be conditionally stomped at * startup time. **********************************************************************/ __private_extern__ id objc_assign_strongCast_gc(id value, id *slot) { id base; base = (id) auto_zone_base_pointer(gc_zone, (void*)slot); if (base) { unsigned int offset = (((char *)slot)-(char *)base); auto_zone_write_barrier(gc_zone, base, (char*)slot - (char*)base, value); } return (*slot = value); } __private_extern__ id objc_assign_global_gc(id value, id *slot) { if (gc_roots_retained) { if (value && ISAUTOOBJECT(value)) { if (auto_zone_is_finalized(gc_zone, value)) _objc_inform("GC: storing an already collected object %p into global memory at %p\n", value, slot); auto_zone_retain(gc_zone, value); } if (*slot && ISAUTOOBJECT(*slot)) { auto_zone_release(gc_zone, *slot); } } else { // use explicit root registration. if (value && ISAUTOOBJECT(value)) { if (auto_zone_is_finalized(gc_zone, value)) _objc_inform("GC: storing an already collected object %p into global memory at %p\n", value, slot); auto_zone_add_root(gc_zone, slot, sizeof(id*)); } } return (*slot = value); } __private_extern__ id objc_assign_ivar_gc(id value, id base, unsigned int offset) { id *slot = (id*) ((char *)base + offset); if (value) { if (ISAUTOOBJECT(base)) { auto_zone_write_barrier(gc_zone, base, offset, value); if (gc_zone_finalizing && (auto_zone_get_layout_type(gc_zone, value) & AUTO_OBJECT) != AUTO_OBJECT) { // XXX_PCB: Hack, don't allow resurrection by inhibiting assigns of garbage, non-object, pointers. // XXX BG: move this check into auto & institute a new policy for resurrection, to wit: // Resurrected Objects should go on a special list during finalization & be zombified afterwards // using the noisy isa-slam hack. if (auto_zone_is_finalized(gc_zone, value) && !auto_zone_is_finalized(gc_zone, base)) { _objc_inform("GC: *** objc_assign_ivar_gc: preventing a resurrecting store of %p into %p + %d\n", value, base, offset); value = nil; } } } else { _objc_inform("GC: *** objc_assign_ivar_gc: %p + %d isn't in the auto_zone.\n", base, offset); } } return (*slot = value); } /*********************************************************************** * Finalization support * Called by auto and Foundation. **********************************************************************/ #define USE_ISA_HACK 1 #define DO_ISA_DEBUG 0 #if USE_ISA_HACK // NSDeallocatedObject silently ignores all messages sent to it. @interface NSDeallocatedObject { @public Class IsA; } + (Class)class; @end static unsigned int FTCount, FTSize; static struct FTTable { NSDeallocatedObject *object; Class class; } *FTTablePtr; /* a quick and very dirty table to map finalized pointers to their isa's */ static void addPointerFT(NSDeallocatedObject *object, Class class) { if (FTCount >= FTSize) { FTSize = 2*(FTSize + 10); FTTablePtr = realloc(FTTablePtr, FTSize*sizeof(struct FTTable)); } FTTablePtr[FTCount].object = object; FTTablePtr[FTCount].class = class; ++FTCount; } static Class classForPointerFT(NSDeallocatedObject *object) { int i; for (i = 0; i < FTCount; ++i) if (FTTablePtr[i].object == object) return FTTablePtr[i].class; return NULL; } void objc_stale(id object) { } @implementation NSDeallocatedObject + (Class)class { return self; } - (Class)class { return classForPointerFT(self); } - (BOOL)isKindOfClass:(Class)aClass { Class cls; for (cls = classForPointerFT(self); nil != cls; cls = cls->super_class) if (cls == (Class)aClass) return YES; return NO; } + forward:(SEL)aSelector :(marg_list)args { return nil; } - forward:(SEL)aSelector :(marg_list)args { Class class = classForPointerFT(self); if (!class) { if (IsaStompBits & 0x2) _objc_inform("***finalized & *recovered* object %p of being sent '%s'!!\n", self, sel_getName(aSelector)); // if its not in the current table, then its being messaged from a STALE REFERENCE!! objc_stale(self); return nil; } if (IsaStompBits & 0x4) _objc_inform("finalized object %p of class %s being sent %s\n", self, class->name, sel_getName(aSelector)); return nil; } @end static Class _NSDeallocatedObject = Nil; static IMP _NSObject_finalize = NULL; // Handed to and then called by auto static void sendFinalize(auto_zone_t *zone, void* ptr, void *context) { if (ptr == NULL) { // special signal to mark end of finalization phase if (IsaStompBits & 0x8) _objc_inform("----finalization phase over-----"); FTCount = 0; return; } id object = ptr; Class cls = object->isa; if (cls == _NSDeallocatedObject) { // already finalized, do nothing _objc_inform("sendFinalize called on NSDeallocatedObject %p", ptr); return; } IMP finalizeMethod = class_lookupMethod(cls, @selector(finalize)); if (finalizeMethod == &_objc_msgForward) { _objc_inform("GC: class '%s' does not implement -finalize!", cls->name); } gc_zone_finalizing = YES; @try { // fixme later, optimize away calls to NSObject's -finalize (*finalizeMethod)(object, @selector(finalize)); } @catch (id exception) { _objc_inform("GC: -finalize resulted in an exception being thrown %p!", exception); // FIXME: what about uncaught C++ exceptions? Here's an idea, define a category // in a .mm file, so we can catch both flavors of exceptions. // @interface NSObject (TryToFinalize) // - (BOOL)tryToFinalize { // try { // @try { // [self finalize]; // } @catch (id exception) { // return NO; // } // } catch (...) { // return NO; // } // return YES; // } // @end } gc_zone_finalizing = NO; if (IsaStompBits) { NSDeallocatedObject *dead = (NSDeallocatedObject *)object; // examine list of okay classes and leave alone XXX get from file // fixme hack: smash isa to dodge some out-of-order finalize bugs // the following are somewhat finalize order safe //if (!strcmp(dead->oldIsA->name, "NSCFArray")) return; //if (!strcmp(dead->oldIsA->name, "NSSortedArray")) return; if (IsaStompBits & 0x8) printf("adding [%d] %p %s\n", FTCount, dead, dead->IsA->name); addPointerFT(dead, dead->IsA); objc_assign_ivar(_NSDeallocatedObject, dead, 0); } } static void finalizeOneObject(void *ptr, void *data) { id object = ptr; Class cls = object->isa; if (cls == _NSDeallocatedObject) { // already finalized, do nothing _objc_inform("finalizeOneObject called on NSDeallocatedObject %p", ptr); return; } IMP finalizeMethod = class_lookupMethod(cls, @selector(finalize)); if (finalizeMethod == &_objc_msgForward) { _objc_inform("GC: class '%s' does not implement -finalize!", cls->name); } // fixme later, optimize away calls to NSObject's -finalize (*finalizeMethod)(object, @selector(finalize)); if (IsaStompBits) { NSDeallocatedObject *dead = (NSDeallocatedObject *)object; // examine list of okay classes and leave alone XXX get from file // fixme hack: smash isa to dodge some out-of-order finalize bugs // the following are somewhat finalize order safe //if (!strcmp(dead->oldIsA->name, "NSCFArray")) return; //if (!strcmp(dead->oldIsA->name, "NSSortedArray")) return; if (gc_finalization_safe_classes && NXMapGet(gc_finalization_safe_classes, cls->name)) { // malloc_printf("&&& finalized safe instance of %s &&&\n", cls->name); return; } if (IsaStompBits & 0x8) printf("adding [%d] %p %s\n", FTCount, dead, dead->IsA->name); addPointerFT(dead, dead->IsA); objc_assign_ivar(_NSDeallocatedObject, dead, 0); } } static void batchFinalize(auto_zone_t *zone, auto_zone_foreach_object_t foreach, auto_zone_cursor_t cursor) { gc_zone_finalizing = YES; for (;;) { @try { // eventually foreach(cursor, objc_msgSend, @selector(finalize)); // foreach(cursor, finalizeOneObject, NULL); foreach(cursor, objc_msgSend, @selector(finalize)); // non-exceptional return means finalization is complete. break; } @catch (id exception) { _objc_inform("GC: -finalize resulted in an exception being thrown %p!", exception); } } gc_zone_finalizing = NO; } @interface NSResurrectedObject { @public Class _isa; // [NSResurrectedObject class] Class _old_isa; // original class unsigned _resurrections; // how many times this object has been resurrected. } + (Class)class; @end @implementation NSResurrectedObject + (Class)class { return self; } - (Class)class { return _isa; } + forward:(SEL)aSelector :(marg_list)args { return nil; } - forward:(SEL)aSelector :(marg_list)args { _objc_inform("**resurrected** object %p of class %s being sent message '%s'\n", self, _old_isa->name, sel_getName(aSelector)); return nil; } - (void)finalize { _objc_inform("**resurrected** object %p of class %s being finalized\n", self, _old_isa->name); } @end static Class _NSResurrectedObject; static void resurrectZombie(auto_zone_t *zone, void *ptr) { NSResurrectedObject *zombie = (NSResurrectedObject*) ptr; if (zombie->_isa != _NSResurrectedObject) { Class old_isa = zombie->_isa; zombie->_isa = _NSResurrectedObject; zombie->_old_isa = old_isa; zombie->_resurrections = 1; } else { zombie->_resurrections++; } } /*********************************************************************** * Allocation recording * For development purposes. **********************************************************************/ static NXMapTable *the_histogram = NULL; static pthread_mutex_t the_histogram_lock = PTHREAD_MUTEX_INITIALIZER; static void record_allocation(Class cls) { pthread_mutex_lock(&the_histogram_lock); unsigned long count = (unsigned long) NXMapGet(the_histogram, cls); NXMapInsert(the_histogram, cls, (const void*) (count + 1)); pthread_mutex_unlock(&the_histogram_lock); } void objc_allocation_histogram(void) { Class cls; unsigned long count; NXMapState state = NXInitMapState(the_histogram); printf("struct histogram {\n\tconst char* name;\n\tunsigned long instance_size;\n\tunsigned long count;\n} the_histogram[] = {\n"); while (NXNextMapState(the_histogram, &state, (const void**) &cls, (const void**) &count)) { printf("\t{ \"%s\", %lu, %lu },\n", cls->name, (unsigned long) cls->instance_size, count); } printf("};\n"); } static char *name_for_address(auto_zone_t *zone, vm_address_t base, vm_address_t offset, int withRetainCount); static char* objc_name_for_address(auto_zone_t *zone, vm_address_t base, vm_address_t offset) { return name_for_address(zone, base, offset, false); } /*********************************************************************** * Initialization **********************************************************************/ // Always called by _objcInit, even if GC is off. __private_extern__ void gc_init(BOOL on) { UseGC = on; if (PrintGC) { _objc_inform("GC: is %s", on ? "ON" : "OFF"); } if (UseGC) { // Set up the GC zone gc_zone = gc_zone_init(); // no NSObject until Foundation calls objc_collect_init() _NSObject_finalize = &_objc_msgForward; // Set up allocation recording RecordAllocations = (getenv("OBJC_RECORD_ALLOCATIONS") != NULL); if (RecordAllocations) the_histogram = NXCreateMapTable(NXPtrValueMapPrototype, 1024); if (getenv("OBJC_FINALIZATION_SAFE_CLASSES")) { FILE *f = fopen(getenv("OBJC_FINALIZATION_SAFE_CLASSES"), "r"); if (f != NULL) { char *line; size_t length; gc_finalization_safe_classes = NXCreateMapTable(NXStrValueMapPrototype, 17); while ((line = fgetln(f, &length)) != NULL) { char *last = &line[length - 1]; if (*last == '\n') *last = '\0'; // strip off trailing newline. char *className = strdup(line); NXMapInsert(gc_finalization_safe_classes, className, className); } fclose(f); } } } else { auto_zone_start_monitor(false); auto_zone_set_class_list(objc_getClassList); } } static auto_zone_t *gc_zone_init(void) { auto_zone_t *result; // result = auto_zone_create("objc auto collected zone"); result = auto_zone(); // honor existing entry point for now (fixme) auto_collection_control_t *control = auto_collection_parameters(result); // set up the magic control parameters control->invalidate = sendFinalize; control->batch_invalidate = batchFinalize; control->resurrect = resurrectZombie; control->name_for_address = objc_name_for_address; // don't collect "on-demand" until... all Cocoa allocations are outside locks control->should_collect = objc_never_collect; control->ask_should_collect_frequency = UINT_MAX; control->trace_stack_conservatively = YES; // No interruption callback yet. Foundation will install one later. control->collection_should_interrupt = NULL; // debug: if set, only do full generational; sometimes useful for bringup control->disable_generational = getenv("AUTO_DISABLE_GENERATIONAL") != NULL; // debug: always compare generational GC result to full GC garbage list // this *can* catch missing write-barriers and other bugs control->paranoid_generational = (getenv("AUTO_PARANOID_GENERATIONAL") != NULL); // if set take a slightly slower path for object allocation control->malloc_stack_logging = (getenv("MallocStackLogging") != NULL || getenv("MallocStackLoggingNoCompact") != NULL); // logging level: none by default control->log = 0; if (getenv("AUTO_LOG_NOISY")) control->log |= AUTO_LOG_COLLECTIONS; if (getenv("AUTO_LOG_ALL")) control->log |= AUTO_LOG_ALL; if (getenv("AUTO_LOG_COLLECTIONS")) control->log |= AUTO_LOG_COLLECTIONS; if (getenv("AUTO_LOG_COLLECT_DECISION")) control->log |= AUTO_LOG_COLLECT_DECISION; if (getenv("AUTO_LOG_GC_IMPL")) control->log |= AUTO_LOG_GC_IMPL; if (getenv("AUTO_LOG_REGIONS")) control->log |= AUTO_LOG_REGIONS; if (getenv("AUTO_LOG_UNUSUAL")) control->log |= AUTO_LOG_UNUSUAL; if (getenv("AUTO_LOG_WEAK")) control->log |= AUTO_LOG_WEAK; if (getenv("OBJC_ISA_STOMP")) { // != 0, stomp isa // 0x1, just stomp, no messages // 0x2, log messaging after reclaim (break on objc_stale()) // 0x4, log messages sent during finalize // 0x8, log all finalizations IsaStompBits = strtol(getenv("OBJC_ISA_STOMP"), NULL, 0); } if (getenv("OBJC_COLLECTION_THRESHOLD")) { gc_collection_threshold = (size_t) strtoul(getenv("OBJC_COLLECTION_THRESHOLD"), NULL, 0); } if (getenv("OBJC_COLLECTION_RATIO")) { gc_collection_ratio = (size_t) strtoul(getenv("OBJC_COLLECTION_RATIO"), NULL, 0); } if (getenv("OBJC_EXPLICIT_ROOTS")) gc_roots_retained = NO; return result; } // Called by Foundation to install auto's interruption callback. malloc_zone_t *objc_collect_init(int (*callback)(void)) { // Find NSObject's finalize method now that Foundation is loaded. // fixme only look for the base implementation, not a category's _NSDeallocatedObject = objc_getClass("NSDeallocatedObject"); _NSResurrectedObject = objc_getClass("NSResurrectedObject"); _NSObject_finalize = class_lookupMethod(objc_getClass("NSObject"), @selector(finalize)); if (_NSObject_finalize == &_objc_msgForward) { _objc_fatal("GC: -[NSObject finalize] unimplemented!"); } // Don't install the callback if OBJC_DISABLE_COLLECTION_INTERRUPT is set if (gc_zone && getenv("OBJC_DISABLE_COLLECTION_INTERRUPT") == NULL) { auto_collection_control_t *ctrl = auto_collection_parameters(gc_zone); ctrl->collection_should_interrupt = callback; } return (malloc_zone_t *)gc_zone; } /*********************************************************************** * Debugging **********************************************************************/ /* This is non-deadlocking with respect to malloc's locks EXCEPT: * %ls, %a, %A formats * more than 8 args */ static void objc_debug_printf(const char *format, ...) { va_list ap; va_start(ap, format); vfprintf(stderr, format, ap); va_end(ap); } static malloc_zone_t *objc_debug_zone(void) { static malloc_zone_t *z = NULL; if (!z) { z = malloc_create_zone(4096, 0); malloc_set_zone_name(z, "objc-auto debug"); } return z; } static char *_malloc_append_unsigned(unsigned value, unsigned base, char *head) { if (!value) { head[0] = '0'; } else { if (value >= base) head = _malloc_append_unsigned(value / base, base, head); value = value % base; head[0] = (value < 10) ? '0' + value : 'a' + value - 10; } return head+1; } static void strcati(char *str, unsigned value) { str = _malloc_append_unsigned(value, 10, str + strlen(str)); str[0] = '\0'; } static void strcatx(char *str, unsigned value) { str = _malloc_append_unsigned(value, 16, str + strlen(str)); str[0] = '\0'; } static Ivar ivar_for_offset(struct objc_class *cls, vm_address_t offset) { int i; int ivar_offset; Ivar super_ivar; struct objc_ivar_list *ivars; if (!cls) return NULL; // scan base classes FIRST super_ivar = ivar_for_offset(cls->super_class, offset); // result is best-effort; our ivars may be closer ivars = cls->ivars; // If we have no ivars, return super's ivar if (!ivars || ivars->ivar_count == 0) return super_ivar; // Try our first ivar. If it's too big, use super's best ivar. ivar_offset = ivars->ivar_list[0].ivar_offset; if (ivar_offset > offset) return super_ivar; else if (ivar_offset == offset) return &ivars->ivar_list[0]; // Try our other ivars. If any is too big, use the previous. for (i = 1; i < ivars->ivar_count; i++) { int ivar_offset = ivars->ivar_list[i].ivar_offset; if (ivar_offset == offset) { return &ivars->ivar_list[i]; } else if (ivar_offset > offset) { return &ivars->ivar_list[i-1]; } } // Found nothing. Return our last ivar. return &ivars->ivar_list[ivars->ivar_count - 1]; } static void append_ivar_at_offset(char *buf, struct objc_class *cls, vm_address_t offset) { Ivar ivar = NULL; if (offset == 0) return; // don't bother with isa if (offset >= cls->instance_size) { strcat(buf, ".+"); strcati(buf, offset); return; } ivar = ivar_for_offset(cls, offset); if (!ivar) { strcat(buf, "."); return; } // fixme doesn't handle structs etc. strcat(buf, "."); if (ivar->ivar_name) strcat(buf, ivar->ivar_name); else strcat(buf, ""); offset -= ivar->ivar_offset; if (offset > 0) { strcat(buf, "+"); strcati(buf, offset); } } static const char *cf_class_for_object(void *cfobj) { // ick - we don't link against CF anymore struct { uint32_t version; const char *className; // don't care about the rest } *cfcls; uint32_t cfid; NSSymbol sym; uint32_t (*CFGetTypeID)(void *); void * (*_CFRuntimeGetClassWithTypeID)(uint32_t); sym = NSLookupAndBindSymbolWithHint("_CFGetTypeID", "CoreFoundation"); if (!sym) return "anonymous_NSCFType"; CFGetTypeID = NSAddressOfSymbol(sym); if (!CFGetTypeID) return "NSCFType"; sym = NSLookupAndBindSymbolWithHint("__CFRuntimeGetClassWithTypeID", "CoreFoundation"); if (!sym) return "anonymous_NSCFType"; _CFRuntimeGetClassWithTypeID = NSAddressOfSymbol(sym); if (!_CFRuntimeGetClassWithTypeID) return "anonymous_NSCFType"; cfid = (*CFGetTypeID)(cfobj); cfcls = (*_CFRuntimeGetClassWithTypeID)(cfid); return cfcls->className; } static char *name_for_address(auto_zone_t *zone, vm_address_t base, vm_address_t offset, int withRetainCount) { #define APPEND_SIZE(s) \ strcat(buf, "["); \ strcati(buf, s); \ strcat(buf, "]"); char buf[500]; char *result; buf[0] = '\0'; unsigned int size = auto_zone_size_no_lock(zone, (void *)base); auto_memory_type_t type = size ? auto_zone_get_layout_type_no_lock(zone, (void *)base) : AUTO_TYPE_UNKNOWN; unsigned int refcount = size ? auto_zone_retain_count_no_lock(zone, (void *)base) : 0; switch (type) { case AUTO_OBJECT_SCANNED: case AUTO_OBJECT_UNSCANNED: { Class cls = *(struct objc_class **)base; if (0 == strcmp(cls->name, "NSCFType")) { strcat(buf, cf_class_for_object((void *)base)); } else { strcat(buf, cls->name); } if (offset) { append_ivar_at_offset(buf, cls, offset); } APPEND_SIZE(size); break; } case AUTO_MEMORY_SCANNED: strcat(buf, "{conservative-block}"); APPEND_SIZE(size); break; case AUTO_MEMORY_UNSCANNED: strcat(buf, "{no-pointers-block}"); APPEND_SIZE(size); break; default: strcat(buf, "{unallocated-or-stack}"); } if (withRetainCount && refcount > 0) { strcat(buf, " [[refcount="); strcati(buf, refcount); strcat(buf, "]]"); } result = malloc_zone_malloc(objc_debug_zone(), 1 + strlen(buf)); strcpy(result, buf); return result; #undef APPEND_SIZE } struct objc_class_recorder_context { malloc_zone_t *zone; void *cls; char *clsname; unsigned int count; }; static void objc_class_recorder(task_t task, void *context, unsigned type_mask, vm_range_t *ranges, unsigned range_count) { struct objc_class_recorder_context *ctx = (struct objc_class_recorder_context *)context; vm_range_t *r; vm_range_t *end; for (r = ranges, end = ranges + range_count; r < end; r++) { auto_memory_type_t type = auto_zone_get_layout_type_no_lock(ctx->zone, (void *)r->address); if (type == AUTO_OBJECT_SCANNED || type == AUTO_OBJECT_UNSCANNED) { // Check if this is an instance of class ctx->cls or some subclass Class cls; Class isa = *(Class *)r->address; for (cls = isa; cls; cls = cls->super_class) { if (cls == ctx->cls) { unsigned int rc; objc_debug_printf("[%p] : %s", r->address, isa->name); if ((rc = auto_zone_retain_count_no_lock(ctx->zone, (void *)r->address))) { objc_debug_printf(" [[refcount %u]]", rc); } objc_debug_printf("\n"); ctx->count++; break; } } } } } void objc_enumerate_class(char *clsname) { struct objc_class_recorder_context ctx; ctx.zone = auto_zone(); ctx.clsname = clsname; ctx.cls = objc_getClass(clsname); // GrP fixme may deadlock if classHash lock is already owned ctx.count = 0; if (!ctx.cls) { objc_debug_printf("No class '%s'\n", clsname); return; } objc_debug_printf("\n\nINSTANCES OF CLASS '%s':\n\n", clsname); (*ctx.zone->introspect->enumerator)(mach_task_self(), &ctx, MALLOC_PTR_IN_USE_RANGE_TYPE, (vm_address_t)ctx.zone, NULL, objc_class_recorder); objc_debug_printf("\n%d instances\n\n", ctx.count); } static void objc_reference_printer(auto_zone_t *zone, void *ctx, auto_reference_t ref) { char *referrer_name = name_for_address(zone, ref.referrer_base, ref.referrer_offset, true); char *referent_name = name_for_address(zone, ref.referent, 0, true); objc_debug_printf("[%p%+d -> %p] : %s -> %s\n", ref.referrer_base, ref.referrer_offset, ref.referent, referrer_name, referent_name); malloc_zone_free(objc_debug_zone(), referrer_name); malloc_zone_free(objc_debug_zone(), referent_name); } void objc_print_references(void *referent, void *stack_bottom, int lock) { if (lock) { auto_enumerate_references(auto_zone(), referent, objc_reference_printer, stack_bottom, NULL); } else { auto_enumerate_references_no_lock(auto_zone(), referent, objc_reference_printer, stack_bottom, NULL); } } typedef struct { vm_address_t address; // of this object int refcount; // of this object - nonzero means ROOT int depth; // number of links away from referent, or -1 auto_reference_t *referrers; // of this object int referrers_used; int referrers_allocated; auto_reference_t back; // reference from this object back toward the target uint32_t ID; // Graphic ID for grafflization } blob; typedef struct { blob **list; unsigned int used; unsigned int allocated; } blob_queue; blob_queue blobs = {NULL, 0, 0}; blob_queue untraced_blobs = {NULL, 0, 0}; blob_queue root_blobs = {NULL, 0, 0}; static void spin(void) { static char* spinner[] = {"\010\010| ", "\010\010/ ", "\010\010- ", "\010\010\\ "}; static int spindex = 0; objc_debug_printf(spinner[spindex++]); if (spindex == 4) spindex = 0; } static void enqueue_blob(blob_queue *q, blob *b) { if (q->used == q->allocated) { q->allocated = q->allocated * 2 + 1; q->list = malloc_zone_realloc(objc_debug_zone(), q->list, q->allocated * sizeof(blob *)); } q->list[q->used++] = b; } static blob *dequeue_blob(blob_queue *q) { blob *result = q->list[0]; q->used--; memmove(&q->list[0], &q->list[1], q->used * sizeof(blob *)); return result; } static blob *blob_for_address(vm_address_t addr) { blob *b, **bp, **end; if (addr == 0) return NULL; for (bp = blobs.list, end = blobs.list+blobs.used; bp < end; bp++) { b = *bp; if (b->address == addr) return b; } b = malloc_zone_calloc(objc_debug_zone(), sizeof(blob), 1); b->address = addr; b->depth = -1; b->refcount = auto_zone_size_no_lock(auto_zone(), (void *)addr) ? auto_zone_retain_count_no_lock(auto_zone(), (void *)addr) : 1; enqueue_blob(&blobs, b); return b; } static int blob_exists(vm_address_t addr) { blob *b, **bp, **end; for (bp = blobs.list, end = blobs.list+blobs.used; bp < end; bp++) { b = *bp; if (b->address == addr) return 1; } return 0; } // Destroy the blobs table and all blob data in it static void free_blobs(void) { blob *b, **bp, **end; for (bp = blobs.list, end = blobs.list+blobs.used; bp < end; bp++) { b = *bp; malloc_zone_free(objc_debug_zone(), b); } if (blobs.list) malloc_zone_free(objc_debug_zone(), blobs.list); } static void print_chain(auto_zone_t *zone, blob *root) { blob *b; for (b = root; b != NULL; b = blob_for_address(b->back.referent)) { char *name; if (b->back.referent) { name = name_for_address(zone, b->address, b->back.referrer_offset, true); objc_debug_printf("[%p%+d] : %s ->\n", b->address, b->back.referrer_offset, name); } else { name = name_for_address(zone, b->address, 0, true); objc_debug_printf("[%p] : %s\n", b->address, name); } malloc_zone_free(objc_debug_zone(), name); } } static void objc_blob_recorder(auto_zone_t *zone, void *ctx, auto_reference_t ref) { blob *b = (blob *)ctx; spin(); if (b->referrers_used == b->referrers_allocated) { b->referrers_allocated = b->referrers_allocated * 2 + 1; b->referrers = malloc_zone_realloc(objc_debug_zone(), b->referrers, b->referrers_allocated * sizeof(auto_reference_t)); } b->referrers[b->referrers_used++] = ref; if (!blob_exists(ref.referrer_base)) { enqueue_blob(&untraced_blobs, blob_for_address(ref.referrer_base)); } } #define INSTANCE_ROOTS 1 #define HEAP_ROOTS 2 #define ALL_REFS 3 static void objc_print_recursive_refs(vm_address_t target, int which, void *stack_bottom, int lock); static void grafflize(blob_queue *blobs, int everything); void objc_print_instance_roots(vm_address_t target, void *stack_bottom, int lock) { objc_print_recursive_refs(target, INSTANCE_ROOTS, stack_bottom, lock); } void objc_print_heap_roots(vm_address_t target, void *stack_bottom, int lock) { objc_print_recursive_refs(target, HEAP_ROOTS, stack_bottom, lock); } void objc_print_all_refs(vm_address_t target, void *stack_bottom, int lock) { objc_print_recursive_refs(target, ALL_REFS, stack_bottom, lock); } static void sort_blobs_by_refcount(blob_queue *blobs) { int i, j; // simple bubble sort for (i = 0; i < blobs->used; i++) { for (j = i+1; j < blobs->used; j++) { if (blobs->list[i]->refcount < blobs->list[j]->refcount) { blob *temp = blobs->list[i]; blobs->list[i] = blobs->list[j]; blobs->list[j] = temp; } } } } static void sort_blobs_by_depth(blob_queue *blobs) { int i, j; // simple bubble sort for (i = 0; i < blobs->used; i++) { for (j = i+1; j < blobs->used; j++) { if (blobs->list[i]->depth > blobs->list[j]->depth) { blob *temp = blobs->list[i]; blobs->list[i] = blobs->list[j]; blobs->list[j] = temp; } } } } static void objc_print_recursive_refs(vm_address_t target, int which, void *stack_bottom, int lock) { objc_debug_printf("\n "); // make spinner draw in a pretty place // Construct pointed-to graph (of things eventually pointing to target) enqueue_blob(&untraced_blobs, blob_for_address(target)); while (untraced_blobs.used > 0) { blob *b = dequeue_blob(&untraced_blobs); spin(); if (lock) { auto_enumerate_references(auto_zone(), (void *)b->address, objc_blob_recorder, stack_bottom, b); } else { auto_enumerate_references_no_lock(auto_zone(), (void *)b->address, objc_blob_recorder, stack_bottom, b); } } // Walk pointed-to graph to find shortest paths from roots to target. // This is BREADTH-FIRST order. blob_for_address(target)->depth = 0; enqueue_blob(&untraced_blobs, blob_for_address(target)); while (untraced_blobs.used > 0) { blob *b = dequeue_blob(&untraced_blobs); blob *other; auto_reference_t *r, *end; int stop = NO; spin(); if (which == ALL_REFS) { // Never stop at roots. stop = NO; } else if (which == HEAP_ROOTS) { // Stop at any root (a block with positive retain count) stop = (b->refcount > 0); } else if (which == INSTANCE_ROOTS) { // Only stop at roots that are instances auto_memory_type_t type = auto_zone_get_layout_type_no_lock(auto_zone(), (void *)b->address); stop = (b->refcount > 0 && (type == AUTO_OBJECT_SCANNED || type == AUTO_OBJECT_UNSCANNED)); // GREG XXX ??? } // If this object is a root, save it and don't walk its referrers. if (stop) { enqueue_blob(&root_blobs, b); continue; } // For any "other object" that points to "this object" // and does not yet have a depth: // (1) other object is one level deeper than this object // (2) (one of) the shortest path(s) from other object to the // target goes through this object for (r = b->referrers, end = b->referrers + b->referrers_used; r < end; r++) { other = blob_for_address(r->referrer_base); if (other->depth == -1) { other->depth = b->depth + 1; other->back = *r; enqueue_blob(&untraced_blobs, other); } } } { char *name = name_for_address(auto_zone(), target, 0, true); objc_debug_printf("\n\n%d %s %p (%s)\n\n", (which==ALL_REFS) ? blobs.used : root_blobs.used, (which==ALL_REFS) ? "INDIRECT REFS TO" : "ROOTS OF", target, name); malloc_zone_free(objc_debug_zone(), name); } if (which == ALL_REFS) { // Print all reference objects, biggest refcount first int i; sort_blobs_by_refcount(&blobs); for (i = 0; i < blobs.used; i++) { char *name = name_for_address(auto_zone(), blobs.list[i]->address, 0, true); objc_debug_printf("[%p] : %s\n", blobs.list[i]->address, name); malloc_zone_free(objc_debug_zone(), name); } } else { // Walk back chain from every root to the target, printing every step. while (root_blobs.used > 0) { blob *root = dequeue_blob(&root_blobs); print_chain(auto_zone(), root); objc_debug_printf("\n"); } } grafflize(&blobs, which == ALL_REFS); objc_debug_printf("\ndone\n\n"); // Clean up free_blobs(); if (untraced_blobs.list) malloc_zone_free(objc_debug_zone(), untraced_blobs.list); if (root_blobs.list) malloc_zone_free(objc_debug_zone(), root_blobs.list); memset(&blobs, 0, sizeof(blobs)); memset(&root_blobs, 0, sizeof(root_blobs)); memset(&untraced_blobs, 0, sizeof(untraced_blobs)); } struct objc_block_recorder_context { malloc_zone_t *zone; int fd; unsigned int count; }; static void objc_block_recorder(task_t task, void *context, unsigned type_mask, vm_range_t *ranges, unsigned range_count) { char buf[20]; struct objc_block_recorder_context *ctx = (struct objc_block_recorder_context *)context; vm_range_t *r; vm_range_t *end; for (r = ranges, end = ranges + range_count; r < end; r++) { char *name = name_for_address(ctx->zone, r->address, 0, true); buf[0] = '\0'; strcatx(buf, r->address); write(ctx->fd, "0x", 2); write(ctx->fd, buf, strlen(buf)); write(ctx->fd, " ", 1); write(ctx->fd, name, strlen(name)); write(ctx->fd, "\n", 1); malloc_zone_free(objc_debug_zone(), name); ctx->count++; } } void objc_dump_block_list(const char* path) { struct objc_block_recorder_context ctx; char filename[] = "/tmp/blocks-XXXXX.txt"; ctx.zone = auto_zone(); ctx.count = 0; ctx.fd = (path ? open(path, O_WRONLY | O_CREAT | O_TRUNC, 0666) : mkstemps(filename, strlen(strrchr(filename, '.')))); objc_debug_printf("\n\nALL AUTO-ALLOCATED BLOCKS\n\n"); (*ctx.zone->introspect->enumerator)(mach_task_self(), &ctx, MALLOC_PTR_IN_USE_RANGE_TYPE, (vm_address_t)ctx.zone, NULL, objc_block_recorder); objc_debug_printf("%d blocks written to file\n", ctx.count); objc_debug_printf("open %s\n", (path ? path : filename)); close(ctx.fd); } static void grafflize_id(int gfile, int ID) { char buf[20] = ""; char *c; strcati(buf, ID); c = "ID"; write(gfile, c, strlen(c)); write(gfile, buf, strlen(buf)); c = ""; write(gfile, c, strlen(c)); } // head = REFERENT end = arrow // tail = REFERRER end = no arrow static void grafflize_reference(int gfile, auto_reference_t reference, int ID, int important) { blob *referrer = blob_for_address(reference.referrer_base); blob *referent = blob_for_address(reference.referent); char *c; // line c = "ClassLineGraphic"; write(gfile, c, strlen(c)); // id grafflize_id(gfile, ID); // head = REFERENT c = "Head"; write(gfile, c, strlen(c)); grafflize_id(gfile, referent->ID); c = ""; write(gfile, c, strlen(c)); // tail = REFERRER c = "Tail"; write(gfile, c, strlen(c)); grafflize_id(gfile, referrer->ID); c = ""; write(gfile, c, strlen(c)); // style - head arrow, thick line if important c = "Stylestroke" "HeadArrowFilledArrow" "LineType1"; write(gfile, c, strlen(c)); if (important) { c = "Width3"; write(gfile, c, strlen(c)); } c = ""; write(gfile, c, strlen(c)); // end line c = ""; write(gfile, c, strlen(c)); } static void grafflize_blob(int gfile, blob *b) { // fixme include ivar names too char *name = name_for_address(auto_zone(), b->address, 0, false); int width = 30 + strlen(name)*6; int height = 40; char buf[40] = ""; char *c; // rectangle c = "" "ClassShapedGraphic" "ShapeRectangle"; write(gfile, c, strlen(c)); // id grafflize_id(gfile, b->ID); // bounds // order vertically by depth c = "Bounds{{0,"; write(gfile, c, strlen(c)); buf[0] = '\0'; strcati(buf, b->depth*60); write(gfile, buf, strlen(buf)); c = "},{"; write(gfile, c, strlen(c)); buf[0] = '\0'; strcati(buf, width); strcat(buf, ","); strcati(buf, height); write(gfile, buf, strlen(buf)); c = "}}"; write(gfile, c, strlen(c)); // label c = "TextText" "{\\rtf1\\mac\\ansicpg10000\\cocoartf102\n" "{\\fonttbl\\f0\\fswiss\\fcharset77 Helvetica;\\fonttbl\\f1\\fswiss\\fcharset77 Helvetica-Bold;}\n" "{\\colortbl;\\red255\\green255\\blue255;}\n" "\\pard\\tx560\\tx1120\\tx1680\\tx2240\\tx3360\\tx3920\\tx4480\\tx5040\\tx5600\\tx6160\\tx6720\\qc\n" "\\f0\\fs20 \\cf0 "; write(gfile, c, strlen(c)); write(gfile, name, strlen(name)); strcpy(buf, "\\\n0x"); strcatx(buf, b->address); write(gfile, buf, strlen(buf)); c = "}"; write(gfile, c, strlen(c)); // styles c = "Style"; write(gfile, c, strlen(c)); // no shadow c = "shadowDrawsNO"; write(gfile, c, strlen(c)); // fat border if refcount > 0 if (b->refcount > 0) { c = "strokeWidth4"; write(gfile, c, strlen(c)); } // end styles c = ""; write(gfile, c, strlen(c)); // done c = "\n"; write(gfile, c, strlen(c)); malloc_zone_free(objc_debug_zone(), name); } #define gheader "GraphDocumentVersion3ReadOnlyNOGraphicsList\n" #define gfooter "\n" static void grafflize(blob_queue *blobs, int everything) { // Don't require linking to Foundation! int i; int gfile; int nextid = 1; char filename[] = "/tmp/gc-XXXXX.graffle"; // Open file gfile = mkstemps(filename, strlen(strrchr(filename, '.'))); if (gfile < 0) { objc_debug_printf("couldn't create a graffle file in /tmp/ (errno %d)\n", errno); return; } // Write header write(gfile, gheader, strlen(gheader)); // Write a rectangle for each blob sort_blobs_by_depth(blobs); for (i = 0; i < blobs->used; i++) { blob *b = blobs->list[i]; b->ID = nextid++; if (everything || b->depth >= 0) { grafflize_blob(gfile, b); } } for (i = 0; i < blobs->used; i++) { int j; blob *b = blobs->list[i]; if (everything) { // Write an arrow for each reference // Use big arrows for backreferences for (j = 0; j < b->referrers_used; j++) { int is_back_ref = (b->referrers[i].referent == b->back.referent && b->referrers[i].referrer_offset == b->back.referrer_offset && b->referrers[i].referrer_base == b->back.referrer_base); grafflize_reference(gfile, b->referrers[j], nextid++, is_back_ref); } } else { // Write an arrow for each backreference if (b->depth > 0) { grafflize_reference(gfile, b->back, nextid++, false); } } } // Write footer and close write(gfile, gfooter, strlen(gfooter)); close(gfile); objc_debug_printf("wrote object graph (%d objects)\nopen %s\n", blobs->used, filename); } #endif // Stubs for non-open-source libauto functions static void auto_collect(auto_zone_t *zone, auto_collection_mode_t mode, void *collection_context) { } static auto_collection_control_t *auto_collection_parameters(auto_zone_t *zone) { return NULL; } static const auto_statistics_t *auto_collection_statistics(auto_zone_t *zone) { return NULL; } static void auto_enumerate_references(auto_zone_t *zone, void *referent, auto_reference_recorder_t callback, void *stack_bottom, void *ctx) { } static void auto_enumerate_references_no_lock(auto_zone_t *zone, void *referent, auto_reference_recorder_t callback, void *stack_bottom, void *ctx) { } static auto_zone_t *auto_zone(void) { return NULL; } static void auto_zone_add_root(auto_zone_t *zone, void *root, size_t size) { } static void* auto_zone_allocate_object(auto_zone_t *zone, size_t size, auto_memory_type_t type, boolean_t initial_refcount_to_one, boolean_t clear) { return NULL; } static const void *auto_zone_base_pointer(auto_zone_t *zone, const void *ptr) { return NULL; } static auto_memory_type_t auto_zone_get_layout_type(auto_zone_t *zone, void *ptr) { return 0; } static auto_memory_type_t auto_zone_get_layout_type_no_lock(auto_zone_t *zone, void *ptr) { return 0; } static boolean_t auto_zone_is_finalized(auto_zone_t *zone, const void *ptr) { return NO; } static boolean_t auto_zone_is_valid_pointer(auto_zone_t *zone, const void *ptr) { return NO; } static unsigned int auto_zone_release(auto_zone_t *zone, void *ptr) { return 0; } static void auto_zone_retain(auto_zone_t *zone, void *ptr) { } static unsigned int auto_zone_retain_count_no_lock(auto_zone_t *zone, const void *ptr) { return 0; } static void auto_zone_set_class_list(int (*get_class_list)(void **buffer, int count)) { } static size_t auto_zone_size_no_lock(auto_zone_t *zone, const void *ptr) { return 0; } static void auto_zone_start_monitor(boolean_t force) { } static void auto_zone_write_barrier(auto_zone_t *zone, void *recipient, const unsigned int offset_in_bytes, const void *new_value) { *(uintptr_t *)(offset_in_bytes + (uint8_t *)recipient) = (uintptr_t)new_value; } static void *auto_zone_write_barrier_memmove(auto_zone_t *zone, void *dst, const void *src, size_t size) { return memmove(dst, src, size); }