X-Git-Url: https://git.saurik.com/apple/objc4.git/blobdiff_plain/1f20c7a710ebedf4bba6ed44c082df068af45b89..809271e4ffbc957d063b093e55a39c85c61f6aaa:/runtime/objc-runtime.m diff --git a/runtime/objc-runtime.m b/runtime/objc-runtime.m index 69f5f1c..6a61be9 100644 --- a/runtime/objc-runtime.m +++ b/runtime/objc-runtime.m @@ -2,23 +2,24 @@ * Copyright (c) 1999 Apple Computer, Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ - * - * Portions Copyright (c) 1999 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 1.1 (the "License"). You may not use this file - * except in compliance with the License. Please obtain a copy of the - * License at http://www.apple.com/publicsource and read it before using - * this file. - * + * + * Copyright (c) 1999-2003 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 + * 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 OR NON- INFRINGEMENT. Please see the - * License for the specific language governing rights and limitations - * under the License. - * + * 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@ */ /*********************************************************************** @@ -28,14 +29,124 @@ * **********************************************************************/ +/*********************************************************************** + * Class loading and connecting (GrP 2004-2-11) + * + * When images are loaded (during program startup or otherwise), the + * runtime needs to load classes and categories from the images, connect + * classes to superclasses and categories to parent classes, and call + * +load methods. + * + * The Objective-C runtime can cope with classes arriving in any order. + * That is, a class may be discovered by the runtime before some + * superclass is known. To handle out-of-order class loads, the + * runtime uses a "pending class" system. + * + * (Historical note) + * Panther and earlier: many classes arrived out-of-order because of + * the poorly-ordered callback from dyld. However, the runtime's + * pending mechanism only handled "missing superclass" and not + * "present superclass but missing higher class". See Radar #3225652. + * Tiger: The runtime's pending mechanism was augmented to handle + * arbitrary missing classes. In addition, dyld was rewritten and + * now sends the callbacks in strictly bottom-up link order. + * The pending mechanism may now be needed only for rare and + * hard to construct programs. + * (End historical note) + * + * A class when first seen in an image is considered "unconnected". + * It is stored in `unconnected_class_hash`. If all of the class's + * superclasses exist and are already "connected", then the new class + * can be connected to its superclasses and moved to `class_hash` for + * normal use. Otherwise, the class waits in `unconnected_class_hash` + * until the superclasses finish connecting. + * + * A "connected" class is + * (1) in `class_hash`, + * (2) connected to its superclasses, + * (3) has no unconnected superclasses, + * (4) is otherwise initialized and ready for use, and + * (5) is eligible for +load if +load has not already been called. + * + * An "unconnected" class is + * (1) in `unconnected_class_hash`, + * (2) not connected to its superclasses, + * (3) has an immediate superclass which is either missing or unconnected, + * (4) is not ready for use, and + * (5) is not yet eligible for +load. + * + * Image mapping is NOT CURRENTLY THREAD-SAFE with respect to just about + * * * anything. Image mapping IS RE-ENTRANT in several places: superclass + * lookup may cause ZeroLink to load another image, and +load calls may + * cause dyld to load another image. + * + * Image mapping sequence: + * + * Read all classes in all new images. + * Add them all to unconnected_class_hash. + * Note any +load implementations before categories are attached. + * Fix up any pended classrefs referring to them. + * Attach any pending categories. + * Read all categories in all new images. + * Attach categories whose parent class exists (connected or not), + * and pend the rest. + * Mark them all eligible for +load (if implemented), even if the + * parent class is missing. + * Try to connect all classes in all new images. + * If the superclass is missing, pend the class + * If the superclass is unconnected, try to recursively connect it + * If the superclass is connected: + * connect the class + * mark the class eligible for +load, if implemented + * connect any pended subclasses of the class + * Resolve selector refs and class refs in all new images. + * Class refs whose classes still do not exist are pended. + * Fix up protocol objects in all new images. + * Call +load for classes and categories. + * May include classes or categories that are not in these images, + * but are newly eligible because of these image. + * Class +loads will be called superclass-first because of the + * superclass-first nature of the connecting process. + * Category +load needs to be deferred until the parent class is + * connected and has had its +load called. + * + * Performance: all classes are read before any categories are read. + * Fewer categories need be pended for lack of a parent class. + * + * Performance: all categories are attempted to be attached before + * any classes are connected. Fewer class caches need be flushed. + * (Unconnected classes and their respective subclasses are guaranteed + * to be un-messageable, so their caches will be empty.) + * + * Performance: all classes are read before any classes are connected. + * Fewer classes need be pended for lack of a superclass. + * + * Correctness: all selector and class refs are fixed before any + * protocol fixups or +load methods. libobjc itself contains selector + * and class refs which are used in protocol fixup and +load. + * + * Correctness: +load methods are scheduled in bottom-up link order. + * This constraint is in addition to superclass order. Some +load + * implementations expect to use another class in a linked-to library, + * even if the two classes don't share a direct superclass relationship. + * + * Correctness: all classes are scanned for +load before any categories + * are attached. Otherwise, if a category implements +load and its class + * has no class methods, the class's +load scan would find the category's + * +load method, which would then be called twice. + * + **********************************************************************/ + + /*********************************************************************** * Imports. **********************************************************************/ - #include #include -#include +#include +#include +#include // project headers first, otherwise we get the installed ones #import "objc-class.h" @@ -45,9 +156,19 @@ #import "objc-private.h" #import #import +#import "objc-rtp.h" +#import "objc-auto.h" #include #include +#include +#include +#include + +/* NXHashTable SPI */ +OBJC_EXPORT unsigned _NXHashCapacity(NXHashTable *table); +OBJC_EXPORT void _NXHashRehashToCapacity(NXHashTable *table, unsigned newCapacity); + OBJC_EXPORT Class _objc_getNonexistentClass(void); @@ -69,41 +190,82 @@ OBJC_EXPORT Class getOriginalClassForPosingClass(Class); typedef struct _objc_unresolved_category { struct _objc_unresolved_category * next; - struct objc_category * cat; + struct objc_category * cat; // may be NULL long version; - int bindme; } _objc_unresolved_category; -typedef struct _PendingClass +typedef struct _PendingSubclass +{ + struct objc_class *subclass; // subclass to finish connecting; may be NULL + struct _PendingSubclass *next; +} PendingSubclass; + +typedef struct _PendingClassRef { - struct objc_class * * ref; - struct objc_class * classToSetUp; - const char * nameof_superclass; - int version; - struct _PendingClass * next; -} PendingClass; + struct objc_class **ref; // class reference to fix up; may be NULL + struct _PendingClassRef *next; +} PendingClassRef; + +struct loadable_class { + struct objc_class *cls; // may be NULL + IMP method; +}; + +struct loadable_category { + struct objc_category *cat; // may be NULL + IMP method; +}; + /*********************************************************************** * Exports. **********************************************************************/ -// Function to call when message sent to nil object. -void (*_objc_msgNil)(id, SEL) = NULL; - // Function called after class has been fixed up (MACH only) void (*callbackFunction)(Class, const char *) = 0; -// Prototype for function passed to -typedef void (*NilObjectMsgCallback) (id nilObject, SEL selector); - // Lock for class hashtable OBJC_DECLARE_LOCK (classLock); -// Condition for logging load progress -static int LaunchingDebug = -1; +// Settings from environment variables +__private_extern__ int PrintImages = -1; // env OBJC_PRINT_IMAGES +__private_extern__ int PrintLoading = -1; // env OBJC_PRINT_LOAD_METHODS +__private_extern__ int PrintConnecting = -1; // env OBJC_PRINT_CONNECTION +__private_extern__ int PrintRTP = -1; // env OBJC_PRINT_RTP +__private_extern__ int PrintGC = -1; // env OBJC_PRINT_GC +__private_extern__ int PrintSharing = -1; // env OBJC_PRINT_SHARING +__private_extern__ int PrintCxxCtors = -1; // env OBJC_PRINT_CXX_CTORS + +__private_extern__ int UseInternalZone = -1; // env OBJC_USE_INTERNAL_ZONE +__private_extern__ int AllowInterposing = -1;// env OBJC_ALLOW_INTERPOSING + +__private_extern__ int DebugUnload = -1; // env OBJC_DEBUG_UNLOAD +__private_extern__ int DebugFragileSuperclasses = -1; // env OBJC_DEBUG_FRAGILE_SUPERCLASSES + +__private_extern__ int ForceGC = -1; // env OBJC_FORCE_GC +__private_extern__ int ForceNoGC = -1; // env OBJC_FORCE_NO_GC +__private_extern__ int CheckFinalizers = -1; // env OBJC_CHECK_FINALIZERS // objc's key for pthread_getspecific -pthread_key_t _objc_pthread_key; +__private_extern__ pthread_key_t _objc_pthread_key = 0; + +// List of classes that need +load called (pending superclass +load) +// This list always has superclasses first because of the way it is constructed +static struct loadable_class *loadable_classes NOBSS = NULL; +static int loadable_classes_used NOBSS = 0; +static int loadable_classes_allocated NOBSS = 0; + +// List of categories that need +load called (pending parent class +load) +static struct loadable_category *loadable_categories NOBSS = NULL; +static int loadable_categories_used NOBSS = 0; +static int loadable_categories_allocated NOBSS = 0; + +// Selectors for which @selector() doesn't work +__private_extern__ SEL cxx_construct_sel = NULL; +__private_extern__ SEL cxx_destruct_sel = NULL; +__private_extern__ const char *cxx_construct_name = ".cxx_construct"; +__private_extern__ const char *cxx_destruct_name = ".cxx_destruct"; + /*********************************************************************** * Function prototypes internal to this module. @@ -115,33 +277,34 @@ static int _objc_defaultClassHandler (const char * clsName); static void _objcTweakMethodListPointerForClass (struct objc_class * cls); static void _objc_add_category_flush_caches(struct objc_class * cls, struct objc_category * category, int version); static void _objc_add_category(struct objc_class * cls, struct objc_category * category, int version); -static void _objc_register_category (struct objc_category * cat, long version, int bindme); -static void _objc_add_categories_from_image (header_info * hi); +static void _objc_register_category (struct objc_category * cat, long version); +static void _objc_read_categories_from_image (header_info * hi); static const header_info * _headerForClass (struct objc_class * cls); -static PendingClass * newPending (void); static NXMapTable * pendingClassRefsMapTable (void); -static void _objc_add_classes_from_image (NXHashTable * clsHash, header_info * hi); -static void _objc_fixup_string_objects_for_image(header_info * hi); +static NXMapTable * pendingSubclassesMapTable (void); +static void _objc_read_classes_from_image (header_info * hi); static void _objc_map_class_refs_for_image (header_info * hi); -static void map_selrefs (SEL * sels, unsigned int cnt); -static void map_method_descs (struct objc_method_description_list * methods); static void _objc_fixup_protocol_objects_for_image (header_info * hi); -static void _objc_bindModuleContainingCategory(Category cat); static void _objc_fixup_selector_refs (const header_info * hi); -static void _objc_call_loads_for_image (header_info * header); -static void _objc_checkForPendingClassReferences (struct objc_class * cls); -static void _objc_map_image(headerType *mh, unsigned long vmaddr_slide); -static void _objc_unmap_image(headerType *mh, unsigned long vmaddr_slide); +static void _objc_unmap_image(const headerType *mh); +static BOOL connect_class(struct objc_class *cls); +static void add_category_to_loadable_list(struct objc_category *cat); +static vm_range_t get_shared_range(vm_address_t start, vm_address_t end); +static void offer_shared_range(vm_address_t start, vm_address_t end); +static void install_shared_range(vm_range_t remote, vm_address_t local); +static void clear_shared_range_file_cache(void); + /*********************************************************************** * Static data internal to this module. **********************************************************************/ // we keep a linked list of header_info's describing each image as told to us by dyld -static header_info * FirstHeader = 0; +static header_info *FirstHeader NOBSS = 0; // NULL means empty list +static header_info *LastHeader NOBSS = 0; // NULL means invalid; recompute it // Hash table of classes -static NXHashTable * class_hash = 0; +static NXHashTable * class_hash NOBSS = 0; static NXHashTablePrototype classHashPrototype = { (unsigned (*) (const void *, const void *)) classHash, @@ -149,19 +312,28 @@ static NXHashTablePrototype classHashPrototype = NXNoEffectFree, 0 }; +// Hash table of unconnected classes +static NXHashTable *unconnected_class_hash NOBSS = NULL; + // Exported copy of class_hash variable (hook for debugging tools) NXHashTable *_objc_debug_class_hash = NULL; // Function pointer objc_getClass calls through when class is not found static int (*objc_classHandler) (const char *) = _objc_defaultClassHandler; +// Function pointer called by objc_getClass and objc_lookupClass when +// class is not found. _objc_classLoader is called before objc_classHandler. +static BOOL (*_objc_classLoader)(const char *) = NULL; + // Category and class registries +// Keys are COPIES of strings, to prevent stale pointers with unloaded bundles +// Use NXMapKeyCopyingInsert and NXMapKeyFreeingRemove static NXMapTable * category_hash = NULL; - -static int Postpone_install_relationships = 0; - -static NXMapTable * pendingClassRefsMap = 0; +// Keys are COPIES of strings, to prevent stale pointers with unloaded bundles +// Use NXMapKeyCopyingInsert and NXMapKeyFreeingRemove +static NXMapTable * pendingClassRefsMap = NULL; +static NXMapTable * pendingSubclassesMap = NULL; /*********************************************************************** * objc_dump_class_hash. Log names of all known classes. @@ -204,10 +376,54 @@ static int classIsEqual (void * info, struct objc_class * cls) { // Standard string comparison - return ((name->name[0] == cls->name[0]) && - (strcmp (name->name, cls->name) == 0)); + // Our local inlined version is significantly shorter on PPC and avoids the + // mflr/mtlr and dyld_stub overhead when calling strcmp. + return _objc_strcmp(name->name, cls->name) == 0; +} + + +/*********************************************************************** +* NXMapKeyCopyingInsert +* Like NXMapInsert, but strdups the key if necessary. +* Used to prevent stale pointers when bundles are unloaded. +**********************************************************************/ +static void *NXMapKeyCopyingInsert(NXMapTable *table, const void *key, const void *value) +{ + void *realKey; + void *realValue = NULL; + + if ((realKey = NXMapMember(table, key, &realValue)) != NX_MAPNOTAKEY) { + // key DOES exist in table - use table's key for insertion + } else { + // key DOES NOT exist in table - copy the new key before insertion + realKey = _strdup_internal(key); + } + return NXMapInsert(table, realKey, value); +} + + +/*********************************************************************** +* NXMapKeyFreeingRemove +* Like NXMapRemove, but frees the existing key if necessary. +* Used to prevent stale pointers when bundles are unloaded. +**********************************************************************/ +static void *NXMapKeyFreeingRemove(NXMapTable *table, const void *key) +{ + void *realKey; + void *realValue = NULL; + + if ((realKey = NXMapMember(table, key, &realValue)) != NX_MAPNOTAKEY) { + // key DOES exist in table - remove pair and free key + realValue = NXMapRemove(table, realKey); + _free_internal(realKey); // the key from the table, not necessarily the one given + return realValue; + } else { + // key DOES NOT exist in table - nothing to do + return NULL; + } } + /*********************************************************************** * _objc_init_class_hash. Return the class lookup table, create it if * necessary. @@ -218,15 +434,17 @@ void _objc_init_class_hash (void) if (class_hash) return; - // Provide a generous initial capacity to cut down on rehashes - // at launch time. A smallish Foundation+AppKit program will have + // class_hash starts small, with only enough capacity for libobjc itself. + // If a second library is found by map_images(), class_hash is immediately + // resized to capacity 1024 to cut down on rehashes. + // Old numbers: A smallish Foundation+AppKit program will have // about 520 classes. Larger apps (like IB or WOB) have more like // 800 classes. Some customers have massive quantities of classes. // Foundation-only programs aren't likely to notice the ~6K loss. class_hash = NXCreateHashTableFromZone (classHashPrototype, - 1024, + 16, nil, - _objc_create_zone ()); + _objc_internal_zone ()); _objc_debug_class_hash = class_hash; } @@ -246,7 +464,9 @@ int objc_getClassList(Class *buffer, int bufferLen) { } cnt = 0; state = NXInitHashState(class_hash); - while (cnt < num && NXNextHashState(class_hash, &state, (void **)&class)) { + while (cnt < bufferLen && + NXNextHashState(class_hash, &state, (void **)&class)) + { buffer[cnt++] = class; } OBJC_UNLOCK(&classLock); @@ -287,58 +507,103 @@ void objc_setClassHandler (int (*userSuppliedHandler) (const char *)) objc_classHandler = userSuppliedHandler; } + /*********************************************************************** -* objc_getClass. Return the id of the named class. If the class does -* not exist, call the objc_classHandler routine with the class name. -* If the objc_classHandler returns a non-zero value, try once more to -* find the class. Default objc_classHandler always returns zero. -* objc_setClassHandler is how someone can install a non-default routine. -* Warning: doesn't work if aClassName is the name of a posed-for class's isa! +* look_up_class +* Map a class name to a class using various methods. +* This is the common implementation of objc_lookUpClass and objc_getClass, +* and is also used internally to get additional search options. +* Sequence: +* 1. class_hash +* 2. unconnected_class_hash (optional) +* 3. classLoader callback +* 4. classHandler callback (optional) **********************************************************************/ -id objc_getClass (const char * aClassName) +static id look_up_class(const char *aClassName, BOOL includeUnconnected, BOOL includeClassHandler) { - struct objc_class cls; - id ret; + BOOL includeClassLoader = YES; // class loader cannot be skipped + id result = nil; + struct objc_class query; - // Synchronize access to hash table - OBJC_LOCK (&classLock); + query.name = aClassName; - // Check the hash table - cls.name = aClassName; - ret = (id) NXHashGet (class_hash, &cls); - OBJC_UNLOCK (&classLock); + retry: - // If not found, go call objc_classHandler and try again - if (!ret && (*objc_classHandler)(aClassName)) - { + if (!result && class_hash) { + // Check ordinary classes OBJC_LOCK (&classLock); - ret = (id) NXHashGet (class_hash, &cls); + result = (id)NXHashGet(class_hash, &query); OBJC_UNLOCK (&classLock); } - return ret; + if (!result && includeUnconnected && unconnected_class_hash) { + // Check not-yet-connected classes + OBJC_LOCK(&classLock); + result = (id)NXHashGet(unconnected_class_hash, &query); + OBJC_UNLOCK(&classLock); + } + + if (!result && includeClassLoader && _objc_classLoader) { + // Try class loader callback + if ((*_objc_classLoader)(aClassName)) { + // Re-try lookup without class loader + includeClassLoader = NO; + goto retry; + } + } + + if (!result && includeClassHandler && objc_classHandler) { + // Try class handler callback + if ((*objc_classHandler)(aClassName)) { + // Re-try lookup without class handler or class loader + includeClassLoader = NO; + includeClassHandler = NO; + goto retry; + } + } + + return result; +} + + +/*********************************************************************** +* objc_getClass. Return the id of the named class. If the class does +* not exist, call _objc_classLoader and then objc_classHandler, either of +* which may create a new class. +* Warning: doesn't work if aClassName is the name of a posed-for class's isa! +**********************************************************************/ +id objc_getClass (const char * aClassName) +{ + // NO unconnected, YES class handler + return look_up_class(aClassName, NO, YES); +} + + +/*********************************************************************** +* objc_getRequiredClass. +* Same as objc_getClass, but kills the process if the class is not found. +* This is used by ZeroLink, where failing to find a class would be a +* compile-time link error without ZeroLink. +**********************************************************************/ +id objc_getRequiredClass(const char *aClassName) +{ + id cls = objc_getClass(aClassName); + if (!cls) _objc_fatal("link error: class '%s' not found.", aClassName); + return cls; } + /*********************************************************************** * objc_lookUpClass. Return the id of the named class. +* If the class does not exist, call _objc_classLoader, which may create +* a new class. * * Formerly objc_getClassWithoutWarning () **********************************************************************/ id objc_lookUpClass (const char * aClassName) { - struct objc_class cls; - id ret; - - // Synchronize access to hash table - OBJC_LOCK (&classLock); - - // Check the hash table - cls.name = aClassName; - ret = (id) NXHashGet (class_hash, &cls); - - // Desynchronize - OBJC_UNLOCK (&classLock); - return ret; + // NO unconnected, NO class handler + return look_up_class(aClassName, NO, NO); } /*********************************************************************** @@ -363,7 +628,7 @@ id objc_getMetaClass (const char * aClassName) * objc_addClass. Add the specified class to the table of known classes, * after doing a little verification and fixup. **********************************************************************/ -void objc_addClass (Class cls) +void objc_addClass (struct objc_class *cls) { // Synchronize access to hash table OBJC_LOCK (&classLock); @@ -372,18 +637,23 @@ void objc_addClass (Class cls) // Clear all bits of the info fields except CLS_CLASS and CLS_META. // Normally these bits are already clear but if someone tries to cons // up their own class on the fly they might need to be cleared. - if (((struct objc_class *)cls)->cache == NULL) - { - ((struct objc_class *)cls)->cache = (Cache) &emptyCache; - ((struct objc_class *)cls)->info = CLS_CLASS; + if (cls->cache == NULL) { + cls->cache = (Cache) &emptyCache; + cls->info = CLS_CLASS; } - if (((struct objc_class *)cls)->isa->cache == NULL) - { - ((struct objc_class *)cls)->isa->cache = (Cache) &emptyCache; - ((struct objc_class *)cls)->isa->info = CLS_META; + if (cls->isa->cache == NULL) { + cls->isa->cache = (Cache) &emptyCache; + cls->isa->info = CLS_META; } + // methodLists should be: + // 1. NULL (Tiger and later only) + // 2. A -1 terminated method list array + // In either case, CLS_NO_METHOD_ARRAY remains clear. + // If the user manipulates the method list directly, + // they must use the magic private format. + // Add the class to the table (void) NXHashInsert (class_hash, cls); @@ -393,6 +663,9 @@ void objc_addClass (Class cls) /*********************************************************************** * _objcTweakMethodListPointerForClass. +* Change the class's method list pointer to a method list array. +* Does nothing if the method list pointer is already a method list array. +* If the class is currently in use, methodListLock must be held by the caller. **********************************************************************/ static void _objcTweakMethodListPointerForClass (struct objc_class * cls) { @@ -401,44 +674,60 @@ static void _objcTweakMethodListPointerForClass (struct objc_class * cls) int mallocSize; struct objc_method_list ** ptr; + // Do nothing if methodLists is already an array. + if (cls->methodLists && !(cls->info & CLS_NO_METHOD_ARRAY)) return; + // Remember existing list originalList = (struct objc_method_list *) cls->methodLists; // Allocate and zero a method list array mallocSize = sizeof(struct objc_method_list *) * initialEntries; - ptr = (struct objc_method_list **) malloc_zone_calloc (_objc_create_zone (), 1, mallocSize); + ptr = (struct objc_method_list **) _calloc_internal(1, mallocSize); // Insert the existing list into the array ptr[initialEntries - 1] = END_OF_METHODS_LIST; ptr[0] = originalList; // Replace existing list with array - ((struct objc_class *)cls)->methodLists = ptr; - ((struct objc_class *)cls)->info |= CLS_METHOD_ARRAY; - - // Do the same thing to the meta-class - if (((((struct objc_class *)cls)->info & CLS_CLASS) != 0) && cls->isa) - _objcTweakMethodListPointerForClass (cls->isa); + cls->methodLists = ptr; + _class_clearInfo(cls, CLS_NO_METHOD_ARRAY); } + /*********************************************************************** * _objc_insertMethods. +* Adds methods to a class. +* Does not flush any method caches. +* Does not take any locks. +* If the class is already in use, use class_addMethods() instead. **********************************************************************/ -void _objc_insertMethods (struct objc_method_list * mlist, - struct objc_method_list *** list) +void _objc_insertMethods(struct objc_class *cls, + struct objc_method_list *mlist) { - struct objc_method_list ** ptr; - volatile struct objc_method_list ** tempList; - int endIndex; - int oldSize; - int newSize; + struct objc_method_list ***list; + struct objc_method_list **ptr; + int endIndex; + int oldSize; + int newSize; + + if (!cls->methodLists) { + // cls has no methods - simply use this method list + cls->methodLists = (struct objc_method_list **)mlist; + _class_setInfo(cls, CLS_NO_METHOD_ARRAY); + return; + } + + // Create method list array if necessary + _objcTweakMethodListPointerForClass(cls); + + list = &cls->methodLists; // Locate unused entry for insertion point ptr = *list; while ((*ptr != 0) && (*ptr != END_OF_METHODS_LIST)) ptr += 1; - // If array is full, double it + // If array is full, add to it if (*ptr == END_OF_METHODS_LIST) { // Calculate old and new dimensions @@ -446,11 +735,9 @@ void _objc_insertMethods (struct objc_method_list * mlist, oldSize = (endIndex + 1) * sizeof(void *); newSize = oldSize + sizeof(struct objc_method_list *); // only increase by 1 - // Replace existing array with copy twice its size - tempList = (struct objc_method_list **) malloc_zone_realloc ((void *) _objc_create_zone (), - (void *) *list, - (size_t) newSize); - *list = tempList; + // Grow the method list array by one. + // This block may be from user code; don't use _realloc_internal + *list = (struct objc_method_list **)realloc(*list, newSize); // Zero out addition part of new array bzero (&((*list)[endIndex]), newSize - oldSize); @@ -471,11 +758,34 @@ void _objc_insertMethods (struct objc_method_list * mlist, /*********************************************************************** * _objc_removeMethods. +* Remove methods from a class. +* Does not take any locks. +* Does not flush any method caches. +* If the class is currently in use, use class_removeMethods() instead. **********************************************************************/ -void _objc_removeMethods (struct objc_method_list * mlist, - struct objc_method_list *** list) +void _objc_removeMethods(struct objc_class *cls, + struct objc_method_list *mlist) { - struct objc_method_list ** ptr; + struct objc_method_list ***list; + struct objc_method_list **ptr; + + if (cls->methodLists == NULL) { + // cls has no methods + return; + } + if (cls->methodLists == (struct objc_method_list **)mlist) { + // mlist is the class's only method list - erase it + cls->methodLists = NULL; + return; + } + if (cls->info & CLS_NO_METHOD_ARRAY) { + // cls has only one method list, and this isn't it - do nothing + return; + } + + // cls has a method list array - search it + + list = &cls->methodLists; // Locate list in the array ptr = *list; @@ -497,16 +807,22 @@ void _objc_removeMethods (struct objc_method_list * mlist, /*********************************************************************** * _objc_add_category. Install the specified category's methods and * protocols into the class it augments. +* The class is assumed not to be in use yet: no locks are taken and +* no method caches are flushed. **********************************************************************/ static inline void _objc_add_category(struct objc_class *cls, struct objc_category *category, int version) { + if (PrintConnecting) { + _objc_inform("CONNECT: attaching category '%s (%s)'", cls->name, category->category_name); + } + // Augment instance methods if (category->instance_methods) - _objc_insertMethods (category->instance_methods, &cls->methodLists); + _objc_insertMethods (cls, category->instance_methods); // Augment class methods if (category->class_methods) - _objc_insertMethods (category->class_methods, &cls->isa->methodLists); + _objc_insertMethods (cls->isa, category->class_methods); // Augment protocols if ((version >= 5) && category->protocols) @@ -526,172 +842,238 @@ static inline void _objc_add_category(struct objc_class *cls, struct objc_catego } /*********************************************************************** -* _objc_add_category_flush_caches. Install the specified category's methods into -* the class it augments, and flush the class' method cache. -* +* _objc_add_category_flush_caches. Install the specified category's +* methods into the class it augments, and flush the class' method cache. **********************************************************************/ static void _objc_add_category_flush_caches(struct objc_class *cls, struct objc_category *category, int version) { // Install the category's methods into its intended class + OBJC_LOCK(&methodListLock); _objc_add_category (cls, category, version); + OBJC_UNLOCK(&methodListLock); // Flush caches so category's methods can get called _objc_flush_caches (cls); } + +/*********************************************************************** +* reverse_cat +* Reverse the given linked list of pending categories. +* The pending category list is built backwards, and needs to be +* reversed before actually attaching the categories to a class. +* Returns the head of the new linked list. +**********************************************************************/ +static _objc_unresolved_category *reverse_cat(_objc_unresolved_category *cat) +{ + if (!cat) return NULL; + + _objc_unresolved_category *prev = NULL; + _objc_unresolved_category *cur = cat; + _objc_unresolved_category *ahead = cat->next; + + while (cur) { + ahead = cur->next; + cur->next = prev; + prev = cur; + cur = ahead; + } + + return prev; +} + + /*********************************************************************** -* _objc_resolve_categories_for_class. Install all categories intended -* for the specified class, in reverse order from the order in which we -* found the categories in the image. -* This is done as lazily as we can. +* resolve_categories_for_class. +* Install all existing categories intended for the specified class. +* cls must be a true class and not a metaclass. **********************************************************************/ -void _objc_resolve_categories_for_class (struct objc_class * cls) +static void resolve_categories_for_class(struct objc_class *cls) { - _objc_unresolved_category * cat; + _objc_unresolved_category * pending; _objc_unresolved_category * next; // Nothing to do if there are no categories at all - if (!category_hash) - return; + if (!category_hash) return; // Locate and remove first element in category list // associated with this class - cat = NXMapRemove (category_hash, cls->name); + pending = NXMapKeyFreeingRemove (category_hash, cls->name); // Traverse the list of categories, if any, registered for this class - while (cat) - { - if (cat->bindme) { - _objc_bindModuleContainingCategory(cat->cat); + + // The pending list is built backwards. Reverse it and walk forwards. + pending = reverse_cat(pending); + + while (pending) { + if (pending->cat) { + // Install the category + // use the non-flush-cache version since we are only + // called from the class intialization code + _objc_add_category(cls, pending->cat, pending->version); } - // Install the category - // use the non-flush-cache version since we are only - // called from the class intialization code - _objc_add_category (cls, cat->cat, cat->version); // Delink and reclaim this registration - next = cat->next; - free (cat); - cat = next; + next = pending->next; + _free_internal(pending); + pending = next; + } +} + + +/*********************************************************************** +* _objc_resolve_categories_for_class. +* Public version of resolve_categories_for_class. This was +* exported pre-10.4 for Omni et al. to workaround a problem +* with too-lazy category attachment. +* cls should be a class, but this function can also cope with metaclasses. +**********************************************************************/ +void _objc_resolve_categories_for_class(struct objc_class *cls) +{ + + // If cls is a metaclass, get the class. + // resolve_categories_for_class() requires a real class to work correctly. + if (ISMETA(cls)) { + if (strncmp(cls->name, "_%", 2) == 0) { + // Posee's meta's name is smashed and isn't in the class_hash, + // so objc_getClass doesn't work. + char *baseName = strchr(cls->name, '%'); // get posee's real name + cls = objc_getClass(baseName); + } else { + cls = objc_getClass(cls->name); + } } + + resolve_categories_for_class(cls); } + /*********************************************************************** -* _objc_register_category. Add the specified category to the registry -* of categories to be installed later (once we know for sure which - * classes we have). If there are multiple categories on a given class, -* they will be processed in reverse order from the order in which they -* were found in the image. +* _objc_register_category. +* Process a category read from an image. +* If the category's class exists, attach the category immediately. +* If the category's class does not exist yet, pend the category for +* later attachment. Pending categories are attached in the order +* they were discovered. **********************************************************************/ -static void _objc_register_category (struct objc_category * cat, - long version, - int bindme) +static void _objc_register_category(struct objc_category *cat, long version) { _objc_unresolved_category * new_cat; _objc_unresolved_category * old; struct objc_class *theClass; - - // If the category's class exists, just add the category - // We could check to see if its initted, and if not, defer this - // work until _objc_resolve_categories_for_class for all cases - // The only trick then is whether we need to bind it. This - // might be doable if we store an obscured pointer so that we - // avoid touching the memory... [BG 5/2001 still in think mode] - if (theClass = objc_lookUpClass (cat->class_name)) - { - if (bindme) { - _objc_bindModuleContainingCategory(cat); - } - _objc_add_category_flush_caches (theClass, cat, version); + // If the category's class exists, attach the category. + if ((theClass = objc_lookUpClass(cat->class_name))) { + _objc_add_category_flush_caches(theClass, cat, version); return; } + + // If the category's class exists but is unconnected, + // then attach the category to the class but don't bother + // flushing any method caches (because they must be empty). + // YES unconnected, NO class_handler + if ((theClass = look_up_class(cat->class_name, YES, NO))) { + _objc_add_category(theClass, cat, version); + return; + } + + + // Category's class does not exist yet. + // Save the category for later attachment. + + if (PrintConnecting) { + _objc_inform("CONNECT: pending category '%s (%s)'", cat->class_name, cat->category_name); + } // Create category lookup table if needed if (!category_hash) category_hash = NXCreateMapTableFromZone (NXStrValueMapPrototype, 128, - _objc_create_zone ()); + _objc_internal_zone ()); - // Locate an existing category, if any, for the class. This is linked - // after the new entry, so list is LIFO. + // Locate an existing list of categories, if any, for the class. old = NXMapGet (category_hash, cat->class_name); - // Register the category to be fixed up later - new_cat = malloc_zone_malloc (_objc_create_zone (), - sizeof(_objc_unresolved_category)); + // Register the category to be fixed up later. + // The category list is built backwards, and is reversed again + // by resolve_categories_for_class(). + new_cat = _malloc_internal(sizeof(_objc_unresolved_category)); new_cat->next = old; new_cat->cat = cat; new_cat->version = version; - new_cat->bindme = bindme; // could use a bit in the next pointer instead of a whole word - (void) NXMapInsert (category_hash, cat->class_name , new_cat); + (void) NXMapKeyCopyingInsert (category_hash, cat->class_name, new_cat); } + /*********************************************************************** -* _objc_add_categories_from_image. +* _objc_read_categories_from_image. +* Read all categories from the given image. +* Install them on their parent classes, or register them for later +* installation. +* Register them for later +load, if implemented. **********************************************************************/ -static void _objc_add_categories_from_image (header_info * hi) +static void _objc_read_categories_from_image (header_info * hi) { Module mods; unsigned int midx; - int isDynamic = (hi->mhdr->filetype == MH_DYLIB) || (hi->mhdr->filetype == MH_BUNDLE); - // Major loop - process all modules in the header - mods = (Module) ((unsigned long) hi->mod_ptr + hi->image_slide); + if (_objcHeaderIsReplacement(hi)) { + // Ignore any categories in this image + return; + } - trace(0xb120, hi->mod_count, 0, 0); + // Major loop - process all modules in the header + mods = hi->mod_ptr; - for (midx = 0; midx < hi->mod_count; midx += 1) - { + // NOTE: The module and category lists are traversed backwards + // to preserve the pre-10.4 processing order. Changing the order + // would have a small chance of introducing binary compatibility bugs. + midx = hi->mod_count; + while (midx-- > 0) { unsigned int index; unsigned int total; - + // Nothing to do for a module without a symbol table if (mods[midx].symtab == NULL) continue; - + // Total entries in symbol table (class entries followed // by category entries) total = mods[midx].symtab->cls_def_cnt + mods[midx].symtab->cat_def_cnt; - - - trace(0xb123, midx, mods[midx].symtab->cat_def_cnt, 0); - + // Minor loop - register all categories from given module - for (index = mods[midx].symtab->cls_def_cnt; index < total; index += 1) - { - _objc_register_category(mods[midx].symtab->defs[index], mods[midx].version, isDynamic); + index = total; + while (index-- > mods[midx].symtab->cls_def_cnt) { + struct objc_category *cat = mods[midx].symtab->defs[index]; + _objc_register_category(cat, mods[midx].version); + add_category_to_loadable_list(cat); } - - trace(0xb124, midx, 0, 0); } - - trace(0xb12f, 0, 0, 0); } + /*********************************************************************** -* _headerForClass. +* _headerForAddress. +* addr can be a class or a category **********************************************************************/ -static const header_info * _headerForClass (struct objc_class * cls) +static const header_info *_headerForAddress(void *addr) { - const struct segment_command * objcSeg; - unsigned int size; - unsigned long vmaddrPlus; + unsigned long size; + unsigned long seg; header_info * hInfo; // Check all headers in the vector for (hInfo = FirstHeader; hInfo != NULL; hInfo = hInfo->next) { // Locate header data, if any - objcSeg = _getObjcHeaderData ((headerType *) hInfo->mhdr, &size); - if (!objcSeg) - continue; + if (!hInfo->objcSegmentHeader) continue; + seg = hInfo->objcSegmentHeader->vmaddr + hInfo->image_slide; + size = hInfo->objcSegmentHeader->filesize; // Is the class in this header? - vmaddrPlus = (unsigned long) objcSeg->vmaddr + hInfo->image_slide; - if ((vmaddrPlus <= (unsigned long) cls) && - ((unsigned long) cls < (vmaddrPlus + size))) + if ((seg <= (unsigned long) addr) && + ((unsigned long) addr < (seg + size))) return hInfo; } @@ -699,6 +1081,18 @@ static const header_info * _headerForClass (struct objc_class * cls) return 0; } + +/*********************************************************************** +* _headerForClass +* Return the image header containing this class, or NULL. +* Returns NULL on runtime-constructed classes, and the NSCF classes. +**********************************************************************/ +static const header_info *_headerForClass(struct objc_class *cls) +{ + return _headerForAddress(cls); +} + + /*********************************************************************** * _nameForHeader. **********************************************************************/ @@ -707,159 +1101,729 @@ const char * _nameForHeader (const headerType * header) return _getObjcHeaderName ((headerType *) header); } + /*********************************************************************** -* checkForPendingClassReferences. Complete any fixups registered for -* this class. +* class_is_connected. +* Returns TRUE if class cls is connected. +* A connected class has either a connected superclass or a NULL superclass, +* and is present in class_hash. **********************************************************************/ -static void _objc_checkForPendingClassReferences (struct objc_class * cls) +static BOOL class_is_connected(struct objc_class *cls) { - PendingClass * pending; - - // Nothing to do if there are no pending classes - if (!pendingClassRefsMap) - return; - - // Get pending list for this class - pending = NXMapGet (pendingClassRefsMap, cls->name); - if (!pending) - return; - - // Remove the list from the table - (void) NXMapRemove (pendingClassRefsMap, cls->name); - - // Process all elements in the list - while (pending) - { - PendingClass * next; - - // Remember follower for loop - next = pending->next; - - // Fill in a pointer to Class - // (satisfies caller of objc_pendClassReference) - if (pending->ref) - *pending->ref = objc_getClass (cls->name); - - // Fill in super, isa, cache, and version for the class - // and its meta-class - // (satisfies caller of objc_pendClassInstallation) - // NOTE: There must be no more than one of these for - // any given classToSetUp - if (pending->classToSetUp) - { - struct objc_class * fixCls; - - // Locate the Class to be fixed up - fixCls = pending->classToSetUp; - - // Set up super class fields with names to be replaced by pointers - fixCls->super_class = (struct objc_class *) pending->nameof_superclass; - fixCls->isa->super_class = (struct objc_class *) pending->nameof_superclass; - - // Fix up class pointers, version, and cache pointers - _class_install_relationships (fixCls, pending->version); - } + BOOL result; + OBJC_LOCK(&classLock); + result = NXHashMember(class_hash, cls); + OBJC_UNLOCK(&classLock); + return result; +} - // Reclaim the element - free (pending); - // Move on - pending = next; +/*********************************************************************** +* pendingClassRefsMapTable. Return a pointer to the lookup table for +* pending class refs. +**********************************************************************/ +static inline NXMapTable *pendingClassRefsMapTable(void) +{ + // Allocate table if needed + if (!pendingClassRefsMap) { + pendingClassRefsMap = + NXCreateMapTableFromZone(NXStrValueMapPrototype, + 10, _objc_internal_zone ()); } + + // Return table pointer + return pendingClassRefsMap; } + /*********************************************************************** -* newPending. Allocate and zero a PendingClass structure. +* pendingSubclassesMapTable. Return a pointer to the lookup table for +* pending subclasses. **********************************************************************/ -static inline PendingClass * newPending (void) +static inline NXMapTable *pendingSubclassesMapTable(void) { - PendingClass * pending; - - pending = (PendingClass *) malloc_zone_calloc (_objc_create_zone (), 1, sizeof(PendingClass)); - - return pending; + // Allocate table if needed + if (!pendingSubclassesMap) { + pendingSubclassesMap = + NXCreateMapTableFromZone(NXStrValueMapPrototype, + 10, _objc_internal_zone ()); + } + + // Return table pointer + return pendingSubclassesMap; } + /*********************************************************************** -* pendingClassRefsMapTable. Return a pointer to the lookup table for -* pending classes. +* pendClassInstallation +* Finish connecting class cls when its superclass becomes connected. +* Check for multiple pends of the same class because connect_class does not. **********************************************************************/ -static inline NXMapTable * pendingClassRefsMapTable (void) +static void pendClassInstallation(struct objc_class *cls, + const char *superName) { - // Allocate table if needed - if (!pendingClassRefsMap) - pendingClassRefsMap = NXCreateMapTableFromZone (NXStrValueMapPrototype, 10, _objc_create_zone ()); + NXMapTable *table; + PendingSubclass *pending; + PendingSubclass *oldList; + PendingSubclass *l; + + // Create and/or locate pending class lookup table + table = pendingSubclassesMapTable (); - // Return table pointer - return pendingClassRefsMap; + // Make sure this class isn't already in the pending list. + oldList = NXMapGet (table, superName); + for (l = oldList; l != NULL; l = l->next) { + if (l->subclass == cls) return; // already here, nothing to do + } + + // Create entry referring to this class + pending = _malloc_internal(sizeof(PendingSubclass)); + pending->subclass = cls; + + // Link new entry into head of list of entries for this class + pending->next = oldList; + + // (Re)place entry list in the table + (void) NXMapKeyCopyingInsert (table, superName, pending); } + /*********************************************************************** -* objc_pendClassReference. Register the specified class pointer (ref) -* to be filled in later with a pointer to the class having the specified -* name. +* pendClassReference +* Fix up a class ref when the class with the given name becomes connected. **********************************************************************/ -void objc_pendClassReference (const char * className, - struct objc_class * * ref) +static void pendClassReference(struct objc_class **ref, + const char *className) { - NXMapTable * table; - PendingClass * pending; - + NXMapTable *table; + PendingClassRef *pending; + // Create and/or locate pending class lookup table table = pendingClassRefsMapTable (); - + // Create entry containing the class reference - pending = newPending (); + pending = _malloc_internal(sizeof(PendingClassRef)); pending->ref = ref; - + // Link new entry into head of list of entries for this class - pending->next = NXMapGet (pendingClassRefsMap, className); - + pending->next = NXMapGet (table, className); + // (Re)place entry list in the table - (void) NXMapInsert (table, className, pending); + (void) NXMapKeyCopyingInsert (table, className, pending); + + if (PrintConnecting) { + _objc_inform("CONNECT: pended reference to class '%s' at %p", + className, (void *)ref); + } } + /*********************************************************************** -* objc_pendClassInstallation. Register the specified class to have its -* super class pointers filled in later because the superclass is not -* yet found. +* resolve_references_to_class +* Fix up any pending class refs to this class. **********************************************************************/ -void objc_pendClassInstallation (struct objc_class *cls, int version) +static void resolve_references_to_class(struct objc_class *cls) { - NXMapTable * table; - PendingClass * pending; + PendingClassRef *pending; + + if (!pendingClassRefsMap) return; // no unresolved refs for any class - // Create and/or locate pending class lookup table - table = pendingClassRefsMapTable (); + pending = NXMapGet(pendingClassRefsMap, cls->name); + if (!pending) return; // no unresolved refs for this class - // Create entry referring to this class - pending = newPending (); - pending->classToSetUp = cls; - pending->nameof_superclass = (const char *) cls->super_class; - pending->version = version; + NXMapKeyFreeingRemove(pendingClassRefsMap, cls->name); - // Link new entry into head of list of entries for this class - pending->next = NXMapGet (pendingClassRefsMap, cls->super_class); + if (PrintConnecting) { + _objc_inform("CONNECT: resolving references to class '%s'", cls->name); + } - // (Re)place entry list in the table - (void) NXMapInsert (table, cls->super_class, pending); + while (pending) { + PendingClassRef *next = pending->next; + if (pending->ref) *pending->ref = cls; + _free_internal(pending); + pending = next; + } + + if (NXCountMapTable(pendingClassRefsMap) == 0) { + NXFreeMapTable(pendingClassRefsMap); + pendingClassRefsMap = NULL; + } } + /*********************************************************************** -* _objc_add_classes_from_image. Install all classes contained in the -* specified image. +* resolve_subclasses_of_class +* Fix up any pending subclasses of this class. **********************************************************************/ -static void _objc_add_classes_from_image(NXHashTable *clsHash, header_info *hi) +static void resolve_subclasses_of_class(struct objc_class *cls) { - unsigned int index; - unsigned int midx; - Module mods; - int isDynamic = (hi->mhdr->filetype == MH_DYLIB) || (hi->mhdr->filetype == MH_BUNDLE); + PendingSubclass *pending; + + if (!pendingSubclassesMap) return; // no unresolved subclasses - // Major loop - process all modules in the image - mods = (Module) ((unsigned long) hi->mod_ptr + hi->image_slide); - for (midx = 0; midx < hi->mod_count; midx += 1) - { + pending = NXMapGet(pendingSubclassesMap, cls->name); + if (!pending) return; // no unresolved subclasses for this class + + NXMapKeyFreeingRemove(pendingSubclassesMap, cls->name); + + // Destroy the pending table if it's now empty, to save memory. + if (NXCountMapTable(pendingSubclassesMap) == 0) { + NXFreeMapTable(pendingSubclassesMap); + pendingSubclassesMap = NULL; + } + + if (PrintConnecting) { + _objc_inform("CONNECT: resolving subclasses of class '%s'", cls->name); + } + + while (pending) { + PendingSubclass *next = pending->next; + if (pending->subclass) connect_class(pending->subclass); + _free_internal(pending); + pending = next; + } +} + + +/*********************************************************************** +* get_base_method_list +* Returns the method list containing the class's own methods, +* ignoring any method lists added by categories or class_addMethods. +* Called only by add_class_to_loadable_list. +* Does not hold methodListLock because add_class_to_loadable_list +* does not manipulate in-use classes. +**********************************************************************/ +static struct objc_method_list *get_base_method_list(struct objc_class *cls) +{ + struct objc_method_list **ptr; + + if (!cls->methodLists) return NULL; + if (cls->info & CLS_NO_METHOD_ARRAY) return (struct objc_method_list *)cls->methodLists; + ptr = cls->methodLists; + if (!*ptr || *ptr == END_OF_METHODS_LIST) return NULL; + while ( *ptr != 0 && *ptr != END_OF_METHODS_LIST ) { ptr++; } + --ptr; + return *ptr; +} + + +/*********************************************************************** +* add_class_to_loadable_list +* Class cls has just become connected. Schedule it for +load if +* it implements a +load method. +**********************************************************************/ +static void add_class_to_loadable_list(struct objc_class *cls) +{ + IMP method = NULL; + struct objc_method_list *mlist; + + if (cls->isa->info & CLS_HAS_LOAD_METHOD) { + mlist = get_base_method_list(cls->isa); + if (mlist) { + method = lookupNamedMethodInMethodList (mlist, "load"); + } + } + // Don't bother if cls has no +load method + if (!method) return; + + if (PrintLoading) { + _objc_inform("LOAD: class '%s' scheduled for +load", cls->name); + } + + if (loadable_classes_used == loadable_classes_allocated) { + loadable_classes_allocated = loadable_classes_allocated*2 + 16; + loadable_classes = + _realloc_internal(loadable_classes, + loadable_classes_allocated * + sizeof(struct loadable_class)); + } + + loadable_classes[loadable_classes_used].cls = cls; + loadable_classes[loadable_classes_used].method = method; + loadable_classes_used++; +} + + +/*********************************************************************** +* add_category_to_loadable_list +* Category cat's parent class exists and the category has been attached +* to its class. Schedule this category for +load after its parent class +* becomes connected and has its own +load method called. +**********************************************************************/ +static void add_category_to_loadable_list(struct objc_category *cat) +{ + IMP method = NULL; + struct objc_method_list *mlist; + + mlist = cat->class_methods; + if (mlist) { + method = lookupNamedMethodInMethodList (mlist, "load"); + } + // Don't bother if cat has no +load method + if (!method) return; + + if (PrintLoading) { + _objc_inform("LOAD: category '%s(%s)' scheduled for +load", + cat->class_name, cat->category_name); + } + + if (loadable_categories_used == loadable_categories_allocated) { + loadable_categories_allocated = loadable_categories_allocated*2 + 16; + loadable_categories = + _realloc_internal(loadable_categories, + loadable_categories_allocated * + sizeof(struct loadable_category)); + } + + loadable_categories[loadable_categories_used].cat = cat; + loadable_categories[loadable_categories_used].method = method; + loadable_categories_used++; +} + + +/*********************************************************************** +* remove_class_from_loadable_list +* Class cls may have been loadable before, but it is now no longer +* loadable (because its image is being unmapped). +**********************************************************************/ +static void remove_class_from_loadable_list(struct objc_class *cls) +{ + if (loadable_classes) { + int i; + for (i = 0; i < loadable_classes_used; i++) { + if (loadable_classes[i].cls == cls) { + loadable_classes[i].cls = NULL; + if (PrintLoading) { + _objc_inform("LOAD: class '%s' unscheduled for +load", cls->name); + } + return; + } + } + } +} + + +/*********************************************************************** +* remove_category_from_loadable_list +* Category cat may have been loadable before, but it is now no longer +* loadable (because its image is being unmapped). +**********************************************************************/ +static void remove_category_from_loadable_list(struct objc_category *cat) +{ + if (loadable_categories) { + int i; + for (i = 0; i < loadable_categories_used; i++) { + if (loadable_categories[i].cat == cat) { + loadable_categories[i].cat = NULL; + if (PrintLoading) { + _objc_inform("LOAD: category '%s(%s)' unscheduled for +load", + cat->class_name, cat->category_name); + } + return; + } + } + } +} + + +/*********************************************************************** +* call_class_loads +* Call all pending class +load methods. +* If new classes become loadable, +load is NOT called for them. +* +* Called only by call_load_methods(). +**********************************************************************/ +static void call_class_loads(void) +{ + int i; + + // Detach current loadable list. + struct loadable_class *classes = loadable_classes; + int used = loadable_classes_used; + loadable_classes = NULL; + loadable_classes_allocated = 0; + loadable_classes_used = 0; + + // Call all +loads for the detached list. + for (i = 0; i < used; i++) { + struct objc_class *cls = classes[i].cls; + IMP load_method = classes[i].method; + if (!cls) continue; + + if (PrintLoading) { + _objc_inform("LOAD: +[%s load]\n", cls->name); + } + (*load_method) ((id) cls, @selector(load)); + } + + // Destroy the detached list. + if (classes) _free_internal(classes); +} + + +/*********************************************************************** +* call_category_loads +* Call some pending category +load methods. +* The parent class of the +load-implementing categories has all of +* its categories attached, in case some are lazily waiting for +initalize. +* Don't call +load unless the parent class is connected. +* If new categories become loadable, +load is NOT called, and they +* are added to the end of the loadable list, and we return TRUE. +* Return FALSE if no new categories became loadable. +* +* Called only by call_load_methods(). +**********************************************************************/ +static BOOL call_category_loads(void) +{ + int i, shift; + BOOL new_categories_added = NO; + + // Detach current loadable list. + struct loadable_category *cats = loadable_categories; + int used = loadable_categories_used; + int allocated = loadable_categories_allocated; + loadable_categories = NULL; + loadable_categories_allocated = 0; + loadable_categories_used = 0; + + // Call all +loads for the detached list. + for (i = 0; i < used; i++) { + struct objc_category *cat = cats[i].cat; + IMP load_method = cats[i].method; + struct objc_class *cls; + if (!cat) continue; + + cls = objc_getClass(cat->class_name); + if (cls && class_is_connected(cls)) { + if (PrintLoading) { + _objc_inform("LOAD: +[%s(%s) load]\n", + cls->name, cat->category_name); + } + (*load_method) ((id) cls, @selector(load)); + cats[i].cat = NULL; + } + } + + // Compact detached list (order-preserving) + shift = 0; + for (i = 0; i < used; i++) { + if (cats[i].cat) { + cats[i-shift] = cats[i]; + } else { + shift++; + } + } + used -= shift; + + // Copy any new +load candidates from the new list to the detached list. + new_categories_added = (loadable_categories_used > 0); + for (i = 0; i < loadable_categories_used; i++) { + if (used == allocated) { + allocated = allocated*2 + 16; + cats = _realloc_internal(cats, allocated * + sizeof(struct loadable_category)); + } + cats[used++] = loadable_categories[i]; + } + + // Destroy the new list. + if (loadable_categories) _free_internal(loadable_categories); + + // Reattach the (now augmented) detached list. + // But if there's nothing left to load, destroy the list. + if (used) { + loadable_categories = cats; + loadable_categories_used = used; + loadable_categories_allocated = allocated; + } else { + if (cats) _free_internal(cats); + loadable_categories = NULL; + loadable_categories_used = 0; + loadable_categories_allocated = 0; + } + + if (PrintLoading) { + if (loadable_categories_used != 0) { + _objc_inform("LOAD: %d categories still waiting for +load\n", + loadable_categories_used); + } + } + + return new_categories_added; +} + + +/*********************************************************************** +* call_load_methods +* Call all pending class and category +load methods. +* Class +load methods are called superclass-first. +* Category +load methods are not called until after the parent class's +load. +* +* This method must be RE-ENTRANT, because a +load could trigger +* more image mapping. In addition, the superclass-first ordering +* must be preserved in the face of re-entrant calls. Therefore, +* only the OUTERMOST call of this function will do anything, and +* that call will handle all loadable classes, even those generated +* while it was running. +* +* The sequence below preserves +load ordering in the face of +* image loading during a +load, and make sure that no +* +load method is forgotten because it was added during +* a +load call. +* Sequence: +* 1. Repeatedly call class +loads until there aren't any more +* 2. Call category +loads ONCE. +* 3. Run more +loads if: +* (a) there are more classes to load, OR +* (b) there are some potential category +loads that have +* still never been attempted. +* Category +loads are only run once to ensure "parent class first" +* ordering, even if a category +load triggers a new loadable class +* and a new loadable category attached to that class. +* +* fixme this is not thread-safe, but neither is the rest of image mapping. +**********************************************************************/ +static void call_load_methods(void) +{ + static pthread_t load_method_thread NOBSS = NULL; + BOOL more_categories; + + if (load_method_thread) { + // +loads are already being called. Do nothing, but complain + // if it looks like multithreaded use of this thread-unsafe code. + + if (! pthread_equal(load_method_thread, pthread_self())) { + _objc_inform("WARNING: multi-threaded library loading detected " + "(implementation is not thread-safe)"); + } + return; + } + + // Nobody else is calling +loads, so we should do it ourselves. + load_method_thread = pthread_self(); + + do { + // 1. Repeatedly call class +loads until there aren't any more + while (loadable_classes_used > 0) { + call_class_loads(); + } + + // 2. Call category +loads ONCE + more_categories = call_category_loads(); + + // 3. Run more +loads if there are classes OR more untried categories + } while (loadable_classes_used > 0 || more_categories); + + load_method_thread = NULL; +} + + +/*********************************************************************** +* really_connect_class +* Connect cls to superclass supercls unconditionally. +* Also adjust the class hash tables and handle +load and pended subclasses. +* +* This should be called from connect_class() ONLY. +**********************************************************************/ +static void really_connect_class(struct objc_class *cls, + struct objc_class *supercls) +{ + struct objc_class *oldCls; + struct objc_class *meta = cls->isa; + + // Wire the classes together. + if (supercls) { + cls->super_class = supercls; + meta->super_class = supercls->isa; + meta->isa = supercls->isa->isa; + } else { + cls->super_class = NULL; // superclass of root class is NULL + meta->super_class = cls; // superclass of root metaclass is root class + meta->isa = meta; // metaclass of root metaclass is root metaclass + } + + OBJC_LOCK(&classLock); + + // Update hash tables. + NXHashRemove(unconnected_class_hash, cls); + oldCls = NXHashInsert(class_hash, cls); + + // Delete unconnected_class_hash if it is now empty. + if (NXCountHashTable(unconnected_class_hash) == 0) { + NXFreeHashTable(unconnected_class_hash); + unconnected_class_hash = NULL; + } + + OBJC_UNLOCK(&classLock); + + // Warn if the new class has the same name as a previously-installed class. + // The new class is kept and the old class is discarded. + if (oldCls) { + const header_info *oldHeader = _headerForClass(oldCls); + const header_info *newHeader = _headerForClass(cls); + const char *oldName = _nameForHeader(oldHeader->mhdr); + const char *newName = _nameForHeader(newHeader->mhdr); + + _objc_inform ("Both %s and %s have implementations of class %s.", + oldName, newName, oldCls->name); + _objc_inform ("Using implementation from %s.", newName); + } + + // Prepare for +load and connect newly-connectable subclasses + add_class_to_loadable_list(cls); + resolve_subclasses_of_class(cls); + + // GC debugging: make sure all classes with -dealloc also have -finalize + if (CheckFinalizers) { + extern IMP findIMPInClass(Class cls, SEL sel); + if (findIMPInClass(cls, sel_getUid("dealloc")) && + ! findIMPInClass(cls, sel_getUid("finalize"))) + { + _objc_inform("GC: class '%s' implements -dealloc but not -finalize", cls->name); + } + } + + // Debugging: if this class has ivars, make sure this class's ivars don't + // overlap with its super's. This catches some broken fragile base classes. + // Do not use super->instance_size vs. self->ivar[0] to check this. + // Ivars may be packed across instance_size boundaries. + if (DebugFragileSuperclasses && cls->ivars && cls->ivars->ivar_count) { + struct objc_class *ivar_cls = supercls; + + // Find closest superclass that has some ivars, if one exists. + while (ivar_cls && + (!ivar_cls->ivars || ivar_cls->ivars->ivar_count == 0)) + { + ivar_cls = ivar_cls->super_class; + } + + if (ivar_cls) { + // Compare superclass's last ivar to this class's first ivar + struct objc_ivar *super_ivar = + &ivar_cls->ivars->ivar_list[ivar_cls->ivars->ivar_count - 1]; + struct objc_ivar *self_ivar = + &cls->ivars->ivar_list[0]; + + // fixme could be smarter about super's ivar size + if (self_ivar->ivar_offset <= super_ivar->ivar_offset) { + _objc_inform("WARNING: ivars of superclass '%s' and " + "subclass '%s' overlap; superclass may have " + "changed since subclass was compiled", + ivar_cls->name, cls->name); + } + } + } +} + + +/*********************************************************************** +* connect_class +* Connect class cls to its superclasses, if possible. +* If cls becomes connected, move it from unconnected_class_hash +* to connected_class_hash. +* Returns TRUE if cls is connected. +* Returns FALSE if cls could not be connected for some reason +* (missing superclass or still-unconnected superclass) +**********************************************************************/ +static BOOL connect_class(struct objc_class *cls) +{ + if (class_is_connected(cls)) { + // This class is already connected to its superclass. + // Do nothing. + return TRUE; + } + else if (cls->super_class == NULL) { + // This class is a root class. + // Connect it to itself. + + if (PrintConnecting) { + _objc_inform("CONNECT: class '%s' now connected (root class)", + cls->name); + } + + really_connect_class(cls, NULL); + return TRUE; + } + else { + // This class is not a root class and is not yet connected. + // Connect it if its superclass and root class are already connected. + // Otherwise, add this class to the to-be-connected list, + // pending the completion of its superclass and root class. + + // At this point, cls->super_class and cls->isa->isa are still STRINGS + char *supercls_name = (char *)cls->super_class; + struct objc_class *supercls; + + // YES unconnected, YES class handler + if (NULL == (supercls = look_up_class(supercls_name, YES, YES))) { + // Superclass does not exist yet. + // pendClassInstallation will handle duplicate pends of this class + pendClassInstallation(cls, supercls_name); + + if (PrintConnecting) { + _objc_inform("CONNECT: class '%s' NOT connected (missing super)", cls->name); + } + return FALSE; + } + + if (! connect_class(supercls)) { + // Superclass exists but is not yet connected. + // pendClassInstallation will handle duplicate pends of this class + pendClassInstallation(cls, supercls_name); + + if (PrintConnecting) { + _objc_inform("CONNECT: class '%s' NOT connected (unconnected super)", cls->name); + } + return FALSE; + } + + // Superclass exists and is connected. + // Connect this class to the superclass. + + if (PrintConnecting) { + _objc_inform("CONNECT: class '%s' now connected", cls->name); + } + + really_connect_class(cls, supercls); + return TRUE; + } +} + + +/*********************************************************************** +* _objc_read_classes_from_image. +* Read classes from the given image, perform assorted minor fixups, +* scan for +load implementation. +* Does not connect classes to superclasses. +* Does attach pended categories to the classes. +* Adds all classes to unconnected_class_hash. class_hash is unchanged. +**********************************************************************/ +static void _objc_read_classes_from_image(header_info *hi) +{ + unsigned int index; + unsigned int midx; + Module mods; + int isBundle = (hi->mhdr->filetype == MH_BUNDLE); + + if (_objcHeaderIsReplacement(hi)) { + // Ignore any classes in this image + return; + } + + // class_hash starts small, enough only for libobjc itself. + // If other Objective-C libraries are found, immediately resize + // class_hash, assuming that Foundation and AppKit are about + // to add lots of classes. + OBJC_LOCK(&classLock); + if (hi->mhdr != &_mh_dylib_header && _NXHashCapacity(class_hash) < 1024) { + _NXHashRehashToCapacity(class_hash, 1024); + } + OBJC_UNLOCK(&classLock); + + // Major loop - process all modules in the image + mods = hi->mod_ptr; + for (midx = 0; midx < hi->mod_count; midx += 1) + { // Skip module containing no classes if (mods[midx].symtab == NULL) continue; @@ -867,101 +1831,96 @@ static void _objc_add_classes_from_image(NXHashTable *clsHash, header_info *hi) // Minor loop - process all the classes in given module for (index = 0; index < mods[midx].symtab->cls_def_cnt; index += 1) { - struct objc_class * oldCls; struct objc_class * newCls; + struct objc_method_list *mlist; // Locate the class description pointer newCls = mods[midx].symtab->defs[index]; - // remember to bind the module on initialization - if (isDynamic) - newCls->info |= CLS_NEED_BIND ; - - // Convert old style method list to the new style - _objcTweakMethodListPointerForClass (newCls); + // Classes loaded from Mach-O bundles can be unloaded later. + // Nothing uses this class yet, so _class_setInfo is not needed. + if (isBundle) newCls->info |= CLS_FROM_BUNDLE; + if (isBundle) newCls->isa->info |= CLS_FROM_BUNDLE; - oldCls = NXHashInsert (clsHash, newCls); - - // Non-Nil oldCls is a class that NXHashInsert just - // bumped from table because it has the same name - // as newCls - if (oldCls) - { - const header_info * oldHeader; - const header_info * newHeader; - const char * oldName; - const char * newName; - - // Log the duplication - oldHeader = _headerForClass (oldCls); - newHeader = _headerForClass (newCls); - oldName = _nameForHeader (oldHeader->mhdr); - newName = _nameForHeader (newHeader->mhdr); - _objc_inform ("Both %s and %s have implementations of class %s.", - oldName, newName, oldCls->name); - _objc_inform ("Using implementation from %s.", newName); - - // Use the chosen class - // NOTE: Isn't this a NOP? - newCls = objc_lookUpClass (oldCls->name); - } + // Use common static empty cache instead of NULL + if (newCls->cache == NULL) + newCls->cache = (Cache) &emptyCache; + if (newCls->isa->cache == NULL) + newCls->isa->cache = (Cache) &emptyCache; - // Unless newCls was a duplicate, and we chose the - // existing one instead, set the version in the meta-class - if (newCls != oldCls) - newCls->isa->version = mods[midx].version; + // Set metaclass version + newCls->isa->version = mods[midx].version; - // Install new categories intended for this class - // NOTE: But, if we displaced an existing "isEqual" - // class, the categories have already been installed - // on an old class and are gone from the registry!! + // methodLists is NULL or a single list, not an array + newCls->info |= CLS_NO_METHOD_ARRAY; + newCls->isa->info |= CLS_NO_METHOD_ARRAY; - // we defer this work until the class is initialized. - //_objc_resolve_categories_for_class (newCls); + // Check for +load implementation before categories are attached + if ((mlist = get_base_method_list(newCls->isa))) { + if (lookupNamedMethodInMethodList (mlist, "load")) { + newCls->isa->info |= CLS_HAS_LOAD_METHOD; + } + } + + // Install into unconnected_class_hash + OBJC_LOCK(&classLock); + if (!unconnected_class_hash) { + unconnected_class_hash = + NXCreateHashTableFromZone(classHashPrototype, 128, NULL, + _objc_internal_zone()); + } + NXHashInsert(unconnected_class_hash, newCls); + OBJC_UNLOCK(&classLock); - // Resolve (a) pointers to the named class, and/or - // (b) the super_class, cache, and version - // fields of newCls and its meta-class - // NOTE: But, if we displaced an existing "isEqual" - // class, this has already been done... with an - // old-now-"unused" class!! - _objc_checkForPendingClassReferences (newCls); + // Fix up pended class refs to this class, if any + resolve_references_to_class(newCls); + // Attach pended categories for this class, if any + resolve_categories_for_class(newCls); } } } + /*********************************************************************** -* _objc_fixup_string_objects_for_image. Initialize the isa pointers -* of all NSConstantString objects. +* _objc_connect_classes_from_image. +* Connect the classes in the given image to their superclasses, +* or register them for later connection if any superclasses are missing. **********************************************************************/ -static void _objc_fixup_string_objects_for_image (header_info * hi) +static void _objc_connect_classes_from_image(header_info *hi) { - unsigned int size; - OBJC_CONSTANT_STRING_PTR section; - struct objc_class * constantStringClass; - unsigned int index; - - // Locate section holding string objects - section = _getObjcStringObjects ((headerType *) hi->mhdr, &size); - if (!section || !size) - return; - section = (OBJC_CONSTANT_STRING_PTR) ((unsigned long) section + hi->image_slide); - - // Luckily NXConstantString is the same size as NSConstantString - constantStringClass = objc_getClass ("NSConstantString"); + unsigned int index; + unsigned int midx; + Module mods; + BOOL replacement = _objcHeaderIsReplacement(hi); - // Process each string object in the section - for (index = 0; index < size; index += 1) + // Major loop - process all modules in the image + mods = hi->mod_ptr; + for (midx = 0; midx < hi->mod_count; midx += 1) { - struct objc_class * * isaptr; + // Skip module containing no classes + if (mods[midx].symtab == NULL) + continue; - isaptr = (struct objc_class * *) OBJC_CONSTANT_STRING_DEREF section[index]; - if (*isaptr == 0) - *isaptr = constantStringClass; + // Minor loop - process all the classes in given module + for (index = 0; index < mods[midx].symtab->cls_def_cnt; index += 1) + { + struct objc_class *cls = mods[midx].symtab->defs[index]; + if (! replacement) { + BOOL connected = connect_class(cls); + if (connected && callbackFunction) { + (*callbackFunction)(cls, 0); + } + } else { + // Replacement image - fix up super_class only (#3704817) + const char *super_name = (const char *) cls->super_class; + if (super_name) cls->super_class = objc_getClass(super_name); + } + } } } + /*********************************************************************** * _objc_map_class_refs_for_image. Convert the class ref entries from * a class name string pointer to a class pointer. If the class does @@ -990,57 +1949,111 @@ static void _objc_map_class_refs_for_image (header_info * hi) ref = (const char *) cls_refs[index]; // Get pointer to class of this name - cls = (struct objc_class *)objc_lookUpClass (ref); - - // If class isn't there yet, use pending mechanism - if (!cls) - { - // Register this ref to be set later - objc_pendClassReference (ref, &cls_refs[index]); - - // Use place-holder class + // YES unconnected, YES class loader + cls = look_up_class(ref, YES, YES); + if (cls) { + // Referenced class exists. Fix up the reference. + cls_refs[index] = cls; + } else { + // Referenced class does not exist yet. Insert a placeholder + // class and fix up the reference later. + pendClassReference (&cls_refs[index], ref); cls_refs[index] = _objc_getNonexistentClass (); } - - // Replace name string pointer with class pointer - else - cls_refs[index] = cls; } } + +/*********************************************************************** +* _objc_remove_pending_class_refs_in_image +* Delete any pending class ref fixups for class refs in the given image, +* because the image is about to be unloaded. +**********************************************************************/ +static void _objc_remove_pending_class_refs_in_image(header_info *hi) +{ + struct objc_class **cls_refs, **cls_refs_end; + unsigned int size; + + if (!pendingClassRefsMap) return; + + // Locate class refs in this image + cls_refs = _getObjcClassRefs ((headerType *) hi->mhdr, &size); + if (!cls_refs) + return; + cls_refs = (struct objc_class **) ((uintptr_t)cls_refs + hi->image_slide); + cls_refs_end = (struct objc_class **)(size + (uintptr_t)cls_refs); + + // Search the pending class ref table for class refs in this range. + // The class refs may have already been stomped with nonexistentClass, + // so there's no way to recover the original class name. + + const char *key; + PendingClassRef *pending; + NXMapState state = NXInitMapState(pendingClassRefsMap); + while(NXNextMapState(pendingClassRefsMap, &state, + (const void **)&key, (const void **)&pending)) + { + for ( ; pending != NULL; pending = pending->next) { + if (pending->ref >= cls_refs && pending->ref < cls_refs_end) { + pending->ref = NULL; + } + } + } +} + + /*********************************************************************** * map_selrefs. Register each selector in the specified array. If a * given selector is already registered, update this array to point to * the registered selector string. +* If copy is TRUE, all selector data is always copied. This is used +* for registering selectors from unloadable bundles, so the selector +* can still be used after the bundle's data segment is unmapped. +* Returns YES if dst was written to, NO if it was unchanged. **********************************************************************/ -static inline void map_selrefs(SEL *sels, unsigned int cnt) +static inline BOOL map_selrefs(SEL *src, SEL *dst, size_t size, BOOL copy) { - unsigned int index; + BOOL result = NO; + unsigned int cnt = size / sizeof(SEL); + unsigned int index; + + sel_lock(); // Process each selector for (index = 0; index < cnt; index += 1) { - SEL sel; + SEL sel; // Lookup pointer to uniqued string - sel = sel_registerNameNoCopy ((const char *) sels[index]); + sel = sel_registerNameNoLock((const char *) src[index], copy); // Replace this selector with uniqued one (avoid // modifying the VM page if this would be a NOP) - if (sels[index] != sel) - sels[index] = sel; + if (dst[index] != sel) { + dst[index] = sel; + result = YES; + } } + + sel_unlock(); + + return result; } /*********************************************************************** * map_method_descs. For each method in the specified method list, * replace the name pointer with a uniqued selector. +* If copy is TRUE, all selector data is always copied. This is used +* for registering selectors from unloadable bundles, so the selector +* can still be used after the bundle's data segment is unmapped. **********************************************************************/ -static void map_method_descs (struct objc_method_description_list * methods) +static void map_method_descs (struct objc_method_description_list * methods, BOOL copy) { unsigned int index; + sel_lock(); + // Process each method for (index = 0; index < methods->count; index += 1) { @@ -1051,13 +2064,15 @@ static void map_method_descs (struct objc_method_description_list * methods) method = &methods->list[index]; // Lookup pointer to uniqued string - sel = sel_registerNameNoCopy ((const char *) method->name); + sel = sel_registerNameNoLock((const char *) method->name, copy); // Replace this selector with uniqued one (avoid // modifying the VM page if this would be a NOP) if (method->name != sel) method->name = sel; } + + sel_unlock(); } /*********************************************************************** @@ -1076,6 +2091,7 @@ static void _objc_fixup_protocol_objects_for_image (header_info * hi) unsigned int size; OBJC_PROTOCOL_PTR protos; unsigned int index; + int isBundle = hi->mhdr->filetype == MH_BUNDLE; // Locate protocols in the image protos = (OBJC_PROTOCOL_PTR) _getObjcProtocols ((headerType *) hi->mhdr, &size); @@ -1090,11 +2106,11 @@ static void _objc_fixup_protocol_objects_for_image (header_info * hi) { // Selectorize the instance methods if (protos[index] OBJC_PROTOCOL_DEREF instance_methods) - map_method_descs (protos[index] OBJC_PROTOCOL_DEREF instance_methods); + map_method_descs (protos[index] OBJC_PROTOCOL_DEREF instance_methods, isBundle); // Selectorize the class methods if (protos[index] OBJC_PROTOCOL_DEREF class_methods) - map_method_descs (protos[index] OBJC_PROTOCOL_DEREF class_methods); + map_method_descs (protos[index] OBJC_PROTOCOL_DEREF class_methods, isBundle); } // Invoke Protocol class method to fix up the protocol @@ -1117,297 +2133,389 @@ void _objc_bindModuleContainingList() { */ } -/********************************************************************** -* _objc_bind_symbol. Bind the module containing the symbol. Use 2-level namespace API -* Only look in images that we know to have ObjC symbols (e.g. 9 for Mail 7/2001) -* Radar 2701686 -***********************************************************************/ -static void _objc_bind_symbol(const char *name) + +/*********************************************************************** +* _objc_addHeader. +**********************************************************************/ + +// tested with 2; typical case is 4, but OmniWeb & Mail push it towards 20 +#define HINFO_SIZE 16 + +static int HeaderInfoCounter NOBSS = 0; +static header_info HeaderInfoTable[HINFO_SIZE] NOBSS = { {0} }; + +static header_info * _objc_addHeader(const struct mach_header *header) { - static header_info *lastHeader = NULL; - header_info *hInfo; - const headerType *imageHeader = lastHeader ? lastHeader->mhdr : NULL; + int mod_count = 0; + uintptr_t mod_unslid; + uint32_t info_size = 0; + uintptr_t image_info_unslid; + const struct segment_command *objc_segment; + ptrdiff_t slide; + header_info *result; - // Ideally we would have a way to not even process a symbol in a module - // we've already visited + // Locate the __OBJC segment + objc_segment = getsegbynamefromheader(header, SEG_OBJC); + if (!objc_segment) return NULL; + // Locate some sections in the __OBJC segment + mod_unslid = (uintptr_t)_getObjcModules(header, &mod_count); + if (!mod_unslid) return NULL; + image_info_unslid = (uintptr_t)_getObjcImageInfo(header, &info_size); - // First assume there is some locality and search where we last found a symbol - if ( imageHeader - && NSIsSymbolNameDefinedInImage(imageHeader, name) - && NSLookupSymbolInImage(imageHeader, name, NSLOOKUPSYMBOLINIMAGE_OPTION_BIND) != NULL ) - { - // Found - return; + // Calculate vm slide. + slide = _getImageSlide(header); + + + // Find or allocate a header_info entry. + if (HeaderInfoCounter < HINFO_SIZE) { + result = &HeaderInfoTable[HeaderInfoCounter++]; + } else { + result = _malloc_internal(sizeof(header_info)); } - // Symbol wasn't found in the image we last searched - // Search in all the images known to contain ObjcC - for ( hInfo = FirstHeader; hInfo != NULL; hInfo = hInfo->next) - { - imageHeader = hInfo->mhdr; - if ( hInfo != lastHeader - && NSIsSymbolNameDefinedInImage(imageHeader, name) - && NSLookupSymbolInImage(imageHeader, name, NSLOOKUPSYMBOLINIMAGE_OPTION_BIND) != NULL ) - { - // found - lastHeader = hInfo; - return; - } + // Set up the new header_info entry. + result->mhdr = header; + result->mod_ptr = (Module)(mod_unslid + slide); + result->mod_count = mod_count; + result->image_slide = slide; + result->objcSegmentHeader = objc_segment; + if (image_info_unslid) { + result->info = (objc_image_info *)(image_info_unslid + slide); + } else { + result->info = NULL; } - // die now, or later ?? - // _objc_fatal("could not find %s", name); -} -/*********************************************************************** -* _objc_bindModuleContainingCategory. Bind the module containing the -* category. -**********************************************************************/ -static void _objc_bindModuleContainingCategory (Category cat) -{ - char * class_name; - char * category_name; - char * name; - char tmp_buf[128]; - unsigned int name_len; + // Make sure every copy of objc_image_info in this image is the same. + // This means same version and same bitwise contents. + if (result->info) { + objc_image_info *start = result->info; + objc_image_info *end = + (objc_image_info *)(info_size + (uint8_t *)start); + objc_image_info *info = start; + while (info < end) { + // version is byte size, except for version 0 + size_t struct_size = info->version; + if (struct_size == 0) struct_size = 2 * sizeof(uint32_t); + if (info->version != start->version || + 0 != memcmp(info, start, struct_size)) + { + _objc_fatal("'%s' has inconsistently-compiled Objective-C " + "code. Please recompile all code in it.", + _nameForHeader(header)); + } + info = (objc_image_info *)(struct_size + (uint8_t *)info); + } + } - // Bind ".objc_category_name__", - // where is the class name with the leading - // '%'s stripped. - class_name = cat->class_name; - category_name = cat->category_name; - name_len = strlen(class_name) + strlen(category_name) + 30; - if ( name_len > 128 ) - name = malloc(name_len); - else - name = tmp_buf; - while (*class_name == '%') - class_name += 1; - strcpy (name, ".objc_category_name_"); - strcat (name, class_name); - strcat (name, "_"); - strcat (name, category_name); - if (LaunchingDebug) { _objc_syslog("_objc_bindModuleContainingCategory for %s on %s", category_name, class_name); } - _objc_bind_symbol(name); - if ( name != tmp_buf ) - free(name); + // Add the header to the header list. + // The header is appended to the list, to preserve the bottom-up order. + result->next = NULL; + if (!FirstHeader) { + // list is empty + FirstHeader = LastHeader = result; + } else { + if (!LastHeader) { + // list is not empty, but LastHeader is invalid - recompute it + LastHeader = FirstHeader; + while (LastHeader->next) LastHeader = LastHeader->next; + } + // LastHeader is now valid + LastHeader->next = result; + LastHeader = result; + } + + return result; } + /*********************************************************************** -* _objc_bindModuleContainingClass. Bind the module containing the -* class. -* This is done lazily, just after initializing the class (if needed) +* _objc_RemoveHeader +* Remove the given header from the header list. +* FirstHeader is updated. +* LastHeader is set to NULL. Any code that uses LastHeader must +* detect this NULL and recompute LastHeader by traversing the list. **********************************************************************/ +static void _objc_removeHeader(header_info *hi) +{ + header_info **hiP; + + for (hiP = &FirstHeader; *hiP != NULL; hiP = &(**hiP).next) { + if (*hiP == hi) { + header_info *deadHead = *hiP; + + // Remove from the linked list (updating FirstHeader if necessary). + *hiP = (**hiP).next; + + // Update LastHeader if necessary. + if (LastHeader == deadHead) { + LastHeader = NULL; // will be recomputed next time it's used + } -void _objc_bindModuleContainingClass (struct objc_class * cls) { - char * name; - const char * class_name; - char tmp_buf[128]; - unsigned int name_len; - - // Use the real class behind the poser - if (CLS_GETINFO (cls, CLS_POSING)) - cls = getOriginalClassForPosingClass (cls); - class_name = cls->name; - - name_len = strlen(class_name) + 20; - if ( name_len > 128 ) - name = malloc(name_len); - else - name = tmp_buf; + // Free the memory, unless it was in the static HeaderInfoTable. + if (deadHead < HeaderInfoTable || + deadHead >= HeaderInfoTable + HINFO_SIZE) + { + _free_internal(deadHead); + } - while (*class_name == '%') - class_name += 1; - strcpy (name, ".objc_class_name_"); - strcat (name, class_name); - if (LaunchingDebug) { _objc_syslog("_objc_bindModuleContainingClass for %s", class_name); } - _objc_bind_symbol(name); - if ( name != tmp_buf ) - free(name); + break; + } + } } /*********************************************************************** -* _objc_bindClassIfNeeded. -* If the given class is still marked as needs-bind, bind the module -* containing it. -* Called during _objc_call_loads_for_image just before sending +load, -* and during class_initialize just before sending +initialize. +* check_gc +* Check whether the executable supports or requires GC, and make sure +* all already-loaded libraries support the executable's GC mode. +* Returns TRUE if the executable wants GC on. **********************************************************************/ -void _objc_bindClassIfNeeded(struct objc_class *cls) +static BOOL check_wants_gc(void) { - // Clear NEED_BIND *after* binding to prevent race - // This assumes that simultaneous binding of one module by two threads is ok. - if (cls->info & CLS_NEED_BIND) { - _objc_bindModuleContainingClass(cls); - cls->info &= ~CLS_NEED_BIND; + // GC is off in Tiger. + return NO; + /* + const header_info *hi; + BOOL appWantsGC; + + // Environment variables can override the following. + if (ForceGC) { + _objc_inform("GC: forcing GC ON because OBJC_FORCE_GC is set"); + appWantsGC = YES; + } + else if (ForceNoGC) { + _objc_inform("GC: forcing GC OFF because OBJC_FORCE_NO_GC is set"); + appWantsGC = NO; + } + else { + // Find the executable and check its GC bits. + // If the executable cannot be found, default to NO. + // (The executable will not be found if the executable contains + // no Objective-C code.) + appWantsGC = NO; + for (hi = FirstHeader; hi != NULL; hi = hi->next) { + if (hi->mhdr->filetype == MH_EXECUTE) { + appWantsGC = _objcHeaderSupportsGC(hi) ? YES : NO; + if (PrintGC) { + _objc_inform("GC: executable '%s' %s GC", + _nameForHeader(hi->mhdr), + appWantsGC ? "supports" : "does not support"); + } + } + } } + return appWantsGC; + */ } - /*********************************************************************** -* _objc_addHeader. -* -**********************************************************************/ +* verify_gc_readiness +* if we want gc, verify that every header describes files compiled +* and presumably ready for gc. +************************************************************************/ -// tested with 2; typical case is 4, but OmniWeb & Mail push it towards 20 -#define HINFO_SIZE 16 - -static int HeaderInfoCounter = 0; -static header_info HeaderInfoTable[HINFO_SIZE] = { {0} }; - -static header_info * _objc_addHeader(const headerType *header, unsigned long vmaddr_slide) +static void verify_gc_readiness(BOOL wantsGC, header_info *hi) { - int mod_count; - Module mod_ptr = _getObjcModules ((headerType *) header, &mod_count); - header_info *result; - - // if there is no objc data - ignore this entry! - if (mod_ptr == NULL) { - return NULL; - } + BOOL busted = NO; - if (HeaderInfoCounter < HINFO_SIZE) { - // avoid mallocs for the common case - result = &HeaderInfoTable[HeaderInfoCounter++]; - } - else { - result = malloc_zone_malloc(_objc_create_zone(), sizeof(header_info)); + // Find the libraries and check their GC bits against the app's request + for (; hi != NULL; hi = hi->next) { + if (hi->mhdr->filetype == MH_EXECUTE) { + continue; + } + else if (hi->mhdr == &_mh_dylib_header) { + // libobjc itself works with anything even though it is not + // compiled with -fobjc-gc (fixme should it be?) + } + else if (wantsGC && ! _objcHeaderSupportsGC(hi)) { + // App wants GC but library does not support it - bad + _objc_inform("'%s' was not compiled with -fobjc-gc, but " + "the application requires GC", + _nameForHeader(hi->mhdr)); + busted = YES; + } + + if (PrintGC) { + _objc_inform("GC: library '%s' %s GC", _nameForHeader(hi->mhdr), + _objcHeaderSupportsGC(hi) ? "supports" : "does not support"); + } } - - // Set up the new vector entry - result->mhdr = header; - result->mod_ptr = mod_ptr; - result->mod_count = mod_count; - result->image_slide = vmaddr_slide; - - // chain it on - // (a simple lock here would go a long way towards thread safety) - result->next = FirstHeader; - FirstHeader = result; - return result; -} - -/********************************************************************** -* _objc_fatalHeader -* -* If we have it we're in trouble -**************************************************************************/ -static void _objc_fatalHeader(const headerType *header) -{ - header_info *hInfo; - - for (hInfo = FirstHeader; hInfo != NULL; hInfo = hInfo->next) { - if (hInfo->mhdr == header) { - _objc_fatal("cannot unmap an image containing ObjC data"); + if (busted) { + // GC state is not consistent. + // Kill the process unless one of the forcing flags is set. + if (!ForceGC && !ForceNoGC) { + _objc_fatal("*** GC capability of application and some libraries did not match"); } } } + /*********************************************************************** * _objc_fixup_selector_refs. Register all of the selectors in each * image, and fix them all up. -* +* +* If the image is a dylib (not a bundle or an executable), and contains +* at least one full aligned page of selector refs, this function uses +* the shared range functions to try to recycle already-written memory +* from other processes. **********************************************************************/ static void _objc_fixup_selector_refs (const header_info * hi) { - unsigned int size; - Module mods; - SEL * messages_refs; + unsigned int count; + Module mods; + vm_address_t local_sels; + vm_size_t local_size; - mods = (Module) ((unsigned long) hi->mod_ptr + hi->image_slide); + mods = hi->mod_ptr; // Fix up message refs - messages_refs = (SEL *) _getObjcMessageRefs ((headerType *) hi->mhdr, &size); - if (messages_refs) - { - messages_refs = (SEL *) ((unsigned long) messages_refs + hi->image_slide); - map_selrefs (messages_refs, size); - } -} - + local_sels = (vm_address_t) _getObjcMessageRefs ((headerType *) hi->mhdr, &count); + local_size = count * sizeof(SEL); + + if (local_sels) { + vm_address_t aligned_start, aligned_end; + + local_sels = local_sels + hi->image_slide; + aligned_start = round_page(local_sels); + aligned_end = trunc_page(local_sels + local_size); + + if (aligned_start >= aligned_end || + hi->mhdr->filetype == MH_BUNDLE || + hi->mhdr->filetype == MH_EXECUTE) + { + // Less than a page of sels, OR bundle or executable - fix in place -/*********************************************************************** -* _objc_call_loads_for_image. -**********************************************************************/ -static void _objc_call_loads_for_image (header_info * header) -{ - struct objc_class * cls; - struct objc_class * * pClass; - Category * pCategory; - IMP load_method; - unsigned int nModules; - unsigned int nClasses; - unsigned int nCategories; - struct objc_symtab * symtab; - struct objc_module * module; + map_selrefs((SEL *)local_sels, (SEL *)local_sels, local_size, + hi->mhdr->filetype == MH_BUNDLE); - // Major loop - process all modules named in header - module = (struct objc_module *) ((unsigned long) header->mod_ptr + header->image_slide); - for (nModules = header->mod_count; nModules; nModules -= 1, module += 1) - { - symtab = module->symtab; - if (symtab == NULL) - continue; + if (PrintSharing) { + _objc_inform("SHARING: NONE [%p..%p) (%d pages) for %s", + local_sels, local_sels+local_size, + (aligned_end > aligned_start ? + (aligned_end-aligned_start) / vm_page_size : 0), + _nameForHeader(hi->mhdr)); + } + } + else { + // At least one page of sels - try to use sharing + vm_range_t remote_range; + + if (PrintSharing) { + _objc_inform("SHARING: looking for range [%p..%p) ...", + aligned_start, aligned_end); + } - // Minor loop - call the +load from each class in the given module - for (nClasses = symtab->cls_def_cnt, pClass = (Class *) symtab->defs; - nClasses; - nClasses -= 1, pClass += 1) - { - struct objc_method_list **mlistp; - cls = (struct objc_class *)*pClass; - mlistp = get_base_method_list(cls->isa); - if (cls->isa->methodLists && mlistp) - { - // Look up the method manually (vs messaging the class) to bypass - // +initialize and cache fill on class that is not even loaded yet - load_method = class_lookupNamedMethodInMethodList (*mlistp, "load"); - if (load_method) { - _objc_bindClassIfNeeded(cls); - (*load_method) ((id) cls, @selector(load)); + remote_range = get_shared_range(aligned_start, aligned_end); + + if (remote_range.address != 0) { + // Sharing succeeded - fix using remote_range + BOOL stomped; + + // local_sels..aligned_start (unshared) + map_selrefs((SEL *)local_sels, (SEL *)local_sels, + aligned_start - local_sels, NO); + // aligned_start..aligned_end (shared) + stomped = + map_selrefs((SEL *)aligned_start, (SEL *)remote_range.address, + aligned_end - aligned_start, NO); + // aligned_end..local_sels+local_size (unshared) + map_selrefs((SEL *)aligned_end, (SEL *)aligned_end, + local_sels+local_size - aligned_end, NO); + + install_shared_range(remote_range, aligned_start); + + if (PrintSharing) { + _objc_inform("SHARING: %s [%p..%p) (%d pages) for %s", + stomped ? "TRIED" : "USING", + local_sels, local_sels+local_size, + (aligned_end-aligned_start) / vm_page_size, + _nameForHeader(hi->mhdr)); } - } - } + } + else { + // Sharing failed, including first process - + // fix in place and then offer to share - // Minor loop - call the +load from augmented class of - // each category in the given module - for (nCategories = symtab->cat_def_cnt, - pCategory = (Category *) &symtab->defs[symtab->cls_def_cnt]; - nCategories; - nCategories -= 1, pCategory += 1) - { - struct objc_method_list * methods; + map_selrefs((SEL *)local_sels, (SEL *)local_sels, local_size, NO); - methods = (*pCategory)->class_methods; - if (methods) - { - load_method = class_lookupNamedMethodInMethodList (methods, "load"); - if (load_method) { - // Strictly speaking we shouldn't need (and don't want) to get the class here - // The side effect we're looking for is to load it if needed. - // Since category +loads are rare we could spend some cycles finding out - // if we have a "bindme" TBD and do it here, saving a class load. - // But chances are the +load will cause class initialization anyway - cls = objc_getClass ((*pCategory)->class_name); - // the class & all categories are now bound in - (*load_method) ((id) cls, @selector(load)); + offer_shared_range(aligned_start, aligned_end); + + if (PrintSharing) { + _objc_inform("SHARING: OFFER [%p..%p) (%d pages) for %s", + local_sels, local_sels+local_size, + (aligned_end-aligned_start) / vm_page_size, + _nameForHeader(hi->mhdr)); } } } } } + /*********************************************************************** -* runtime configuration +* objc_setConfiguration +* Read environment variables that affect the runtime. +* Also print environment variable help, if requested. **********************************************************************/ static void objc_setConfiguration() { - if ( LaunchingDebug == -1 ) { - // watch image loading and binding - LaunchingDebug = getenv("LaunchingDebug") != NULL; + int PrintHelp = (getenv("OBJC_HELP") != NULL); + int PrintOptions = (getenv("OBJC_PRINT_OPTIONS") != NULL); + + if (PrintHelp) { + _objc_inform("OBJC_HELP: describe Objective-C runtime environment variables"); + if (PrintOptions) { + _objc_inform("OBJC_HELP is set"); + } + _objc_inform("OBJC_PRINT_OPTIONS: list which options are set"); } + if (PrintOptions) { + _objc_inform("OBJC_PRINT_OPTIONS is set"); + } + +#define OPTION(var, env, help) \ + if ( var == -1 ) { \ + var = getenv(#env) != NULL; \ + if (PrintHelp) _objc_inform(#env ": " help); \ + if (PrintOptions && var) _objc_inform(#env " is set"); \ + } + + OPTION(PrintImages, OBJC_PRINT_IMAGES, + "log image and library names as the runtime loads them"); + OPTION(PrintConnecting, OBJC_PRINT_CONNECTION, + "log progress of class and category connections"); + OPTION(PrintLoading, OBJC_PRINT_LOAD_METHODS, + "log class and category +load methods as they are called"); + OPTION(PrintRTP, OBJC_PRINT_RTP, + "log initialization of the Objective-C runtime pages"); + OPTION(PrintGC, OBJC_PRINT_GC, + "log some GC operations"); + OPTION(PrintSharing, OBJC_PRINT_SHARING, + "log cross-process memory sharing"); + OPTION(PrintCxxCtors, OBJC_PRINT_CXX_CTORS, + "log calls to C++ ctors and dtors for instance variables"); + + OPTION(DebugUnload, OBJC_DEBUG_UNLOAD, + "warn about poorly-behaving bundles when unloaded"); + OPTION(DebugFragileSuperclasses, OBJC_DEBUG_FRAGILE_SUPERCLASSES, + "warn about subclasses that may have been broken by subsequent changes to superclasses"); + + OPTION(UseInternalZone, OBJC_USE_INTERNAL_ZONE, + "allocate runtime data in a dedicated malloc zone"); + OPTION(AllowInterposing, OBJC_ALLOW_INTERPOSING, + "allow function interposing of objc_msgSend()"); + + OPTION(ForceGC, OBJC_FORCE_GC, + "force GC ON, even if the executable wants it off"); + OPTION(ForceNoGC, OBJC_FORCE_NO_GC, + "force GC OFF, even if the executable wants it on"); + OPTION(CheckFinalizers, OBJC_CHECK_FINALIZERS, + "warn about classes that implement -dealloc but not -finalize"); +#undef OPTION } + + /*********************************************************************** * objc_setMultithreaded. **********************************************************************/ @@ -1432,262 +2540,973 @@ void _objc_pthread_destroyspecific(void *arg) // add further cleanup here... - free(data); + _free_internal(data); } } /*********************************************************************** -* _objcInit. -* Library initializer called by dyld & from crt0 +* _objcInit +* Former library initializer. This function is now merely a placeholder +* for external callers. All runtime initialization has now been moved +* to map_images(). **********************************************************************/ +void _objcInit(void) +{ + // do nothing +} -void _objcInit(void) { + +/*********************************************************************** +* map_images +* Process the given images which are being mapped in by dyld. +* All class registration and fixups are performed (or deferred pending +* discovery of missing superclasses etc), and +load methods are called. +* +* info[] is in bottom-up order i.e. libobjc will be earlier in the +* array than any library that links to libobjc. +**********************************************************************/ +static void map_images(const struct dyld_image_info infoList[], + uint32_t infoCount) +{ + static BOOL firstTime = YES; + static BOOL wantsGC NOBSS = NO; + uint32_t i; + header_info *firstNewHeader = NULL; header_info *hInfo; - static int _done = 0; - extern void __CFInitialize(void); - extern int ptrace(int, int, int, int); // a system call visible to sc_trace - /* Protect against multiple invocations, as all library - * initializers should. */ - if (0 != _done) return; - _done = 1; + // Perform first-time initialization if necessary. + // This function is called before ordinary library initializers. + if (firstTime) { + pthread_key_create(&_objc_pthread_key, _objc_pthread_destroyspecific); + objc_setConfiguration(); // read environment variables + _objc_init_class_hash (); // create class_hash + // grab selectors for which @selector() doesn't work + cxx_construct_sel = sel_registerName(cxx_construct_name); + cxx_destruct_sel = sel_registerName(cxx_destruct_name); + } - ptrace(0xb000, 0, 0, 0); - trace(0xb000, 0, 0, 0); + if (PrintImages) { + _objc_inform("IMAGES: processing %u newly-mapped images...\n", infoCount); + } - // make sure CF is initialized before we go further; - // someday this can be removed, as it'll probably be automatic - __CFInitialize(); - - pthread_key_create(&_objc_pthread_key, _objc_pthread_destroyspecific); - // Create the class lookup table - _objc_init_class_hash (); + // Find all images with an __OBJC segment. + // firstNewHeader is set the the first one, and the header_info + // linked list following firstNewHeader is the rest. + for (i = 0; i < infoCount; i++) { + const struct mach_header *mhdr = infoList[i].imageLoadAddress; - trace(0xb001, 0, 0, 0); + hInfo = _objc_addHeader(mhdr); + if (!hInfo) { + // no objc data in this entry + if (PrintImages) { + _objc_inform("IMAGES: image '%s' contains no __OBJC segment\n", + infoList[i].imageFilePath); + } + continue; + } - objc_setConfiguration(); // Get our configuration - - trace(0xb003, 0, 0, 0); + if (!firstNewHeader) firstNewHeader = hInfo; + + if (PrintImages) { + _objc_inform("IMAGES: loading image for %s%s%s%s\n", + _nameForHeader(mhdr), + mhdr->filetype == MH_BUNDLE ? " (bundle)" : "", + _objcHeaderIsReplacement(hInfo) ? " (replacement)":"", + _objcHeaderSupportsGC(hInfo) ? " (supports GC)":""); + } + } - // a pre-cheetah comment: - // XXXXX BEFORE HERE *NO* PAGES ARE STOMPED ON; + // Perform one-time runtime initialization that must be deferred until + // the executable itself is found. This needs to be done before + // further initialization. + // (The executable may not be present in this infoList if the + // executable does not contain Objective-C code but Objective-C + // is dynamically loaded later. In that case, check_wants_gc() + // will do the right thing.) + if (firstTime) { + wantsGC = check_wants_gc(); + verify_gc_readiness(wantsGC, FirstHeader); + // TIGER DEVELOPMENT ONLY + // REQUIRE A SPECIAL NON-SHIPPING FILE TO ENABLE GC + if (wantsGC) { + // make sure that the special file is there before proceeding with GC + struct stat ignored; + wantsGC = stat("/autozone", &ignored) != -1; + if (!wantsGC && PrintGC) + _objc_inform("GC: disabled, lacking /autozone file"); + } + + gc_init(wantsGC); // needs executable for GC decision + rtp_init(); // needs GC decision first + } else { + verify_gc_readiness(wantsGC, firstNewHeader); + } - // Register our image mapping routine with dyld so it - // gets invoked when an image is added. This also invokes - // the callback right now on any images already present. - // The modules present in the application and the existing - // mapped images are treated differently than a newly discovered - // mapped image - we process all modules for classes before - // trying to install_relationships (glue up their superclasses) - // or trying to send them any +load methods. + // Initialize everything. Parts of this order are important for + // correctness or performance. - // So we tell the map_image dyld callback to not do this part... + // Read classes from all images. + for (hInfo = firstNewHeader; hInfo != NULL; hInfo = hInfo->next) { + _objc_read_classes_from_image(hInfo); + } - Postpone_install_relationships = 1; + // Read categories from all images. + for (hInfo = firstNewHeader; hInfo != NULL; hInfo = hInfo->next) { + _objc_read_categories_from_image(hInfo); + } - // register for unmapping first so we can't miss any during load attempts - _dyld_register_func_for_remove_image (&_objc_unmap_image); + // Connect classes from all images. + for (hInfo = firstNewHeader; hInfo != NULL; hInfo = hInfo->next) { + _objc_connect_classes_from_image(hInfo); + } - // finally, register for images - _dyld_register_func_for_add_image (&_objc_map_image); + // Fix up class refs, selector refs, and protocol objects from all images. + for (hInfo = firstNewHeader; hInfo != NULL; hInfo = hInfo->next) { + _objc_map_class_refs_for_image(hInfo); + _objc_fixup_selector_refs(hInfo); + _objc_fixup_protocol_objects_for_image(hInfo); + } - // a pre-cheetah comment: - // XXXXX BEFORE HERE *ALL* PAGES ARE STOMPED ON + // Close any shared range file left open during selector uniquing + clear_shared_range_file_cache(); - Postpone_install_relationships = 0; + firstTime = NO; - trace(0xb006, 0, 0, 0); - - // Install relations on classes that were found - for (hInfo = FirstHeader; hInfo != NULL; hInfo = hInfo->next) - { - int nModules; - int index; - struct objc_module * module; - struct objc_class * cls; + // Call pending +load methods. + // Note that this may in turn cause map_images() to be called again. + call_load_methods(); +} - module = (struct objc_module *) ((unsigned long) hInfo->mod_ptr + hInfo->image_slide); - for (nModules = hInfo->mod_count; nModules; nModules -= 1) - { - for (index = 0; index < module->symtab->cls_def_cnt; index += 1) - { - cls = (struct objc_class *) module->symtab->defs[index]; - _class_install_relationships (cls, module->version); - } - module += 1; +/*********************************************************************** +* unmap_images +* Process the given images which are about to be unmapped by dyld. +* Currently we assume only MH_BUNDLE images are unmappable, and +* print warnings about anything else. +**********************************************************************/ +static void unmap_images(const struct dyld_image_info infoList[], + uint32_t infoCount) +{ + uint32_t i; + + if (PrintImages) { + _objc_inform("IMAGES: processing %u newly-unmapped images...\n", infoCount); + } + + for (i = 0; i < infoCount; i++) { + const struct mach_header *mhdr = infoList[i].imageLoadAddress; + + if (mhdr->filetype == MH_BUNDLE) { + _objc_unmap_image(mhdr); + } else { + // currently only MH_BUNDLEs can be unmapped safely + if (PrintImages) { + _objc_inform("IMAGES: unmapped image '%s' was not a Mach-O bundle; ignoring\n", infoList[i].imageFilePath); + } } + } +} - trace(0xb007, hInfo, hInfo->mod_count, 0); +/*********************************************************************** +* _objc_notify_images +* Callback from dyld informing objc of images to be added or removed. +* This function is never called directly. Instead, a section +* __OBJC,__image_notify contains a function pointer to this, and dyld +* discovers it from there. +**********************************************************************/ +__private_extern__ +void _objc_notify_images(enum dyld_image_mode mode, uint32_t infoCount, + const struct dyld_image_info infoList[]) +{ + if (mode == dyld_image_adding) { + map_images(infoList, infoCount); + } else if (mode == dyld_image_removing) { + unmap_images(infoList, infoCount); } +} - trace(0xb008, 0, 0, 0); - for (hInfo = FirstHeader; hInfo != NULL; hInfo = hInfo->next) +/*********************************************************************** +* _objc_remove_classes_in_image +* Remove all classes in the given image from the runtime, because +* the image is about to be unloaded. +* Things to clean up: +* class_hash +* unconnected_class_hash +* pending subclasses list (only if class is still unconnected) +* loadable class list +* class's method caches +* class refs in all other images +**********************************************************************/ +static void _objc_remove_classes_in_image(header_info *hi) +{ + unsigned int index; + unsigned int midx; + Module mods; + + OBJC_LOCK(&classLock); + + // Major loop - process all modules in the image + mods = hi->mod_ptr; + for (midx = 0; midx < hi->mod_count; midx += 1) { - // Initialize the isa pointers of all NXConstantString objects - (void)_objc_fixup_string_objects_for_image (hInfo); + // Skip module containing no classes + if (mods[midx].symtab == NULL) + continue; + + // Minor loop - process all the classes in given module + for (index = 0; index < mods[midx].symtab->cls_def_cnt; index += 1) + { + struct objc_class * cls; + + // Locate the class description pointer + cls = mods[midx].symtab->defs[index]; + + // Remove from loadable class list, if present + remove_class_from_loadable_list(cls); + + // Remove from unconnected_class_hash and pending subclasses + if (unconnected_class_hash && NXHashMember(unconnected_class_hash, cls)) { + NXHashRemove(unconnected_class_hash, cls); + if (pendingSubclassesMap) { + // Find this class in its superclass's pending list + char *supercls_name = (char *)cls->super_class; + PendingSubclass *pending = + NXMapGet(pendingSubclassesMap, supercls_name); + for ( ; pending != NULL; pending = pending->next) { + if (pending->subclass == cls) { + pending->subclass = Nil; + break; + } + } + } + } + + // Remove from class_hash + NXHashRemove(class_hash, cls); + + // Free method list array (from objcTweakMethodListPointerForClass) + // These blocks might be from user code; don't use free_internal + if (cls->methodLists && !(cls->info & CLS_NO_METHOD_ARRAY)) { + free(cls->methodLists); + } + if (cls->isa->methodLists && !(cls->isa->info & CLS_NO_METHOD_ARRAY)) { + free(cls->isa->methodLists); + } + + // Free method caches, if any + if (cls->cache && cls->cache != &emptyCache) { + _free_internal(cls->cache); + } + if (cls->isa->cache && cls->isa->cache != &emptyCache) { + _free_internal(cls->isa->cache); + } + } + } + - // Convert class refs from name pointers to ids - (void)_objc_map_class_refs_for_image (hInfo); + // Search all other images for class refs that point back to this range. + // Un-fix and re-pend any such class refs. + + // Get the location of the dying image's __OBJC segment + uintptr_t seg = hi->objcSegmentHeader->vmaddr + hi->image_slide; + size_t seg_size = hi->objcSegmentHeader->filesize; + + header_info *other_hi; + for (other_hi = FirstHeader; other_hi != NULL; other_hi = other_hi->next) { + struct objc_class **other_refs; + unsigned int size; + if (other_hi == hi) continue; // skip the image being unloaded + + // Locate class refs in the other image + other_refs = _getObjcClassRefs((headerType *)other_hi->mhdr, &size); + if (!other_refs) continue; + other_refs = (struct objc_class **)((uintptr_t)other_refs + other_hi->image_slide); + + // Process each class ref + for (index = 0; index < size; index++) { + if ((uintptr_t)(other_refs[index]) >= seg && + (uintptr_t)(other_refs[index]) < seg+seg_size) + { + pendClassReference(&other_refs[index],other_refs[index]->name); + other_refs[index] = _objc_getNonexistentClass (); + } + } } - trace(0xb00a, 0, 0, 0); + OBJC_UNLOCK(&classLock); +} - // For each image selectorize the method names and +_fixup each of - // protocols in the image - for (hInfo = FirstHeader; hInfo != NULL; hInfo = hInfo->next) - _objc_fixup_protocol_objects_for_image (hInfo); - for (hInfo = FirstHeader; hInfo != NULL; hInfo = hInfo->next) - _objc_call_loads_for_image (hInfo); +/*********************************************************************** +* _objc_remove_categories_in_image +* Remove all categories in the given image from the runtime, because +* the image is about to be unloaded. +* Things to clean up: +* unresolved category list +* loadable category list +**********************************************************************/ +static void _objc_remove_categories_in_image(header_info *hi) +{ + Module mods; + unsigned int midx; + + // Major loop - process all modules in the header + mods = hi->mod_ptr; - ptrace(0xb00f, 0, 0, 0); // end of __initialize_objc ObjC init - trace(0xb00f, 0, 0, 0); // end of __initialize_objc ObjC init + for (midx = 0; midx < hi->mod_count; midx++) { + unsigned int index; + unsigned int total; + Symtab symtab = mods[midx].symtab; + + // Nothing to do for a module without a symbol table + if (symtab == NULL) continue; + + // Total entries in symbol table (class entries followed + // by category entries) + total = symtab->cls_def_cnt + symtab->cat_def_cnt; + + // Minor loop - check all categories from given module + for (index = symtab->cls_def_cnt; index < total; index++) { + struct objc_category *cat = symtab->defs[index]; + + // Clean up loadable category list + remove_category_from_loadable_list(cat); + + // Clean up category_hash + if (category_hash) { + _objc_unresolved_category *cat_entry = + NXMapGet(category_hash, cat->class_name); + for ( ; cat_entry != NULL; cat_entry = cat_entry->next) { + if (cat_entry->cat == cat) { + cat_entry->cat = NULL; + break; + } + } + } + } + } } /*********************************************************************** -* _objc_map_image. +* unload_paranoia +* Various paranoid debugging checks that look for poorly-behaving +* unloadable bundles. +* Called by _objc_unmap_image when OBJC_UNLOAD_DEBUG is set. **********************************************************************/ -static void _objc_map_image(headerType *mh, unsigned long vmaddr_slide) +static void unload_paranoia(header_info *hi) { - static int dumpClasses = -1; - header_info *hInfo; + // Get the location of the dying image's __OBJC segment + uintptr_t seg = hi->objcSegmentHeader->vmaddr + hi->image_slide; + size_t seg_size = hi->objcSegmentHeader->filesize; + + _objc_inform("UNLOAD DEBUG: unloading image '%s' [%p..%p]", + _nameForHeader(hi->mhdr), seg, seg+seg_size); - if ( dumpClasses == -1 ) { - if ( getenv("OBJC_DUMP_CLASSES") ) dumpClasses = 1; - else dumpClasses = 0; + OBJC_LOCK(&classLock); + + // Make sure the image contains no categories on surviving classes. + { + Module mods; + unsigned int midx; + + // Major loop - process all modules in the header + mods = hi->mod_ptr; + + for (midx = 0; midx < hi->mod_count; midx++) { + unsigned int index; + unsigned int total; + Symtab symtab = mods[midx].symtab; + + // Nothing to do for a module without a symbol table + if (symtab == NULL) continue; + + // Total entries in symbol table (class entries followed + // by category entries) + total = symtab->cls_def_cnt + symtab->cat_def_cnt; + + // Minor loop - check all categories from given module + for (index = symtab->cls_def_cnt; index < total; index++) { + struct objc_category *cat = symtab->defs[index]; + struct objc_class query; + + query.name = cat->class_name; + if (NXHashMember(class_hash, &query)) { + _objc_inform("UNLOAD DEBUG: dying image contains category '%s(%s)' on surviving class '%s'!", cat->class_name, cat->category_name, cat->class_name); + } + } + } } - trace(0xb100, 0, 0, 0); + // Make sure no surviving class is in the dying image. + // Make sure no surviving class has a superclass in the dying image. + // fixme check method implementations too + { + struct objc_class *cls; + NXHashState state; + + state = NXInitHashState(class_hash); + while (NXNextHashState(class_hash, &state, (void **)&cls)) { + if ((vm_address_t)cls >= seg && + (vm_address_t)cls < seg+seg_size) + { + _objc_inform("UNLOAD DEBUG: dying image contains surviving class '%s'!", cls->name); + } + + if ((vm_address_t)cls->super_class >= seg && + (vm_address_t)cls->super_class < seg+seg_size) + { + _objc_inform("UNLOAD DEBUG: dying image contains superclass '%s' of surviving class '%s'!", cls->super_class->name, cls->name); + } + } + } + + OBJC_UNLOCK(&classLock); +} - // Add this header to the chain - hInfo = _objc_addHeader (mh, vmaddr_slide); - if (!hInfo) return; +/*********************************************************************** +* _objc_unmap_image. +* Destroy any Objective-C data for the given image, which is about to +* be unloaded by dyld. +* Note: not thread-safe, but image loading isn't either. +**********************************************************************/ +static void _objc_unmap_image(const headerType *mh) +{ + header_info *hi; - if (LaunchingDebug) { _objc_syslog("objc_map_image for %s\n", _nameForHeader(mh)); } + // Find the runtime's header_info struct for the image + for (hi = FirstHeader; hi != NULL; hi = hi->next) { + if (hi->mhdr == mh) break; + } + if (hi == NULL) return; // no objc data for this image + + if (PrintImages) { + _objc_inform("IMAGES: unloading image for %s%s%s%s\n", + _nameForHeader(mh), + mh->filetype == MH_BUNDLE ? " (bundle)" : "", + _objcHeaderIsReplacement(hi) ? " (replacement)" : "", + _objcHeaderSupportsGC(hi) ? " (supports GC)" : ""); + } - trace(0xb101, 0, 0, 0); + // Cleanup: + // Remove image's classes from the class list and free auxiliary data. + // Remove image's unresolved or loadable categories and free auxiliary data + // Remove image's unresolved class refs. + _objc_remove_classes_in_image(hi); + _objc_remove_categories_in_image(hi); + _objc_remove_pending_class_refs_in_image(hi); + + // Perform various debugging checks if requested. + if (DebugUnload) unload_paranoia(hi); - // Register any categories and/or classes and/or selectors this image contains - _objc_add_categories_from_image (hInfo); + // Remove header_info from header list + _objc_removeHeader(hi); +} - trace(0xb103, 0, 0, 0); - _objc_add_classes_from_image (class_hash, hInfo); +/*********************************************************************** +* _objc_setNilReceiver +**********************************************************************/ +id _objc_setNilReceiver(id newNilReceiver) +{ + id oldNilReceiver; - trace(0xb104, 0, 0, 0); + oldNilReceiver = _objc_nilReceiver; + _objc_nilReceiver = newNilReceiver; - _objc_fixup_selector_refs (hInfo); + return oldNilReceiver; +} - trace(0xb105, 0, 0, 0); +/*********************************************************************** +* _objc_getNilReceiver +**********************************************************************/ +id _objc_getNilReceiver(void) +{ + return _objc_nilReceiver; +} - // Log all known class names, if asked - if ( dumpClasses ) - { - printf ("classes...\n"); - objc_dump_class_hash (); - } - if (!Postpone_install_relationships) - { - int nModules; - int index; - struct objc_module * module; +/*********************************************************************** +* _objc_setClassLoader +* Similar to objc_setClassHandler, but objc_classLoader is used for +* both objc_getClass() and objc_lookupClass(), and objc_classLoader +* pre-empts objc_classHandler. +**********************************************************************/ +void _objc_setClassLoader(BOOL (*newClassLoader)(const char *)) +{ + _objc_classLoader = newClassLoader; +} - // Major loop - process each module - module = (struct objc_module *) ((unsigned long) hInfo->mod_ptr + hInfo->image_slide); - trace(0xb106, hInfo->mod_count, 0, 0); +#if defined(__ppc__) - for (nModules = hInfo->mod_count; nModules; nModules -= 1) - { - // Minor loop - process each class in a given module - for (index = 0; index < module->symtab->cls_def_cnt; index += 1) - { - struct objc_class * cls; +/********************************************************************** +* objc_write_branch +* Writes at entry a PPC branch instruction sequence that branches to target. +* The sequence written will be 1 or 4 instructions long. +* Returns the number of instructions written. +**********************************************************************/ +__private_extern__ size_t objc_write_branch(void *entry, void *target) +{ + unsigned *address = (unsigned *)entry; // location to store the 32 bit PPC instructions + intptr_t destination = (intptr_t)target; // destination as an absolute address + intptr_t displacement = (intptr_t)destination - (intptr_t)address; // destination as a branch relative offset + + // Test to see if either the displacement or destination is within the +/- 2^25 range needed + // for a simple PPC branch instruction. Shifting the high bit of the displacement (or destination) + // left 6 bits and then 6 bits arithmetically to the right does a sign extend of the 26th bit. If + // that result is equivalent to the original value, then the displacement (or destination) will fit + // into a simple branch. Otherwise a four instruction branch sequence is required. + if (((displacement << 6) >> 6) == displacement) { + // use a relative branch with the displacement + address[0] = 0x48000000 | (displacement & 0x03fffffc); // b *+displacement + // issued 1 instruction + return 1; + } else if (((destination << 6) >> 6) == destination) { + // use an absolute branch with the destination + address[0] = 0x48000000 | (destination & 0x03fffffc) | 2; // ba destination (2 is the absolute flag) + // issued 1 instruction + return 1; + } else { + // The four instruction branch sequence requires that the destination be loaded + // into a register, moved to the CTR register then branch using the contents + // of the CTR register. + unsigned lo = destination & 0xffff; + unsigned hi = (destination >> 16) & 0xffff; + + address[0] = 0x3d800000 | hi; // lis r12,hi ; load the hi half of destination + address[1] = 0x618c0000 | lo; // ori r12,r12,lo ; merge in the lo half of destination + address[2] = 0x7d8903a6; // mtctr ; move destination to the CTR register + address[3] = 0x4e800420; // bctr ; branch to destination + // issued 4 instructions + return 4; + } +} - // Locate the class description - cls = (struct objc_class *) module->symtab->defs[index]; +// defined(__ppc__) +#endif - // If there is no superclass or the superclass can be found, - // install this class, and invoke the expected callback - if (!((struct objc_class *)cls)->super_class || objc_lookUpClass ((char *) ((struct objc_class *)cls)->super_class)) - { - _class_install_relationships (cls, module->version); - if (callbackFunction) - (*callbackFunction) (cls, 0); - } - else + +/********************************************************************** +* secure_open +* Securely open a file from a world-writable directory (like /tmp) +* If the file does not exist, it will be atomically created with mode 0600 +* If the file exists, it must be, and remain after opening: +* 1. a regular file (in particular, not a symlink) +* 2. owned by euid +* 3. permissions 0600 +* 4. link count == 1 +* Returns a file descriptor or -1. Errno may or may not be set on error. +**********************************************************************/ +__private_extern__ int secure_open(const char *filename, int flags, uid_t euid) +{ + struct stat fs, ls; + int fd = -1; + BOOL truncate = NO; + BOOL create = NO; + + if (flags & O_TRUNC) { + // Don't truncate the file until after it is open and verified. + truncate = YES; + flags &= ~O_TRUNC; + } + if (flags & O_CREAT) { + // Don't create except when we're ready for it + create = YES; + flags &= ~O_CREAT; + flags &= ~O_EXCL; + } + + if (lstat(filename, &ls) < 0) { + if (errno == ENOENT && create) { + // No such file - create it + fd = open(filename, flags | O_CREAT | O_EXCL, 0600); + if (fd >= 0) { + // File was created successfully. + // New file does not need to be truncated. + return fd; + } else { + // File creation failed. + return -1; + } + } else { + // lstat failed, or user doesn't want to create the file + return -1; + } + } else { + // lstat succeeded - verify attributes and open + if (S_ISREG(ls.st_mode) && // regular file? + ls.st_nlink == 1 && // link count == 1? + ls.st_uid == euid && // owned by euid? + (ls.st_mode & ALLPERMS) == (S_IRUSR | S_IWUSR)) // mode 0600? + { + // Attributes look ok - open it and check attributes again + fd = open(filename, flags, 0000); + if (fd >= 0) { + // File is open - double-check attributes + if (0 == fstat(fd, &fs) && + fs.st_nlink == ls.st_nlink && // link count == 1? + fs.st_uid == ls.st_uid && // owned by euid? + fs.st_mode == ls.st_mode && // regular file, 0600? + fs.st_ino == ls.st_ino && // same inode as before? + fs.st_dev == ls.st_dev) // same device as before? { - // Super class can not be found yet, arrange for this class to - // be filled in later - objc_pendClassInstallation (cls, module->version); - ((struct objc_class *)cls)->super_class = _objc_getNonexistentClass (); - ((struct objc_class *)cls)->isa->super_class = _objc_getNonexistentClass (); + // File is open and OK + if (truncate) ftruncate(fd, 0); + return fd; + } else { + // Opened file looks funny - close it + close(fd); + return -1; } + } else { + // File didn't open + return -1; } - - // Move on - module += 1; + } else { + // Unopened file looks funny - don't open it + return -1; } + } +} - trace(0xb108, 0, 0, 0); - - // Initialize the isa pointers of all NXConstantString objects - _objc_fixup_string_objects_for_image (hInfo); - trace(0xb109, 0, 0, 0); +/********************************************************************** + * Shared range support: + * + * Some libraries contain many pages worth of selector references. + * In most processes, these libraries get loaded at the same addresses, + * so the selectors are uniqued to the same values. To save memory, + * the runtime tries to share these memory pages across processes. + * + * A file /tmp/objc_sharing__ records memory ranges and process + * IDs. When a set of selector refs is to be uniqued, this file is checked + * for a matching memory range being shared by another process. If + * such a range is found: + * 1. map the sharing process's memory somewhere into this address space + * 2. read from the real selector refs and write into the mapped memory. + * 3. vm_copy from the mapped memory to the real selector refs location + * 4. deallocate the mapped memory + * + * The mapped memory is merely used as a guess. Correct execution is + * guaranteed no matter what values the mapped memory actually contains. + * If the mapped memory really matches the values needed in this process, + * the mapped memory will be unchanged. If the mapped memory doesn't match, + * or contains random values, it will be fixed up to the correct values. + * The memory is shared whenever the guess happens to be correct. + * + * The file of shared ranges is imprecise. Processes may die leaving + * their entries in the file. A PID may be recycled to some process that + * does not use Objective-C. The sharing mechanism is robust in the face + * of these failures. Bad shared memory is simply fixed up. No shared + * memory means the selectors are fixed in place. If an entry in the + * file is found to be unusable, the process that finds it will instead + * offer to share its own memory, replacing the bad entry in the file. + * + * Individual entries in the file are written atomically, but the file is + * otherwise unsynchronized. At worst, a sharing opportunity may be missed + * because two new entries are written simultaneously in the same place. + **********************************************************************/ + + +struct remote_range_t { + vm_range_t range; + pid_t pid; +}; - // Convert class refs from name pointers to ids - _objc_map_class_refs_for_image (hInfo); - trace(0xb10a, 0, 0, 0); +// Cache for the last shared range file used, and its EUID. +static pthread_mutex_t sharedRangeLock = PTHREAD_MUTEX_INITIALIZER; +static uid_t sharedRangeEUID = 0; +static FILE * sharedRangeFile = NULL; +static BOOL sharedRangeFileInUse = NO; - // Selectorize the method names and +_fixup each of - // protocols in the image - _objc_fixup_protocol_objects_for_image (hInfo); - trace(0xb10b, 0, 0, 0); +/********************************************************************** +* open_shared_range_file +* Open the shared range file "/tmp/objc_sharing__" in +* the given mode. +* The returned file should be closed with close_shared_range_file(). +**********************************************************************/ +static FILE *open_shared_range_file(BOOL create) +{ + const char arch[] = +#if defined(__ppc__) || defined(ppc) + "ppc"; +#elif defined(__ppc64__) || defined(ppc64) + "ppc64"; +#elif defined(__i386__) || defined(i386) + "i386"; +#else +# error "unknown architecture" +#endif + char filename[18 + sizeof(arch) + 1 + 3*sizeof(uid_t) + 1]; + uid_t euid; + FILE *file = NULL; + int fd; + + // Never share when superuser + euid = geteuid(); + if (euid == 0) { + if (PrintSharing) { + _objc_inform("SHARING: superuser never shares"); + } + return NULL; + } - // Call +load on all classes and categorized classes - _objc_call_loads_for_image (hInfo); + // Return cached file if it matches and it's not still being used + pthread_mutex_lock(&sharedRangeLock); + if (!sharedRangeFileInUse && euid == sharedRangeEUID) { + file = sharedRangeFile; + sharedRangeFileInUse = YES; + pthread_mutex_unlock(&sharedRangeLock); + rewind(file); + return file; + } + pthread_mutex_unlock(&sharedRangeLock); - trace(0xb10c, 0, 0, 0); + // Open /tmp/objc_sharing_ + snprintf(filename,sizeof(filename), "/tmp/objc_sharing_%s_%u", arch, euid); + fd = secure_open(filename, O_RDWR | (create ? O_CREAT : 0), euid); + if (fd >= 0) { + file = fdopen(fd, "r+"); } - trace(0xb10f, 0, 0, 0); + if (file) { + // Cache this file if there's no already-open file cached + pthread_mutex_lock(&sharedRangeLock); + if (!sharedRangeFileInUse) { + sharedRangeFile = file; + sharedRangeEUID = euid; + sharedRangeFileInUse = YES; + } + pthread_mutex_unlock(&sharedRangeLock); + } + else { + // open() or fdopen() failed + if (PrintSharing) { + _objc_inform("SHARING: bad or missing sharing file '%s': %s", + filename, errno ? strerror(errno) : + "potential security violation"); + } + } + + return file; } -/*********************************************************************** -* _objc_unmap_image. + +/********************************************************************** +* close_shared_range_file +* Close a file opened with open_shared_range_file. +* The file may actually be kept open and cached for a future +* open_shared_range_file call. If so, clear_shared_range_file_cache() +* can be used to really close the file. **********************************************************************/ -static void _objc_unmap_image(headerType *mh, unsigned long vmaddr_slide) { - // we shouldn't have it if it didn't have objc data - // if we do have it, do a fatal - _objc_fatalHeader(mh); +static void close_shared_range_file(FILE *file) +{ + // Flush any writes in case the file is kept open. + fflush(file); + + pthread_mutex_lock(&sharedRangeLock); + if (file == sharedRangeFile && sharedRangeFileInUse) { + // This file is the cached shared file. + // Leave the file open and cached, but no longer in use. + sharedRangeFileInUse = NO; + } else { + // This is not the cached file. + fclose(file); + } + pthread_mutex_unlock(&sharedRangeLock); } -/*********************************************************************** -* objc_setNilObjectMsgHandler. + +/********************************************************************** +* clear_shared_range_file_cache +* Really close any file left open by close_shared_range_file. +* This is called by map_images() after loading multiple images, each +* of which may have used the shared range file. **********************************************************************/ -void objc_setNilObjectMsgHandler (NilObjectMsgCallback nilObjMsgCallback) +static void clear_shared_range_file_cache(void) { - _objc_msgNil = nilObjMsgCallback; + pthread_mutex_lock(&sharedRangeLock); + if (sharedRangeFile && !sharedRangeFileInUse) { + fclose(sharedRangeFile); + sharedRangeFile = NULL; + sharedRangeEUID = 0; + sharedRangeFileInUse = 0; + } + pthread_mutex_unlock(&sharedRangeLock); } -/*********************************************************************** -* objc_getNilObjectMsgHandler. + +/********************************************************************** +* get_shared_range +* Try to find a shared range matching addresses [aligned_start..aligned_end). +* If a range is found, it is mapped into this process and returned. +* If no range is found, or the found range could not be mapped for +* some reason, the range {0, 0} is returned. +* aligned_start and aligned_end must be page-aligned. **********************************************************************/ -NilObjectMsgCallback objc_getNilObjectMsgHandler (void) +static vm_range_t get_shared_range(vm_address_t aligned_start, + vm_address_t aligned_end) { - return _objc_msgNil; + struct remote_range_t remote; + vm_range_t result; + FILE *file; + + result.address = 0; + result.size = 0; + + // Open shared range file, but don't create it + file = open_shared_range_file(NO); + if (!file) return result; + + // Search for the desired memory range + while (1 == fread(&remote, sizeof(remote), 1, file)) { + if (remote.pid != 0 && + remote.range.address == aligned_start && + remote.range.size == aligned_end - aligned_start) + { + // Found a match in the file - try to grab the memory + mach_port_name_t remote_task; + vm_prot_t cur_prot, max_prot; + vm_address_t local_addr; + kern_return_t kr; + + // Find the task offering the memory + kr = task_for_pid(mach_task_self(), remote.pid, &remote_task); + if (kr != KERN_SUCCESS) { + // task is dead + if (PrintSharing) { + _objc_inform("SHARING: no task for pid %d: %s", + remote.pid, mach_error_string(kr)); + } + break; + } + + // Map the memory into our process + local_addr = 0; + kr = vm_remap(mach_task_self(), &local_addr, remote.range.size, + 0 /*alignment*/, 1 /*anywhere*/, + remote_task, remote.range.address, + 1 /*copy*/, &cur_prot, &max_prot, VM_INHERIT_NONE); + mach_port_deallocate(mach_task_self(), remote_task); + + if (kr != KERN_SUCCESS) { + // couldn't map memory + if (PrintSharing) { + _objc_inform("SHARING: vm_remap from pid %d failed: %s", + remote.pid, mach_error_string(kr)); + } + break; + } + + if (!(cur_prot & VM_PROT_READ) || !(cur_prot & VM_PROT_WRITE)) { + // Received memory is not mapped read/write - don't use it + // fixme try to change permissions? check max_prot? + if (PrintSharing) { + _objc_inform("SHARING: memory from pid %d not read/write", + remote.pid); + } + vm_deallocate(mach_task_self(), local_addr, remote.range.size); + break; + } + + // Success + result.address = local_addr; + result.size = remote.range.size; + } + } + + close_shared_range_file(file); + return result; } +/********************************************************************** +* offer_shared_range +* Offer memory range [aligned_start..aligned_end) in this process +* to other Objective-C-using processes. +* If some other entry in the shared range list matches this range, +* is is overwritten with this process's PID. (Thus any stale PIDs are +* replaced.) +* If the shared range file could not be updated for any reason, this +* function fails silently. +* aligned_start and aligned_end must be page-aligned. +**********************************************************************/ +static void offer_shared_range(vm_address_t aligned_start, + vm_address_t aligned_end) +{ + struct remote_range_t remote; + struct remote_range_t local; + BOOL found = NO; + FILE *file; + int err = 0; + + local.range.address = aligned_start; + local.range.size = aligned_end - aligned_start; + local.pid = getpid(); + + // Open shared range file, creating if necessary + file = open_shared_range_file(YES); + if (!file) return; + + // Find an existing entry for this range, if any + while (1 == fread(&remote, sizeof(remote), 1, file)) { + if (remote.pid != 0 && + remote.range.address == aligned_start && + remote.range.size == aligned_end - aligned_start) + { + // Found a match - overwrite it + err = fseek(file, -sizeof(remote), SEEK_CUR); + found = YES; + break; + } + } + + if (!found) { + // No existing entry - write at the end of the file + err = fseek(file, 0, SEEK_END); + } + + if (err == 0) { + fwrite(&local, sizeof(local), 1, file); + } + + close_shared_range_file(file); +} + + +/********************************************************************** +* install_shared_range +* Install a shared range received from get_shared_range() into +* its final resting place. +* If possible, the memory is copied using virtual memory magic rather +* than actual data writes. dst always gets updated values, even if +* virtual memory magic is not possible. +* The shared range is always deallocated. +* src and dst must be page-aligned. +**********************************************************************/ +static void install_shared_range(vm_range_t src, vm_address_t dst) +{ + kern_return_t kr; + + // Copy from src to dst + kr = vm_copy(mach_task_self(), src.address, src.size, dst); + if (kr != KERN_SUCCESS) { + // VM copy failed. Use non-VM copy. + if (PrintSharing) { + _objc_inform("SHARING: vm_copy failed: %s", mach_error_string(kr)); + } + memmove((void *)dst, (void *)src.address, src.size); + } + + // Unmap the shared range at src + vm_deallocate(mach_task_self(), src.address, src.size); +}