/* * 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 "objc-private.h" #include "objc-loadmethod.h" #include "objc-file.h" #include "message.h" /*********************************************************************** * Exports. **********************************************************************/ /* Linker metadata symbols */ // NSObject was in Foundation/CF on macOS < 10.8. #if TARGET_OS_OSX #if __OBJC2__ const char __objc_nsobject_class_10_5 = 0; const char __objc_nsobject_class_10_6 = 0; const char __objc_nsobject_class_10_7 = 0; const char __objc_nsobject_metaclass_10_5 = 0; const char __objc_nsobject_metaclass_10_6 = 0; const char __objc_nsobject_metaclass_10_7 = 0; const char __objc_nsobject_isa_10_5 = 0; const char __objc_nsobject_isa_10_6 = 0; const char __objc_nsobject_isa_10_7 = 0; #else const char __objc_nsobject_class_10_5 = 0; const char __objc_nsobject_class_10_6 = 0; const char __objc_nsobject_class_10_7 = 0; #endif #endif // Settings from environment variables #define OPTION(var, env, help) bool var = false; #include "objc-env.h" #undef OPTION struct option_t { bool* var; const char *env; const char *help; size_t envlen; }; const option_t Settings[] = { #define OPTION(var, env, help) option_t{&var, #env, help, strlen(#env)}, #include "objc-env.h" #undef OPTION }; // objc's key for pthread_getspecific #if SUPPORT_DIRECT_THREAD_KEYS #define _objc_pthread_key TLS_DIRECT_KEY #else static tls_key_t _objc_pthread_key; #endif // Selectors SEL SEL_cxx_construct = NULL; SEL SEL_cxx_destruct = NULL; struct objc::SafeRanges objc::dataSegmentsRanges; header_info *FirstHeader = 0; // NULL means empty list header_info *LastHeader = 0; // NULL means invalid; recompute it // Set to true on the child side of fork() // if the parent process was multithreaded when fork() was called. bool MultithreadedForkChild = false; /*********************************************************************** * objc_noop_imp. Used when we need to install a do-nothing method somewhere. **********************************************************************/ id objc_noop_imp(id self, SEL _cmd __unused) { return self; } /*********************************************************************** * _objc_isDebugBuild. Defined in debug builds only. * Some test code looks for the presence of this symbol. **********************************************************************/ #if DEBUG != OBJC_IS_DEBUG_BUILD // #error mismatch in debug-ness macros // DEBUG is used in our code. OBJC_IS_DEBUG_BUILD is used in the // header declaration of _objc_isDebugBuild() because that header // is visible to other clients who might have their own DEBUG macro. #endif #if OBJC_IS_DEBUG_BUILD void _objc_isDebugBuild(void) { } #endif /*********************************************************************** * 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! **********************************************************************/ Class 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. **********************************************************************/ Class objc_getRequiredClass(const char *aClassName) { Class 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 () **********************************************************************/ Class objc_lookUpClass(const char *aClassName) { if (!aClassName) return Nil; // 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! **********************************************************************/ Class objc_getMetaClass(const char *aClassName) { Class cls; if (!aClassName) return Nil; cls = objc_getClass (aClassName); if (!cls) { _objc_inform ("class `%s' not linked into application", aClassName); return Nil; } return cls->ISA(); } /*********************************************************************** * objc::SafeRanges::find. Find an image data segment that contains address **********************************************************************/ bool objc::SafeRanges::find(uintptr_t ptr, uint32_t &pos) { if (!sorted) { std::sort(ranges, ranges + count, [](const Range &s1, const Range &s2){ return s1.start < s2.start; }); sorted = true; } uint32_t l = 0, r = count; while (l < r) { uint32_t i = (l + r) / 2; if (ptr < ranges[i].start) { r = i; } else if (ptr >= ranges[i].end) { l = i + 1; } else { pos = i; return true; } } pos = UINT32_MAX; return false; } /*********************************************************************** * objc::SafeRanges::add. Register a new well known data segment. **********************************************************************/ void objc::SafeRanges::add(uintptr_t start, uintptr_t end) { if (count == size) { // Have a typical malloc growth: // - size <= 32: grow by 4 // - size <= 64: grow by 8 // - size <= 128: grow by 16 // ... etc size += size < 16 ? 4 : 1 << (fls(size) - 3); ranges = (Range *)realloc(ranges, sizeof(Range) * size); } ranges[count++] = Range{ start, end }; sorted = false; } /*********************************************************************** * objc::SafeRanges::remove. Remove a previously known data segment. **********************************************************************/ void objc::SafeRanges::remove(uintptr_t start, uintptr_t end) { uint32_t pos; if (!find(start, pos) || ranges[pos].end != end) { _objc_fatal("Cannot find range %#lx..%#lx", start, end); } if (pos < --count) { ranges[pos] = ranges[count]; sorted = false; } } /*********************************************************************** * appendHeader. Add a newly-constructed header_info to the list. **********************************************************************/ void appendHeader(header_info *hi) { // Add the header to the header list. // The header is appended to the list, to preserve the bottom-up order. hi->setNext(NULL); if (!FirstHeader) { // list is empty FirstHeader = LastHeader = hi; } else { if (!LastHeader) { // list is not empty, but LastHeader is invalid - recompute it LastHeader = FirstHeader; while (LastHeader->getNext()) LastHeader = LastHeader->getNext(); } // LastHeader is now valid LastHeader->setNext(hi); LastHeader = hi; } #if __OBJC2__ if ((hi->mhdr()->flags & MH_DYLIB_IN_CACHE) == 0) { foreach_data_segment(hi->mhdr(), [](const segmentType *seg, intptr_t slide) { uintptr_t start = (uintptr_t)seg->vmaddr + slide; objc::dataSegmentsRanges.add(start, start + seg->vmsize); }); } #endif } /*********************************************************************** * removeHeader * Remove the given header from the header list. * FirstHeader is updated. * LastHeader is set to NULL. Any code that uses LastHeader must * detect this NULL and recompute LastHeader by traversing the list. **********************************************************************/ void removeHeader(header_info *hi) { header_info *prev = NULL; header_info *current = NULL; for (current = FirstHeader; current != NULL; current = current->getNext()) { if (current == hi) { header_info *deadHead = current; // Remove from the linked list. if (prev) prev->setNext(current->getNext()); else FirstHeader = current->getNext(); // no prev so removing head // Update LastHeader if necessary. if (LastHeader == deadHead) { LastHeader = NULL; // will be recomputed next time it's used } break; } prev = current; } #if __OBJC2__ if ((hi->mhdr()->flags & MH_DYLIB_IN_CACHE) == 0) { foreach_data_segment(hi->mhdr(), [](const segmentType *seg, intptr_t slide) { uintptr_t start = (uintptr_t)seg->vmaddr + slide; objc::dataSegmentsRanges.remove(start, start + seg->vmsize); }); } #endif } /*********************************************************************** * environ_init * Read environment variables that affect the runtime. * Also print environment variable help, if requested. **********************************************************************/ void environ_init(void) { if (issetugid()) { // All environment variables are silently ignored when setuid or setgid // This includes OBJC_HELP and OBJC_PRINT_OPTIONS themselves. return; } bool PrintHelp = false; bool PrintOptions = false; bool maybeMallocDebugging = false; // Scan environ[] directly instead of calling getenv() a lot. // This optimizes the case where none are set. for (char **p = *_NSGetEnviron(); *p != nil; p++) { if (0 == strncmp(*p, "Malloc", 6) || 0 == strncmp(*p, "DYLD", 4) || 0 == strncmp(*p, "NSZombiesEnabled", 16)) { maybeMallocDebugging = true; } if (0 != strncmp(*p, "OBJC_", 5)) continue; if (0 == strncmp(*p, "OBJC_HELP=", 10)) { PrintHelp = true; continue; } if (0 == strncmp(*p, "OBJC_PRINT_OPTIONS=", 19)) { PrintOptions = true; continue; } const char *value = strchr(*p, '='); if (!*value) continue; value++; for (size_t i = 0; i < sizeof(Settings)/sizeof(Settings[0]); i++) { const option_t *opt = &Settings[i]; if ((size_t)(value - *p) == 1+opt->envlen && 0 == strncmp(*p, opt->env, opt->envlen)) { *opt->var = (0 == strcmp(value, "YES")); break; } } } // Special case: enable some autorelease pool debugging // when some malloc debugging is enabled // and OBJC_DEBUG_POOL_ALLOCATION is not set to something other than NO. if (maybeMallocDebugging) { const char *insert = getenv("DYLD_INSERT_LIBRARIES"); const char *zombie = getenv("NSZombiesEnabled"); const char *pooldebug = getenv("OBJC_DEBUG_POOL_ALLOCATION"); if ((getenv("MallocStackLogging") || getenv("MallocStackLoggingNoCompact") || (zombie && (*zombie == 'Y' || *zombie == 'y')) || (insert && strstr(insert, "libgmalloc"))) && (!pooldebug || 0 == strcmp(pooldebug, "YES"))) { DebugPoolAllocation = true; } } // Print OBJC_HELP and OBJC_PRINT_OPTIONS output. if (PrintHelp || PrintOptions) { if (PrintHelp) { _objc_inform("Objective-C runtime debugging. Set variable=YES to enable."); _objc_inform("OBJC_HELP: describe available 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"); } for (size_t i = 0; i < sizeof(Settings)/sizeof(Settings[0]); i++) { const option_t *opt = &Settings[i]; if (PrintHelp) _objc_inform("%s: %s", opt->env, opt->help); if (PrintOptions && *opt->var) _objc_inform("%s is set", opt->env); } } } /*********************************************************************** * logReplacedMethod * OBJC_PRINT_REPLACED_METHODS implementation **********************************************************************/ void logReplacedMethod(const char *className, SEL s, bool isMeta, const char *catName, IMP oldImp, IMP newImp) { const char *oldImage = "??"; const char *newImage = "??"; // Silently ignore +load replacement because category +load is special if (s == @selector(load)) return; #if TARGET_OS_WIN32 // don't know dladdr()/dli_fname equivalent #else Dl_info dl; if (dladdr((void*)oldImp, &dl) && dl.dli_fname) oldImage = dl.dli_fname; if (dladdr((void*)newImp, &dl) && dl.dli_fname) newImage = dl.dli_fname; #endif _objc_inform("REPLACED: %c[%s %s] %s%s (IMP was %p (%s), now %p (%s))", isMeta ? '+' : '-', className, sel_getName(s), catName ? "by category " : "", catName ? catName : "", oldImp, oldImage, newImp, newImage); } /*********************************************************************** * _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. **********************************************************************/ _objc_pthread_data *_objc_fetch_pthread_data(bool create) { _objc_pthread_data *data; data = (_objc_pthread_data *)tls_get(_objc_pthread_key); if (!data && create) { data = (_objc_pthread_data *) calloc(1, sizeof(_objc_pthread_data)); tls_set(_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); void _objc_pthread_destroyspecific(void *arg) { _objc_pthread_data *data = (_objc_pthread_data *)arg; if (data != NULL) { _destroyInitializingClassList(data->initializingClasses); _destroySyncCache(data->syncCache); _destroyAltHandlerList(data->handlerList); for (int i = 0; i < (int)countof(data->printableNames); i++) { if (data->printableNames[i]) { free(data->printableNames[i]); } } free(data->classNameLookups); // add further cleanup here... free(data); } } void tls_init(void) { #if SUPPORT_DIRECT_THREAD_KEYS pthread_key_init_np(TLS_DIRECT_KEY, &_objc_pthread_destroyspecific); #else _objc_pthread_key = tls_create(&_objc_pthread_destroyspecific); #endif } /*********************************************************************** * _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 } /*********************************************************************** * objc_setForwardHandler **********************************************************************/ #if !__OBJC2__ // Default forward handler (nil) goes to forward:: dispatch. void *_objc_forward_handler = nil; void *_objc_forward_stret_handler = nil; #else // Default forward handler halts the process. __attribute__((noreturn, cold)) void objc_defaultForwardHandler(id self, SEL sel) { _objc_fatal("%c[%s %s]: unrecognized selector sent to instance %p " "(no message forward handler is installed)", class_isMetaClass(object_getClass(self)) ? '+' : '-', object_getClassName(self), sel_getName(sel), self); } void *_objc_forward_handler = (void*)objc_defaultForwardHandler; #if SUPPORT_STRET struct stret { int i[100]; }; __attribute__((noreturn, cold)) struct stret objc_defaultForwardStretHandler(id self, SEL sel) { objc_defaultForwardHandler(self, sel); } void *_objc_forward_stret_handler = (void*)objc_defaultForwardStretHandler; #endif #endif void objc_setForwardHandler(void *fwd, void *fwd_stret) { _objc_forward_handler = fwd; #if SUPPORT_STRET _objc_forward_stret_handler = fwd_stret; #endif } #if !__OBJC2__ // GrP fixme extern "C" Class _objc_getOrigClass(const char *name); #endif static BOOL internal_class_getImageName(Class cls, const char **outName) { #if !__OBJC2__ cls = _objc_getOrigClass(cls->demangledName()); #endif auto result = dyld_image_path_containing_address(cls); *outName = result; return (result != nil); } static ChainedHookFunction GetImageNameHook{internal_class_getImageName}; void objc_setHook_getImageName(objc_hook_getImageName newValue, objc_hook_getImageName *outOldValue) { GetImageNameHook.set(newValue, outOldValue); } const char *class_getImageName(Class cls) { if (!cls) return nil; const char *name; if (GetImageNameHook.get()(cls, &name)) return name; else return nil; } /********************************************************************** * 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 %p.", (void*)object); } (*enumerationMutationHandler)(object); } /********************************************************************** * objc_setEnumerationMutationHandler * an entry point to customize mutation error handing **********************************************************************/ void objc_setEnumerationMutationHandler(void (*handler)(id)) { enumerationMutationHandler = handler; } /********************************************************************** * Associative Reference Support **********************************************************************/ id objc_getAssociatedObject(id object, const void *key) { return _object_get_associative_reference(object, key); } static void _base_objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) { _object_set_associative_reference(object, key, value, policy); } static ChainedHookFunction SetAssocHook{_base_objc_setAssociatedObject}; void objc_setHook_setAssociatedObject(objc_hook_setAssociatedObject _Nonnull newValue, objc_hook_setAssociatedObject _Nullable * _Nonnull outOldValue) { SetAssocHook.set(newValue, outOldValue); } void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) { SetAssocHook.get()(object, key, value, policy); } void objc_removeAssociatedObjects(id object) { if (object && object->hasAssociatedObjects()) { _object_remove_assocations(object); } } #if SUPPORT_GC_COMPAT #include // GC preflight for an app executable. enum GCness { WithGC = 1, WithoutGC = 0, Error = -1 }; // Overloaded template wrappers around clang's overflow-checked arithmetic. template bool uadd_overflow(T x, T y, T* sum); template bool usub_overflow(T x, T y, T* diff); template bool umul_overflow(T x, T y, T* prod); template bool sadd_overflow(T x, T y, T* sum); template bool ssub_overflow(T x, T y, T* diff); template bool smul_overflow(T x, T y, T* prod); template <> bool uadd_overflow(unsigned x, unsigned y, unsigned* sum) { return __builtin_uadd_overflow(x, y, sum); } template <> bool uadd_overflow(unsigned long x, unsigned long y, unsigned long* sum) { return __builtin_uaddl_overflow(x, y, sum); } template <> bool uadd_overflow(unsigned long long x, unsigned long long y, unsigned long long* sum) { return __builtin_uaddll_overflow(x, y, sum); } template <> bool usub_overflow(unsigned x, unsigned y, unsigned* diff) { return __builtin_usub_overflow(x, y, diff); } template <> bool usub_overflow(unsigned long x, unsigned long y, unsigned long* diff) { return __builtin_usubl_overflow(x, y, diff); } template <> bool usub_overflow(unsigned long long x, unsigned long long y, unsigned long long* diff) { return __builtin_usubll_overflow(x, y, diff); } template <> bool umul_overflow(unsigned x, unsigned y, unsigned* prod) { return __builtin_umul_overflow(x, y, prod); } template <> bool umul_overflow(unsigned long x, unsigned long y, unsigned long* prod) { return __builtin_umull_overflow(x, y, prod); } template <> bool umul_overflow(unsigned long long x, unsigned long long y, unsigned long long* prod) { return __builtin_umulll_overflow(x, y, prod); } template <> bool sadd_overflow(signed x, signed y, signed* sum) { return __builtin_sadd_overflow(x, y, sum); } template <> bool sadd_overflow(signed long x, signed long y, signed long* sum) { return __builtin_saddl_overflow(x, y, sum); } template <> bool sadd_overflow(signed long long x, signed long long y, signed long long* sum) { return __builtin_saddll_overflow(x, y, sum); } template <> bool ssub_overflow(signed x, signed y, signed* diff) { return __builtin_ssub_overflow(x, y, diff); } template <> bool ssub_overflow(signed long x, signed long y, signed long* diff) { return __builtin_ssubl_overflow(x, y, diff); } template <> bool ssub_overflow(signed long long x, signed long long y, signed long long* diff) { return __builtin_ssubll_overflow(x, y, diff); } template <> bool smul_overflow(signed x, signed y, signed* prod) { return __builtin_smul_overflow(x, y, prod); } template <> bool smul_overflow(signed long x, signed long y, signed long* prod) { return __builtin_smull_overflow(x, y, prod); } template <> bool smul_overflow(signed long long x, signed long long y, signed long long* prod) { return __builtin_smulll_overflow(x, y, prod); } // Range-checking subview of a file. class FileSlice { int fd; uint64_t sliceOffset; uint64_t sliceSize; public: FileSlice() : fd(-1), sliceOffset(0), sliceSize(0) { } FileSlice(int newfd, uint64_t newOffset, uint64_t newSize) : fd(newfd) , sliceOffset(newOffset) , sliceSize(newSize) { } // Read bytes from this slice. // Returns YES if all bytes were read successfully. bool pread(void *buf, uint64_t readSize, uint64_t readOffset = 0) { uint64_t readEnd; if (uadd_overflow(readOffset, readSize, &readEnd)) return NO; if (readEnd > sliceSize) return NO; uint64_t preadOffset; if (uadd_overflow(sliceOffset, readOffset, &preadOffset)) return NO; int64_t readed = ::pread(fd, buf, (size_t)readSize, preadOffset); if (readed < 0 || (uint64_t)readed != readSize) return NO; return YES; } // Create a new slice that is a subset of this slice. // Returnes YES if successful. bool slice(uint64_t newOffset, uint64_t newSize, FileSlice& result) { // fixme arithmetic overflow uint64_t newEnd; if (uadd_overflow(newOffset, newSize, &newEnd)) return NO; if (newEnd > sliceSize) return NO; if (uadd_overflow(sliceOffset, newOffset, &result.sliceOffset)) { return NO; } result.sliceSize = newSize; result.fd = fd; return YES; } // Shorten this slice in place by removing a range from the start. bool advance(uint64_t distance) { if (distance > sliceSize) return NO; if (uadd_overflow(sliceOffset, distance, &sliceOffset)) return NO; if (usub_overflow(sliceSize, distance, &sliceSize)) return NO; return YES; } }; // Arch32 and Arch64 are used to specialize sliceRequiresGC() // to interrogate old-ABI i386 and new-ABI x86_64 files. struct Arch32 { using mh_t = struct mach_header; using segment_command_t = struct segment_command; using section_t = struct section; enum : cpu_type_t { cputype = CPU_TYPE_X86 }; enum : int { segment_cmd = LC_SEGMENT }; static bool isObjCSegment(const char *segname) { return segnameEquals(segname, "__OBJC"); } static bool isImageInfoSection(const char *sectname) { return sectnameEquals(sectname, "__image_info"); } static bool countClasses(FileSlice file, section_t& sect, int& classCount, int& classrefCount) { if (sectnameEquals(sect.sectname, "__cls_refs")) { classrefCount += sect.size / 4; } else if (sectnameEquals(sect.sectname, "__module_info")) { struct module_t { uint32_t version; uint32_t size; uint32_t name; // not bound uint32_t symtab; // not bound }; size_t mod_count = sect.size / sizeof(module_t); if (mod_count == 0) { // no classes defined } else if (mod_count > 1) { // AppleScriptObjC apps only have one module. // Disqualify this app by setting classCount to non-zero. // We don't actually need an accurate count. classCount = 1; } else if (mod_count == 1) { FileSlice moduleSlice; if (!file.slice(sect.offset, sect.size, moduleSlice)) return NO; module_t module; if (!moduleSlice.pread(&module, sizeof(module))) return NO; if (module.symtab) { // AppleScriptObjC apps only have a module with no symtab. // Disqualify this app by setting classCount to non-zero. // We don't actually need an accurate count. classCount = 1; } } } return YES; } }; struct Arch64 { using mh_t = struct mach_header_64; using segment_command_t = struct segment_command_64; using section_t = struct section_64; enum : cpu_type_t { cputype = CPU_TYPE_X86_64 }; enum : int { segment_cmd = LC_SEGMENT_64 }; static bool isObjCSegment(const char *segname) { return segnameEquals(segname, "__DATA") || segnameEquals(segname, "__DATA_CONST") || segnameEquals(segname, "__DATA_DIRTY"); } static bool isImageInfoSection(const char *sectname) { return sectnameEquals(sectname, "__objc_imageinfo"); } static bool countClasses(FileSlice, section_t& sect, int& classCount, int& classrefCount) { if (sectnameEquals(sect.sectname, "__objc_classlist")) { classCount += sect.size / 8; } else if (sectnameEquals(sect.sectname, "__objc_classrefs")) { classrefCount += sect.size / 8; } return YES; } }; #define SANE_HEADER_SIZE (32*1024) template static int sliceRequiresGC(typename Arch::mh_t mh, FileSlice file) { // We assume there is only one arch per pointer size that can support GC. // (i386 and x86_64) if (mh.cputype != Arch::cputype) return 0; // We only check the main executable. if (mh.filetype != MH_EXECUTE) return 0; // Look for ObjC segment. // Look for AppleScriptObjC linkage. FileSlice cmds; if (!file.slice(sizeof(mh), mh.sizeofcmds, cmds)) return Error; // Exception: Some AppleScriptObjC apps built for GC can run without GC. // 1. executable defines no classes // 2. executable references NSBundle only // 3. executable links to AppleScriptObjC.framework // Note that shouldRejectGCApp() also knows about this. bool wantsGC = NO; bool linksToAppleScriptObjC = NO; int classCount = 0; int classrefCount = 0; // Disallow abusively-large executables that could hang this checker. // dyld performs similar checks (MAX_MACH_O_HEADER_AND_LOAD_COMMANDS_SIZE) if (mh.sizeofcmds > SANE_HEADER_SIZE) return Error; if (mh.ncmds > mh.sizeofcmds / sizeof(struct load_command)) return Error; for (uint32_t cmdindex = 0; cmdindex < mh.ncmds; cmdindex++) { struct load_command lc; if (!cmds.pread(&lc, sizeof(lc))) return Error; // Disallow abusively-small load commands that could hang this checker. // dyld performs a similar check. if (lc.cmdsize < sizeof(lc)) return Error; if (lc.cmd == LC_LOAD_DYLIB || lc.cmd == LC_LOAD_UPWARD_DYLIB || lc.cmd == LC_LOAD_WEAK_DYLIB || lc.cmd == LC_REEXPORT_DYLIB) { // Look for AppleScriptObjC linkage. FileSlice dylibSlice; if (!cmds.slice(0, lc.cmdsize, dylibSlice)) return Error; struct dylib_command dylib; if (!dylibSlice.pread(&dylib, sizeof(dylib))) return Error; const char *asoFramework = "/System/Library/Frameworks/AppleScriptObjC.framework" "/Versions/A/AppleScriptObjC"; size_t asoLen = strlen(asoFramework); FileSlice nameSlice; if (dylibSlice.slice(dylib.dylib.name.offset, asoLen, nameSlice)) { char name[asoLen]; if (!nameSlice.pread(name, asoLen)) return Error; if (0 == memcmp(name, asoFramework, asoLen)) { linksToAppleScriptObjC = YES; } } } else if (lc.cmd == Arch::segment_cmd) { typename Arch::segment_command_t seg; if (!cmds.pread(&seg, sizeof(seg))) return Error; if (Arch::isObjCSegment(seg.segname)) { // ObjC segment. // Look for image info section. // Look for class implementations and class references. FileSlice sections; if (!cmds.slice(0, seg.cmdsize, sections)) return Error; if (!sections.advance(sizeof(seg))) return Error; for (uint32_t segindex = 0; segindex < seg.nsects; segindex++) { typename Arch::section_t sect; if (!sections.pread(§, sizeof(sect))) return Error; if (!Arch::isObjCSegment(sect.segname)) return Error; if (!Arch::countClasses(file, sect, classCount, classrefCount)) { return Error; } if ((sect.flags & SECTION_TYPE) == S_REGULAR && Arch::isImageInfoSection(sect.sectname)) { // ObjC image info section. // Check its contents. FileSlice section; if (!file.slice(sect.offset, sect.size, section)) { return Error; } // The subset of objc_image_info that was in use for GC. struct { uint32_t version; uint32_t flags; } ii; if (!section.pread(&ii, sizeof(ii))) return Error; if (ii.flags & (1<<1)) { // App wants GC. // Don't return yet because we need to // check the AppleScriptObjC exception. wantsGC = YES; } } if (!sections.advance(sizeof(sect))) return Error; } } } if (!cmds.advance(lc.cmdsize)) return Error; } if (!wantsGC) { // No GC bit set. return WithoutGC; } else if (linksToAppleScriptObjC && classCount == 0 && classrefCount == 1) { // Has GC bit but falls under the AppleScriptObjC exception. return WithoutGC; } else { // Has GC bit and is not AppleScriptObjC. return WithGC; } } static int sliceRequiresGC(FileSlice file) { // Read mach-o header. struct mach_header_64 mh; if (!file.pread(&mh, sizeof(mh))) return Error; // Check header magic. We assume only host-endian slices can support GC. switch (mh.magic) { case MH_MAGIC: return sliceRequiresGC(*(struct mach_header *)&mh, file); case MH_MAGIC_64: return sliceRequiresGC(mh, file); default: return WithoutGC; } } // Returns 1 if any slice requires GC. // Returns 0 if no slice requires GC. // Returns -1 on any I/O or file format error. int objc_appRequiresGC(int fd) { struct stat st; if (fstat(fd, &st) < 0) return Error; FileSlice file(fd, 0, st.st_size); // Read fat header, if any. struct fat_header fh; if (! file.pread(&fh, sizeof(fh))) return Error; int result; if (OSSwapBigToHostInt32(fh.magic) == FAT_MAGIC) { // Fat header. size_t nfat_arch = OSSwapBigToHostInt32(fh.nfat_arch); // Disallow abusively-large files that could hang this checker. if (nfat_arch > SANE_HEADER_SIZE/sizeof(struct fat_arch)) return Error; size_t fat_size; if (umul_overflow(nfat_arch, sizeof(struct fat_arch), &fat_size)) { return Error; } FileSlice archlist; if (!file.slice(sizeof(fh), fat_size, archlist)) return Error; result = WithoutGC; for (size_t i = 0; i < nfat_arch; i++) { struct fat_arch fa; if (!archlist.pread(&fa, sizeof(fa))) return Error; if (!archlist.advance(sizeof(fa))) return Error; FileSlice thin; if (!file.slice(OSSwapBigToHostInt32(fa.offset), OSSwapBigToHostInt32(fa.size), thin)) { return Error; } switch (sliceRequiresGC(thin)) { case WithoutGC: break; // no change case WithGC: if (result != Error) result = WithGC; break; case Error: result = Error; break; } } } else { // Thin header or not a header. result = sliceRequiresGC(file); } return result; } // SUPPORT_GC_COMPAT #endif