#include #include #include #include #include "mach_inject.h" #include "SCPatchPrivate.h" #include "SCPatchCommon.h" #include "SCPatchMessages.h" #undef USE_CFBUNDLE #undef USE_CFRUNLOOP #undef CHECK_SYMBOL_REFERENCES #undef SCPL_DEBUG_LOG #ifdef SCPL_DEBUG_LOG #define SCPLLog(...) syslog(LOG_ALERT, "SCPL: "__VA_ARGS__); #else #define SCPLLog(...) ; #endif //------------------------------------------------------------------------------------------------------------- static void SendError(HFSUniStr255 *uniBundleID, HFSUniStr255 *uniControllerBundleID, mach_error_t err) { SInt32 result = kCFMessagePortTransportError; CFStringRef clientBundleID, controllerBundleID; OSType sig = 'SCPM'; long descSize; Ptr descData; CFDataRef dataRef; CFMessagePortRef portRef; AEDesc desc; if((clientBundleID = CFStringCreateWithCharacters(NULL, uniBundleID->unicode, uniBundleID->length)) != NULL && (controllerBundleID = CFStringCreateWithCharacters(NULL, uniControllerBundleID->unicode, uniControllerBundleID->length)) != NULL && AEBuildAppleEvent(kSCMessageClass, kSCLoadResult, typeApplSignature, &sig, sizeof(sig), kAutoGenerateReturnID, kAnyTransactionID, &desc, NULL, "bndl:TEXT(@), err :long(@)", CFStringGetCStringPtr(clientBundleID, kCFStringEncodingMacRoman), err) == noErr && (descSize = AEGetDescDataSize(&desc)) > 0 && (descData = NewPtr(descSize)) != NULL && AEGetDescData(&desc, descData, descSize) == noErr && (dataRef = CFDataCreate(NULL, (unsigned char*)descData, descSize)) != NULL) { if((portRef = CFMessagePortCreateRemote(NULL, controllerBundleID)) != NULL) { result = CFMessagePortSendRequest(portRef, kSCMessageClass, dataRef, 5, 0, NULL, NULL); CFRelease(portRef); } CFRelease(dataRef); CFRelease(clientBundleID); CFRelease(controllerBundleID); DisposePtr(descData); } else { syslog(LOG_ALERT, "SCPL: unable to send notification of patch loading error %d\n", err); } } //------------------------------------------------------------------------------------------------------------- // Yes, this is ugly, but it avoids runtime dyld errors caused by loading Cocoa code into a Carbon-only app, // and other such messes. static Boolean CanLoadImage(NSObjectFileImage image) { #ifdef CHECK_SYMBOL_REFERENCES UInt32 ii, symbolCount = NSSymbolReferenceCountInObjectFileImage(image); for(ii = 0; ii < symbolCount; ii++) { if(!NSIsSymbolNameDefined(NSSymbolReferenceNameInObjectFileImage(image, ii, NULL))) { SCPLLog("failed to find %s\n", NSSymbolReferenceNameInObjectFileImage(image, ii, NULL)); return false; } } #endif return true; } //------------------------------------------------------------------------------------------------------------- #ifdef USE_CFRUNLOOP void SCPatchObserver(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *paramBlock) #else pascal void SCPatchTimerHandler(EventLoopTimerRef inTimer, void *paramBlock) #endif { SCPatchLoaderParams *params = (SCPatchLoaderParams *)paramBlock; mach_error_t err = err_none; short ii; SCPLLog("loading patches in thread %x\n", pthread_self()); #ifdef USE_CFRUNLOOP CFRunLoopObserverInvalidate(observer); #endif for(ii = 0; ii < params->patchCount; ii++) { SCPatchParams patchParams; SCPatchEntry patchCode = NULL; HFSUniStr255 *bundleID = SCPatchGetHFSUniStrPointer(params, ii); char *urlData = SCPatchGetStringPointer(params, ii); #ifdef USE_CFBUNDLE CFURLRef url = NULL; CFBundleRef bundle = NULL; CFStringRef urlString = NULL; if((urlString = CFStringCreateWithCString(NULL, urlData, kCFStringEncodingUTF8)) != NULL && (url = CFURLCreateWithString(NULL, urlString, NULL)) != NULL && (bundle = CFBundleCreate(NULL, url)) != NULL && CFBundleLoadExecutable(bundle)) { if((patchCode = (SCPatchEntry)CFBundleGetFunctionPointerForName(bundle, CFSTR("SCPatchInit"))) == NULL) patchCode = (SCPatchEntry)CFBundleGetFunctionPointerForName(bundle, CFSTR("_Z11SCPatchInitP13SCPatchParams")); if(patchCode) err = err_none; else err = err_couldnt_find_injectedThread_symbol; } else { err = err_couldnt_load_injection_bundle; } #else NSObjectFileImage image; NSModule module; NSSymbol symbol; if(NSCreateObjectFileImageFromFile(urlData, &image) == NSObjectFileImageSuccess && CanLoadImage(image) && (module = NSLinkModule(image, urlData, NSLINKMODULE_OPTION_BINDNOW | NSLINKMODULE_OPTION_PRIVATE | NSLINKMODULE_OPTION_RETURN_ON_ERROR)) != NULL) { if((symbol = NSLookupSymbolInModule(module, "_SCPatchInit")) == NULL) symbol = NSLookupSymbolInModule(module, "__Z11SCPatchInitP13SCPatchParams"); if(symbol) patchCode = (SCPatchEntry)NSAddressOfSymbol(symbol); if(patchCode) err = err_none; else err = err_couldnt_find_injectedThread_symbol; } else { err = err_couldnt_load_injection_bundle; } #endif // Run the startup code if(!err) { // Make copies of the params for the patch code patchParams.version = 0; patchParams.parent = params->parent; patchParams.parentBundleID = params->parentBundleID; patchParams.patchBundleID = *bundleID; // CFURLGetFSRef(url, &patchParams.patchRef); SCPLLog("calling patch init code\n"); err = patchCode(&patchParams); } else { char *name = rindex(urlData, '/'); SCPLLog("error 0x%x loading patch \"%s\"\n", err, name ? name + 1 : urlData); } #ifdef USE_CFBUNDLE if(url) CFRelease(url); if(urlString); CFRelease(urlString); #endif SendError(bundleID, ¶ms->parentBundleID, err); } } //------------------------------------------------------------------------------------------------------------- void *SCPatchThreadEntry( void *paramBlock ) { // Now that we've got a pthread, we have to hop onto the main event loop to do our code loading so that // we don't screw up dyld or Cocoa apps that don't have multithreading turned on. #ifdef USE_CFRUNLOOP CFRunLoopObserverContext context = { 0 }; CFRunLoopObserverRef observer; CFRunLoopRef runLoop; if((runLoop = (CFRunLoopRef)GetCFRunLoopFromEventLoop(GetMainEventLoop())) != NULL) { context.info = paramBlock; observer = CFRunLoopObserverCreate (kCFAllocatorDefault, kCFRunLoopAllActivities, false, 0, SCPatchObserver + ((SCPatchLoaderParams *)paramBlock)->codeOffset, &context); CFRunLoopAddObserver(runLoop, observer, kCFRunLoopCommonModes); } #else InstallEventLoopTimer(GetMainEventLoop(), 0, 0, NewEventLoopTimerUPP(SCPatchTimerHandler + ((SCPatchLoaderParams *)paramBlock)->codeOffset), paramBlock, NULL); #endif return 0; } //------------------------------------------------------------------------------------------------------------- mach_error_t INJECT_ENTRY( ptrdiff_t codeOffset, void *paramBlock, size_t paramSize, char *dummy_pthread_struct ) { pthread_t thread; pthread_attr_t attr; struct sched_param sched; int policy; #if defined (__i386__) // On intel, per-pthread data is a zone of data that must be allocated. // if not, all function trying to access per-pthread data (all mig functions for instance) // will crash. extern void __pthread_set_self(char*); __pthread_set_self(dummy_pthread_struct); #endif // We need to fix up function addresses in gcc code, but not for code generated by CW. // It turns out that this is not needed for gcc 4.0 //#ifdef __MWERKS__ codeOffset = 0; //#endif // Stash the code offset where we can get at it later. ((SCPatchLoaderParams *)paramBlock)->codeOffset = codeOffset; // OK, first we need a pthread so that CoreFoundation will work correctly // (it uses pthread tokens to make itself thread-safe). pthread_attr_init(&attr); pthread_attr_getschedpolicy(&attr, &policy); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED); sched.sched_priority = sched_get_priority_max(policy); pthread_attr_setschedparam(&attr, &sched); pthread_create(&thread, &attr, SCPatchThreadEntry + codeOffset, (void *)paramBlock); pthread_attr_destroy(&attr); thread_terminate(mach_thread_self()); return err_none; }