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