X-Git-Url: https://git.saurik.com/apple/objc4.git/blobdiff_plain/390d58628f96731c7736302877e6c16d006d1a4f..809271e4ffbc957d063b093e55a39c85c61f6aaa:/runtime/objc-runtime.m?ds=sidebyside diff --git a/runtime/objc-runtime.m b/runtime/objc-runtime.m index 5498f60..6a61be9 100644 --- a/runtime/objc-runtime.m +++ b/runtime/objc-runtime.m @@ -29,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" @@ -46,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); @@ -70,19 +190,32 @@ 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. @@ -94,15 +227,45 @@ void (*callbackFunction)(Class, const char *) = 0; // Lock for class hashtable OBJC_DECLARE_LOCK (classLock); -// Condition for logging load progress -static int LaunchingDebug = -1; // env "LaunchingDebug" -static int PrintBinding = -1; // env "OBJC_PRINT_BIND" +// 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"; -// Is every library up to now prebound? -static int all_modules_prebound = 0; /*********************************************************************** * Function prototypes internal to this module. @@ -114,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, @@ -148,6 +312,9 @@ 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; @@ -159,12 +326,14 @@ static int (*objc_classHandler) (const char *) = _objc_defaultClassHandler; 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. @@ -212,6 +381,49 @@ static int classIsEqual (void * info, 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. @@ -222,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; } @@ -293,43 +507,92 @@ 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 _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! +* 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; - cls.name = aClassName; + query.name = aClassName; - // Check the hash table - OBJC_LOCK (&classLock); - ret = (id) NXHashGet (class_hash, &cls); - OBJC_UNLOCK (&classLock); + retry: - // If not found, go call objc_classLoader and try again - if (!ret && _objc_classLoader && (*_objc_classLoader)(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); } - // If not found, go call objc_classHandler and try again - if (!ret && (*objc_classHandler)(aClassName)) - { - OBJC_LOCK (&classLock); - ret = (id) NXHashGet (class_hash, &cls); - OBJC_UNLOCK (&classLock); + 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 ret; + 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 @@ -339,27 +602,8 @@ id objc_getClass (const char * aClassName) **********************************************************************/ id objc_lookUpClass (const char * aClassName) { - struct objc_class cls; - id ret; - - cls.name = aClassName; - - // Check the hash table - OBJC_LOCK (&classLock); - ret = (id) NXHashGet (class_hash, &cls); - OBJC_UNLOCK (&classLock); - - // If not found, go call objc_classLoader and try again - if (!ret && _objc_classLoader && (*_objc_classLoader)(aClassName)) - { - OBJC_LOCK (&classLock); - ret = (id) NXHashGet (class_hash, &cls); - OBJC_UNLOCK (&classLock); - } - - // Don't call objc_classHandler; it's only used by objc_getClass(). - - return ret; + // NO unconnected, NO class handler + return look_up_class(aClassName, NO, NO); } /*********************************************************************** @@ -384,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); @@ -393,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); @@ -414,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) { @@ -422,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 @@ -467,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); @@ -492,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; @@ -518,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) @@ -547,115 +842,180 @@ 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); if (_objcHeaderIsReplacement(hi)) { // Ignore any categories in this image @@ -663,37 +1023,33 @@ static void _objc_add_categories_from_image (header_info * hi) } // Major loop - process all modules in the header - mods = (Module) ((unsigned long) hi->mod_ptr + hi->image_slide); + mods = hi->mod_ptr; - trace(0xb120, hi->mod_count, 0, 0); - - 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 && !all_modules_prebound); + 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); } @@ -703,24 +1059,21 @@ static void _objc_add_categories_from_image (header_info * hi) **********************************************************************/ 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 = (struct segment_command *)hInfo->objcData; - size = hInfo->objcDataSize; - 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) addr) && - ((unsigned long) addr < (vmaddrPlus + size))) + if ((seg <= (unsigned long) addr) && + ((unsigned long) addr < (seg + size))) return hInfo; } @@ -729,16 +1082,6 @@ static const header_info *_headerForAddress(void *addr) } -/*********************************************************************** -* _headerForCategory -* Return the image header containing this category, or NULL -**********************************************************************/ -static const header_info *_headerForCategory(struct objc_category *cat) -{ - return _headerForAddress(cat); -} - - /*********************************************************************** * _headerForClass * Return the image header containing this class, or NULL. @@ -751,220 +1094,734 @@ static const header_info *_headerForClass(struct objc_class *cls) /*********************************************************************** -* _moduleForClassFromImage -* Returns the module containing the definition for a class, or NULL. -* The class is assumed to be in the given image, and the module -* returned will be in that image. +* _nameForHeader. **********************************************************************/ -static Module _moduleForClassFromImage(struct objc_class *cls, - const header_info *hInfo) +const char * _nameForHeader (const headerType * header) { - Module mods = (Module)((unsigned long)hInfo->mod_ptr + hInfo->image_slide); - int m, d; - for (m = 0; m < hInfo->mod_count; m++) { - if (mods[m].symtab) { - for (d = 0; d < mods[m].symtab->cls_def_cnt; d++) { - if (cls == (struct objc_class *) mods[m].symtab->defs[d]) { - return &mods[m]; - } - } - } - } - return NULL; + return _getObjcHeaderName ((headerType *) header); } /*********************************************************************** -* _moduleForCategoryFromImage -* Returns the module containing the definition for a category, or NULL. -* The category is assumed to be in the given image, and the module -* returned will be in that image. +* 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 Module _moduleForCategoryFromImage(struct objc_category *cat, - const header_info *hInfo) +static BOOL class_is_connected(struct objc_class *cls) { - Module mods = (Module)((unsigned long)hInfo->mod_ptr + hInfo->image_slide); - int m, d; - for (m = 0; m < hInfo->mod_count; m++) { - if (mods[m].symtab) { - for (d = mods[m].symtab->cls_def_cnt; - d < mods[m].symtab->cls_def_cnt + mods[m].symtab->cat_def_cnt; - d++) - { - if (cat == (struct objc_category *) mods[m].symtab->defs[d]) { - return &mods[m]; - } - } - } - } - return NULL; + BOOL result; + OBJC_LOCK(&classLock); + result = NXHashMember(class_hash, cls); + OBJC_UNLOCK(&classLock); + return result; } /*********************************************************************** -* _nameForHeader. +* pendingClassRefsMapTable. Return a pointer to the lookup table for +* pending class refs. **********************************************************************/ -const char * _nameForHeader (const headerType * header) +static inline NXMapTable *pendingClassRefsMapTable(void) { - return _getObjcHeaderName ((headerType *) header); + // Allocate table if needed + if (!pendingClassRefsMap) { + pendingClassRefsMap = + NXCreateMapTableFromZone(NXStrValueMapPrototype, + 10, _objc_internal_zone ()); + } + + // Return table pointer + return pendingClassRefsMap; } + /*********************************************************************** -* checkForPendingClassReferences. Complete any fixups registered for -* this class. +* pendingSubclassesMapTable. Return a pointer to the lookup table for +* pending subclasses. **********************************************************************/ -static void _objc_checkForPendingClassReferences (struct objc_class * cls) +static inline NXMapTable *pendingSubclassesMapTable(void) { - 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); - } - - // Reclaim the element - free (pending); - - // Move on - pending = next; + // Allocate table if needed + if (!pendingSubclassesMap) { + pendingSubclassesMap = + NXCreateMapTableFromZone(NXStrValueMapPrototype, + 10, _objc_internal_zone ()); } + + // Return table pointer + return pendingSubclassesMap; } -/*********************************************************************** -* newPending. Allocate and zero a PendingClass structure. -**********************************************************************/ -static inline PendingClass * newPending (void) -{ - PendingClass * pending; - - pending = (PendingClass *) malloc_zone_calloc (_objc_create_zone (), 1, sizeof(PendingClass)); - - return pending; -} /*********************************************************************** -* 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; + } +} + + +/*********************************************************************** +* resolve_subclasses_of_class +* Fix up any pending subclasses of this class. +**********************************************************************/ +static void resolve_subclasses_of_class(struct objc_class *cls) +{ + PendingSubclass *pending; + + if (!pendingSubclassesMap) return; // no unresolved subclasses + + 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_add_classes_from_image. Install all classes contained in the -* specified image. +* _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_add_classes_from_image(NXHashTable *clsHash, header_info *hi) +static void _objc_read_classes_from_image(header_info *hi) { unsigned int index; unsigned int midx; Module mods; - int isDynamic = (hi->mhdr->filetype == MH_DYLIB) || (hi->mhdr->filetype == MH_BUNDLE); + 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 = (Module) ((unsigned long) hi->mod_ptr + hi->image_slide); + mods = hi->mod_ptr; for (midx = 0; midx < hi->mod_count; midx += 1) { // Skip module containing no classes @@ -974,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 && !all_modules_prebound) - 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 @@ -1097,58 +1949,106 @@ 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_registerNameNoCopyNoLock ((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; @@ -1164,7 +2064,7 @@ static void map_method_descs (struct objc_method_description_list * methods) method = &methods->list[index]; // Lookup pointer to uniqued string - sel = sel_registerNameNoCopyNoLock ((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) @@ -1191,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); @@ -1205,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 @@ -1232,363 +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) -{ - static header_info *lastHeader = NULL; - header_info *hInfo; - const headerType *imageHeader = lastHeader ? lastHeader->mhdr : NULL; - - // 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; - } - - // 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) - { - if (_objcHeaderIsReplacement(hInfo)) { - continue; // no classes or categories used from image; skip it - } - imageHeader = hInfo->mhdr; - if ( hInfo != lastHeader - && NSIsSymbolNameDefinedInImage(imageHeader, name) - && NSLookupSymbolInImage(imageHeader, name, NSLOOKUPSYMBOLINIMAGE_OPTION_BIND) != NULL ) - { - // found - lastHeader = hInfo; - return; - } - } - // die now, or later ?? - // _objc_fatal("could not find %s", name); -} /*********************************************************************** -* _symbolNameForCategory -* Constructs the symbol name for the given category. -* The symbol name is ".objc_category_name__", -* where is the class name with the leading '%'s stripped. -* The caller must free() the result. +* _objc_addHeader. **********************************************************************/ -static char *_symbolNameForCategory(Category cat) -{ - char * class_name; - char * category_name; - char * name; - class_name = cat->class_name; - category_name = cat->category_name; - - name = malloc(strlen(class_name) + strlen(category_name) + 30); +// tested with 2; typical case is 4, but OmniWeb & Mail push it towards 20 +#define HINFO_SIZE 16 - while (*class_name == '%') - class_name += 1; - strcpy (name, ".objc_category_name_"); - strcat (name, class_name); - strcat (name, "_"); - strcat (name, category_name); - return name; -} +static int HeaderInfoCounter NOBSS = 0; +static header_info HeaderInfoTable[HINFO_SIZE] NOBSS = { {0} }; -/*********************************************************************** -* _symbolNameForClass -* Constructs the symbol name for the given class. -* The symbol name is ".objc_class_name_", -* where is the class name with the leading '%'s stripped. -* The caller must free() the result. -**********************************************************************/ -static char *_symbolNameForClass(Class cls) +static header_info * _objc_addHeader(const struct mach_header *header) { - char * name; - const char * class_name; + 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; - class_name = cls->name; + // Locate the __OBJC segment + objc_segment = getsegbynamefromheader(header, SEG_OBJC); + if (!objc_segment) return NULL; - name = malloc(strlen(class_name) + 20); + // 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); - while (*class_name == '%') - class_name += 1; - strcpy (name, ".objc_class_name_"); - strcat (name, class_name); - return name; -} + // Calculate vm slide. + slide = _getImageSlide(header); -/*********************************************************************** -* _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. -**********************************************************************/ -void _objc_bindClassIfNeeded(struct objc_class *cls) -{ - // 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; + // Find or allocate a header_info entry. + if (HeaderInfoCounter < HINFO_SIZE) { + result = &HeaderInfoTable[HeaderInfoCounter++]; + } else { + result = _malloc_internal(sizeof(header_info)); } -} + // 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; + } -/*********************************************************************** -* _objc_bindModuleContainingCategory. Bind the module containing the -* category. If that module is already bound, do nothing. -**********************************************************************/ -static void _objc_bindModuleContainingCategory(struct objc_category *cat) -{ - const header_info *hInfo; - - if (PrintBinding) { - _objc_inform("binding category %s(%s)", - cat->class_name, cat->category_name); + // 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); + } } - - hInfo = _headerForCategory(cat); - if (hInfo) { - Module module = _moduleForCategoryFromImage(cat, hInfo); - if (module) { - _dyld_bind_objc_module(module); - return; + + // 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; } - { - // Header-finding or module-finding shortcut didn't work. - // Bind using symbol name. - char *symbolName = _symbolNameForCategory(cat); - _objc_bind_symbol(symbolName); - free(symbolName); - } + return result; } - + /*********************************************************************** -* _objc_bindModuleContainingClass. Bind the module containing the -* class. If that module is already bound, do nothing. -* This is done lazily, just before calling +load or +initialize. +* _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. **********************************************************************/ -void _objc_bindModuleContainingClass(struct objc_class *cls) +static void _objc_removeHeader(header_info *hi) { - const header_info *hInfo; - - if (PrintBinding) { - _objc_inform("binding class %s", cls->name); - } - - // Use the real class behind the poser - if (CLS_GETINFO (cls, CLS_POSING)) { - cls = getOriginalClassForPosingClass (cls); - } - - hInfo = _headerForClass(cls); - if (hInfo) { - Module module = _moduleForClassFromImage(cls, hInfo); - if (module) { - _dyld_bind_objc_module(module); - return; + 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 + } + + // Free the memory, unless it was in the static HeaderInfoTable. + if (deadHead < HeaderInfoTable || + deadHead >= HeaderInfoTable + HINFO_SIZE) + { + _free_internal(deadHead); + } + + break; } } - - { - // Module not bound, and header-finding or module-finding shortcut - // didn't work. Bind using symbol name. - // This happens for the NSCF class structs which are copied elsewhere. - char *symbolName = _symbolNameForClass(cls); - _objc_bind_symbol(symbolName); - free(symbolName); - } } /*********************************************************************** -* _objc_addHeader. -* +* 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. **********************************************************************/ - -// 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 BOOL check_wants_gc(void) { - int mod_count; - Module mod_ptr = _getObjcModules ((headerType *) header, &mod_count); - char *image_info_ptr = (char *)_getObjcImageInfo((headerType *)header); - header_info *result; - - // if there is no objc data - ignore this entry! - if (mod_ptr == NULL) { - return NULL; - } - - if (HeaderInfoCounter < HINFO_SIZE) { - // avoid mallocs for the common case - result = &HeaderInfoTable[HeaderInfoCounter++]; + // 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 { - result = malloc_zone_malloc(_objc_create_zone(), sizeof(header_info)); - } - - // Set up the new vector entry - result->mhdr = header; - result->mod_ptr = mod_ptr; - result->mod_count = mod_count; - result->image_slide = vmaddr_slide; - result->objcData = _getObjcHeaderData(header, &result->objcDataSize); - if (image_info_ptr) { - result->info = (objc_image_info *)(vmaddr_slide + image_info_ptr); - } else { - result->info = NULL; + // 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"); + } + } + } } - - // chain it on - // (a simple lock here would go a long way towards thread safety) - result->next = FirstHeader; - FirstHeader = result; - - return result; + return appWantsGC; + */ } -/********************************************************************** -* _objc_fatalHeader -* -* If we have it we're in trouble -**************************************************************************/ -static void _objc_fatalHeader(const headerType *header) +/*********************************************************************** +* verify_gc_readiness +* if we want gc, verify that every header describes files compiled +* and presumably ready for gc. +************************************************************************/ + +static void verify_gc_readiness(BOOL wantsGC, header_info *hi) { - header_info *hInfo; + BOOL busted = NO; + + // 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"); + } + } - 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); - } -} - - -/*********************************************************************** -* _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; + 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 - if (_objcHeaderIsReplacement(header)) { - return; // Don't call +load again - } + 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"); } - if ( PrintBinding == -1 ) { - PrintBinding = getenv("OBJC_PRINT_BIND") != NULL; + +#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. **********************************************************************/ @@ -1613,265 +2540,476 @@ 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--, module++) - { - if (module->symtab) { - for (index = 0; index < module->symtab->cls_def_cnt; index++) - { - cls = (struct objc_class *) module->symtab->defs[index]; - _class_install_relationships (cls, module->version); - } - } - } - trace(0xb007, hInfo, hInfo->mod_count, 0); +/*********************************************************************** +* 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); } - trace(0xb008, 0, 0, 0); - - for (hInfo = FirstHeader; hInfo != NULL; hInfo = hInfo->next) - { - // Initialize the isa pointers of all NXConstantString objects - (void)_objc_fixup_string_objects_for_image (hInfo); + for (i = 0; i < infoCount; i++) { + const struct mach_header *mhdr = infoList[i].imageLoadAddress; - // Convert class refs from name pointers to ids - (void)_objc_map_class_refs_for_image (hInfo); + 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(0xb00a, 0, 0, 0); - - // 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); - - ptrace(0xb00f, 0, 0, 0); // end of __initialize_objc ObjC init - trace(0xb00f, 0, 0, 0); // end of __initialize_objc ObjC init } /*********************************************************************** -* _objc_map_image. +* _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. **********************************************************************/ -static void _objc_map_image(headerType *mh, unsigned long vmaddr_slide) +__private_extern__ +void _objc_notify_images(enum dyld_image_mode mode, uint32_t infoCount, + const struct dyld_image_info infoList[]) { - static int dumpClasses = -1; - header_info *hInfo; - - if ( dumpClasses == -1 ) { - if ( getenv("OBJC_DUMP_CLASSES") ) dumpClasses = 1; - else dumpClasses = 0; + if (mode == dyld_image_adding) { + map_images(infoList, infoCount); + } else if (mode == dyld_image_removing) { + unmap_images(infoList, infoCount); } +} - trace(0xb100, 0, 0, 0); - // Add this header to the chain - hInfo = _objc_addHeader (mh, vmaddr_slide); +/*********************************************************************** +* _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; - if (!hInfo) return; + OBJC_LOCK(&classLock); - if (LaunchingDebug) { - _objc_syslog("objc_map_image for %s%s\n", - _nameForHeader(mh), - _objcHeaderIsReplacement(hInfo) ? " (replacement)" : ""); - } - - trace(0xb101, 0, 0, 0); - -#ifdef MACOSX_PANTHER - // Check if all loaded libraries up to and including this one are prebound - // SPI introduced in Panther (Mac OS X 10.3) - all_modules_prebound = _dyld_all_twolevel_modules_prebound(); - if (PrintBinding) { - static int warned = -1; - if (warned != all_modules_prebound) { - _objc_inform("objc: binding: all_modules_prebound is now %d", - all_modules_prebound); - warned = all_modules_prebound; + // 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; + + // 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); + } } } -#endif - - // Register any categories and/or classes and/or selectors this image contains - _objc_add_categories_from_image (hInfo); - trace(0xb103, 0, 0, 0); - _objc_add_classes_from_image (class_hash, hInfo); + // Search all other images for class refs that point back to this range. + // Un-fix and re-pend any such class refs. - trace(0xb104, 0, 0, 0); + // 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_fixup_selector_refs (hInfo); + 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 - trace(0xb105, 0, 0, 0); + // 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); - // Log all known class names, if asked - if ( dumpClasses ) - { - printf ("classes...\n"); - objc_dump_class_hash (); + // 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 (); + } + } } - if (!Postpone_install_relationships) - { - int nModules; - int index; - struct objc_module * module; - - // Major loop - process each module - module = (struct objc_module *) ((unsigned long) hInfo->mod_ptr + hInfo->image_slide); - - trace(0xb106, hInfo->mod_count, 0, 0); - - for (nModules = hInfo->mod_count; nModules; nModules--, module++) - { - if (!module->symtab) continue; - - // 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_UNLOCK(&classLock); +} - // Locate the class description - cls = (struct objc_class *) module->symtab->defs[index]; - // 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 - { - // 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 (); +/*********************************************************************** +* _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; + + 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; + } } } } + } +} - 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); - // Convert class refs from name pointers to ids - _objc_map_class_refs_for_image (hInfo); +/*********************************************************************** +* 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 unload_paranoia(header_info *hi) +{ + // 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; - trace(0xb10a, 0, 0, 0); + _objc_inform("UNLOAD DEBUG: unloading image '%s' [%p..%p]", + _nameForHeader(hi->mhdr), seg, seg+seg_size); - // Selectorize the method names and +_fixup each of - // protocols in the image - _objc_fixup_protocol_objects_for_image (hInfo); + OBJC_LOCK(&classLock); - trace(0xb10b, 0, 0, 0); + // 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); + } + } + } + } - // Call +load on all classes and categorized classes - _objc_call_loads_for_image (hInfo); + // 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; - trace(0xb10c, 0, 0, 0); + 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); + } + } } - trace(0xb10f, 0, 0, 0); + OBJC_UNLOCK(&classLock); } + /*********************************************************************** * _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(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 _objc_unmap_image(const headerType *mh) +{ + header_info *hi; + + // 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)" : ""); + } + + // 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); + + // Remove header_info from header list + _objc_removeHeader(hi); } + /*********************************************************************** * _objc_setNilReceiver **********************************************************************/ @@ -1904,3 +3042,471 @@ void _objc_setClassLoader(BOOL (*newClassLoader)(const char *)) { _objc_classLoader = newClassLoader; } + + +#if defined(__ppc__) + +/********************************************************************** +* 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; + } +} + +// defined(__ppc__) +#endif + + +/********************************************************************** +* 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? + { + // 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; + } + } else { + // Unopened file looks funny - don't open it + return -1; + } + } +} + + +/********************************************************************** + * 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; +}; + + +// 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; + + +/********************************************************************** +* 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; + } + + // 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); + + // 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+"); + } + + 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; +} + + +/********************************************************************** +* 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 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); +} + + +/********************************************************************** +* 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. +**********************************************************************/ +static void clear_shared_range_file_cache(void) +{ + pthread_mutex_lock(&sharedRangeLock); + if (sharedRangeFile && !sharedRangeFileInUse) { + fclose(sharedRangeFile); + sharedRangeFile = NULL; + sharedRangeEUID = 0; + sharedRangeFileInUse = 0; + } + pthread_mutex_unlock(&sharedRangeLock); +} + + +/********************************************************************** +* 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. +**********************************************************************/ +static vm_range_t get_shared_range(vm_address_t aligned_start, + vm_address_t aligned_end) +{ + 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); +}