/*
* Copyright (c) 1999-2007 Apple Inc. All Rights Reserved.
*
* @APPLE_LICENSE_HEADER_START@
*
* 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
*
**********************************************************************/
/***********************************************************************
* Imports.
**********************************************************************/
#include <mach-o/ldsyms.h>
#include <mach-o/dyld.h>
#include <mach-o/dyld_gdb.h>
#include <mach-o/dyld_priv.h>
#include <mach/mach.h>
#include <mach/mach_error.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <crt_externs.h>
#include <libgen.h>
#include <crt_externs.h>
#include <libgen.h>
#import "objc-private.h"
#import "hashtable2.h"
#import "maptable.h"
#import "Object.h"
#import "objc-rtp.h"
#import "objc-auto.h"
#import "objc-loadmethod.h"
OBJC_EXPORT Class getOriginalClassForPosingClass(Class);
/***********************************************************************
* Exports.
**********************************************************************/
// 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 PrintInitializing = -1; // env OBJC_PRINT_INITIALIZE_METHODS
__private_extern__ int PrintResolving = -1; // env OBJC_PRINT_RESOLVED_METHODS
__private_extern__ int PrintConnecting = -1; // env OBJC_PRINT_CLASS_SETUP
__private_extern__ int PrintProtocols = -1; // env OBJC_PRINT_PROTOCOL_SETUP
__private_extern__ int PrintIvars = -1; // env OBJC_PRINT_IVAR_SETUP
__private_extern__ int PrintFuture = -1; // env OBJC_PRINT_FUTURE_CLASSES
__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 PrintExceptions = -1; // env OBJC_PRINT_EXCEPTIONS
__private_extern__ int PrintAltHandlers = -1; // env OBJC_PRINT_ALT_HANDLERS
__private_extern__ int PrintDeprecation = -1;// env OBJC_PRINT_DEPRECATION_WARNINGS
__private_extern__ int PrintReplacedMethods = -1; // env OBJC_PRINT_REPLACED_METHODS
__private_extern__ int PrintCacheCollection = -1; // env OBJC_PRINT_CACHE_COLLECTION
__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 DebugNilSync = -1; // env OBJC_DEBUG_NIL_SYNC
__private_extern__ int DisableGC = -1; // env OBJC_DISABLE_GC
__private_extern__ int DebugFinalizers = -1; // env OBJC_DEBUG_FINALIZERS
// objc's key for pthread_getspecific
static pthread_key_t _objc_pthread_key = 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 void _objc_unmap_image(header_info *hi);
/***********************************************************************
* 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
static int HeaderCount NOBSS = 0;
/***********************************************************************
* 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)
{
if (!aClassName) return Nil;
// 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)
{
if (!aClassName) return Nil;
// NO unconnected, NO class handler
return look_up_class(aClassName, NO, NO);
}
/***********************************************************************
* objc_getFutureClass. Return the id of the named class.
* If the class does not exist, return an uninitialized class
* structure that will be used for the class when and if it
* does get loaded.
* Not thread safe.
**********************************************************************/
Class objc_getFutureClass(const char *name)
{
Class cls;
// YES unconnected, NO class handler
// (unconnected is OK because it will someday be the real class)
cls = look_up_class(name, YES, NO);
if (cls) {
if (PrintFuture) {
_objc_inform("FUTURE: found }
return cls;
}
// No class or future class with that name yet. Make one.
// fixme not thread-safe with respect to
// simultaneous library load or getFutureClass.
return _objc_allocateFutureClass(name);
}
/***********************************************************************
* 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)
{
Class cls;
if (!aClassName) return Nil;
cls = objc_getClass (aClassName);
if (!cls)
{
_objc_inform ("class ` return Nil;
}
return ((id)cls)->isa;
}
#if !__LP64__
// Not updated for 64-bit ABI
/***********************************************************************
* _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.
**********************************************************************/
__private_extern__ const header_info *_headerForClass(Class cls)
{
return _headerForAddress(cls);
}
// !__LP64__
#endif
/***********************************************************************
* _nameForHeader.
**********************************************************************/
__private_extern__ const char *_nameForHeader(const headerType *header)
{
return _getObjcHeaderName ((headerType *) header);
}
/***********************************************************************
* _gcForHInfo.
**********************************************************************/
__private_extern__ const char *_gcForHInfo(const header_info *hinfo)
{
if (_objcHeaderRequiresGC(hinfo)) return "requires GC";
else if (_objcHeaderSupportsGC(hinfo)) return "supports GC";
else return "does not support GC";
}
__private_extern__ const char *_gcForHInfo2(const header_info *hinfo)
{
if (_objcHeaderRequiresGC(hinfo)) return " (requires GC)";
else if (_objcHeaderSupportsGC(hinfo)) return " (supports GC)";
else return "";
}
/***********************************************************************
* bad_magic.
* Return YES if the header has invalid Mach-o magic.
**********************************************************************/
static BOOL bad_magic(const headerType *mhdr)
{
return (mhdr->magic != MH_MAGIC && mhdr->magic != MH_MAGIC_64 &&
mhdr->magic != MH_CIGAM && mhdr->magic != MH_CIGAM_64);
}
/***********************************************************************
* _objc_headerStart. Return what headers we know about.
**********************************************************************/
__private_extern__ header_info *_objc_headerStart(void)
{
// Take advatage of our previous work
return FirstHeader;
}
/***********************************************************************
* _objc_addHeader.
* Returns NULL if the header has no ObjC metadata.
**********************************************************************/
// 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 headerType *header)
{
size_t info_size = 0;
const segmentType *objc_segment;
const segmentType *objc2_segment;
const objc_image_info *image_info;
const segmentType *data_segment;
header_info *result;
ptrdiff_t image_slide;
// Weed out duplicates
for (result = FirstHeader; result; result = result->next) {
if (header == result->mhdr) return NULL;
}
// Locate the __OBJC segment
image_slide = _getImageSlide(header);
image_info = _getObjcImageInfo(header, image_slide, &info_size);
objc_segment = getsegbynamefromheader(header, SEG_OBJC);
objc2_segment = getsegbynamefromheader(header, SEG_OBJC2);
data_segment = getsegbynamefromheader(header, SEG_DATA);
if (!objc_segment && !image_info && !objc2_segment) return NULL;
// 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->image_slide = image_slide;
result->objcSegmentHeader = objc_segment;
result->dataSegmentHeader = data_segment;
#if !__OBJC2__
result->mod_count = 0;
result->mod_ptr = _getObjcModules(header, result->image_slide, &result->mod_count);
#endif
result->info = image_info;
dladdr(result->mhdr, &result->dl_info);
result->allClassesRealized = NO;
// dylibs are not allowed to unload
if (result->mhdr->filetype == MH_DYLIB) {
dlopen(result->dl_info.dli_fname, RTLD_NOLOAD);
}
// 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) {
const objc_image_info *start = result->info;
const objc_image_info *end =
(objc_image_info *)(info_size + (uint8_t *)start);
const 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_inform("'%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.
HeaderCount++;
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);
}
HeaderCount--;
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)
{
const header_info *hi;
BOOL appWantsGC;
// Environment variables can override the following.
if (DisableGC) {
_objc_inform("GC: forcing GC OFF because OBJC_DISABLE_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), _gcForHInfo(hi));
}
}
}
}
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 **hList,
uint32_t hCount)
{
BOOL busted = NO;
uint32_t i;
// Find the libraries and check their GC bits against the app's request
for (i = 0; i < hCount; i++) {
header_info *hi = hList[i];
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_now_and_on_crash
("'%s' was not compiled with -fobjc-gc or -fobjc-gc-only, "
"but the application requires GC",
_nameForHeader(hi->mhdr));
busted = YES;
}
else if (!wantsGC && _objcHeaderRequiresGC(hi)) {
// App doesn't want GC but library requires it - bad
_objc_inform_now_and_on_crash
("'%s' was compiled with -fobjc-gc-only, "
"but the application does not support GC",
_nameForHeader(hi->mhdr));
busted = YES;
}
if (PrintGC) {
_objc_inform("GC: library '%s' _nameForHeader(hi->mhdr), _gcForHInfo(hi));
}
}
if (busted) {
// GC state is not consistent.
// Kill the process unless one of the forcing flags is set.
if (!DisableGC) {
_objc_fatal("*** GC capability of application and some libraries did not match");
}
}
}
/***********************************************************************
* 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);
int secure = issetugid();
if (secure) {
// All environment variables are ignored when setuid or setgid.
if (PrintHelp) _objc_inform("OBJC_HELP ignored when running setuid or setgid");
if (PrintOptions) _objc_inform("OBJC_PRINT_OPTIONS ignored when running setuid or setgid");
}
else {
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 ) { \
char *value = getenv(#env); \
var = value != NULL && !strcmp("YES", value); \
if (secure) { \
if (var) _objc_inform(#env " ignored when running setuid or setgid"); \
var = 0; \
} else { \
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 they are loaded");
OPTION(PrintLoading, OBJC_PRINT_LOAD_METHODS,
"log calls to class and category +load methods");
OPTION(PrintInitializing, OBJC_PRINT_INITIALIZE_METHODS,
"log calls to class +initialize methods");
OPTION(PrintResolving, OBJC_PRINT_RESOLVED_METHODS,
"log methods created by +resolveClassMethod: and +resolveInstanceMethod:");
OPTION(PrintConnecting, OBJC_PRINT_CLASS_SETUP,
"log progress of class and category setup");
OPTION(PrintProtocols, OBJC_PRINT_PROTOCOL_SETUP,
"log progresso of protocol setup");
OPTION(PrintIvars, OBJC_PRINT_IVAR_SETUP,
"log processing of non-fragile ivars");
OPTION(PrintFuture, OBJC_PRINT_FUTURE_CLASSES,
"log use of future classes for toll-free bridging");
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(PrintExceptions, OBJC_PRINT_EXCEPTIONS,
"log exception handling");
OPTION(PrintAltHandlers, OBJC_PRINT_ALT_HANDLERS,
"log processing of exception alt handlers");
OPTION(PrintReplacedMethods, OBJC_PRINT_REPLACED_METHODS,
"log methods replaced by category implementations");
OPTION(PrintDeprecation, OBJC_PRINT_DEPRECATION_WARNINGS,
"warn about calls to deprecated runtime functions");
OPTION(PrintCacheCollection, OBJC_PRINT_CACHE_COLLECTION,
"log cleanup of stale method caches");
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(DebugFinalizers, OBJC_DEBUG_FINALIZERS,
"warn about classes that implement -dealloc but not -finalize");
OPTION(DebugNilSync, OBJC_DEBUG_NIL_SYNC,
"warn about @synchronized(nil), which does no synchronization");
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(DisableGC, OBJC_DISABLE_GC,
"force GC OFF, even if the executable wants it on");
#undef OPTION
}
/***********************************************************************
* objc_setMultithreaded.
**********************************************************************/
void objc_setMultithreaded (BOOL flag)
{
OBJC_WARN_DEPRECATED;
// Nothing here. Thread synchronization in the runtime is always active.
}
/***********************************************************************
* _objc_fetch_pthread_data
* Fetch objc's pthread data for this thread.
* If the data doesn't exist yet and create is NO, return NULL.
* If the data doesn't exist yet and create is YES, allocate and return it.
**********************************************************************/
__private_extern__ _objc_pthread_data *_objc_fetch_pthread_data(BOOL create)
{
_objc_pthread_data *data;
data = pthread_getspecific(_objc_pthread_key);
if (!data && create) {
data = _calloc_internal(1, sizeof(_objc_pthread_data));
pthread_setspecific(_objc_pthread_key, data);
}
return data;
}
/***********************************************************************
* _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);
__private_extern__ void _objc_pthread_destroyspecific(void *arg)
{
_objc_pthread_data *data = (_objc_pthread_data *)arg;
if (data != NULL) {
_destroyInitializingClassList(data->initializingClasses);
_destroyLockList(data->lockList);
_destroySyncCache(data->syncCache);
_destroyAltHandlerList(data->handlerList);
// 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() and _objc_init.
**********************************************************************/
void _objcInit(void)
{
// do nothing
}
/***********************************************************************
* gc_enforcer
* Make sure that images about to be loaded by dyld are GC-acceptable.
* Images linked to the executable are always permitted; they are
* enforced inside map_images() itself.
**********************************************************************/
static BOOL InitialDyldRegistration = NO;
static const char *gc_enforcer(enum dyld_image_states state,
uint32_t infoCount,
const struct dyld_image_info info[])
{
uint32_t i;
// Linked images get a free pass
if (InitialDyldRegistration) return NULL;
if (PrintImages) {
_objc_inform("IMAGES: checking infoCount);
}
for (i = 0; i < infoCount; i++) {
const headerType *mhdr = (const headerType *)info[i].imageLoadAddress;
if (bad_magic(mhdr)) continue;
objc_image_info *image_info;
size_t size;
if (mhdr == &_mh_dylib_header) {
// libobjc itself - OK
continue;
}
#if !__LP64__
// 32-bit: __OBJC seg but no image_info means no GC support
if (!getsegbynamefromheader(mhdr, SEG_OBJC)) {
// not objc - assume OK
continue;
}
image_info = _getObjcImageInfo(mhdr, _getImageSlide(mhdr), &size);
if (!image_info) {
// No image_info - assume GC unsupported
if (!UseGC) {
// GC is OFF - ok
continue;
} else {
// GC is ON - bad
if (PrintImages || PrintGC) {
_objc_inform("IMAGES: rejecting }
return "GC capability mismatch";
}
}
#else
// 64-bit: no image_info means no objc at all
image_info = _getObjcImageInfo(mhdr, _getImageSlide(mhdr), &size);
if (!image_info) {
// not objc - assume OK
continue;
}
#endif
if (UseGC && !_objcInfoSupportsGC(image_info)) {
// GC is ON, but image does not support GC
if (PrintImages || PrintGC) {
_objc_inform("IMAGES: rejecting }
return "GC capability mismatch";
}
if (!UseGC && _objcInfoRequiresGC(image_info)) {
// GC is OFF, but image requires GC
if (PrintImages || PrintGC) {
_objc_inform("IMAGES: rejecting }
return "GC capability mismatch";
}
}
return NULL;
}
/***********************************************************************
* 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 const char *map_images(enum dyld_image_states state, uint32_t infoCount,
const struct dyld_image_info infoList[])
{
static BOOL firstTime = YES;
static BOOL wantsGC NOBSS = NO;
uint32_t i;
header_info *hInfo;
header_info *hList[infoCount];
uint32_t hCount;
// Perform first-time initialization if necessary.
// This function is called before ordinary library initializers.
if (firstTime) {
extern SEL FwdSel; // in objc-msg-*.s
// workaround for rdar://5198739
pthread_key_t unused;
pthread_key_create(&unused, NULL);
pthread_key_create(&_objc_pthread_key, _objc_pthread_destroyspecific);
objc_setConfiguration(); // read environment variables
// grab selectors for which @selector() doesn't work
cxx_construct_sel = sel_registerName(cxx_construct_name);
cxx_destruct_sel = sel_registerName(cxx_destruct_name);
FwdSel = sel_registerName("forward::"); // in objc-msg-*.s
exception_init();
InitialDyldRegistration = YES;
dyld_register_image_state_change_handler(dyld_image_state_mapped, 0 /* batch */, &gc_enforcer);
InitialDyldRegistration = NO;
}
if (PrintImages) {
_objc_inform("IMAGES: processing }
// Find all images with Objective-C metadata.
hCount = 0;
i = infoCount;
while (i--) {
const headerType *mhdr = (headerType *)infoList[i].imageLoadAddress;
if (bad_magic(mhdr)) continue;
hInfo = _objc_addHeader(mhdr);
if (!hInfo) {
// no objc data in this entry
continue;
}
hList[hCount++] = hInfo;
if (PrintImages) {
_objc_inform("IMAGES: loading image for _nameForHeader(mhdr),
mhdr->filetype == MH_BUNDLE ? " (bundle)" : "",
_objcHeaderIsReplacement(hInfo) ? " (replacement)":"",
_gcForHInfo2(hInfo));
}
}
// 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, hList, hCount);
gc_init(wantsGC); // needs executable for GC decision
rtp_init(); // needs GC decision first
} else {
verify_gc_readiness(wantsGC, hList, hCount);
}
_read_images(hList, hCount);
firstTime = NO;
return NULL;
}
static const char *load_images(enum dyld_image_states state,uint32_t infoCount,
const struct dyld_image_info infoList[])
{
BOOL found = NO;
uint32_t i;
i = infoCount;
while (i--) {
header_info *hi;
for (hi = FirstHeader; hi != NULL; hi = hi->next) {
const headerType *mhdr = (headerType*)infoList[i].imageLoadAddress;
if (hi->mhdr == mhdr) {
prepare_load_methods(hi);
found = YES;
}
}
}
if (found) call_load_methods();
return NULL;
}
/***********************************************************************
* unmap_image
* Process the given image which is about to be unmapped by dyld.
* mh is mach_header instead of headerType because that's what
* dyld_priv.h says even for 64-bit.
**********************************************************************/
static void unmap_image(const struct mach_header *mh, intptr_t vmaddr_slide)
{
if (PrintImages) {
_objc_inform("IMAGES: processing 1 newly-unmapped image...\n");
}
header_info *hi;
// Find the runtime's header_info struct for the image
for (hi = FirstHeader; hi != NULL; hi = hi->next) {
if (hi->mhdr == (const headerType *)mh) {
_objc_unmap_image(hi);
return;
}
}
// no objc data for this image
}
/***********************************************************************
* _objc_init
* Static initializer. Registers our image notifier with dyld.
* fixme move map_images' firstTime code here - but GC code might need
* another earlier image notifier
**********************************************************************/
static __attribute__((constructor))
void _objc_init(void)
{
// Register for unmap first, in case some +load unmaps something
_dyld_register_func_for_remove_image(&unmap_image);
dyld_register_image_state_change_handler(dyld_image_state_bound,
1/*batch*/, &map_images);
dyld_register_image_state_change_handler(dyld_image_state_dependents_initialized, 0/*not batch*/, &load_images);
}
/***********************************************************************
* _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(header_info *hi)
{
if (PrintImages) {
_objc_inform("IMAGES: unloading image for _nameForHeader(hi->mhdr),
hi->mhdr->filetype == MH_BUNDLE ? " (bundle)" : "",
_objcHeaderIsReplacement(hi) ? " (replacement)" : "",
_gcForHInfo2(hi));
}
_unload_image(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_setForwardHandler
**********************************************************************/
void objc_setForwardHandler(void *fwd, void *fwd_stret)
{
_objc_forward_handler = fwd;
_objc_forward_stret_handler = fwd_stret;
}
#if defined(__ppc__) || defined(__ppc64__)
// Test to see if either the displacement or destination is within
// the +/- 2^25 range needed for a PPC branch immediate 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 larger branch sequence is required.
// ppc64: max displacement is still +/- 2^25, but intptr_t is bigger
// tiny: bc*
// small: b, ba (unconditional only)
// 32: bctr with lis+ori only
static BOOL ppc_tiny_displacement(intptr_t displacement)
{
size_t shift = sizeof(intptr_t) - 16; // ilp32=16, lp64=48
return (((displacement << shift) >> shift) == displacement);
}
static BOOL ppc_small_displacement(intptr_t displacement)
{
size_t shift = sizeof(intptr_t) - 26; // ilp32=6, lp64=38
return (((displacement << shift) >> shift) == displacement);
}
#if defined(__ppc64__)
// Same as ppc_small_displacement, but decides whether 32 bits is big enough.
static BOOL ppc_32bit_displacement(intptr_t displacement)
{
size_t shift = sizeof(intptr_t) - 32;
return (((displacement << shift) >> shift) == displacement);
}
#endif
/**********************************************************************
* objc_branch_size
* Returns the number of instructions needed
* for a branch from entry to target.
**********************************************************************/
__private_extern__ size_t objc_branch_size(void *entry, void *target)
{
return objc_cond_branch_size(entry, target, COND_ALWAYS);
}
__private_extern__ size_t
objc_cond_branch_size(void *entry, void *target, unsigned cond)
{
intptr_t destination = (intptr_t)target;
intptr_t displacement = (intptr_t)destination - (intptr_t)entry;
if (cond == COND_ALWAYS && ppc_small_displacement(displacement)) {
// fits in unconditional relative branch immediate
return 1;
}
if (cond == COND_ALWAYS && ppc_small_displacement(destination)) {
// fits in unconditional absolute branch immediate
return 1;
}
if (ppc_tiny_displacement(displacement)) {
// fits in conditional relative branch immediate
return 1;
}
if (ppc_tiny_displacement(destination)) {
// fits in conditional absolute branch immediate
return 1;
}
#if defined(__ppc64__)
if (!ppc_32bit_displacement(destination)) {
// fits in 64-bit absolute branch through CTR
return 7;
}
#endif
// fits in 32-bit absolute branch through CTR
return 4;
}
/**********************************************************************
* objc_write_branch
* Writes at entry a PPC branch instruction sequence that branches to target.
* The sequence written will be objc_branch_size(entry, target) instructions.
* Returns the number of instructions written.
**********************************************************************/
__private_extern__ size_t objc_write_branch(void *entry, void *target)
{
return objc_write_cond_branch(entry, target, COND_ALWAYS);
}
__private_extern__ size_t
objc_write_cond_branch(void *entry, void *target, unsigned cond)
{
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
if (cond == COND_ALWAYS && ppc_small_displacement(displacement)) {
// use unconditional relative branch with the displacement
address[0] = 0x48000000 | (unsigned)(displacement & 0x03fffffc); // b *+displacement
// issued 1 instruction
return 1;
}
if (cond == COND_ALWAYS && ppc_small_displacement(destination)) {
// use unconditional absolute branch with the destination
address[0] = 0x48000000 | (unsigned)(destination & 0x03fffffc) | 2; // ba destination (2 is the absolute flag)
// issued 1 instruction
return 1;
}
if (ppc_tiny_displacement(displacement)) {
// use conditional relative branch with the displacement
address[0] = 0x40000000 | cond | (unsigned)(displacement & 0x0000fffc); // b *+displacement
// issued 1 instruction
return 1;
}
if (ppc_tiny_displacement(destination)) {
// use conditional absolute branch with the destination
address[0] = 0x40000000 | cond | (unsigned)(destination & 0x0000fffc) | 2; // ba destination (2 is the absolute flag)
// issued 1 instruction
return 1;
}
// destination is large and far away.
// Use an absolute branch via CTR.
#if defined(__ppc64__)
if (!ppc_32bit_displacement(destination)) {
uint16_t lo = destination & 0xffff;
uint16_t hi = (destination >> 16) & 0xffff;
uint16_t hi2 = (destination >> 32) & 0xffff;
uint16_t hi3 = (destination >> 48) & 0xffff;
address[0] = 0x3d800000 | hi3; // lis r12, hi3
address[1] = 0x618c0000 | hi2; // ori r12, r12, hi2
address[2] = 0x798c07c6; // sldi r12, r12, 32
address[3] = 0x658c0000 | hi; // oris r12, r12, hi
address[4] = 0x618c0000 | lo; // ori r12, r12, lo
address[5] = 0x7d8903a6; // mtctr r12
address[6] = 0x4c000420 | cond; // bctr
// issued 7 instructions
return 7;
}
#endif
{
uint16_t lo = destination & 0xffff;
uint16_t hi = (destination >> 16) & 0xffff;
address[0] = 0x3d800000 | hi; // lis r12,hi
address[1] = 0x618c0000 | lo; // ori r12,r12,lo
address[2] = 0x7d8903a6; // mtctr r12
address[3] = 0x4c000420 | cond; // bctr
// issued 4 instructions
return 4;
}
}
// defined(__ppc__) || defined(__ppc64__)
#endif
#if defined(__i386__) || defined(__x86_64__)
/**********************************************************************
* objc_branch_size
* Returns the number of BYTES needed
* for a branch from entry to target.
**********************************************************************/
__private_extern__ size_t objc_branch_size(void *entry, void *target)
{
return objc_cond_branch_size(entry, target, COND_ALWAYS);
}
__private_extern__ size_t
objc_cond_branch_size(void *entry, void *target, unsigned cond)
{
// For simplicity, always use 32-bit relative jumps.
if (cond == COND_ALWAYS) return 5;
else return 6;
}
/**********************************************************************
* objc_write_branch
* Writes at entry an i386 branch instruction sequence that branches to target.
* The sequence written will be objc_branch_size(entry, target) BYTES.
* Returns the number of BYTES written.
**********************************************************************/
__private_extern__ size_t objc_write_branch(void *entry, void *target)
{
return objc_write_cond_branch(entry, target, COND_ALWAYS);
}
__private_extern__ size_t
objc_write_cond_branch(void *entry, void *target, unsigned cond)
{
uint8_t *address = (uint8_t *)entry; // instructions written to here
intptr_t destination = (intptr_t)target; // branch dest as absolute address
intptr_t displacement = (intptr_t)destination - ((intptr_t)address + objc_cond_branch_size(entry, target, cond)); // branch dest as relative offset
// For simplicity, always use 32-bit relative jumps
if (cond != COND_ALWAYS) {
*address++ = 0x0f; // Jcc prefix
}
*address++ = cond;
*address++ = displacement & 0xff;
*address++ = (displacement >> 8) & 0xff;
*address++ = (displacement >> 16) & 0xff;
*address++ = (displacement >> 24) & 0xff;
return address - (uint8_t *)entry;
}
// defined __i386__
#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;
}
}
}
#if !__OBJC2__
// GrP fixme
extern Class _objc_getOrigClass(const char *name);
#endif
const char *class_getImageName(Class cls)
{
int ok;
Dl_info info;
if (!cls) return NULL;
#if !__OBJC2__
cls = _objc_getOrigClass(_class_getName(cls));
#endif
ok = dladdr(cls, &info);
if (ok) return info.dli_fname;
else return NULL;
}
const char **objc_copyImageNames(unsigned int *outCount)
{
header_info *hi;
int count = 0;
int max = HeaderCount;
const char **names = calloc(max+1, sizeof(char *));
for (hi = _objc_headerStart();
hi != NULL && count < max;
hi = hi->next)
{
if (hi->dl_info.dli_fname) {
names[count++] = hi->dl_info.dli_fname;
}
}
names[count] = NULL;
if (count == 0) {
// Return NULL instead of empty list if there are no images
free(names);
names = NULL;
}
if (outCount) *outCount = count;
return names;
}
/**********************************************************************
*
**********************************************************************/
const char **
objc_copyClassNamesForImage(const char *image, unsigned int *outCount)
{
header_info *hi;
if (!image) {
if (outCount) *outCount = 0;
return NULL;
}
// Find the image.
for (hi = _objc_headerStart(); hi != NULL; hi = hi->next) {
if (0 == strcmp(image, hi->dl_info.dli_fname)) break;
}
if (!hi) {
if (outCount) *outCount = 0;
return NULL;
}
return _objc_copyClassNamesForImage(hi, outCount);
}
/**********************************************************************
* Fast Enumeration Support
**********************************************************************/
static void (*enumerationMutationHandler)(id);
/**********************************************************************
* objc_enumerationMutation
* called by compiler when a mutation is detected during foreach iteration
**********************************************************************/
void objc_enumerationMutation(id object) {
if (enumerationMutationHandler == nil) {
_objc_fatal("mutation detected during 'for(... in ...)' enumeration of object }
(*enumerationMutationHandler)(object);
}
/**********************************************************************
* objc_setEnumerationMutationHandler
* an entry point to customize mutation error handing
**********************************************************************/
void objc_setEnumerationMutationHandler(void (*handler)(id)) {
enumerationMutationHandler = handler;
}