mirror of
https://github.com/darlinghq/darling-objc4.git
synced 2024-11-23 04:09:46 +00:00
1a12df76d1
The Objective-C runtime version 2 (used for non-32-bit architectures) added a runtime lock. This lock is always taken before the message log lock, but the lockdebug subsystem wasn't made aware of this; because of this, it would throw an error saying the lock order wasn't defined. Let's define it so it can calm down.
1105 lines
35 KiB
Plaintext
1105 lines
35 KiB
Plaintext
/*
|
|
* Copyright (c) 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-os.m
|
|
* OS portability layer.
|
|
**********************************************************************/
|
|
|
|
#include "objc-private.h"
|
|
#include "objc-loadmethod.h"
|
|
#ifndef DARLING
|
|
#include "objc-bp-assist.h"
|
|
#endif
|
|
|
|
#if TARGET_OS_WIN32
|
|
|
|
#include "objc-runtime-old.h"
|
|
#include "objcrt.h"
|
|
|
|
const fork_unsafe_lock_t fork_unsafe_lock;
|
|
|
|
int monitor_init(monitor_t *c)
|
|
{
|
|
// fixme error checking
|
|
HANDLE mutex = CreateMutex(NULL, TRUE, NULL);
|
|
while (!c->mutex) {
|
|
// fixme memory barrier here?
|
|
if (0 == InterlockedCompareExchangePointer(&c->mutex, mutex, 0)) {
|
|
// we win - finish construction
|
|
c->waiters = CreateSemaphore(NULL, 0, 0x7fffffff, NULL);
|
|
c->waitersDone = CreateEvent(NULL, FALSE, FALSE, NULL);
|
|
InitializeCriticalSection(&c->waitCountLock);
|
|
c->waitCount = 0;
|
|
c->didBroadcast = 0;
|
|
ReleaseMutex(c->mutex);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
// someone else allocated the mutex and constructed the monitor
|
|
ReleaseMutex(mutex);
|
|
CloseHandle(mutex);
|
|
return 0;
|
|
}
|
|
|
|
void mutex_init(mutex_t *m)
|
|
{
|
|
while (!m->lock) {
|
|
CRITICAL_SECTION *newlock = malloc(sizeof(CRITICAL_SECTION));
|
|
InitializeCriticalSection(newlock);
|
|
// fixme memory barrier here?
|
|
if (0 == InterlockedCompareExchangePointer(&m->lock, newlock, 0)) {
|
|
return;
|
|
}
|
|
// someone else installed their lock first
|
|
DeleteCriticalSection(newlock);
|
|
free(newlock);
|
|
}
|
|
}
|
|
|
|
|
|
void recursive_mutex_init(recursive_mutex_t *m)
|
|
{
|
|
// fixme error checking
|
|
HANDLE newmutex = CreateMutex(NULL, FALSE, NULL);
|
|
while (!m->mutex) {
|
|
// fixme memory barrier here?
|
|
if (0 == InterlockedCompareExchangePointer(&m->mutex, newmutex, 0)) {
|
|
// we win
|
|
return;
|
|
}
|
|
}
|
|
|
|
// someone else installed their lock first
|
|
CloseHandle(newmutex);
|
|
}
|
|
|
|
|
|
WINBOOL APIENTRY DllMain( HMODULE hModule,
|
|
DWORD ul_reason_for_call,
|
|
LPVOID lpReserved
|
|
)
|
|
{
|
|
switch (ul_reason_for_call) {
|
|
case DLL_PROCESS_ATTACH:
|
|
environ_init();
|
|
tls_init();
|
|
runtime_init();
|
|
sel_init(3500); // old selector heuristic
|
|
exception_init();
|
|
break;
|
|
|
|
case DLL_THREAD_ATTACH:
|
|
break;
|
|
|
|
case DLL_THREAD_DETACH:
|
|
case DLL_PROCESS_DETACH:
|
|
break;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
OBJC_EXPORT void *_objc_init_image(HMODULE image, const objc_sections *sects)
|
|
{
|
|
header_info *hi = malloc(sizeof(header_info));
|
|
size_t count, i;
|
|
|
|
hi->mhdr = (const headerType *)image;
|
|
hi->info = sects->iiStart;
|
|
hi->allClassesRealized = NO;
|
|
hi->modules = sects->modStart ? (Module *)((void **)sects->modStart+1) : 0;
|
|
hi->moduleCount = (Module *)sects->modEnd - hi->modules;
|
|
hi->protocols = sects->protoStart ? (struct old_protocol **)((void **)sects->protoStart+1) : 0;
|
|
hi->protocolCount = (struct old_protocol **)sects->protoEnd - hi->protocols;
|
|
hi->imageinfo = NULL;
|
|
hi->imageinfoBytes = 0;
|
|
// hi->imageinfo = sects->iiStart ? (uint8_t *)((void **)sects->iiStart+1) : 0;;
|
|
// hi->imageinfoBytes = (uint8_t *)sects->iiEnd - hi->imageinfo;
|
|
hi->selrefs = sects->selrefsStart ? (SEL *)((void **)sects->selrefsStart+1) : 0;
|
|
hi->selrefCount = (SEL *)sects->selrefsEnd - hi->selrefs;
|
|
hi->clsrefs = sects->clsrefsStart ? (Class *)((void **)sects->clsrefsStart+1) : 0;
|
|
hi->clsrefCount = (Class *)sects->clsrefsEnd - hi->clsrefs;
|
|
|
|
count = 0;
|
|
for (i = 0; i < hi->moduleCount; i++) {
|
|
if (hi->modules[i]) count++;
|
|
}
|
|
hi->mod_count = 0;
|
|
hi->mod_ptr = 0;
|
|
if (count > 0) {
|
|
hi->mod_ptr = malloc(count * sizeof(struct objc_module));
|
|
for (i = 0; i < hi->moduleCount; i++) {
|
|
if (hi->modules[i]) memcpy(&hi->mod_ptr[hi->mod_count++], hi->modules[i], sizeof(struct objc_module));
|
|
}
|
|
}
|
|
|
|
hi->moduleName = malloc(MAX_PATH * sizeof(TCHAR));
|
|
GetModuleFileName((HMODULE)(hi->mhdr), hi->moduleName, MAX_PATH * sizeof(TCHAR));
|
|
|
|
appendHeader(hi);
|
|
|
|
if (PrintImages) {
|
|
_objc_inform("IMAGES: loading image for %s%s%s%s\n",
|
|
hi->fname,
|
|
headerIsBundle(hi) ? " (bundle)" : "",
|
|
hi->info->isReplacement() ? " (replacement)":"",
|
|
hi->info->hasCategoryClassProperties() ? " (has class properties)":"");
|
|
}
|
|
|
|
// Count classes. Size various table based on the total.
|
|
int total = 0;
|
|
int unoptimizedTotal = 0;
|
|
{
|
|
if (_getObjc2ClassList(hi, &count)) {
|
|
total += (int)count;
|
|
if (!hi->getInSharedCache()) unoptimizedTotal += count;
|
|
}
|
|
}
|
|
|
|
_read_images(&hi, 1, total, unoptimizedTotal);
|
|
|
|
return hi;
|
|
}
|
|
|
|
OBJC_EXPORT void _objc_load_image(HMODULE image, header_info *hinfo)
|
|
{
|
|
prepare_load_methods(hinfo);
|
|
call_load_methods();
|
|
}
|
|
|
|
OBJC_EXPORT void _objc_unload_image(HMODULE image, header_info *hinfo)
|
|
{
|
|
_objc_fatal("image unload not supported");
|
|
}
|
|
|
|
|
|
// TARGET_OS_WIN32
|
|
#elif TARGET_OS_MAC
|
|
|
|
#include "objc-file-old.h"
|
|
#include "objc-file.h"
|
|
|
|
|
|
/***********************************************************************
|
|
* libobjc must never run static destructors.
|
|
* Cover libc's __cxa_atexit with our own definition that runs nothing.
|
|
* rdar://21734598 ER: Compiler option to suppress C++ static destructors
|
|
**********************************************************************/
|
|
extern "C" int __cxa_atexit();
|
|
extern "C" int __cxa_atexit() { return 0; }
|
|
|
|
|
|
/***********************************************************************
|
|
* bad_magic.
|
|
* Return YES if the header has invalid Mach-o magic.
|
|
**********************************************************************/
|
|
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);
|
|
}
|
|
|
|
|
|
static header_info * addHeader(const headerType *mhdr, const char *path, int &totalClasses, int &unoptimizedTotalClasses)
|
|
{
|
|
header_info *hi;
|
|
|
|
if (bad_magic(mhdr)) return NULL;
|
|
|
|
bool inSharedCache = false;
|
|
|
|
// Look for hinfo from the dyld shared cache.
|
|
hi = preoptimizedHinfoForHeader(mhdr);
|
|
if (hi) {
|
|
// Found an hinfo in the dyld shared cache.
|
|
|
|
// Weed out duplicates.
|
|
if (hi->isLoaded()) {
|
|
return NULL;
|
|
}
|
|
|
|
inSharedCache = true;
|
|
|
|
// Initialize fields not set by the shared cache
|
|
// hi->next is set by appendHeader
|
|
hi->setLoaded(true);
|
|
|
|
if (PrintPreopt) {
|
|
_objc_inform("PREOPTIMIZATION: honoring preoptimized header info at %p for %s", hi, hi->fname());
|
|
}
|
|
|
|
#if !__OBJC2__
|
|
_objc_fatal("shouldn't be here");
|
|
#endif
|
|
#if DEBUG
|
|
// Verify image_info
|
|
size_t info_size = 0;
|
|
const objc_image_info *image_info = _getObjcImageInfo(mhdr,&info_size);
|
|
ASSERT(image_info == hi->info());
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
// Didn't find an hinfo in the dyld shared cache.
|
|
|
|
// Locate the __OBJC segment
|
|
size_t info_size = 0;
|
|
unsigned long seg_size;
|
|
const objc_image_info *image_info = _getObjcImageInfo(mhdr,&info_size);
|
|
const uint8_t *objc_segment = getsegmentdata(mhdr,SEG_OBJC,&seg_size);
|
|
if (!objc_segment && !image_info) return NULL;
|
|
|
|
// Allocate a header_info entry.
|
|
// Note we also allocate space for a single header_info_rw in the
|
|
// rw_data[] inside header_info.
|
|
hi = (header_info *)calloc(sizeof(header_info) + sizeof(header_info_rw), 1);
|
|
|
|
// Set up the new header_info entry.
|
|
hi->setmhdr(mhdr);
|
|
#if !__OBJC2__
|
|
// mhdr must already be set
|
|
hi->mod_count = 0;
|
|
hi->mod_ptr = _getObjcModules(hi, &hi->mod_count);
|
|
#endif
|
|
// Install a placeholder image_info if absent to simplify code elsewhere
|
|
static const objc_image_info emptyInfo = {0, 0};
|
|
hi->setinfo(image_info ?: &emptyInfo);
|
|
|
|
hi->setLoaded(true);
|
|
hi->setAllClassesRealized(NO);
|
|
}
|
|
|
|
#if __OBJC2__
|
|
{
|
|
size_t count = 0;
|
|
if (_getObjc2ClassList(hi, &count)) {
|
|
totalClasses += (int)count;
|
|
if (!inSharedCache) unoptimizedTotalClasses += count;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
appendHeader(hi);
|
|
|
|
return hi;
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* linksToLibrary
|
|
* Returns true if the image links directly to a dylib whose install name
|
|
* is exactly the given name.
|
|
**********************************************************************/
|
|
bool
|
|
linksToLibrary(const header_info *hi, const char *name)
|
|
{
|
|
const struct dylib_command *cmd;
|
|
unsigned long i;
|
|
|
|
cmd = (const struct dylib_command *) (hi->mhdr() + 1);
|
|
for (i = 0; i < hi->mhdr()->ncmds; i++) {
|
|
if (cmd->cmd == LC_LOAD_DYLIB || cmd->cmd == LC_LOAD_UPWARD_DYLIB ||
|
|
cmd->cmd == LC_LOAD_WEAK_DYLIB || cmd->cmd == LC_REEXPORT_DYLIB)
|
|
{
|
|
const char *dylib = cmd->dylib.name.offset + (const char *)cmd;
|
|
if (0 == strcmp(dylib, name)) return true;
|
|
}
|
|
cmd = (const struct dylib_command *)((char *)cmd + cmd->cmdsize);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
#if SUPPORT_GC_COMPAT
|
|
|
|
/***********************************************************************
|
|
* shouldRejectGCApp
|
|
* Return YES if the executable requires GC.
|
|
**********************************************************************/
|
|
static bool shouldRejectGCApp(const header_info *hi)
|
|
{
|
|
ASSERT(hi->mhdr()->filetype == MH_EXECUTE);
|
|
|
|
if (!hi->info()->supportsGC()) {
|
|
// App does not use GC. Don't reject it.
|
|
return NO;
|
|
}
|
|
|
|
// Exception: Trivial AppleScriptObjC apps can run without GC.
|
|
// 1. executable defines no classes
|
|
// 2. executable references NSBundle only
|
|
// 3. executable links to AppleScriptObjC.framework
|
|
// Note that objc_appRequiresGC() also knows about this.
|
|
size_t classcount = 0;
|
|
size_t refcount = 0;
|
|
#if __OBJC2__
|
|
_getObjc2ClassList(hi, &classcount);
|
|
_getObjc2ClassRefs(hi, &refcount);
|
|
#else
|
|
if (hi->mod_count == 0 || (hi->mod_count == 1 && !hi->mod_ptr[0].symtab)) classcount = 0;
|
|
else classcount = 1;
|
|
_getObjcClassRefs(hi, &refcount);
|
|
#endif
|
|
if (classcount == 0 && refcount == 1 &&
|
|
linksToLibrary(hi, "/System/Library/Frameworks"
|
|
"/AppleScriptObjC.framework/Versions/A"
|
|
"/AppleScriptObjC"))
|
|
{
|
|
// It's AppleScriptObjC. Don't reject it.
|
|
return NO;
|
|
}
|
|
else {
|
|
// GC and not trivial AppleScriptObjC. Reject it.
|
|
return YES;
|
|
}
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* rejectGCImage
|
|
* Halt if an image requires GC.
|
|
* Testing of the main executable should use rejectGCApp() instead.
|
|
**********************************************************************/
|
|
static bool shouldRejectGCImage(const headerType *mhdr)
|
|
{
|
|
ASSERT(mhdr->filetype != MH_EXECUTE);
|
|
|
|
objc_image_info *image_info;
|
|
size_t size;
|
|
|
|
#if !__OBJC2__
|
|
unsigned long seg_size;
|
|
// 32-bit: __OBJC seg but no image_info means no GC support
|
|
if (!getsegmentdata(mhdr, "__OBJC", &seg_size)) {
|
|
// Not objc, therefore not GC. Don't reject it.
|
|
return NO;
|
|
}
|
|
image_info = _getObjcImageInfo(mhdr, &size);
|
|
if (!image_info) {
|
|
// No image_info, therefore not GC. Don't reject it.
|
|
return NO;
|
|
}
|
|
#else
|
|
// 64-bit: no image_info means no objc at all
|
|
image_info = _getObjcImageInfo(mhdr, &size);
|
|
if (!image_info) {
|
|
// Not objc, therefore not GC. Don't reject it.
|
|
return NO;
|
|
}
|
|
#endif
|
|
|
|
return image_info->requiresGC();
|
|
}
|
|
|
|
// SUPPORT_GC_COMPAT
|
|
#endif
|
|
|
|
|
|
// Swift currently adds 4 callbacks.
|
|
static GlobalSmallVector<objc_func_loadImage, 4> loadImageFuncs;
|
|
|
|
void objc_addLoadImageFunc(objc_func_loadImage _Nonnull func) {
|
|
// Not supported on the old runtime. Not that the old runtime is supported anyway.
|
|
#if __OBJC2__
|
|
mutex_locker_t lock(runtimeLock);
|
|
|
|
// Call it with all the existing images first.
|
|
for (auto header = FirstHeader; header; header = header->getNext()) {
|
|
func((struct mach_header *)header->mhdr());
|
|
}
|
|
|
|
// Add it to the vector for future loads.
|
|
loadImageFuncs.append(func);
|
|
#endif
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* map_images_nolock
|
|
* 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.
|
|
*
|
|
* Locking: loadMethodLock(old) or runtimeLock(new) acquired by map_images.
|
|
**********************************************************************/
|
|
#if __OBJC2__
|
|
#include "objc-file.h"
|
|
#else
|
|
#include "objc-file-old.h"
|
|
#endif
|
|
|
|
void
|
|
map_images_nolock(unsigned mhCount, const char * const mhPaths[],
|
|
const struct mach_header * const mhdrs[])
|
|
{
|
|
static bool firstTime = YES;
|
|
header_info *hList[mhCount];
|
|
uint32_t hCount;
|
|
size_t selrefCount = 0;
|
|
|
|
// Perform first-time initialization if necessary.
|
|
// This function is called before ordinary library initializers.
|
|
// fixme defer initialization until an objc-using image is found?
|
|
if (firstTime) {
|
|
preopt_init();
|
|
}
|
|
|
|
if (PrintImages) {
|
|
_objc_inform("IMAGES: processing %u newly-mapped images...\n", mhCount);
|
|
}
|
|
|
|
|
|
// Find all images with Objective-C metadata.
|
|
hCount = 0;
|
|
|
|
// Count classes. Size various table based on the total.
|
|
int totalClasses = 0;
|
|
int unoptimizedTotalClasses = 0;
|
|
{
|
|
uint32_t i = mhCount;
|
|
while (i--) {
|
|
const headerType *mhdr = (const headerType *)mhdrs[i];
|
|
|
|
auto hi = addHeader(mhdr, mhPaths[i], totalClasses, unoptimizedTotalClasses);
|
|
if (!hi) {
|
|
// no objc data in this entry
|
|
continue;
|
|
}
|
|
|
|
if (mhdr->filetype == MH_EXECUTE) {
|
|
// Size some data structures based on main executable's size
|
|
#if __OBJC2__
|
|
// If dyld3 optimized the main executable, then there shouldn't
|
|
// be any selrefs needed in the dynamic map so we can just init
|
|
// to a 0 sized map
|
|
if ( !hi->hasPreoptimizedSelectors() ) {
|
|
size_t count;
|
|
_getObjc2SelectorRefs(hi, &count);
|
|
selrefCount += count;
|
|
_getObjc2MessageRefs(hi, &count);
|
|
selrefCount += count;
|
|
}
|
|
#else
|
|
_getObjcSelectorRefs(hi, &selrefCount);
|
|
#endif
|
|
|
|
#if SUPPORT_GC_COMPAT
|
|
// Halt if this is a GC app.
|
|
if (shouldRejectGCApp(hi)) {
|
|
_objc_fatal_with_reason
|
|
(OBJC_EXIT_REASON_GC_NOT_SUPPORTED,
|
|
OS_REASON_FLAG_CONSISTENT_FAILURE,
|
|
"Objective-C garbage collection "
|
|
"is no longer supported.");
|
|
}
|
|
#endif
|
|
}
|
|
|
|
hList[hCount++] = hi;
|
|
|
|
if (PrintImages) {
|
|
_objc_inform("IMAGES: loading image for %s%s%s%s%s\n",
|
|
hi->fname(),
|
|
mhdr->filetype == MH_BUNDLE ? " (bundle)" : "",
|
|
hi->info()->isReplacement() ? " (replacement)" : "",
|
|
hi->info()->hasCategoryClassProperties() ? " (has class properties)" : "",
|
|
hi->info()->optimizedByDyld()?" (preoptimized)":"");
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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.
|
|
if (firstTime) {
|
|
sel_init(selrefCount);
|
|
arr_init();
|
|
|
|
#if SUPPORT_GC_COMPAT
|
|
// Reject any GC images linked to the main executable.
|
|
// We already rejected the app itself above.
|
|
// Images loaded after launch will be rejected by dyld.
|
|
|
|
for (uint32_t i = 0; i < hCount; i++) {
|
|
auto hi = hList[i];
|
|
auto mh = hi->mhdr();
|
|
if (mh->filetype != MH_EXECUTE && shouldRejectGCImage(mh)) {
|
|
_objc_fatal_with_reason
|
|
(OBJC_EXIT_REASON_GC_NOT_SUPPORTED,
|
|
OS_REASON_FLAG_CONSISTENT_FAILURE,
|
|
"%s requires Objective-C garbage collection "
|
|
"which is no longer supported.", hi->fname());
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#if TARGET_OS_OSX
|
|
// Disable +initialize fork safety if the app is too old (< 10.13).
|
|
// Disable +initialize fork safety if the app has a
|
|
// __DATA,__objc_fork_ok section.
|
|
|
|
if (!dyld_program_sdk_at_least(dyld_platform_version_macOS_10_13)) {
|
|
DisableInitializeForkSafety = true;
|
|
if (PrintInitializing) {
|
|
_objc_inform("INITIALIZE: disabling +initialize fork "
|
|
"safety enforcement because the app is "
|
|
"too old.)");
|
|
}
|
|
}
|
|
|
|
for (uint32_t i = 0; i < hCount; i++) {
|
|
auto hi = hList[i];
|
|
auto mh = hi->mhdr();
|
|
if (mh->filetype != MH_EXECUTE) continue;
|
|
unsigned long size;
|
|
if (getsectiondata(hi->mhdr(), "__DATA", "__objc_fork_ok", &size)) {
|
|
DisableInitializeForkSafety = true;
|
|
if (PrintInitializing) {
|
|
_objc_inform("INITIALIZE: disabling +initialize fork "
|
|
"safety enforcement because the app has "
|
|
"a __DATA,__objc_fork_ok section");
|
|
}
|
|
}
|
|
break; // assume only one MH_EXECUTE image
|
|
}
|
|
#endif
|
|
|
|
}
|
|
|
|
if (hCount > 0) {
|
|
_read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
|
|
}
|
|
|
|
firstTime = NO;
|
|
|
|
// Call image load funcs after everything is set up.
|
|
for (auto func : loadImageFuncs) {
|
|
for (uint32_t i = 0; i < mhCount; i++) {
|
|
func(mhdrs[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* unmap_image_nolock
|
|
* 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.
|
|
*
|
|
* Locking: loadMethodLock(both) and runtimeLock(new) acquired by unmap_image.
|
|
**********************************************************************/
|
|
void
|
|
unmap_image_nolock(const struct mach_header *mh)
|
|
{
|
|
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->getNext()) {
|
|
if (hi->mhdr() == (const headerType *)mh) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!hi) return;
|
|
|
|
if (PrintImages) {
|
|
_objc_inform("IMAGES: unloading image for %s%s%s\n",
|
|
hi->fname(),
|
|
hi->mhdr()->filetype == MH_BUNDLE ? " (bundle)" : "",
|
|
hi->info()->isReplacement() ? " (replacement)" : "");
|
|
}
|
|
|
|
_unload_image(hi);
|
|
|
|
// Remove header_info from header list
|
|
removeHeader(hi);
|
|
free(hi);
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* static_init
|
|
* Run C++ static constructor functions.
|
|
* libc calls _objc_init() before dyld would call our static constructors,
|
|
* so we have to do it ourselves.
|
|
**********************************************************************/
|
|
static void static_init()
|
|
{
|
|
size_t count;
|
|
auto inits = getLibobjcInitializers(&_mh_dylib_header, &count);
|
|
for (size_t i = 0; i < count; i++) {
|
|
inits[i]();
|
|
}
|
|
#if __OBJC2__
|
|
auto offsets = getLibobjcInitializerOffsets(&_mh_dylib_header, &count);
|
|
for (size_t i = 0; i < count; i++) {
|
|
UnsignedInitializer init(offsets[i]);
|
|
init();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* _objc_atfork_prepare
|
|
* _objc_atfork_parent
|
|
* _objc_atfork_child
|
|
* Allow ObjC to be used between fork() and exec().
|
|
* libc requires this because it has fork-safe functions that use os_objects.
|
|
*
|
|
* _objc_atfork_prepare() acquires all locks.
|
|
* _objc_atfork_parent() releases the locks again.
|
|
* _objc_atfork_child() forcibly resets the locks.
|
|
**********************************************************************/
|
|
|
|
// Declare lock ordering.
|
|
#if LOCKDEBUG
|
|
__attribute__((constructor))
|
|
static void defineLockOrder()
|
|
{
|
|
// Every lock precedes crashlog_lock
|
|
// on the assumption that fatal errors could be anywhere.
|
|
lockdebug_lock_precedes_lock(&loadMethodLock, &crashlog_lock);
|
|
lockdebug_lock_precedes_lock(&classInitLock, &crashlog_lock);
|
|
#if __OBJC2__
|
|
lockdebug_lock_precedes_lock(&runtimeLock, &crashlog_lock);
|
|
lockdebug_lock_precedes_lock(&DemangleCacheLock, &crashlog_lock);
|
|
#else
|
|
lockdebug_lock_precedes_lock(&classLock, &crashlog_lock);
|
|
lockdebug_lock_precedes_lock(&methodListLock, &crashlog_lock);
|
|
lockdebug_lock_precedes_lock(&NXUniqueStringLock, &crashlog_lock);
|
|
lockdebug_lock_precedes_lock(&impLock, &crashlog_lock);
|
|
#endif
|
|
lockdebug_lock_precedes_lock(&selLock, &crashlog_lock);
|
|
#if CONFIG_USE_CACHE_LOCK
|
|
lockdebug_lock_precedes_lock(&cacheUpdateLock, &crashlog_lock);
|
|
#endif
|
|
lockdebug_lock_precedes_lock(&objcMsgLogLock, &crashlog_lock);
|
|
lockdebug_lock_precedes_lock(&AltHandlerDebugLock, &crashlog_lock);
|
|
lockdebug_lock_precedes_lock(&AssociationsManagerLock, &crashlog_lock);
|
|
SideTableLocksPrecedeLock(&crashlog_lock);
|
|
PropertyLocks.precedeLock(&crashlog_lock);
|
|
StructLocks.precedeLock(&crashlog_lock);
|
|
CppObjectLocks.precedeLock(&crashlog_lock);
|
|
|
|
// loadMethodLock precedes everything
|
|
// because it is held while +load methods run
|
|
lockdebug_lock_precedes_lock(&loadMethodLock, &classInitLock);
|
|
#if __OBJC2__
|
|
lockdebug_lock_precedes_lock(&loadMethodLock, &runtimeLock);
|
|
lockdebug_lock_precedes_lock(&loadMethodLock, &DemangleCacheLock);
|
|
#else
|
|
lockdebug_lock_precedes_lock(&loadMethodLock, &methodListLock);
|
|
lockdebug_lock_precedes_lock(&loadMethodLock, &classLock);
|
|
lockdebug_lock_precedes_lock(&loadMethodLock, &NXUniqueStringLock);
|
|
lockdebug_lock_precedes_lock(&loadMethodLock, &impLock);
|
|
#endif
|
|
lockdebug_lock_precedes_lock(&loadMethodLock, &selLock);
|
|
#if CONFIG_USE_CACHE_LOCK
|
|
lockdebug_lock_precedes_lock(&loadMethodLock, &cacheUpdateLock);
|
|
#endif
|
|
lockdebug_lock_precedes_lock(&loadMethodLock, &objcMsgLogLock);
|
|
lockdebug_lock_precedes_lock(&loadMethodLock, &AltHandlerDebugLock);
|
|
lockdebug_lock_precedes_lock(&loadMethodLock, &AssociationsManagerLock);
|
|
SideTableLocksSucceedLock(&loadMethodLock);
|
|
PropertyLocks.succeedLock(&loadMethodLock);
|
|
StructLocks.succeedLock(&loadMethodLock);
|
|
CppObjectLocks.succeedLock(&loadMethodLock);
|
|
|
|
// PropertyLocks and CppObjectLocks and AssociationManagerLock
|
|
// precede everything because they are held while objc_retain()
|
|
// or C++ copy are called.
|
|
// (StructLocks do not precede everything because it calls memmove only.)
|
|
auto PropertyAndCppObjectAndAssocLocksPrecedeLock = [&](const void *lock) {
|
|
PropertyLocks.precedeLock(lock);
|
|
CppObjectLocks.precedeLock(lock);
|
|
lockdebug_lock_precedes_lock(&AssociationsManagerLock, lock);
|
|
};
|
|
#if __OBJC2__
|
|
PropertyAndCppObjectAndAssocLocksPrecedeLock(&runtimeLock);
|
|
PropertyAndCppObjectAndAssocLocksPrecedeLock(&DemangleCacheLock);
|
|
#else
|
|
PropertyAndCppObjectAndAssocLocksPrecedeLock(&methodListLock);
|
|
PropertyAndCppObjectAndAssocLocksPrecedeLock(&classLock);
|
|
PropertyAndCppObjectAndAssocLocksPrecedeLock(&NXUniqueStringLock);
|
|
PropertyAndCppObjectAndAssocLocksPrecedeLock(&impLock);
|
|
#endif
|
|
PropertyAndCppObjectAndAssocLocksPrecedeLock(&classInitLock);
|
|
PropertyAndCppObjectAndAssocLocksPrecedeLock(&selLock);
|
|
#if CONFIG_USE_CACHE_LOCK
|
|
PropertyAndCppObjectAndAssocLocksPrecedeLock(&cacheUpdateLock);
|
|
#endif
|
|
PropertyAndCppObjectAndAssocLocksPrecedeLock(&objcMsgLogLock);
|
|
PropertyAndCppObjectAndAssocLocksPrecedeLock(&AltHandlerDebugLock);
|
|
|
|
SideTableLocksSucceedLocks(PropertyLocks);
|
|
SideTableLocksSucceedLocks(CppObjectLocks);
|
|
SideTableLocksSucceedLock(&AssociationsManagerLock);
|
|
|
|
PropertyLocks.precedeLock(&AssociationsManagerLock);
|
|
CppObjectLocks.precedeLock(&AssociationsManagerLock);
|
|
|
|
#if __OBJC2__
|
|
lockdebug_lock_precedes_lock(&classInitLock, &runtimeLock);
|
|
#endif
|
|
|
|
#if __OBJC2__ && defined(DARLING)
|
|
lockdebug_lock_precedes_lock(&runtimeLock, &objcMsgLogLock);
|
|
#endif
|
|
|
|
#if __OBJC2__
|
|
// Runtime operations may occur inside SideTable locks
|
|
// (such as storeWeak calling getMethodImplementation)
|
|
SideTableLocksPrecedeLock(&runtimeLock);
|
|
SideTableLocksPrecedeLock(&classInitLock);
|
|
// Some operations may occur inside runtimeLock.
|
|
lockdebug_lock_precedes_lock(&runtimeLock, &selLock);
|
|
#if CONFIG_USE_CACHE_LOCK
|
|
lockdebug_lock_precedes_lock(&runtimeLock, &cacheUpdateLock);
|
|
#endif
|
|
lockdebug_lock_precedes_lock(&runtimeLock, &DemangleCacheLock);
|
|
#else
|
|
// Runtime operations may occur inside SideTable locks
|
|
// (such as storeWeak calling getMethodImplementation)
|
|
SideTableLocksPrecedeLock(&methodListLock);
|
|
SideTableLocksPrecedeLock(&classInitLock);
|
|
// Method lookup and fixup.
|
|
lockdebug_lock_precedes_lock(&methodListLock, &classLock);
|
|
lockdebug_lock_precedes_lock(&methodListLock, &selLock);
|
|
#if CONFIG_USE_CACHE_LOCK
|
|
lockdebug_lock_precedes_lock(&methodListLock, &cacheUpdateLock);
|
|
#endif
|
|
lockdebug_lock_precedes_lock(&methodListLock, &impLock);
|
|
lockdebug_lock_precedes_lock(&classLock, &selLock);
|
|
lockdebug_lock_precedes_lock(&classLock, &cacheUpdateLock);
|
|
#endif
|
|
|
|
// Striped locks use address order internally.
|
|
SideTableDefineLockOrder();
|
|
PropertyLocks.defineLockOrder();
|
|
StructLocks.defineLockOrder();
|
|
CppObjectLocks.defineLockOrder();
|
|
}
|
|
// LOCKDEBUG
|
|
#endif
|
|
|
|
static bool ForkIsMultithreaded;
|
|
void _objc_atfork_prepare()
|
|
{
|
|
// Save threaded-ness for the child's use.
|
|
ForkIsMultithreaded = pthread_is_threaded_np();
|
|
|
|
lockdebug_assert_no_locks_locked();
|
|
lockdebug_setInForkPrepare(true);
|
|
|
|
loadMethodLock.lock();
|
|
PropertyLocks.lockAll();
|
|
CppObjectLocks.lockAll();
|
|
AssociationsManagerLock.lock();
|
|
SideTableLockAll();
|
|
classInitLock.enter();
|
|
#if __OBJC2__
|
|
runtimeLock.lock();
|
|
DemangleCacheLock.lock();
|
|
#else
|
|
methodListLock.lock();
|
|
classLock.lock();
|
|
NXUniqueStringLock.lock();
|
|
impLock.lock();
|
|
#endif
|
|
selLock.lock();
|
|
#if CONFIG_USE_CACHE_LOCK
|
|
cacheUpdateLock.lock();
|
|
#endif
|
|
objcMsgLogLock.lock();
|
|
AltHandlerDebugLock.lock();
|
|
StructLocks.lockAll();
|
|
crashlog_lock.lock();
|
|
|
|
lockdebug_assert_all_locks_locked();
|
|
lockdebug_setInForkPrepare(false);
|
|
}
|
|
|
|
void _objc_atfork_parent()
|
|
{
|
|
lockdebug_assert_all_locks_locked();
|
|
|
|
CppObjectLocks.unlockAll();
|
|
StructLocks.unlockAll();
|
|
PropertyLocks.unlockAll();
|
|
AssociationsManagerLock.unlock();
|
|
AltHandlerDebugLock.unlock();
|
|
objcMsgLogLock.unlock();
|
|
crashlog_lock.unlock();
|
|
loadMethodLock.unlock();
|
|
#if CONFIG_USE_CACHE_LOCK
|
|
cacheUpdateLock.unlock();
|
|
#endif
|
|
selLock.unlock();
|
|
SideTableUnlockAll();
|
|
#if __OBJC2__
|
|
DemangleCacheLock.unlock();
|
|
runtimeLock.unlock();
|
|
#else
|
|
impLock.unlock();
|
|
NXUniqueStringLock.unlock();
|
|
methodListLock.unlock();
|
|
classLock.unlock();
|
|
#endif
|
|
classInitLock.leave();
|
|
|
|
lockdebug_assert_no_locks_locked();
|
|
}
|
|
|
|
void _objc_atfork_child()
|
|
{
|
|
// Turn on +initialize fork safety enforcement if applicable.
|
|
if (ForkIsMultithreaded && !DisableInitializeForkSafety) {
|
|
MultithreadedForkChild = true;
|
|
}
|
|
|
|
lockdebug_assert_all_locks_locked();
|
|
|
|
CppObjectLocks.forceResetAll();
|
|
StructLocks.forceResetAll();
|
|
PropertyLocks.forceResetAll();
|
|
AssociationsManagerLock.forceReset();
|
|
AltHandlerDebugLock.forceReset();
|
|
objcMsgLogLock.forceReset();
|
|
crashlog_lock.forceReset();
|
|
loadMethodLock.forceReset();
|
|
#if CONFIG_USE_CACHE_LOCK
|
|
cacheUpdateLock.forceReset();
|
|
#endif
|
|
selLock.forceReset();
|
|
SideTableForceResetAll();
|
|
#if __OBJC2__
|
|
DemangleCacheLock.forceReset();
|
|
runtimeLock.forceReset();
|
|
#else
|
|
impLock.forceReset();
|
|
NXUniqueStringLock.forceReset();
|
|
methodListLock.forceReset();
|
|
classLock.forceReset();
|
|
#endif
|
|
classInitLock.forceReset();
|
|
|
|
lockdebug_assert_no_locks_locked();
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* _objc_init
|
|
* Bootstrap initialization. Registers our image notifier with dyld.
|
|
* Called by libSystem BEFORE library initialization time
|
|
**********************************************************************/
|
|
|
|
void _objc_init(void)
|
|
{
|
|
static bool initialized = false;
|
|
if (initialized) return;
|
|
initialized = true;
|
|
|
|
// fixme defer initialization until an objc-using image is found?
|
|
environ_init();
|
|
tls_init();
|
|
static_init();
|
|
runtime_init();
|
|
exception_init();
|
|
#if __OBJC2__
|
|
cache_t::init();
|
|
#endif
|
|
_imp_implementationWithBlock_init();
|
|
|
|
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
|
|
|
|
#if __OBJC2__
|
|
didCallDyldNotifyRegister = true;
|
|
#endif
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* _headerForAddress.
|
|
* addr can be a class or a category
|
|
**********************************************************************/
|
|
static const header_info *_headerForAddress(void *addr)
|
|
{
|
|
#if __OBJC2__
|
|
const char *segnames[] = { "__DATA", "__DATA_CONST", "__DATA_DIRTY" };
|
|
#else
|
|
const char *segnames[] = { "__OBJC" };
|
|
#endif
|
|
header_info *hi;
|
|
|
|
for (hi = FirstHeader; hi != NULL; hi = hi->getNext()) {
|
|
for (size_t i = 0; i < sizeof(segnames)/sizeof(segnames[0]); i++) {
|
|
unsigned long seg_size;
|
|
uint8_t *seg = getsegmentdata(hi->mhdr(), segnames[i], &seg_size);
|
|
if (!seg) continue;
|
|
|
|
// Is the class in this header?
|
|
if ((uint8_t *)addr >= seg && (uint8_t *)addr < seg + seg_size) {
|
|
return hi;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Not found
|
|
return 0;
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* _headerForClass
|
|
* Return the image header containing this class, or NULL.
|
|
* Returns NULL on runtime-constructed classes, and the NSCF classes.
|
|
**********************************************************************/
|
|
const header_info *_headerForClass(Class cls)
|
|
{
|
|
return _headerForAddress(cls);
|
|
}
|
|
|
|
|
|
/**********************************************************************
|
|
* 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.
|
|
**********************************************************************/
|
|
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 TARGET_OS_IPHONE
|
|
|
|
const char *__crashreporter_info__ = NULL;
|
|
|
|
const char *CRSetCrashLogMessage(const char *msg)
|
|
{
|
|
__crashreporter_info__ = msg;
|
|
return msg;
|
|
}
|
|
const char *CRGetCrashLogMessage(void)
|
|
{
|
|
return __crashreporter_info__;
|
|
}
|
|
|
|
#endif
|
|
|
|
// TARGET_OS_MAC
|
|
#else
|
|
|
|
|
|
#error unknown OS
|
|
|
|
|
|
#endif
|