/*
* Copyright (c) 1999 Apple Computer, Inc. All rights reserved.
*
* @APPLE_LICENSE_HEADER_START@
*
* 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
* EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
* INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
* Please see the License for the specific language governing rights and
* limitations under the License.
*
* @APPLE_LICENSE_HEADER_END@
*/
/***********************************************************************
* objc-runtime.m
* Copyright 1988-1996, NeXT Software, Inc.
* Author: s. naroff
*
**********************************************************************/
/***********************************************************************
* 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-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/objc-runtime.h>
#import <objc/hashtable2.h>
#import "maptable.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);
OBJC_EXPORT Class getOriginalClassForPosingClass(Class);
/***********************************************************************
* Constants and macros internal to this module.
**********************************************************************/
/* Turn on support for literal string objects. */
#define LITERAL_STRING_OBJECTS
/***********************************************************************
* Types internal to this module.
**********************************************************************/
typedef struct _objc_unresolved_category
{
struct _objc_unresolved_category * next;
struct objc_category * cat; // may be NULL
long version;
} _objc_unresolved_category;
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; // 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 called after class has been fixed up (MACH only)
void (*callbackFunction)(Class, const char *) = 0;
// Lock for class hashtable
OBJC_DECLARE_LOCK (classLock);
// 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
__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.
**********************************************************************/
static unsigned classHash (void * info, struct objc_class * data);
static int classIsEqual (void * info, struct objc_class * name, struct objc_class * cls);
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);
static void _objc_read_categories_from_image (header_info * hi);
static const header_info * _headerForClass (struct objc_class * cls);
static NXMapTable * pendingClassRefsMapTable (void);
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 _objc_fixup_protocol_objects_for_image (header_info * hi);
static void _objc_fixup_selector_refs (const header_info * hi);
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 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 NOBSS = 0;
static NXHashTablePrototype classHashPrototype =
{
(unsigned (*) (const void *, const void *)) classHash,
(int (*)(const void *, const void *, const void *)) classIsEqual,
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;
// 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.
**********************************************************************/
void objc_dump_class_hash (void)
{
NXHashTable * table;
unsigned count;
struct objc_class * data;
NXHashState state;
table = class_hash;
count = 0;
state = NXInitHashState (table);
while (NXNextHashState (table, &state, (void **) &data))
printf ("class }
/***********************************************************************
* classHash.
**********************************************************************/
static unsigned classHash (void * info,
struct objc_class * data)
{
// Nil classes hash to zero
if (!data)
return 0;
// Call through to real hash function
return _objc_strhash ((unsigned char *) ((struct objc_class *) data)->name);
}
/***********************************************************************
* classIsEqual. Returns whether the class names match. If we ever
* check more than the name, routines like objc_lookUpClass have to
* change as well.
**********************************************************************/
static int classIsEqual (void * info,
struct objc_class * name,
struct objc_class * cls)
{
// Standard string comparison
// 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.
**********************************************************************/
void _objc_init_class_hash (void)
{
// Do nothing if class hash table already exists
if (class_hash)
return;
// 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,
16,
nil,
_objc_internal_zone ());
_objc_debug_class_hash = class_hash;
}
/***********************************************************************
* objc_getClassList. Return the known classes.
**********************************************************************/
int objc_getClassList(Class *buffer, int bufferLen) {
NXHashState state;
struct objc_class * class;
int cnt, num;
OBJC_LOCK(&classLock);
num = NXCountHashTable(class_hash);
if (NULL == buffer) {
OBJC_UNLOCK(&classLock);
return num;
}
cnt = 0;
state = NXInitHashState(class_hash);
while (cnt < bufferLen &&
NXNextHashState(class_hash, &state, (void **)&class))
{
buffer[cnt++] = class;
}
OBJC_UNLOCK(&classLock);
return num;
}
/***********************************************************************
* objc_getClasses. Return class lookup table.
*
* NOTE: This function is very dangerous, since you cannot safely use
* the hashtable without locking it, and the lock is private!
**********************************************************************/
void * objc_getClasses (void)
{
// Return the class lookup hash table
return class_hash;
}
/***********************************************************************
* _objc_defaultClassHandler. Default objc_classHandler. Does nothing.
**********************************************************************/
static int _objc_defaultClassHandler (const char * clsName)
{
// Return zero so objc_getClass doesn't bother re-searching
return 0;
}
/***********************************************************************
* objc_setClassHandler. Set objc_classHandler to the specified value.
*
* NOTE: This should probably deal with userSuppliedHandler being NULL,
* because the objc_classHandler caller does not check... it would bus
* error. It would make sense to handle NULL by restoring the default
* handler. Is anyone hacking with this, though?
**********************************************************************/
void objc_setClassHandler (int (*userSuppliedHandler) (const char *))
{
objc_classHandler = userSuppliedHandler;
}
/***********************************************************************
* 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)
**********************************************************************/
static id look_up_class(const char *aClassName, BOOL includeUnconnected, BOOL includeClassHandler)
{
BOOL includeClassLoader = YES; // class loader cannot be skipped
id result = nil;
struct objc_class query;
query.name = aClassName;
retry:
if (!result && class_hash) {
// Check ordinary classes
OBJC_LOCK (&classLock);
result = (id)NXHashGet(class_hash, &query);
OBJC_UNLOCK (&classLock);
}
if (!result && includeUnconnected && unconnected_class_hash) {
// Check not-yet-connected classes
OBJC_LOCK(&classLock);
result = (id)NXHashGet(unconnected_class_hash, &query);
OBJC_UNLOCK(&classLock);
}
if (!result && includeClassLoader && _objc_classLoader) {
// Try class loader callback
if ((*_objc_classLoader)(aClassName)) {
// Re-try lookup without class loader
includeClassLoader = NO;
goto retry;
}
}
if (!result && includeClassHandler && objc_classHandler) {
// Try class handler callback
if ((*objc_classHandler)(aClassName)) {
// Re-try lookup without class handler or class loader
includeClassLoader = NO;
includeClassHandler = NO;
goto retry;
}
}
return 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)
{
// NO unconnected, NO class handler
return look_up_class(aClassName, NO, NO);
}
/***********************************************************************
* objc_getMetaClass. Return the id of the meta class the named class.
* Warning: doesn't work if aClassName is the name of a posed-for class's isa!
**********************************************************************/
id objc_getMetaClass (const char * aClassName)
{
struct objc_class * cls;
cls = objc_getClass (aClassName);
if (!cls)
{
_objc_inform ("class ` return Nil;
}
return cls->isa;
}
/***********************************************************************
* objc_addClass. Add the specified class to the table of known classes,
* after doing a little verification and fixup.
**********************************************************************/
void objc_addClass (struct objc_class *cls)
{
// Synchronize access to hash table
OBJC_LOCK (&classLock);
// Make sure both the class and the metaclass have caches!
// 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 (cls->cache == NULL) {
cls->cache = (Cache) &emptyCache;
cls->info = CLS_CLASS;
}
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);
// Desynchronize
OBJC_UNLOCK (&classLock);
}
/***********************************************************************
* _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)
{
struct objc_method_list * originalList;
const int initialEntries = 4;
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 **) _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
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_class *cls,
struct objc_method_list *mlist)
{
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, add to it
if (*ptr == END_OF_METHODS_LIST)
{
// Calculate old and new dimensions
endIndex = ptr - *list;
oldSize = (endIndex + 1) * sizeof(void *);
newSize = oldSize + sizeof(struct objc_method_list *); // only increase by 1
// 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);
// Place new end marker
(*list)[(newSize/sizeof(void *)) - 1] = END_OF_METHODS_LIST;
// Insertion point corresponds to old array end
ptr = &((*list)[endIndex]);
}
// Right shift existing entries by one
bcopy (*list, (*list) + 1, ((void *) ptr) - ((void *) *list));
// Insert at method list at beginning of array
**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_class *cls,
struct objc_method_list *mlist)
{
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;
while (*ptr != mlist) {
// fix for radar # 2538790
if ( *ptr == END_OF_METHODS_LIST ) return;
ptr += 1;
}
// Remove this entry
*ptr = 0;
// Left shift the following entries
while (*(++ptr) != END_OF_METHODS_LIST)
*(ptr-1) = *ptr;
*(ptr-1) = 0;
}
/***********************************************************************
* _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 (cls, category->instance_methods);
// Augment class methods
if (category->class_methods)
_objc_insertMethods (cls->isa, category->class_methods);
// Augment protocols
if ((version >= 5) && category->protocols)
{
if (cls->isa->version >= 5)
{
category->protocols->next = cls->protocols;
cls->protocols = category->protocols;
cls->isa->protocols = category->protocols;
}
else
{
_objc_inform ("unable to add protocols from category _objc_inform ("class ` }
}
}
/***********************************************************************
* _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;
}
/***********************************************************************
* resolve_categories_for_class.
* Install all existing categories intended for the specified class.
* cls must be a true class and not a metaclass.
**********************************************************************/
static void resolve_categories_for_class(struct objc_class *cls)
{
_objc_unresolved_category * pending;
_objc_unresolved_category * next;
// Nothing to do if there are no categories at all
if (!category_hash) return;
// Locate and remove first element in category list
// associated with this class
pending = NXMapKeyFreeingRemove (category_hash, cls->name);
// Traverse the list of categories, if any, registered for this class
// 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);
}
// Delink and reclaim this registration
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, "_ // 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.
* 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)
{
_objc_unresolved_category * new_cat;
_objc_unresolved_category * old;
struct objc_class *theClass;
// 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_internal_zone ());
// 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.
// 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;
(void) NXMapKeyCopyingInsert (category_hash, cat->class_name, new_cat);
}
/***********************************************************************
* _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_read_categories_from_image (header_info * hi)
{
Module mods;
unsigned int midx;
if (_objcHeaderIsReplacement(hi)) {
// Ignore any categories in this image
return;
}
// Major loop - process all modules in the header
mods = hi->mod_ptr;
// 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;
// Minor loop - register all categories from given module
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);
}
}
}
/***********************************************************************
* _headerForAddress.
* addr can be a class or a category
**********************************************************************/
static const header_info *_headerForAddress(void *addr)
{
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
if (!hInfo->objcSegmentHeader) continue;
seg = hInfo->objcSegmentHeader->vmaddr + hInfo->image_slide;
size = hInfo->objcSegmentHeader->filesize;
// Is the class in this header?
if ((seg <= (unsigned long) addr) &&
((unsigned long) addr < (seg + size)))
return hInfo;
}
// Not found
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.
**********************************************************************/
const char * _nameForHeader (const headerType * header)
{
return _getObjcHeaderName ((headerType *) header);
}
/***********************************************************************
* 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 BOOL class_is_connected(struct objc_class *cls)
{
BOOL result;
OBJC_LOCK(&classLock);
result = NXHashMember(class_hash, cls);
OBJC_UNLOCK(&classLock);
return result;
}
/***********************************************************************
* 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;
}
/***********************************************************************
* pendingSubclassesMapTable. Return a pointer to the lookup table for
* pending subclasses.
**********************************************************************/
static inline NXMapTable *pendingSubclassesMapTable(void)
{
// Allocate table if needed
if (!pendingSubclassesMap) {
pendingSubclassesMap =
NXCreateMapTableFromZone(NXStrValueMapPrototype,
10, _objc_internal_zone ());
}
// Return table pointer
return pendingSubclassesMap;
}
/***********************************************************************
* pendClassInstallation
* Finish connecting class cls when its superclass becomes connected.
* Check for multiple pends of the same class because connect_class does not.
**********************************************************************/
static void pendClassInstallation(struct objc_class *cls,
const char *superName)
{
NXMapTable *table;
PendingSubclass *pending;
PendingSubclass *oldList;
PendingSubclass *l;
// Create and/or locate pending class lookup table
table = pendingSubclassesMapTable ();
// 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);
}
/***********************************************************************
* pendClassReference
* Fix up a class ref when the class with the given name becomes connected.
**********************************************************************/
static void pendClassReference(struct objc_class **ref,
const char *className)
{
NXMapTable *table;
PendingClassRef *pending;
// Create and/or locate pending class lookup table
table = pendingClassRefsMapTable ();
// Create entry containing the class reference
pending = _malloc_internal(sizeof(PendingClassRef));
pending->ref = ref;
// Link new entry into head of list of entries for this class
pending->next = NXMapGet (table, className);
// (Re)place entry list in the table
(void) NXMapKeyCopyingInsert (table, className, pending);
if (PrintConnecting) {
_objc_inform("CONNECT: pended reference to class '%s' at className, (void *)ref);
}
}
/***********************************************************************
* resolve_references_to_class
* Fix up any pending class refs to this class.
**********************************************************************/
static void resolve_references_to_class(struct objc_class *cls)
{
PendingClassRef *pending;
if (!pendingClassRefsMap) return; // no unresolved refs for any class
pending = NXMapGet(pendingClassRefsMap, cls->name);
if (!pending) return; // no unresolved refs for this class
NXMapKeyFreeingRemove(pendingClassRefsMap, cls->name);
if (PrintConnecting) {
_objc_inform("CONNECT: resolving references to class '%s'", cls->name);
}
while (pending) {
PendingClassRef *next = pending->next;
if (pending->ref) *pending->ref = cls;
_free_internal(pending);
pending = next;
}
if (NXCountMapTable(pendingClassRefsMap) == 0) {
NXFreeMapTable(pendingClassRefsMap);
pendingClassRefsMap = NULL;
}
}
/***********************************************************************
* resolve_subclasses_of_class
* Fix up any pending subclasses of this class.
**********************************************************************/
static void resolve_subclasses_of_class(struct objc_class *cls)
{
PendingSubclass *pending;
if (!pendingSubclassesMap) return; // no unresolved subclasses
pending = NXMapGet(pendingSubclassesMap, cls->name);
if (!pending) return; // no unresolved subclasses for this class
NXMapKeyFreeingRemove(pendingSubclassesMap, cls->name);
// Destroy the pending table if it's now empty, to save memory.
if (NXCountMapTable(pendingSubclassesMap) == 0) {
NXFreeMapTable(pendingSubclassesMap);
pendingSubclassesMap = NULL;
}
if (PrintConnecting) {
_objc_inform("CONNECT: resolving subclasses of class '%s'", cls->name);
}
while (pending) {
PendingSubclass *next = pending->next;
if (pending->subclass) connect_class(pending->subclass);
_free_internal(pending);
pending = next;
}
}
/***********************************************************************
* get_base_method_list
* Returns the method list containing the class's own methods,
* ignoring any method lists added by categories or class_addMethods.
* Called only by add_class_to_loadable_list.
* Does not hold methodListLock because add_class_to_loadable_list
* does not manipulate in-use classes.
**********************************************************************/
static struct objc_method_list *get_base_method_list(struct objc_class *cls)
{
struct objc_method_list **ptr;
if (!cls->methodLists) return NULL;
if (cls->info & CLS_NO_METHOD_ARRAY) return (struct objc_method_list *)cls->methodLists;
ptr = cls->methodLists;
if (!*ptr || *ptr == END_OF_METHODS_LIST) return NULL;
while ( *ptr != 0 && *ptr != END_OF_METHODS_LIST ) { ptr++; }
--ptr;
return *ptr;
}
/***********************************************************************
* add_class_to_loadable_list
* Class cls has just become connected. Schedule it for +load if
* it implements a +load method.
**********************************************************************/
static void add_class_to_loadable_list(struct objc_class *cls)
{
IMP method = NULL;
struct objc_method_list *mlist;
if (cls->isa->info & CLS_HAS_LOAD_METHOD) {
mlist = get_base_method_list(cls->isa);
if (mlist) {
method = lookupNamedMethodInMethodList (mlist, "load");
}
}
// Don't bother if cls has no +load method
if (!method) return;
if (PrintLoading) {
_objc_inform("LOAD: class '%s' scheduled for +load", cls->name);
}
if (loadable_classes_used == loadable_classes_allocated) {
loadable_classes_allocated = loadable_classes_allocated*2 + 16;
loadable_classes =
_realloc_internal(loadable_classes,
loadable_classes_allocated *
sizeof(struct loadable_class));
}
loadable_classes[loadable_classes_used].cls = cls;
loadable_classes[loadable_classes_used].method = method;
loadable_classes_used++;
}
/***********************************************************************
* add_category_to_loadable_list
* Category cat's parent class exists and the category has been attached
* to its class. Schedule this category for +load after its parent class
* becomes connected and has its own +load method called.
**********************************************************************/
static void add_category_to_loadable_list(struct objc_category *cat)
{
IMP method = NULL;
struct objc_method_list *mlist;
mlist = cat->class_methods;
if (mlist) {
method = lookupNamedMethodInMethodList (mlist, "load");
}
// Don't bother if cat has no +load method
if (!method) return;
if (PrintLoading) {
_objc_inform("LOAD: category '%s(%s)' scheduled for +load",
cat->class_name, cat->category_name);
}
if (loadable_categories_used == loadable_categories_allocated) {
loadable_categories_allocated = loadable_categories_allocated*2 + 16;
loadable_categories =
_realloc_internal(loadable_categories,
loadable_categories_allocated *
sizeof(struct loadable_category));
}
loadable_categories[loadable_categories_used].cat = cat;
loadable_categories[loadable_categories_used].method = method;
loadable_categories_used++;
}
/***********************************************************************
* remove_class_from_loadable_list
* Class cls may have been loadable before, but it is now no longer
* loadable (because its image is being unmapped).
**********************************************************************/
static void remove_class_from_loadable_list(struct objc_class *cls)
{
if (loadable_classes) {
int i;
for (i = 0; i < loadable_classes_used; i++) {
if (loadable_classes[i].cls == cls) {
loadable_classes[i].cls = NULL;
if (PrintLoading) {
_objc_inform("LOAD: class '%s' unscheduled for +load", cls->name);
}
return;
}
}
}
}
/***********************************************************************
* remove_category_from_loadable_list
* Category cat may have been loadable before, but it is now no longer
* loadable (because its image is being unmapped).
**********************************************************************/
static void remove_category_from_loadable_list(struct objc_category *cat)
{
if (loadable_categories) {
int i;
for (i = 0; i < loadable_categories_used; i++) {
if (loadable_categories[i].cat == cat) {
loadable_categories[i].cat = NULL;
if (PrintLoading) {
_objc_inform("LOAD: category '%s(%s)' unscheduled for +load",
cat->class_name, cat->category_name);
}
return;
}
}
}
}
/***********************************************************************
* call_class_loads
* Call all pending class +load methods.
* If new classes become loadable, +load is NOT called for them.
*
* Called only by call_load_methods().
**********************************************************************/
static void call_class_loads(void)
{
int i;
// Detach current loadable list.
struct loadable_class *classes = loadable_classes;
int used = loadable_classes_used;
loadable_classes = NULL;
loadable_classes_allocated = 0;
loadable_classes_used = 0;
// Call all +loads for the detached list.
for (i = 0; i < used; i++) {
struct objc_class *cls = classes[i].cls;
IMP load_method = classes[i].method;
if (!cls) continue;
if (PrintLoading) {
_objc_inform("LOAD: +[ }
(*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: +[ 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: 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 oldName, newName, oldCls->name);
_objc_inform ("Using implementation from }
// 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;
// Minor loop - process all the classes in given module
for (index = 0; index < mods[midx].symtab->cls_def_cnt; index += 1)
{
struct objc_class * newCls;
struct objc_method_list *mlist;
// Locate the class description pointer
newCls = mods[midx].symtab->defs[index];
// 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;
// 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;
// Set metaclass version
newCls->isa->version = mods[midx].version;
// methodLists is NULL or a single list, not an array
newCls->info |= CLS_NO_METHOD_ARRAY;
newCls->isa->info |= CLS_NO_METHOD_ARRAY;
// 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);
// 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_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_connect_classes_from_image(header_info *hi)
{
unsigned int index;
unsigned int midx;
Module mods;
BOOL replacement = _objcHeaderIsReplacement(hi);
// Major loop - process all modules in the image
mods = hi->mod_ptr;
for (midx = 0; midx < hi->mod_count; midx += 1)
{
// Skip module containing no classes
if (mods[midx].symtab == NULL)
continue;
// Minor loop - process all the classes in given module
for (index = 0; index < mods[midx].symtab->cls_def_cnt; index += 1)
{
struct objc_class *cls = 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
* not yet exist, the reference is added to a list of pending references
* to be fixed up at a later date.
**********************************************************************/
static void _objc_map_class_refs_for_image (header_info * hi)
{
struct objc_class * * cls_refs;
unsigned int size;
unsigned int index;
// Locate class refs in image
cls_refs = _getObjcClassRefs ((headerType *) hi->mhdr, &size);
if (!cls_refs)
return;
cls_refs = (struct objc_class * *) ((unsigned long) cls_refs + hi->image_slide);
// Process each class ref
for (index = 0; index < size; index += 1)
{
const char * ref;
struct objc_class * cls;
// Get ref to convert from name string to class pointer
ref = (const char *) cls_refs[index];
// Get pointer to class of this name
// 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 ();
}
}
}
/***********************************************************************
* _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 BOOL map_selrefs(SEL *src, SEL *dst, size_t size, BOOL copy)
{
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;
// Lookup pointer to uniqued string
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 (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, BOOL copy)
{
unsigned int index;
sel_lock();
// Process each method
for (index = 0; index < methods->count; index += 1)
{
struct objc_method_description * method;
SEL sel;
// Get method entry to fix up
method = &methods->list[index];
// Lookup pointer to uniqued string
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();
}
/***********************************************************************
* _fixup.
**********************************************************************/
@interface Protocol(RuntimePrivate)
+ _fixup: (OBJC_PROTOCOL_PTR)protos numElements: (int) nentries;
@end
/***********************************************************************
* _objc_fixup_protocol_objects_for_image. For each protocol in the
* specified image, selectorize the method names and call +_fixup.
**********************************************************************/
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);
if (!protos)
return;
// Apply the slide bias
protos = (OBJC_PROTOCOL_PTR) ((unsigned long) protos + hi->image_slide);
// Process each protocol
for (index = 0; index < size; index += 1)
{
// Selectorize the instance methods
if (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, isBundle);
}
// Invoke Protocol class method to fix up the protocol
[Protocol _fixup:(OBJC_PROTOCOL_PTR)protos numElements:size];
}
/***********************************************************************
* _objc_headerStart. Return what headers we know about.
**********************************************************************/
header_info * _objc_headerStart ()
{
// Take advatage of our previous work
return FirstHeader;
}
void _objc_bindModuleContainingList() {
/* We define this for backwards binary compat with things which should not
* have been using it (cough OmniWeb), but now it does nothing for them.
*/
}
/***********************************************************************
* _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)
{
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;
// 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);
// 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));
}
// 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;
}
// 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);
}
}
// 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_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
}
// Free the memory, unless it was in the static HeaderInfoTable.
if (deadHead < HeaderInfoTable ||
deadHead >= HeaderInfoTable + HINFO_SIZE)
{
_free_internal(deadHead);
}
break;
}
}
}
/***********************************************************************
* 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.
**********************************************************************/
static BOOL check_wants_gc(void)
{
// 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' _nameForHeader(hi->mhdr),
appWantsGC ? "supports" : "does not support");
}
}
}
}
return appWantsGC;
*/
}
/***********************************************************************
* verify_gc_readiness
* if we want gc, verify that every header describes files compiled
* and presumably ready for gc.
************************************************************************/
static void verify_gc_readiness(BOOL wantsGC, header_info *hi)
{
BOOL busted = NO;
// Find the libraries and check their GC bits against the app's request
for (; hi != NULL; hi = hi->next) {
if (hi->mhdr->filetype == MH_EXECUTE) {
continue;
}
else if (hi->mhdr == &_mh_dylib_header) {
// libobjc itself works with anything even though it is not
// compiled with -fobjc-gc (fixme should it be?)
}
else if (wantsGC && ! _objcHeaderSupportsGC(hi)) {
// App wants GC but library does not support it - bad
_objc_inform("'%s' was not compiled with -fobjc-gc, but "
"the application requires GC",
_nameForHeader(hi->mhdr));
busted = YES;
}
if (PrintGC) {
_objc_inform("GC: library '%s' _objcHeaderSupportsGC(hi) ? "supports" : "does not support");
}
}
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 count;
Module mods;
vm_address_t local_sels;
vm_size_t local_size;
mods = hi->mod_ptr;
// Fix up message refs
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
map_selrefs((SEL *)local_sels, (SEL *)local_sels, local_size,
hi->mhdr->filetype == MH_BUNDLE);
if (PrintSharing) {
_objc_inform("SHARING: NONE [ 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 [ aligned_start, aligned_end);
}
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: 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
map_selrefs((SEL *)local_sels, (SEL *)local_sels, local_size, NO);
offer_shared_range(aligned_start, aligned_end);
if (PrintSharing) {
_objc_inform("SHARING: OFFER [ local_sels, local_sels+local_size,
(aligned_end-aligned_start) / vm_page_size,
_nameForHeader(hi->mhdr));
}
}
}
}
}
/***********************************************************************
* objc_setConfiguration
* Read environment variables that affect the runtime.
* Also print environment variable help, if requested.
**********************************************************************/
static void objc_setConfiguration() {
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.
**********************************************************************/
void objc_setMultithreaded (BOOL flag)
{
// Nothing here. Thread synchronization in the runtime is always active.
}
/***********************************************************************
* _objc_pthread_destroyspecific
* Destructor for objc's per-thread data.
* arg shouldn't be NULL, but we check anyway.
**********************************************************************/
extern void _destroyInitializingClassList(struct _objc_initializing_classes *list);
void _objc_pthread_destroyspecific(void *arg)
{
_objc_pthread_data *data = (_objc_pthread_data *)arg;
if (data != NULL) {
_destroyInitializingClassList(data->initializingClasses);
// add further cleanup here...
_free_internal(data);
}
}
/***********************************************************************
* _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
}
/***********************************************************************
* 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;
// 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);
}
if (PrintImages) {
_objc_inform("IMAGES: processing }
// 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;
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;
}
if (!firstNewHeader) firstNewHeader = hInfo;
if (PrintImages) {
_objc_inform("IMAGES: loading image for _nameForHeader(mhdr),
mhdr->filetype == MH_BUNDLE ? " (bundle)" : "",
_objcHeaderIsReplacement(hInfo) ? " (replacement)":"",
_objcHeaderSupportsGC(hInfo) ? " (supports GC)":"");
}
}
// 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);
}
// Initialize everything. Parts of this order are important for
// correctness or performance.
// Read classes from all images.
for (hInfo = firstNewHeader; hInfo != NULL; hInfo = hInfo->next) {
_objc_read_classes_from_image(hInfo);
}
// Read categories from all images.
for (hInfo = firstNewHeader; hInfo != NULL; hInfo = hInfo->next) {
_objc_read_categories_from_image(hInfo);
}
// Connect classes from all images.
for (hInfo = firstNewHeader; hInfo != NULL; hInfo = hInfo->next) {
_objc_connect_classes_from_image(hInfo);
}
// 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);
}
// Close any shared range file left open during selector uniquing
clear_shared_range_file_cache();
firstTime = NO;
// Call pending +load methods.
// Note that this may in turn cause map_images() to be called again.
call_load_methods();
}
/***********************************************************************
* 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 }
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);
}
}
}
}
/***********************************************************************
* _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);
}
}
/***********************************************************************
* _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)
{
// 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);
}
}
}
// 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 ();
}
}
}
OBJC_UNLOCK(&classLock);
}
/***********************************************************************
* _objc_remove_categories_in_image
* Remove all categories in the given image from the runtime, because
* the image is about to be unloaded.
* Things to clean up:
* unresolved category list
* loadable category list
**********************************************************************/
static void _objc_remove_categories_in_image(header_info *hi)
{
Module mods;
unsigned int midx;
// Major loop - process all modules in the header
mods = hi->mod_ptr;
for (midx = 0; midx < hi->mod_count; midx++) {
unsigned int index;
unsigned int total;
Symtab symtab = mods[midx].symtab;
// Nothing to do for a module without a symbol table
if (symtab == NULL) continue;
// Total entries in symbol table (class entries followed
// by category entries)
total = symtab->cls_def_cnt + symtab->cat_def_cnt;
// Minor loop - check all categories from given module
for (index = symtab->cls_def_cnt; index < total; index++) {
struct objc_category *cat = symtab->defs[index];
// Clean up loadable category list
remove_category_from_loadable_list(cat);
// Clean up category_hash
if (category_hash) {
_objc_unresolved_category *cat_entry =
NXMapGet(category_hash, cat->class_name);
for ( ; cat_entry != NULL; cat_entry = cat_entry->next) {
if (cat_entry->cat == cat) {
cat_entry->cat = NULL;
break;
}
}
}
}
}
}
/***********************************************************************
* unload_paranoia
* Various paranoid debugging checks that look for poorly-behaving
* unloadable bundles.
* Called by _objc_unmap_image when OBJC_UNLOAD_DEBUG is set.
**********************************************************************/
static void unload_paranoia(header_info *hi)
{
// Get the location of the dying image's __OBJC segment
uintptr_t seg = hi->objcSegmentHeader->vmaddr + hi->image_slide;
size_t seg_size = hi->objcSegmentHeader->filesize;
_objc_inform("UNLOAD DEBUG: unloading image '%s' [ _nameForHeader(hi->mhdr), seg, seg+seg_size);
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);
}
}
}
}
// 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);
}
/***********************************************************************
* _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;
// 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 _nameForHeader(mh),
mh->filetype == MH_BUNDLE ? " (bundle)" : "",
_objcHeaderIsReplacement(hi) ? " (replacement)" : "",
_objcHeaderSupportsGC(hi) ? " (supports GC)" : "");
}
// Cleanup:
// Remove image's classes from the class list and free auxiliary data.
// Remove image's unresolved or loadable categories and free auxiliary data
// Remove image's unresolved class refs.
_objc_remove_classes_in_image(hi);
_objc_remove_categories_in_image(hi);
_objc_remove_pending_class_refs_in_image(hi);
// Perform various debugging checks if requested.
if (DebugUnload) unload_paranoia(hi);
// Remove header_info from header list
_objc_removeHeader(hi);
}
/***********************************************************************
* _objc_setNilReceiver
**********************************************************************/
id _objc_setNilReceiver(id newNilReceiver)
{
id oldNilReceiver;
oldNilReceiver = _objc_nilReceiver;
_objc_nilReceiver = newNilReceiver;
return oldNilReceiver;
}
/***********************************************************************
* _objc_getNilReceiver
**********************************************************************/
id _objc_getNilReceiver(void)
{
return _objc_nilReceiver;
}
/***********************************************************************
* _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;
}
#if defined(__ppc__)
/**********************************************************************
* objc_write_branch
* Writes at entry a PPC branch instruction sequence that branches to target.
* The sequence written will be 1 or 4 instructions long.
* Returns the number of instructions written.
**********************************************************************/
__private_extern__ size_t objc_write_branch(void *entry, void *target)
{
unsigned *address = (unsigned *)entry; // location to store the 32 bit PPC instructions
intptr_t destination = (intptr_t)target; // destination as an absolute address
intptr_t displacement = (intptr_t)destination - (intptr_t)address; // destination as a branch relative offset
// Test to see if either the displacement or destination is within the +/- 2^25 range needed
// for a simple PPC branch instruction. Shifting the high bit of the displacement (or destination)
// left 6 bits and then 6 bits arithmetically to the right does a sign extend of the 26th bit. If
// that result is equivalent to the original value, then the displacement (or destination) will fit
// into a simple branch. Otherwise a four instruction branch sequence is required.
if (((displacement << 6) >> 6) == displacement) {
// use a relative branch with the displacement
address[0] = 0x48000000 | (displacement & 0x03fffffc); // b *+displacement
// issued 1 instruction
return 1;
} else if (((destination << 6) >> 6) == destination) {
// use an absolute branch with the destination
address[0] = 0x48000000 | (destination & 0x03fffffc) | 2; // ba destination (2 is the absolute flag)
// issued 1 instruction
return 1;
} else {
// The four instruction branch sequence requires that the destination be loaded
// into a register, moved to the CTR register then branch using the contents
// of the CTR register.
unsigned lo = destination & 0xffff;
unsigned hi = (destination >> 16) & 0xffff;
address[0] = 0x3d800000 | hi; // lis r12,hi ; load the hi half of destination
address[1] = 0x618c0000 | lo; // ori r12,r12,lo ; merge in the lo half of destination
address[2] = 0x7d8903a6; // mtctr ; move destination to the CTR register
address[3] = 0x4e800420; // bctr ; branch to destination
// issued 4 instructions
return 4;
}
}
// defined(__ppc__)
#endif
/**********************************************************************
* secure_open
* Securely open a file from a world-writable directory (like /tmp)
* If the file does not exist, it will be atomically created with mode 0600
* If the file exists, it must be, and remain after opening:
* 1. a regular file (in particular, not a symlink)
* 2. owned by euid
* 3. permissions 0600
* 4. link count == 1
* Returns a file descriptor or -1. Errno may or may not be set on error.
**********************************************************************/
__private_extern__ int secure_open(const char *filename, int flags, uid_t euid)
{
struct stat fs, ls;
int fd = -1;
BOOL truncate = NO;
BOOL create = NO;
if (flags & O_TRUNC) {
// Don't truncate the file until after it is open and verified.
truncate = YES;
flags &= ~O_TRUNC;
}
if (flags & O_CREAT) {
// Don't create except when we're ready for it
create = YES;
flags &= ~O_CREAT;
flags &= ~O_EXCL;
}
if (lstat(filename, &ls) < 0) {
if (errno == ENOENT && create) {
// No such file - create it
fd = open(filename, flags | O_CREAT | O_EXCL, 0600);
if (fd >= 0) {
// File was created successfully.
// New file does not need to be truncated.
return fd;
} else {
// File creation failed.
return -1;
}
} else {
// lstat failed, or user doesn't want to create the file
return -1;
}
} else {
// lstat succeeded - verify attributes and open
if (S_ISREG(ls.st_mode) && // regular file?
ls.st_nlink == 1 && // link count == 1?
ls.st_uid == euid && // owned by euid?
(ls.st_mode & ALLPERMS) == (S_IRUSR | S_IWUSR)) // mode 0600?
{
// Attributes look ok - open it and check attributes again
fd = open(filename, flags, 0000);
if (fd >= 0) {
// File is open - double-check attributes
if (0 == fstat(fd, &fs) &&
fs.st_nlink == ls.st_nlink && // link count == 1?
fs.st_uid == ls.st_uid && // owned by euid?
fs.st_mode == ls.st_mode && // regular file, 0600?
fs.st_ino == ls.st_ino && // same inode as before?
fs.st_dev == ls.st_dev) // same device as before?
{
// File is open and OK
if (truncate) ftruncate(fd, 0);
return fd;
} else {
// Opened file looks funny - close it
close(fd);
return -1;
}
} else {
// File didn't open
return -1;
}
} else {
// Unopened file looks funny - don't open it
return -1;
}
}
}
/**********************************************************************
* Shared range support:
*
* Some libraries contain many pages worth of selector references.
* In most processes, these libraries get loaded at the same addresses,
* so the selectors are uniqued to the same values. To save memory,
* the runtime tries to share these memory pages across processes.
*
* A file /tmp/objc_sharing_<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;
};
// Cache for the last shared range file used, and its EUID.
static pthread_mutex_t sharedRangeLock = PTHREAD_MUTEX_INITIALIZER;
static uid_t sharedRangeEUID = 0;
static FILE * sharedRangeFile = NULL;
static BOOL sharedRangeFileInUse = NO;
/**********************************************************************
* open_shared_range_file
* Open the shared range file "/tmp/objc_sharing_<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;
}
// Return cached file if it matches and it's not still being used
pthread_mutex_lock(&sharedRangeLock);
if (!sharedRangeFileInUse && euid == sharedRangeEUID) {
file = sharedRangeFile;
sharedRangeFileInUse = YES;
pthread_mutex_unlock(&sharedRangeLock);
rewind(file);
return file;
}
pthread_mutex_unlock(&sharedRangeLock);
// Open /tmp/objc_sharing_<euid>
snprintf(filename,sizeof(filename), "/tmp/objc_sharing_ fd = secure_open(filename, O_RDWR | (create ? O_CREAT : 0), euid);
if (fd >= 0) {
file = fdopen(fd, "r+");
}
if (file) {
// Cache this file if there's no already-open file cached
pthread_mutex_lock(&sharedRangeLock);
if (!sharedRangeFileInUse) {
sharedRangeFile = file;
sharedRangeEUID = euid;
sharedRangeFileInUse = YES;
}
pthread_mutex_unlock(&sharedRangeLock);
}
else {
// open() or fdopen() failed
if (PrintSharing) {
_objc_inform("SHARING: bad or missing sharing file '%s': filename, errno ? strerror(errno) :
"potential security violation");
}
}
return file;
}
/**********************************************************************
* close_shared_range_file
* Close a file opened with open_shared_range_file.
* The file may actually be kept open and cached for a future
* open_shared_range_file call. If so, clear_shared_range_file_cache()
* can be used to really close the file.
**********************************************************************/
static void close_shared_range_file(FILE *file)
{
// Flush any writes in case the file is kept open.
fflush(file);
pthread_mutex_lock(&sharedRangeLock);
if (file == sharedRangeFile && sharedRangeFileInUse) {
// This file is the cached shared file.
// Leave the file open and cached, but no longer in use.
sharedRangeFileInUse = NO;
} else {
// This is not the cached file.
fclose(file);
}
pthread_mutex_unlock(&sharedRangeLock);
}
/**********************************************************************
* clear_shared_range_file_cache
* Really close any file left open by close_shared_range_file.
* This is called by map_images() after loading multiple images, each
* of which may have used the shared range file.
**********************************************************************/
static void clear_shared_range_file_cache(void)
{
pthread_mutex_lock(&sharedRangeLock);
if (sharedRangeFile && !sharedRangeFileInUse) {
fclose(sharedRangeFile);
sharedRangeFile = NULL;
sharedRangeEUID = 0;
sharedRangeFileInUse = 0;
}
pthread_mutex_unlock(&sharedRangeLock);
}
/**********************************************************************
* get_shared_range
* Try to find a shared range matching addresses [aligned_start..aligned_end).
* If a range is found, it is mapped into this process and returned.
* If no range is found, or the found range could not be mapped for
* some reason, the range {0, 0} is returned.
* aligned_start and aligned_end must be page-aligned.
**********************************************************************/
static vm_range_t get_shared_range(vm_address_t aligned_start,
vm_address_t aligned_end)
{
struct remote_range_t remote;
vm_range_t result;
FILE *file;
result.address = 0;
result.size = 0;
// Open shared range file, but don't create it
file = open_shared_range_file(NO);
if (!file) return result;
// Search for the desired memory range
while (1 == fread(&remote, sizeof(remote), 1, file)) {
if (remote.pid != 0 &&
remote.range.address == aligned_start &&
remote.range.size == aligned_end - aligned_start)
{
// Found a match in the file - try to grab the memory
mach_port_name_t remote_task;
vm_prot_t cur_prot, max_prot;
vm_address_t local_addr;
kern_return_t kr;
// Find the task offering the memory
kr = task_for_pid(mach_task_self(), remote.pid, &remote_task);
if (kr != KERN_SUCCESS) {
// task is dead
if (PrintSharing) {
_objc_inform("SHARING: no task for pid 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 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 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: }
memmove((void *)dst, (void *)src.address, src.size);
}
// Unmap the shared range at src
vm_deallocate(mach_task_self(), src.address, src.size);
}