Loading...
--- dyld/dyld-1340/dyld/PrebuiltObjC.cpp
+++ /dev/null
@@ -1,1457 +0,0 @@
-/*
- * Copyright (c) 2019-2020 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@
- */
-
-#include <TargetConditionals.h>
-
-#if !TARGET_OS_EXCLAVEKIT
-
-#include <assert.h>
-#include <unistd.h>
-#include <sys/types.h>
-
-#include "Defines.h"
-#include "Header.h"
-#include "Loader.h"
-#include "PrebuiltLoader.h"
-#include "JustInTimeLoader.h"
-#include "MachOFile.h"
-#include "BumpAllocator.h"
-#include "DyldProcessConfig.h"
-#include "DyldRuntimeState.h"
-#include "OptimizerObjC.h"
-#include "ObjCVisitor.h"
-#include "PerfectHash.h"
-#include "PrebuiltObjC.h"
-#include "objc-shared-cache.h"
-
-using mach_o::Header;
-
-#if SUPPORT_PREBUILTLOADERS || BUILDING_UNIT_TESTS || BUILDING_CACHE_BUILDER_UNIT_TESTS
-using dyld3::OverflowSafeArray;
-typedef dyld4::PrebuiltObjC::ObjCOptimizerImage ObjCOptimizerImage;
-
-// This holds all the maps we are going to serialize.
-namespace prebuilt_objc
-{
-
-void forEachSelectorStringEntry(const void* selMap, void (^handler)(const PrebuiltLoader::BindTargetRef& target))
-{
- // The on-disk map is really an ObjCSelectorMapOnDisk
- const ObjCSelectorMapOnDisk map(selMap);
- map.forEachEntry(^(const ObjCSelectorMapOnDisk::NodeT& node) {
- handler(node.first.stringTarget);
- });
-}
-
-#if SUPPORT_VM_LAYOUT
-
-const char* findSelector(dyld4::RuntimeState* state, const ObjCSelectorMapOnDisk& map,
- const char* selectorName)
-{
- auto it = map.find((void*)state, selectorName);
- if ( it == map.end() )
- return nullptr;
-
- return (const char*)it->first.stringTarget.value(*state);
-}
-
-void forEachClass(dyld4::RuntimeState* state, const ObjCClassMapOnDisk& classMap, const char* className,
- void (^handler)(const dyld3::Array<const PrebuiltLoader::BindTargetRef*>& values))
-{
- classMap.forEachEntry(state, className, ^(const dyld3::Array<const ObjCObjectOnDiskLocation*>& values) {
- if ( values.empty() )
- return;
-
- STACK_ALLOC_ARRAY(const PrebuiltLoader::BindTargetRef*, newValues, values.count());
- for ( const ObjCObjectOnDiskLocation* value : values )
- newValues.push_back(&value->objectLocation);
-
- handler(newValues);
- });
-}
-
-void forEachProtocol(dyld4::RuntimeState* state, const ObjCProtocolMapOnDisk& protocolMap, const char* protocolName,
- void (^handler)(const dyld3::Array<const PrebuiltLoader::BindTargetRef*>& values))
-{
- protocolMap.forEachEntry(state, protocolName, ^(const dyld3::Array<const ObjCObjectOnDiskLocation*>& values) {
- if ( values.empty() )
- return;
-
- STACK_ALLOC_ARRAY(const PrebuiltLoader::BindTargetRef*, newValues, values.count());
- for ( const ObjCObjectOnDiskLocation* value : values )
- newValues.push_back(&value->objectLocation);
-
- handler(newValues);
- });
-}
-
-#endif // SUPPORT_VM_LAYOUT
-
-void forEachClass(const void* classMap,
- void (^handler)(const PrebuiltLoader::BindTargetRef& nameTarget,
- const dyld3::Array<const PrebuiltLoader::BindTargetRef*>& values))
-{
- // The on-disk map is really an ObjCClassMapOnDisk
- const ObjCClassMapOnDisk map(classMap);
- map.forEachEntry(^(const ObjCStringKeyOnDisk& key, const dyld3::Array<const ObjCObjectOnDiskLocation*>& values) {
- STACK_ALLOC_ARRAY(const PrebuiltLoader::BindTargetRef*, newValues, values.count());
- for ( const ObjCObjectOnDiskLocation* value : values )
- newValues.push_back(&value->objectLocation);
- handler(key.stringTarget, newValues);
- });
-}
-
-void forEachProtocol(const void* protocolMap,
- void (^handler)(const PrebuiltLoader::BindTargetRef& nameTarget,
- const dyld3::Array<const PrebuiltLoader::BindTargetRef*>& values))
-{
- // The on-disk map is really an ObjCProtocolMapOnDisk
- const ObjCProtocolMapOnDisk map(protocolMap);
- map.forEachEntry(^(const ObjCStringKeyOnDisk& key, const dyld3::Array<const ObjCObjectOnDiskLocation*>& values) {
- STACK_ALLOC_ARRAY(const PrebuiltLoader::BindTargetRef*, newValues, values.count());
- for ( const ObjCObjectOnDiskLocation* value : values )
- newValues.push_back(&value->objectLocation);
- handler(key.stringTarget, newValues);
- });
-}
-
-uint64_t hashStringKey(const std::string_view& str)
-{
- return murmurHash(str.data(), (int)str.size(), 0);
-}
-
-} // namespace prebuilt_objc
-
-namespace dyld4 {
-
-//////////////////////// ObjCOptimizerImage /////////////////////////////////
-
-ObjCOptimizerImage::ObjCOptimizerImage(const JustInTimeLoader* jitLoader, uint64_t loadAddress, uint32_t pointerSize)
- : jitLoader(jitLoader)
- , pointerSize(pointerSize)
- , loadAddress(loadAddress)
-{
-}
-
-#if BUILDING_CACHE_BUILDER || BUILDING_CLOSURE_UTIL
-void ObjCOptimizerImage::calculateMissingWeakImports(RuntimeState& state)
-{
- const mach_o::MachOFileRef& mf = jitLoader->mf(state);
-
- // build targets table
- STACK_ALLOC_OVERFLOW_SAFE_ARRAY(bool, bindTargetsAreWeakImports, 512);
- STACK_ALLOC_OVERFLOW_SAFE_ARRAY(bool, overrideBindTargetsAreWeakImports, 16);
- __block bool foundMissingWeakImport = false;
- bool allowLazyBinds = false;
- JustInTimeLoader::CacheWeakDefOverride cacheWeakDefFixup = ^(uint32_t cachedDylibIndex, uint32_t cachedDylibVMOffset, const JustInTimeLoader::ResolvedSymbol& target) {};
- jitLoader->forEachBindTarget(diag, state, cacheWeakDefFixup, allowLazyBinds, ^(const JustInTimeLoader::ResolvedSymbol& target, bool& stop) {
- if ( (target.kind == Loader::ResolvedSymbol::Kind::bindAbsolute) && (target.targetRuntimeOffset == 0) ) {
- foundMissingWeakImport = true;
- bindTargetsAreWeakImports.push_back(true);
- }
- else {
- bindTargetsAreWeakImports.push_back(false);
- }
- }, ^(const JustInTimeLoader::ResolvedSymbol& target, bool& stop) {
- if ( (target.kind == Loader::ResolvedSymbol::Kind::bindAbsolute) && (target.targetRuntimeOffset == 0) ) {
- foundMissingWeakImport = true;
- overrideBindTargetsAreWeakImports.push_back(true);
- }
- else {
- overrideBindTargetsAreWeakImports.push_back(false);
- }
- });
- if ( diag.hasError() )
- return;
-
- if ( foundMissingWeakImport ) {
- jitLoader->withLayout(diag, state, ^(const mach_o::Layout& layout) {
- mach_o::Fixups fixups(layout);
-
- if ( mf->hasChainedFixups() ) {
- // walk all chains
- auto handler = ^(dyld3::MachOFile::ChainedFixupPointerOnDisk *fixupLocation,
- InputDylibVMAddress fixupVMAddr, uint16_t pointerFormat,
- bool &stopChain) {
- uint32_t bindOrdinal;
- int64_t addend;
- if ( fixupLocation->isBind(pointerFormat, bindOrdinal, addend) ) {
- if ( bindOrdinal < bindTargetsAreWeakImports.count() ) {
- if ( bindTargetsAreWeakImports[bindOrdinal] )
- missingWeakImports.insert(fixupVMAddr);
- }
- else {
- diag.error("out of range bind ordinal %d (max %llu)", bindOrdinal, bindTargetsAreWeakImports.count());
- stopChain = true;
- }
- }
- };
-
- fixups.withChainStarts(diag, ^(const dyld_chained_starts_in_image* startsInfo) {
- fixups.forEachFixupChainSegment(diag, startsInfo, ^(const dyld_chained_starts_in_segment *segInfo,
- uint32_t segIndex, bool &stopSegment) {
- InputDylibVMAddress segmentVMAddr(layout.segments[segIndex].vmAddr);
- auto adaptor = ^(dyld3::MachOFile::ChainedFixupPointerOnDisk *fixupLocation,
- uint64_t fixupSegmentOffset,
- bool &stopChain) {
- InputDylibVMAddress fixupVMAddr = segmentVMAddr + VMOffset(fixupSegmentOffset);
- handler(fixupLocation, fixupVMAddr, segInfo->pointer_format, stopChain);
- };
- fixups.forEachFixupInSegmentChains(diag, segInfo, segIndex, true, adaptor);
- });
- });
- if ( diag.hasError() )
- return;
- } else if ( mf->hasOpcodeFixups() ) {
- // process all bind opcodes
- fixups.forEachBindLocation_Opcodes(diag, ^(uint64_t runtimeOffset, uint32_t segmentIndex,
- unsigned targetIndex, bool& fixupsStop) {
- if ( targetIndex < bindTargetsAreWeakImports.count() ) {
- if ( bindTargetsAreWeakImports[targetIndex] ) {
- InputDylibVMAddress fixupVMAddr(layout.textUnslidVMAddr() + runtimeOffset);
- missingWeakImports.insert(fixupVMAddr);
- }
- }
- else {
- diag.error("out of range bind ordinal %d (max %llu)", targetIndex, bindTargetsAreWeakImports.count());
- fixupsStop = true;
- }
- }, ^(uint64_t runtimeOffset, uint32_t segmentIndex,
- unsigned overrideBindTargetIndex, bool& fixupsStop) {
- if ( overrideBindTargetIndex < overrideBindTargetsAreWeakImports.count() ) {
- if ( overrideBindTargetsAreWeakImports[overrideBindTargetIndex] ) {
- InputDylibVMAddress fixupVMAddr(layout.textUnslidVMAddr() + runtimeOffset);
- missingWeakImports.insert(fixupVMAddr);
- }
- }
- else {
- diag.error("out of range bind ordinal %d (max %llu)", overrideBindTargetIndex, overrideBindTargetsAreWeakImports.count());
- fixupsStop = true;
- }
- });
- if ( diag.hasError() )
- return;
- }
- else {
- // process external relocations
- fixups.forEachBindLocation_Relocations(diag, ^(uint64_t runtimeOffset, unsigned targetIndex, bool& fixupsStop) {
- if ( targetIndex < bindTargetsAreWeakImports.count() ) {
- if ( bindTargetsAreWeakImports[targetIndex] ) {
- InputDylibVMAddress fixupVMAddr(layout.textUnslidVMAddr() + runtimeOffset);
- missingWeakImports.insert(fixupVMAddr);
- }
- }
- else {
- diag.error("out of range bind ordinal %d (max %llu)", targetIndex, bindTargetsAreWeakImports.count());
- fixupsStop = true;
- }
- });
- if ( diag.hasError() )
- return;
- }
- });
- }
-}
-#endif // (BUILDING_CACHE_BUILDER || BUILDING_CLOSURE_UTIL)
-
-bool ObjCOptimizerImage::isNull(InputDylibVMAddress vmAddr, const void* address) const
-{
-#if BUILDING_CACHE_BUILDER || BUILDING_CLOSURE_UTIL
- return (missingWeakImports.find(vmAddr) != missingWeakImports.end());
-#elif BUILDING_DYLD
- // In dyld, we are live, so we can just check if we point to a null value
- uintptr_t* pointer = (uintptr_t*)address;
- return (*pointer == 0);
-#else
- // FIXME: Have we been slide or not in the non-dyld case?
- assert(0);
- return false;
-#endif
-}
-
-void ObjCOptimizerImage::visitReferenceToObjCSelector(const objc::SelectorHashTable* objcSelOpt,
- PrebuiltObjC::SelectorMapTy& appSelectorMap,
- VMOffset selectorReferenceRuntimeOffset, VMOffset selectorStringRuntimeOffset,
- const char* selectorString)
-{
-
- // fprintf(stderr, "selector: %p -> %p %s\n", (void*)selectorReferenceRuntimeOffset, (void*)selectorStringRuntimeOffset, selectorString);
- if ( const char* sharedCacheSelector = objcSelOpt->get(selectorString) ) {
- // We got the selector from the cache so add a fixup to point there.
- // We use an absolute bind here, to reference the offset from the shared cache selector table base
- uint64_t sharedCacheOffset = (uint64_t)sharedCacheSelector - (uint64_t)objcSelOpt;
- PrebuiltLoader::BindTargetRef bindTarget = PrebuiltLoader::BindTargetRef::makeAbsolute(sharedCacheOffset);
-
- //printf("Overriding fixup at 0x%08llX to cache offset 0x%08llX\n", selectorUseImageOffset, (uint64_t)objcSelOpt->getEntryForIndex(cacheSelectorIndex) - (uint64_t)state.config.dyldCache());
- selectorFixups.push_back(bindTarget);
- return;
- }
-
- // See if this selector is already in the app map from a previous image
- prebuilt_objc::ObjCStringKey selectorMapKey { selectorString };
- auto appSelectorIt = appSelectorMap.find(selectorMapKey);
- if ( appSelectorIt != appSelectorMap.end() ) {
- // This selector was found in a previous image, so use it here.
-
- //printf("Overriding fixup at 0x%08llX to other image\n", selectorUseImageOffset);
- selectorFixups.push_back(PrebuiltLoader::BindTargetRef(appSelectorIt->second.nameLocation));
- return;
- }
-
- // See if this selector is already in the map for this image
- prebuilt_objc::ObjCSelectorLocation selectorMapValue = { Loader::BindTarget() };
- auto itAndInserted = selectorMap.insert({ selectorMapKey, selectorMapValue });
- if ( itAndInserted.second ) {
- // We added the selector so its pointing in to our own image.
- Loader::BindTarget target;
- target.loader = jitLoader;
- target.runtimeOffset = selectorStringRuntimeOffset.rawValue();
- itAndInserted.first->second.nameLocation = target;
-
- // We'll add a fixup anyway as we want a sel ref fixup for every entry in the sel refs section
-
- //printf("Fixup at 0x%08llX to '%s' offset 0x%08llX\n", selectorUseImageOffset, findLoadedImage(target.image.imageNum).path(), target.image.offset);
- selectorFixups.push_back(PrebuiltLoader::BindTargetRef(target));
- return;
- }
-
- // This selector was found elsewhere in our image. As we want a fixup for every selref, we'll
- // add one here too
- Loader::BindTarget& target = itAndInserted.first->second.nameLocation;
-
- //printf("Overriding fixup at 0x%08llX to '%s' offset 0x%08llX\n", selectorUseImageOffset, findLoadedImage(target.image.imageNum).path(), target.image.offset);
- selectorFixups.push_back(PrebuiltLoader::BindTargetRef(target));
-}
-
-// Check if the given class is in an image loaded in the shared cache.
-// If so, add the class to the duplicate map
-static void checkForDuplicateClass(const VMAddress dyldCacheBaseAddress,
- const char* className, const objc::ClassHashTable* objcClassOpt,
- PrebuiltObjC::SharedCacheImagesMapTy& sharedCacheImagesMap,
- PrebuiltObjC::DuplicateClassesMapTy& duplicateSharedCacheClasses,
- ObjCOptimizerImage& image)
-{
- objcClassOpt->forEachClass(className,
- ^(uint64_t classCacheOffset, uint16_t dylibObjCIndex, bool &stopObjects) {
- // Check if this image is loaded
- if ( auto cacheIt = sharedCacheImagesMap.find(dylibObjCIndex); cacheIt != sharedCacheImagesMap.end() ) {
- const Loader* ldr = cacheIt->second.second;
-
- // We have a duplicate class, so check if we've already got it in our map.
- if ( duplicateSharedCacheClasses.find(className) == duplicateSharedCacheClasses.end() ) {
- // We haven't seen this one yet, so record it in the map for this image
- VMAddress cacheDylibUnslidVMAddr = cacheIt->second.first;
- VMAddress classVMAddr = dyldCacheBaseAddress + VMOffset(classCacheOffset);
- VMOffset classDylibVMOffset = classVMAddr - cacheDylibUnslidVMAddr;
- Loader::BindTarget classTarget = { ldr, classDylibVMOffset.rawValue() };
- image.duplicateSharedCacheClassMap.insert({ className, classTarget });
- }
-
- stopObjects = true;
- }
- });
-}
-
-void ObjCOptimizerImage::visitClass(const VMAddress dyldCacheBaseAddress,
- const objc::ClassHashTable* objcClassOpt,
- SharedCacheImagesMapTy& sharedCacheImagesMap,
- DuplicateClassesMapTy& duplicateSharedCacheClasses,
- InputDylibVMAddress classVMAddr, InputDylibVMAddress classNameVMAddr, const char* className)
-{
-
- // If the class also exists in a shared cache image which is loaded, then objc
- // would have found that one, regardless of load order.
- // In that case, we still add this class to the map, but also track which shared cache class it is a duplicate of
- checkForDuplicateClass(dyldCacheBaseAddress, className, objcClassOpt, sharedCacheImagesMap,
- duplicateSharedCacheClasses, *this);
-
- VMOffset classNameVMOffset = classNameVMAddr - loadAddress;
- VMOffset classObjectVMOffset = classVMAddr - loadAddress;
- classLocations.push_back({ className, classNameVMOffset, classObjectVMOffset });
-}
-
-static bool protocolIsInSharedCache(const char* protocolName,
- const objc::ProtocolHashTable* objcProtocolOpt,
- PrebuiltObjC::SharedCacheImagesMapTy& sharedCacheImagesMap)
-{
- __block bool foundProtocol = false;
- objcProtocolOpt->forEachProtocol(protocolName,
- ^(uint64_t classCacheOffset, uint16_t dylibObjCIndex, bool &stopObjects) {
- // Check if this image is loaded
- if ( auto cacheIt = sharedCacheImagesMap.find(dylibObjCIndex); cacheIt != sharedCacheImagesMap.end() ) {
- foundProtocol = true;
- stopObjects = true;
- }
- });
- return foundProtocol;
-}
-
-void ObjCOptimizerImage::visitProtocol(const objc::ProtocolHashTable* objcProtocolOpt,
- SharedCacheImagesMapTy& sharedCacheImagesMap,
- InputDylibVMAddress protocolVMAddr, InputDylibVMAddress protocolNameVMAddr,
- const char* protocolName)
-{
-
- uint32_t protocolIndex = (uint32_t)protocolISAFixups.count();
- protocolISAFixups.push_back(false);
-
- // If the protocol also exists in a shared cache image which is loaded, then objc
- // would have found that one, regardless of load order. So we can just skip this one.
- if ( protocolIsInSharedCache(protocolName, objcProtocolOpt, sharedCacheImagesMap) )
- return;
-
- VMOffset protocolNameVMOffset = protocolNameVMAddr - loadAddress;
- VMOffset protocolObjectVMOffset = protocolVMAddr - loadAddress;
- protocolLocations.push_back({ protocolName, protocolNameVMOffset, protocolObjectVMOffset });
-
- // Record which index this protocol uses in protocolISAFixups. Later we can change its entry if we
- // choose this protocol as the canonical definition.
- protocolIndexMap[protocolObjectVMOffset] = protocolIndex;
-}
-
-//////////////////////// ObjC Optimisations /////////////////////////////////
-
-// HACK!: dyld3 used to know if each image in a closure has been rebased or not when it was building the closure
-// Now we try to make good guesses based on whether its the shared cache or not, and which binary is executing this code
-#if 0
-static bool hasBeenRebased(const Loader* ldr)
-{
-#if BUILDING_DYLD
- // In dyld, we always run this analysis after everything has already been fixed up
- return true;
-#elif BUILDING_CLOSURE_UTIL
- // dyld_closure_util assumes that on disk binaries haven't had fixups applied
- return false;
-#else
- // In the shared cache builder, nothing has been rebased yet
- return false;
-#endif
-}
-#endif
-
-static objc_visitor::Visitor makeObjCVisitor(Diagnostics& diag, RuntimeState& state,
- const Loader* ldr)
-{
-
-#if POINTERS_ARE_UNSLID
- const dyld3::MachOAnalyzer* dylibMA = ldr->analyzer(state);
-
- const DyldSharedCache* dyldCache = (const DyldSharedCache*)state.config.dyldCache.addr;
- uint64_t sharedCacheRelativeSelectorBaseVMAddress = dyldCache->sharedCacheRelativeSelectorBaseVMAddress();
- objc_visitor::Visitor objcVisitor(dyldCache, dylibMA, VMAddress(sharedCacheRelativeSelectorBaseVMAddress));
- return objcVisitor;
-#elif SUPPORT_VM_LAYOUT
- const dyld3::MachOAnalyzer* dylibMA = ldr->analyzer(state);
-
- objc_visitor::Visitor objcVisitor(dylibMA);
- return objcVisitor;
-#else
- const dyld3::MachOFile* dylibMF = ldr->mf(state);
- return dylibMF->makeObjCVisitor(diag);
-#endif
-}
-
-static void optimizeObjCSelectors(RuntimeState& state,
- const objc::SelectorHashTable* objcSelOpt,
- PrebuiltObjC::SelectorMapTy& appSelectorMap,
- ObjCOptimizerImage& image)
-{
-
- const Header* hdr = (const Header*)image.jitLoader->mf(state);
- uint32_t pointerSize = hdr->pointerSize();
-
- // The legacy (objc1) codebase uses a bunch of sections we don't want to reason about. If we see them just give up.
- __block bool foundBadSection = false;
- hdr->forEachSection(^(const Header::SectionInfo& sectInfo, bool& stop) {
- if ( sectInfo.segmentName != "__OBJC" )
- return;
- if ( sectInfo.sectionName == "__module_info" ) {
- foundBadSection = true;
- stop = true;
- return;
- }
- if ( sectInfo.sectionName == "__protocol" ) {
- foundBadSection = true;
- stop = true;
- return;
- }
- if ( sectInfo.sectionName == "__message_refs" ) {
- foundBadSection = true;
- stop = true;
- return;
- }
- });
- if ( foundBadSection ) {
- image.diag.error("Old objc section");
- return;
- }
-
- // Visit the message refs
- // Note this isn't actually supported in libobjc any more. Its logic for deciding whether to support it is if this is true:
- // #if (defined(__x86_64__) && (TARGET_OS_OSX || TARGET_OS_SIMULATOR))
- // So to keep it simple, lets only do this walk if we are x86_64
- if ( hdr->isArch("x86_64") || hdr->isArch("x86_64h") ) {
- if ( hdr->hasObjCMessageReferences() ) {
- image.diag.error("Cannot handle message refs");
- return;
- }
- }
-
- // FIXME: Don't make a duplicate one of these if we can pass one in instead
- __block objc_visitor::Visitor objcVisitor = makeObjCVisitor(image.diag, state, image.jitLoader);
- if ( image.diag.hasError() )
- return;
-
- // We only record selector references for __objc_selrefs and pointer based method lists.
- // If we find a relative method list pointing outside of __objc_selrefs then we give up for now
- uint64_t selRefsStartRuntimeOffset = image.binaryInfo.selRefsRuntimeOffset;
- uint64_t selRefsEndRuntimeOffset = selRefsStartRuntimeOffset + (pointerSize * image.binaryInfo.selRefsCount);
- auto visitRelativeMethod = ^(const objc_visitor::Method& method, bool& stop) {
- VMAddress selectorRefVMAddress = method.getNameSelRefVMAddr(objcVisitor);
- VMOffset selectorReferenceRuntimeOffset = selectorRefVMAddress - VMAddress(image.loadAddress.rawValue());
- if ( (selectorReferenceRuntimeOffset.rawValue() < selRefsStartRuntimeOffset)
- || (selectorReferenceRuntimeOffset.rawValue() >= selRefsEndRuntimeOffset) ) {
- image.diag.error("Cannot handle relative method list pointing outside of __objc_selrefs");
- stop = true;
- }
- };
-
- auto visitMethodList = ^(const objc_visitor::MethodList& methodList,
- bool& hasPointerBasedMethodList, bool &stop) {
- if ( methodList.numMethods() == 0 )
- return;
-
- if ( methodList.usesRelativeOffsets() ) {
- // Check relative method lists
- uint32_t numMethods = methodList.numMethods();
- for ( uint32_t i = 0; i != numMethods; ++i ) {
- const objc_visitor::Method& method = methodList.getMethod(objcVisitor, i);
- visitRelativeMethod(method, stop);
- }
- } else {
- // Record if we found a pointer based method list. This lets us skip walking method lists later if
- // they are all relative method lists
- hasPointerBasedMethodList = true;
- }
- };
-
- if ( image.binaryInfo.classListCount != 0 ) {
- __block bool hasPointerBasedMethodList = false;
- objcVisitor.forEachClassAndMetaClass(^(const objc_visitor::Class &objcClass, bool &stopClass) {
- objc_visitor::MethodList methodList = objcClass.getBaseMethods(objcVisitor);
- visitMethodList(methodList, hasPointerBasedMethodList, stopClass);
- });
- image.binaryInfo.hasClassMethodListsToUnique = hasPointerBasedMethodList;
- image.binaryInfo.hasClassMethodListsToSetUniqued = hasPointerBasedMethodList;
- }
-
- if ( image.binaryInfo.categoryCount != 0 ) {
- __block bool hasPointerBasedMethodList = false;
- objcVisitor.forEachCategory(^(const objc_visitor::Category& objcCategory, bool &stopCategory) {
- objc_visitor::MethodList instanceMethodList = objcCategory.getInstanceMethods(objcVisitor);
- objc_visitor::MethodList classMethodList = objcCategory.getClassMethods(objcVisitor);
-
- visitMethodList(instanceMethodList, hasPointerBasedMethodList, stopCategory);
- if ( stopCategory )
- return;
-
- visitMethodList(classMethodList, hasPointerBasedMethodList, stopCategory);
- });
- image.binaryInfo.hasCategoryMethodListsToUnique = hasPointerBasedMethodList;
- image.binaryInfo.hasCategoryMethodListsToSetUniqued = hasPointerBasedMethodList;
- }
-
- if ( image.binaryInfo.protocolListCount != 0 ) {
- __block bool hasPointerBasedMethodList = false;
- objcVisitor.forEachProtocol(^(const objc_visitor::Protocol& objcProtocol, bool& stopProtocol) {
- objc_visitor::MethodList instanceMethodList = objcProtocol.getInstanceMethods(objcVisitor);
- objc_visitor::MethodList classMethodList = objcProtocol.getClassMethods(objcVisitor);
- objc_visitor::MethodList optionalInstanceMethodList = objcProtocol.getOptionalInstanceMethods(objcVisitor);
- objc_visitor::MethodList optionalClassMethodList = objcProtocol.getOptionalClassMethods(objcVisitor);
-
- visitMethodList(instanceMethodList, hasPointerBasedMethodList, stopProtocol);
- if ( stopProtocol )
- return;
-
- visitMethodList(classMethodList, hasPointerBasedMethodList, stopProtocol);
- if ( stopProtocol )
- return;
-
- visitMethodList(optionalInstanceMethodList, hasPointerBasedMethodList, stopProtocol);
- if ( stopProtocol )
- return;
-
- visitMethodList(optionalClassMethodList, hasPointerBasedMethodList, stopProtocol);
- });
- image.binaryInfo.hasProtocolMethodListsToUnique = hasPointerBasedMethodList;
- image.binaryInfo.hasProtocolMethodListsToSetUniqued = hasPointerBasedMethodList;
- }
-
- auto visitSelRef = ^(uint64_t selectorReferenceRuntimeOffset, uint64_t selectorStringRuntimeOffset,
- const char* selectorString) {
- // Note we don't check if the string is printable. We already checked earlier that this image doesn't have
- // Fairplay or protected segments, which would prevent seeing the strings.
- image.visitReferenceToObjCSelector(objcSelOpt, appSelectorMap,
- VMOffset(selectorReferenceRuntimeOffset),
- VMOffset(selectorStringRuntimeOffset), selectorString);
- };
-
- PrebuiltObjC::forEachSelectorReferenceToUnique(state, image.jitLoader, image.loadAddress.rawValue(), image.binaryInfo, visitSelRef);
-}
-
-static void optimizeObjCClasses(RuntimeState& state,
- const objc::ClassHashTable* objcClassOpt,
- PrebuiltObjC::SharedCacheImagesMapTy& sharedCacheImagesMap,
- PrebuiltObjC::DuplicateClassesMapTy& duplicateSharedCacheClasses,
- ObjCOptimizerImage& image)
-{
- if ( image.binaryInfo.classListCount == 0 )
- return;
-
-#if BUILDING_CACHE_BUILDER || BUILDING_CLOSURE_UTIL
- image.calculateMissingWeakImports(state);
- if ( image.diag.hasError() )
- return;
-#endif
-
- // FIXME: Don't make a duplicate one of these if we can pass one in instead
- __block objc_visitor::Visitor objcVisitor = makeObjCVisitor(image.diag, state, image.jitLoader);
- if ( image.diag.hasError() )
- return;
-
- VMAddress dyldCacheBaseAddress(state.config.dyldCache.unslidLoadAddress);
-
- // Note we skip metaclasses
- objcVisitor.forEachClass(^(const objc_visitor::Class& objcClass, bool &stopClass) {
- // Make sure the superclass pointer is not nil. Unless we are a root class as those don't have a superclass
- if ( !objcClass.isRootClass(objcVisitor) ) {
- metadata_visitor::ResolvedValue classSuperclassField = objcClass.getSuperclassField(objcVisitor);
- InputDylibVMAddress superclassFieldVMAddr(classSuperclassField.vmAddress().rawValue());
- if ( image.isNull(superclassFieldVMAddr, classSuperclassField.value()) ) {
- const char* className = objcClass.getName(objcVisitor);
- image.diag.error("Missing weak superclass of class %s in %s", className, image.jitLoader->path(state));
- return;
- }
- }
-
- // Does this class need to be fixed up for stable Swift ABI.
- // Note the order matches the objc runtime in that we always do this fix before checking for dupes,
- // but after excluding classes with missing weak superclasses.
- if ( objcClass.isUnfixedBackwardDeployingStableSwift(objcVisitor) ) {
- // Class really is stable Swift, pretending to be pre-stable.
- image.binaryInfo.hasClassStableSwiftFixups = true;
- }
-
- VMAddress classVMAddr = objcClass.getVMAddress();
- VMAddress classNameVMAddr = objcClass.getNameVMAddr(objcVisitor);
- // Note we don't check if the string is printable. We already checked earlier that this image doesn't have
- // Fairplay or protected segments, which would prevent seeing the strings.
- const char* className = objcClass.getName(objcVisitor);
- image.visitClass(dyldCacheBaseAddress, objcClassOpt, sharedCacheImagesMap, duplicateSharedCacheClasses,
- InputDylibVMAddress(classVMAddr.rawValue()), InputDylibVMAddress(classNameVMAddr.rawValue()),
- className);
- });
-}
-
-static void optimizeObjCProtocols(RuntimeState& state,
- const objc::ProtocolHashTable* objcProtocolOpt,
- PrebuiltObjC::SharedCacheImagesMapTy& sharedCacheImagesMap,
- ObjCOptimizerImage& image)
-{
- if ( image.binaryInfo.protocolListCount == 0 )
- return;
-
- image.protocolISAFixups.reserve(image.binaryInfo.protocolListCount);
-
- // FIXME: Don't make a duplicate one of these if we can pass one in instead
- __block objc_visitor::Visitor objcVisitor = makeObjCVisitor(image.diag, state, image.jitLoader);
- if ( image.diag.hasError() )
- return;
-
- objcVisitor.forEachProtocol(^(const objc_visitor::Protocol& objcProtocol, bool& stopProtocol) {
- std::optional<VMAddress> isaVMAddr = objcProtocol.getISAVMAddr(objcVisitor);
- if ( isaVMAddr.has_value() ) {
- // We can't optimize this protocol if it has an ISA as we want to override it
- image.diag.error("Protocol ISA must be null");
- stopProtocol = true;
- return;
- }
-
- VMAddress protocolVMAddr = objcProtocol.getVMAddress();
- VMAddress protocolNameVMAddr = objcProtocol.getNameVMAddr(objcVisitor);
- // Note we don't check if the string is printable. We already checked earlier that this image doesn't have
- // Fairplay or protected segments, which would prevent seeing the strings.
- const char* protocolName = objcProtocol.getName(objcVisitor);
-
- image.visitProtocol(objcProtocolOpt, sharedCacheImagesMap, InputDylibVMAddress(protocolVMAddr.rawValue()),
- InputDylibVMAddress(protocolNameVMAddr.rawValue()), protocolName);
- });
-}
-
-static void optimizeObjCProtocolReferences(RuntimeState& state,
- const objc::ProtocolHashTable* objcProtocolOpt,
- PrebuiltObjC::SharedCacheImagesMapTy& sharedCacheImagesMap,
- PrebuiltObjC::ProtocolMapTy& protocolMap,
- ObjCOptimizerImage& image)
-{
- if ( image.binaryInfo.protocolRefsCount == 0 )
- return;
-
- image.protocolFixups.reserve(image.binaryInfo.protocolRefsCount);
-
- // FIXME: Don't make a duplicate one of these if we can pass one in instead
- __block objc_visitor::Visitor objcVisitor = makeObjCVisitor(image.diag, state, image.jitLoader);
- if ( image.diag.hasError() )
- return;
-
- objcVisitor.forEachProtocolReference(^(metadata_visitor::ResolvedValue& protocolRefValue) {
- if ( image.diag.hasError() )
- return;
-
- // Follow the protocol reference to get to the actual protocol
- metadata_visitor::ResolvedValue protocolValue = objcVisitor.resolveRebase(protocolRefValue);
- objc_visitor::Protocol objcProtocol(protocolValue);
-
- const char* protocolName = objcProtocol.getName(objcVisitor);
-
- // Check if this protocol is in the map in the shared cache. If so use that one
- __block std::optional<uint64_t> protocolCacheOffset;
- objcProtocolOpt->forEachProtocol(protocolName,
- ^(uint64_t classCacheOffset, uint16_t dylibObjCIndex, bool& stopObjects) {
- // Check if this image is loaded
- if ( auto cacheIt = sharedCacheImagesMap.find(dylibObjCIndex); cacheIt != sharedCacheImagesMap.end() ) {
- protocolCacheOffset = classCacheOffset;
- stopObjects = true;
- }
- });
- if ( protocolCacheOffset.has_value() ) {
- // We use an absolute bind to point in to the shared cache protocols
- PrebuiltLoader::BindTargetRef bindTarget = PrebuiltLoader::BindTargetRef::makeAbsolute(protocolCacheOffset.value());
- image.protocolFixups.push_back(bindTarget);
- return;
- }
-
- // Not using the shared cache, so we should find the protocol in the map in the closure
- prebuilt_objc::ObjCStringKey key = { protocolName };
- auto nameIt = protocolMap.find(key);
- if ( nameIt == protocolMap.end() ) {
- // FIXME: What do we do here? The protocols are wrong? Skip this image for now.
- image.diag.error("Could not find protocol '%s'", protocolName);
- return;
- }
- const prebuilt_objc::ObjCObjectLocation& protocolLocation = nameIt->value;
- image.protocolFixups.push_back(protocolLocation.objectLocation);
- });
-}
-
-
-static void
-generateClassOrProtocolHashTable(PrebuiltObjC::ObjCStructKind objcKind,
- Array<ObjCOptimizerImage>& objcImages,
- const PrebuiltObjC::DuplicateClassesMapTy& duplicateSharedCacheClassMap,
- PrebuiltObjC::ObjectMapTy& objectMap, bool& hasDuplicates)
-{
- // Note we walk the images backwards as we want them in load order to match the order they are registered with objc
- for ( uint64_t imageIndex = 0, reverseIndex = (objcImages.count() - 1); imageIndex != objcImages.count(); ++imageIndex, --reverseIndex ) {
- if ( objcImages[reverseIndex].diag.hasError() )
- continue;
- ObjCOptimizerImage& image = objcImages[reverseIndex];
-
- if ( objcKind == PrebuiltObjC::ObjCStructKind::classes ) {
- for ( const ObjCOptimizerImage::ObjCObject& classLocation : image.classLocations ) {
- //uint64_t nameVMAddr = ma->preferredLoadAddress() + classImage.offsetOfClassNames + classNameTarget.classNameImageOffset;
- //printf("%s: 0x%08llx = '%s'\n", li.path(), nameVMAddr, className);
-
- // Also track the name
- PrebuiltLoader::BindTarget nameTarget = { image.jitLoader, classLocation.nameRuntimeOffset.rawValue() };
- PrebuiltLoader::BindTarget valueTarget = { image.jitLoader, classLocation.valueRuntimeOffset.rawValue() };
- prebuilt_objc::ObjCStringKey key = { classLocation.name };
- prebuilt_objc::ObjCObjectLocation value = {
- nameTarget,
- valueTarget
- };
- bool alreadyHaveNodeWithKey = false;
- auto objectIt = objectMap.insert({ key, value }, alreadyHaveNodeWithKey);
- if ( !alreadyHaveNodeWithKey ) {
- // Check if we have a duplicate. If we do, it will be on the last image which had a duplicate class name,
- // but as we walk images backwards, we'll see this before all other images with duplicates.
- // Note we only check for duplicates when we know we just inserted the object name in to the map, as this
- // ensure's that we only insert each duplicate once
- auto duplicateClassIt = duplicateSharedCacheClassMap.find(classLocation.name);
- if ( duplicateClassIt != duplicateSharedCacheClassMap.end() ) {
- // This is gross. Change this entry to the duplicate, and add a new one
- objectIt->value = { nameTarget, duplicateClassIt->second };
-
- bool unusedAlreadyHaveNodeWithKey;
- objectMap.insert({ key, value }, unusedAlreadyHaveNodeWithKey);
- hasDuplicates = true;
- }
- } else {
- // We didn't add the node, so we have duplicates
- hasDuplicates = true;
- }
- }
- }
-
- if ( objcKind == PrebuiltObjC::ObjCStructKind::protocols ) {
- for ( const ObjCOptimizerImage::ObjCObject& protocolLocation : image.protocolLocations ) {
- // Also track the name
- PrebuiltLoader::BindTarget nameTarget = { image.jitLoader, protocolLocation.nameRuntimeOffset.rawValue() };
- PrebuiltLoader::BindTarget valueTarget = { image.jitLoader, protocolLocation.valueRuntimeOffset.rawValue() };
- prebuilt_objc::ObjCStringKey key = { protocolLocation.name };
- prebuilt_objc::ObjCObjectLocation value = {
- nameTarget,
- valueTarget
- };
- bool alreadyHaveNodeWithKey = false;
- objectMap.insert({ key, value }, alreadyHaveNodeWithKey);
- if ( !alreadyHaveNodeWithKey ) {
- // We are processing protocols, and this is the first one we've seen, so track its ISA to be fixed up
- auto protocolIndexIt = image.protocolIndexMap.find(protocolLocation.valueRuntimeOffset);
- assert(protocolIndexIt != image.protocolIndexMap.end());
- image.protocolISAFixups[protocolIndexIt->second] = true;
- }
- }
- }
- }
-}
-
-//////////////////////// PrebuiltObjC /////////////////////////////////
-
-void PrebuiltObjC::commitImage(const ObjCOptimizerImage& image)
-{
- // As this image is still valid, then add its intermediate results to the main tables
- for ( const auto& stringAndDuplicate : image.duplicateSharedCacheClassMap ) {
- // Note we want to overwrite any existing entries here. We want the last seen
- // class with a duplicate to be in the map as writeClassOrProtocolHashTable walks the images
- // from back to front.
- duplicateSharedCacheClassMap[stringAndDuplicate.first] = stringAndDuplicate.second;
- }
-
- // Selector results
- // Note we don't need to add the selector binds here. Its easier just to process them later from each image
- for ( const auto& stringAndTarget : image.selectorMap ) {
- this->selectorMap[stringAndTarget.first] = stringAndTarget.second;
- }
-}
-
-uint32_t PrebuiltObjC::serializeSelectorMap(dyld4::BumpAllocator& alloc) const
-{
- // The key on the new map is the name bind target
- typedef prebuilt_objc::ObjCSelectorMapOnDisk::KeyType (^KeyFuncTy)(const SelectorMapTy::KeyType&, const SelectorMapTy::ValueType&);
- KeyFuncTy convertKey = ^(const SelectorMapTy::KeyType& key, const SelectorMapTy::ValueType& value) {
- return (prebuilt_objc::ObjCSelectorMapOnDisk::KeyType){ PrebuiltLoader::BindTargetRef(value.nameLocation) };
- };
-
- // The value on the new map is unused
- typedef prebuilt_objc::ObjCSelectorMapOnDisk::ValueType (^ValueFuncTy)(const SelectorMapTy::KeyType&, const SelectorMapTy::ValueType&);
- ValueFuncTy convertValue = ^(const SelectorMapTy::KeyType& key, const SelectorMapTy::ValueType& value) {
- return (prebuilt_objc::ObjCSelectorMapOnDisk::ValueType)0;
- };
-
- uint32_t offset = (uint32_t)alloc.size();
- this->selectorMap.serialize(alloc, convertKey, convertValue);
- return offset;
-}
-
-uint32_t PrebuiltObjC::serializeClassMap(dyld4::BumpAllocator& alloc) const
-{
- // The key on the new map is the name bind taret
- typedef prebuilt_objc::ObjCClassMapOnDisk::KeyType (^KeyFuncTy)(const ClassMapTy::KeyType&, const ClassMapTy::ValueType&);
- KeyFuncTy convertKey = ^(const ClassMapTy::KeyType& key, const ClassMapTy::ValueType& value) {
- return (prebuilt_objc::ObjCClassMapOnDisk::KeyType){ PrebuiltLoader::BindTargetRef(value.nameLocation) };
- };
-
- // The value on the new map is just the class impl
- typedef prebuilt_objc::ObjCClassMapOnDisk::ValueType (^ValueFuncTy)(const ClassMapTy::KeyType&, const ClassMapTy::ValueType&);
- ValueFuncTy convertValue = ^(const ClassMapTy::KeyType& key, const ClassMapTy::ValueType& value) {
- return (prebuilt_objc::ObjCClassMapOnDisk::ValueType){ PrebuiltLoader::BindTargetRef(value.objectLocation) };
- };
-
- uint32_t offset = (uint32_t)alloc.size();
- this->classMap.serialize(alloc, convertKey, convertValue);
- return offset;
-}
-
-uint32_t PrebuiltObjC::serializeProtocolMap(dyld4::BumpAllocator& alloc) const
-{
- // The key on the new map is the name bind taret
- typedef prebuilt_objc::ObjCProtocolMapOnDisk::KeyType (^KeyFuncTy)(const ProtocolMapTy::KeyType&, const ProtocolMapTy::ValueType&);
- KeyFuncTy convertKey = ^(const ProtocolMapTy::KeyType& key, const ProtocolMapTy::ValueType& value) {
- return (prebuilt_objc::ObjCProtocolMapOnDisk::KeyType){ PrebuiltLoader::BindTargetRef(value.nameLocation) };
- };
-
- // The value on the new map is just the protocol impl
- typedef prebuilt_objc::ObjCProtocolMapOnDisk::ValueType (^ValueFuncTy)(const ProtocolMapTy::KeyType&, const ProtocolMapTy::ValueType&);
- ValueFuncTy convertValue = ^(const ProtocolMapTy::KeyType& key, const ProtocolMapTy::ValueType& value) {
- return (prebuilt_objc::ObjCProtocolMapOnDisk::ValueType){ PrebuiltLoader::BindTargetRef(value.objectLocation) };
- };
-
- uint32_t offset = (uint32_t)alloc.size();
- this->protocolMap.serialize(alloc, convertKey, convertValue);
- return offset;
-}
-
-void PrebuiltObjC::generateHashTables()
-{
- generateClassOrProtocolHashTable(PrebuiltObjC::ObjCStructKind::classes, objcImages,
- duplicateSharedCacheClassMap, classMap, this->hasClassDuplicates);
-
- bool unusedHasProtocolDuplicates = false;
- generateClassOrProtocolHashTable(PrebuiltObjC::ObjCStructKind::protocols, objcImages,
- duplicateSharedCacheClassMap, protocolMap, unusedHasProtocolDuplicates);
-}
-
-void PrebuiltObjC::generatePerImageFixups(RuntimeState& state, uint32_t pointerSize)
-{
- // Find the largest JIT loader index so that we know how many images we might serialize
- uint16_t largestLoaderIndex = 0;
- for ( const Loader* l : state.loaded ) {
- if ( !l->isPrebuilt ) {
- JustInTimeLoader* jl = (JustInTimeLoader*)l;
- assert(jl->ref.app);
- largestLoaderIndex = std::max(largestLoaderIndex, jl->ref.index);
- }
- }
- ++largestLoaderIndex;
-
- imageFixups.reserve(largestLoaderIndex);
- for ( uint16_t i = 0; i != largestLoaderIndex; ++i ) {
- imageFixups.default_constuct_back();
- }
-
- // Add per-image fixups
- for ( ObjCOptimizerImage& image : objcImages ) {
- if ( image.diag.hasError() )
- continue;
-
- ObjCImageFixups& fixups = imageFixups[image.jitLoader->ref.index];
-
- // Copy all the binary info for use later when applying fixups
- fixups.binaryInfo = image.binaryInfo;
-
- // Protocol ISA references
- // These are a single boolean value for each protocol to identify if it is canonical or not
- // We convert from bool to uint8_t as that seems better for saving to disk.
- if ( !image.protocolISAFixups.empty() ) {
- fixups.protocolISAFixups.reserve(image.protocolISAFixups.count());
- for ( bool isCanonical : image.protocolISAFixups )
- fixups.protocolISAFixups.push_back(isCanonical ? 1 : 0);
- }
-
- // Selector references.
- // These are a BindTargetRef for every selector reference to fixup
- if ( !image.selectorFixups.empty() ) {
- fixups.selectorReferenceFixups.reserve(image.selectorFixups.count());
- for ( const PrebuiltLoader::BindTargetRef& target : image.selectorFixups ) {
- fixups.selectorReferenceFixups.push_back(target);
- }
- }
-
- // Protocol references.
- // These are a BindTargetRef for every protocol reference to fixup
- if ( !image.protocolFixups.empty() ) {
- fixups.protocolReferenceFixups.reserve(image.protocolFixups.count());
- for ( const PrebuiltLoader::BindTargetRef& target : image.protocolFixups ) {
- fixups.protocolReferenceFixups.push_back(target);
- }
- }
- }
-}
-
-__attribute__((noinline))
-static void forEachSelectorReferenceToUnique(objc_visitor::Visitor& objcVisitor,
- uint64_t loadAddress,
- const ObjCBinaryInfo& binaryInfo,
- void (^callback)(uint64_t selectorReferenceRuntimeOffset,
- uint64_t selectorStringRuntimeOffset,
- const char* selectorString))
-{
- if ( binaryInfo.selRefsCount != 0 ) {
- objcVisitor.forEachSelectorReference(^(VMAddress selRefVMAddr, VMAddress selRefTargetVMAddr,
- const char* selectorString) {
- VMOffset selectorReferenceRuntimeOffset = selRefVMAddr - VMAddress(loadAddress);
- VMOffset selectorStringRuntimeOffset = selRefTargetVMAddr - VMAddress(loadAddress);
- callback(selectorReferenceRuntimeOffset.rawValue(), selectorStringRuntimeOffset.rawValue(),
- selectorString);
- });
- }
-}
-
-__attribute__((noinline))
-static void forEachClassSelectorReferenceToUnique(objc_visitor::Visitor& objcVisitor,
- uint64_t loadAddress,
- const ObjCBinaryInfo& binaryInfo,
- void (^callback)(uint64_t selectorReferenceRuntimeOffset,
- uint64_t selectorStringRuntimeOffset,
- const char* selectorString))
-{
-
- // We only make the callback for method list selrefs which are not already covered by the __objc_selrefs section.
- // For pointer based method lists, this is all sel ref pointers.
- // For relative method lists, we should always point to the __objc_selrefs section. This was checked earlier, so
- // we skip this callback on relative method lists as we know here they must point to the (already uniqied) __objc_selrefs.
- auto visitPointerBasedMethod = ^(const objc_visitor::Method& method) {
- VMAddress nameVMAddr = method.getNameVMAddr(objcVisitor);
- VMAddress nameLocationVMAddr = method.getNameField(objcVisitor).vmAddress();
- const char* selectorString = method.getName(objcVisitor);
-
- VMOffset selectorStringRuntimeOffset = nameVMAddr - VMAddress(loadAddress);
- VMOffset selectorReferenceRuntimeOffset = nameLocationVMAddr - VMAddress(loadAddress);
- callback(selectorReferenceRuntimeOffset.rawValue(), selectorStringRuntimeOffset.rawValue(), selectorString);
- };
-
- auto visitMethodList = ^(const objc_visitor::MethodList& methodList) {
- if ( methodList.numMethods() == 0 )
- return;
- if ( methodList.usesRelativeOffsets() )
- return;
-
- // Check pointer based method lists
- uint32_t numMethods = methodList.numMethods();
- for ( uint32_t i = 0; i != numMethods; ++i ) {
- const objc_visitor::Method& method = methodList.getMethod(objcVisitor, i);
- visitPointerBasedMethod(method);
- }
- };
-
- if ( binaryInfo.hasClassMethodListsToUnique && (binaryInfo.classListCount != 0) ) {
- // FIXME: Use binaryInfo.classListRuntimeOffset and binaryInfo.classListCount
- objcVisitor.forEachClassAndMetaClass(^(const objc_visitor::Class &objcClass, bool &stopClass) {
- objc_visitor::MethodList methodList = objcClass.getBaseMethods(objcVisitor);
- visitMethodList(methodList);
- });
- }
-}
-
-__attribute__((noinline))
-static void forEachCategorySelectorReferenceToUnique(objc_visitor::Visitor& objcVisitor,
- uint64_t loadAddress,
- const ObjCBinaryInfo& binaryInfo,
- void (^callback)(uint64_t selectorReferenceRuntimeOffset,
- uint64_t selectorStringRuntimeOffset,
- const char* selectorString))
-{
- // We only make the callback for method list selrefs which are not already covered by the __objc_selrefs section.
- // For pointer based method lists, this is all sel ref pointers.
- // For relative method lists, we should always point to the __objc_selrefs section. This was checked earlier, so
- // we skip this callback on relative method lists as we know here they must point to the (already uniqied) __objc_selrefs.
- auto visitPointerBasedMethod = ^(const objc_visitor::Method& method) {
- VMAddress nameVMAddr = method.getNameVMAddr(objcVisitor);
- VMAddress nameLocationVMAddr = method.getNameField(objcVisitor).vmAddress();
- const char* selectorString = method.getName(objcVisitor);
-
- VMOffset selectorStringRuntimeOffset = nameVMAddr - VMAddress(loadAddress);
- VMOffset selectorReferenceRuntimeOffset = nameLocationVMAddr - VMAddress(loadAddress);
- callback(selectorReferenceRuntimeOffset.rawValue(), selectorStringRuntimeOffset.rawValue(), selectorString);
- };
-
- auto visitMethodList = ^(const objc_visitor::MethodList& methodList) {
- if ( methodList.numMethods() == 0 )
- return;
- if ( methodList.usesRelativeOffsets() )
- return;
-
- // Check pointer based method lists
- uint32_t numMethods = methodList.numMethods();
- for ( uint32_t i = 0; i != numMethods; ++i ) {
- const objc_visitor::Method& method = methodList.getMethod(objcVisitor, i);
- visitPointerBasedMethod(method);
- }
- };
-
- if ( binaryInfo.hasCategoryMethodListsToUnique && (binaryInfo.categoryCount != 0) ) {
- // FIXME: Use binaryInfo.categoryListRuntimeOffset and binaryInfo.categoryCount
- objcVisitor.forEachCategory(^(const objc_visitor::Category& objcCategory, bool &stopCategory) {
- objc_visitor::MethodList instanceMethodList = objcCategory.getInstanceMethods(objcVisitor);
- objc_visitor::MethodList classMethodList = objcCategory.getClassMethods(objcVisitor);
-
- visitMethodList(instanceMethodList);
- visitMethodList(classMethodList);
- });
- }
-}
-
-__attribute__((noinline))
-static void forEachProtocolSelectorReferenceToUnique(objc_visitor::Visitor& objcVisitor,
- uint64_t loadAddress,
- const ObjCBinaryInfo& binaryInfo,
- void (^callback)(uint64_t selectorReferenceRuntimeOffset,
- uint64_t selectorStringRuntimeOffset,
- const char* selectorString))
-{
- // We only make the callback for method list selrefs which are not already covered by the __objc_selrefs section.
- // For pointer based method lists, this is all sel ref pointers.
- // For relative method lists, we should always point to the __objc_selrefs section. This was checked earlier, so
- // we skip this callback on relative method lists as we know here they must point to the (already uniqied) __objc_selrefs.
- auto visitPointerBasedMethod = ^(const objc_visitor::Method& method) {
- VMAddress nameVMAddr = method.getNameVMAddr(objcVisitor);
- VMAddress nameLocationVMAddr = method.getNameField(objcVisitor).vmAddress();
- const char* selectorString = method.getName(objcVisitor);
-
- VMOffset selectorStringRuntimeOffset = nameVMAddr - VMAddress(loadAddress);
- VMOffset selectorReferenceRuntimeOffset = nameLocationVMAddr - VMAddress(loadAddress);
- callback(selectorReferenceRuntimeOffset.rawValue(), selectorStringRuntimeOffset.rawValue(), selectorString);
- };
-
- auto visitMethodList = ^(const objc_visitor::MethodList& methodList) {
- if ( methodList.numMethods() == 0 )
- return;
- if ( methodList.usesRelativeOffsets() )
- return;
-
- // Check pointer based method lists
- uint32_t numMethods = methodList.numMethods();
- for ( uint32_t i = 0; i != numMethods; ++i ) {
- const objc_visitor::Method& method = methodList.getMethod(objcVisitor, i);
- visitPointerBasedMethod(method);
- }
- };
-
- if ( binaryInfo.hasProtocolMethodListsToUnique && (binaryInfo.protocolListCount != 0) ) {
- // FIXME: Use binaryInfo.protocolListRuntimeOffset and binaryInfo.protocolListCount
- objcVisitor.forEachProtocol(^(const objc_visitor::Protocol& objcProtocol, bool& stopProtocol) {
- objc_visitor::MethodList instanceMethodList = objcProtocol.getInstanceMethods(objcVisitor);
- objc_visitor::MethodList classMethodList = objcProtocol.getClassMethods(objcVisitor);
- objc_visitor::MethodList optionalInstanceMethodList = objcProtocol.getOptionalInstanceMethods(objcVisitor);
- objc_visitor::MethodList optionalClassMethodList = objcProtocol.getOptionalClassMethods(objcVisitor);
-
- visitMethodList(instanceMethodList);
- visitMethodList(classMethodList);
- visitMethodList(optionalInstanceMethodList);
- visitMethodList(optionalClassMethodList);
- });
- }
-}
-
-// Visits each selector reference once, in order. Note the order this visits selector references has to
-// match for serializing/deserializing the PrebuiltLoader.
-void PrebuiltObjC::forEachSelectorReferenceToUnique(RuntimeState& state,
- const Loader* ldr,
- uint64_t loadAddress,
- const ObjCBinaryInfo& binaryInfo,
- void (^callback)(uint64_t selectorReferenceRuntimeOffset,
- uint64_t selectorStringRuntimeOffset,
- const char* selectorString))
-
-{
- // FIXME: Don't make a duplicate one of these if we can pass one in instead
- Diagnostics diag;
- __block objc_visitor::Visitor objcVisitor = makeObjCVisitor(diag, state, ldr);
- assert(!diag.hasError());
-
- dyld4::forEachSelectorReferenceToUnique(objcVisitor, loadAddress, binaryInfo, callback);
- dyld4::forEachClassSelectorReferenceToUnique(objcVisitor, loadAddress, binaryInfo, callback);
- dyld4::forEachCategorySelectorReferenceToUnique(objcVisitor, loadAddress, binaryInfo, callback);
- dyld4::forEachProtocolSelectorReferenceToUnique(objcVisitor, loadAddress, binaryInfo, callback);
-}
-
-static std::optional<VMOffset> getImageInfo(Diagnostics& diag, RuntimeState& state,
- const Loader* ldr, const Header* hdr)
-{
- __block std::optional<VMOffset> objcImageInfoRuntimeOffset;
- hdr->forEachSection(^(const Header::SectionInfo& sectionInfo, bool& stop) {
- if ( !sectionInfo.segmentName.starts_with("__DATA") )
- return;
- if ( sectionInfo.sectionName != "__objc_imageinfo" )
- return;
- if ( sectionInfo.size != 8 ) {
- stop = true;
- return;
- }
-
- // We can't just access the image info directly from the MachOFile. Instead we have to
- // use the layout to find the actual location of the segment, as we might be in the cache builder
- ldr->withLayout(diag, state, ^(const mach_o::Layout& layout) {
- const mach_o::SegmentLayout& segment = layout.segments[sectionInfo.segIndex];
- uint64_t offsetInSegment = sectionInfo.address - segment.vmAddr;
- const auto* imageInfo = (MachOAnalyzer::ObjCImageInfo*)(segment.buffer + offsetInSegment);
-
- if ( (imageInfo->flags & MachOAnalyzer::ObjCImageInfo::dyldPreoptimized) != 0 )
- return;
-
- objcImageInfoRuntimeOffset = VMOffset(sectionInfo.address - layout.textUnslidVMAddr());
- });
- stop = true;
- });
-
- return objcImageInfoRuntimeOffset;
-}
-
-static std::optional<VMOffset> getProtocolClassCacheOffset(RuntimeState& state)
-{
-#if BUILDING_CACHE_BUILDER || BUILDING_CACHE_BUILDER_UNIT_TESTS
- assert(state.config.dyldCache.objcProtocolClassCacheOffset != 0);
- return VMOffset(state.config.dyldCache.objcProtocolClassCacheOffset);
-#else
- // Make sure we have the pointers section with the pointer to the protocol class
- const void* objcOptPtrs = state.config.dyldCache.addr->objcOptPtrs();
- if ( objcOptPtrs == nullptr )
- return { };
-
- uint32_t pointerSize = state.mainExecutableLoader->loadAddress(state)->pointerSize();
- uint64_t classProtocolVMAddr = (pointerSize == 8) ? *(uint64_t*)objcOptPtrs : *(uint32_t*)objcOptPtrs;
-
-#if BUILDING_DYLD || BUILDING_UNIT_TESTS
- // As we are running in dyld/tests, the cache is live
-
-#if __has_feature(ptrauth_calls)
- // If we are on arm64e, the protocol ISA in the shared cache was signed. We don't
- // want the signature bits in the encoded value
- classProtocolVMAddr = (uint64_t)__builtin_ptrauth_strip((void*)classProtocolVMAddr, ptrauth_key_asda);
-#endif // __has_feature(ptrauth_calls)
-
- return VMOffset(classProtocolVMAddr - (uint64_t)state.config.dyldCache.addr);
-#elif BUILDING_CLOSURE_UTIL
- // FIXME: This assumes an on-disk cache
- classProtocolVMAddr = state.config.dyldCache.addr->makeVMAddrConverter(false).convertToVMAddr(classProtocolVMAddr);
- return VMOffset(classProtocolVMAddr - state.config.dyldCache.addr->unslidLoadAddress());
-#else
- // Running offline so the cache is not live
- //objcProtocolClassCacheOffset = classProtocolVMAddr - dyldCache->unslidLoadAddress();
-#error Unknown tool
-#endif // BUILDING_DYLD
-
-#endif // BUILDING_CACHE_BUILDER
-}
-
-void PrebuiltObjC::make(Diagnostics& diag, RuntimeState& state)
-{
-
- // If we have the read only data, make sure it has a valid selector table inside.
- const objc::ClassHashTable* objcClassOpt = state.config.dyldCache.objcClassHashTable;
- const objc::SelectorHashTable* objcSelOpt = state.config.dyldCache.objcSelectorHashTable;
- const objc::ProtocolHashTable* objcProtocolOpt = state.config.dyldCache.objcProtocolHashTable;
- const void* headerInfoRO = state.config.dyldCache.objcHeaderInfoRO;
- const void* headerInfoRW = state.config.dyldCache.objcHeaderInfoRW;
- VMAddress headerInfoROUnslidVMAddr(state.config.dyldCache.objcHeaderInfoROUnslidVMAddr);
-
- if ( !objcClassOpt || !objcSelOpt || !objcProtocolOpt)
- return;
-
- if ( std::optional<VMOffset> offset = getProtocolClassCacheOffset(state); offset.has_value() )
- objcProtocolClassCacheOffset = offset.value();
-
- for ( const Loader* ldr : state.delayLoaded ) {
- if ( ldr->isJustInTimeLoader() ) {
- // TODO: Handle apps which delay-init on-disk dylibs
- // This will lead to the closure not optimizing the objc, and libobjc will do it instead
- // in map_images(). This is safe as we tell objc (via dyldDoesObjCFixups()) whether we optimized or not
- return;
- }
- }
-
- // Find all the images with valid objc info
- SharedCacheImagesMapTy sharedCacheImagesMap;
-
- // Note we have done the delay-init partitioning by this point, so state.loaded is just the loaders
- // we known we need at launch. This is important for the shared cache in particular as the shared cache
- // classes/protocols are always preferred over the app ones, so a shared cache image being delayed or not
- // impacts the choice of classes/protocols. See protocolIsInSharedCache() for example.
- for ( const Loader* ldr : state.loaded ) {
- const Header* hdr = (const Header*)ldr->mf(state);
- uint32_t pointerSize = hdr->pointerSize();
-
- std::optional<VMOffset> objcImageInfoRuntimeOffset = getImageInfo(diag, state, ldr, hdr);
-
- if ( !objcImageInfoRuntimeOffset.has_value() )
- continue;
-
- if ( ldr->dylibInDyldCache ) {
- // Add shared cache images to a map so that we can see them later for looking up classes
- uint64_t dylibUnslidVMAddr = hdr->preferredLoadAddress();
-
- std::optional<uint16_t> objcIndex;
- objcIndex = objc::getPreoptimizedHeaderROIndex(headerInfoRO, headerInfoRW,
- headerInfoROUnslidVMAddr.rawValue(),
- dylibUnslidVMAddr,
- hdr->is64());
- if ( !objcIndex.has_value() )
- return;
- sharedCacheImagesMap.insert({ *objcIndex, { VMAddress(dylibUnslidVMAddr), ldr } });
- continue;
- }
-
- // If we have a root of libobjc, just give up for now
- if ( ldr->matchesPath(state, "/usr/lib/libobjc.A.dylib") )
- return;
-
- // dyld can see the strings in Fairplay binaries and protected segments, but other tools cannot.
- // Skip generating the PrebuiltObjC in these other cases
-#if !BUILDING_DYLD
- // Find FairPlay encryption range if encrypted
- uint32_t fairPlayFileOffset;
- uint32_t fairPlaySize;
- if ( hdr->isFairPlayEncrypted(fairPlayFileOffset, fairPlaySize) )
- return;
-
- __block bool hasProtectedSegment = false;
- hdr->forEachSegment(^(const Header::SegmentInfo& segInfo, bool& stop) {
- if ( segInfo.isProtected() ) {
- hasProtectedSegment = true;
- stop = true;
- }
- });
- if ( hasProtectedSegment )
- return;
-#endif
-
-#if BUILDING_CACHE_BUILDER
- // The cache builder will crash if it gets a binary with cheaper roots and bind opcodes
- // Given up if we see this case
- if ( hdr->hasOpcodeFixups() )
- return;
-#endif
-
- // This image is good so record it for use later.
- objcImages.emplace_back((const JustInTimeLoader*)ldr, hdr->preferredLoadAddress(), pointerSize);
- ObjCOptimizerImage& image = objcImages.back();
- image.jitLoader = (const JustInTimeLoader*)ldr;
-
- // Set the offset to the objc image info
- image.binaryInfo.imageInfoRuntimeOffset = objcImageInfoRuntimeOffset->rawValue();
-
- // Get the range of a section which is required to contain pointers, i.e., be pointer sized.
- auto getPointerBasedSection = ^(const char* name, uint64_t& runtimeOffset, uint32_t& pointerCount) {
- uint64_t offset;
- uint64_t count;
- if ( hdr->findObjCDataSection(name, offset, count) ) {
- if ( (count % pointerSize) != 0 ) {
- image.diag.error("Invalid objc pointer section size");
- return;
- }
- runtimeOffset = offset;
- pointerCount = (uint32_t)count / pointerSize;
- }
- else {
- runtimeOffset = 0;
- pointerCount = 0;
- }
- };
-
- // Find the offsets to all other sections we need for the later optimizations
- getPointerBasedSection("__objc_selrefs", image.binaryInfo.selRefsRuntimeOffset, image.binaryInfo.selRefsCount);
- getPointerBasedSection("__objc_classlist", image.binaryInfo.classListRuntimeOffset, image.binaryInfo.classListCount);
- getPointerBasedSection("__objc_catlist", image.binaryInfo.categoryListRuntimeOffset, image.binaryInfo.categoryCount);
- getPointerBasedSection("__objc_protolist", image.binaryInfo.protocolListRuntimeOffset, image.binaryInfo.protocolListCount);
- getPointerBasedSection("__objc_protorefs", image.binaryInfo.protocolRefsRuntimeOffset, image.binaryInfo.protocolRefsCount);
- }
-
- for ( ObjCOptimizerImage& image : objcImages ) {
- if ( image.diag.hasError() )
- continue;
-
- optimizeObjCClasses(state, objcClassOpt, sharedCacheImagesMap, duplicateSharedCacheClassMap, image);
- if ( image.diag.hasError() )
- continue;
-
- optimizeObjCProtocols(state, objcProtocolOpt, sharedCacheImagesMap, image);
- if ( image.diag.hasError() )
- continue;
-
- optimizeObjCSelectors(state, objcSelOpt, selectorMap, image);
- if ( image.diag.hasError() )
- continue;
-
- commitImage(image);
- }
-
- // If we successfully analyzed the classes and selectors, we can now make the maps
- generateHashTables();
-
- // Once we have the hash tables with the canonical protocols, we can generate the fixups
- // for the protorefs, which need to point to the canonical protocol
- for ( ObjCOptimizerImage& image : objcImages ) {
- if ( image.diag.hasError() )
- continue;
-
- optimizeObjCProtocolReferences(state, objcProtocolOpt, sharedCacheImagesMap, protocolMap, image);
- }
-
- uint32_t pointerSize = state.mainExecutableLoader->mf(state)->pointerSize();
- generatePerImageFixups(state, pointerSize);
-
- builtObjC = true;
-}
-
-uint32_t PrebuiltObjC::serializeFixups(const Loader& jitLoader, BumpAllocator& allocator)
-{
- if ( !builtObjC )
- return 0;
-
- assert(jitLoader.ref.app);
- uint16_t index = jitLoader.ref.index;
-
- const ObjCImageFixups& fixups = imageFixups[index];
-
- if ( fixups.binaryInfo.imageInfoRuntimeOffset == 0 ) {
- // No fixups to apply
- return 0;
- }
-
- uint32_t serializationStart = (uint32_t)allocator.size();
- BumpAllocatorPtr<ObjCBinaryInfo> fixupInfo(allocator, serializationStart);
-
- allocator.append(&fixups.binaryInfo, sizeof(fixups.binaryInfo));
-
- // Protocols
- if ( !fixups.protocolISAFixups.empty() ) {
- // If we have protocol fixups, then we must have 1 for every protocol in this image.
- assert(fixups.protocolISAFixups.count() == fixups.binaryInfo.protocolListCount);
-
- uint16_t protocolArrayOff = allocator.size() - serializationStart;
- fixupInfo->protocolFixupsOffset = protocolArrayOff;
- allocator.zeroFill(fixups.protocolISAFixups.count() * sizeof(uint8_t));
- allocator.align(8);
- BumpAllocatorPtr<uint8_t> protocolArray(allocator, serializationStart + protocolArrayOff);
- memcpy(protocolArray.get(), fixups.protocolISAFixups.data(), (size_t)(fixups.protocolISAFixups.count() * sizeof(uint8_t)));
- }
-
- // Selector references
- if ( !fixups.selectorReferenceFixups.empty() ) {
- uint64_t selectorsArrayOff = allocator.size() - serializationStart;
- fixupInfo->selectorReferencesFixupsOffset = (uint32_t)selectorsArrayOff;
- fixupInfo->selectorReferencesFixupsCount = (uint32_t)fixups.selectorReferenceFixups.count();
- allocator.zeroFill(fixups.selectorReferenceFixups.count() * sizeof(PrebuiltLoader::BindTargetRef));
- BumpAllocatorPtr<uint8_t> selectorsArray(allocator, serializationStart + selectorsArrayOff);
- memcpy(selectorsArray.get(), fixups.selectorReferenceFixups.data(), (size_t)(fixups.selectorReferenceFixups.count() * sizeof(PrebuiltLoader::BindTargetRef)));
- }
-
- // Protocol references
- if ( !fixups.protocolReferenceFixups.empty() ) {
- uint64_t protocolsArrayOff = allocator.size() - serializationStart;
- fixupInfo->protocolReferencesFixupsOffset = (uint32_t)protocolsArrayOff;
- fixupInfo->protocolReferencesFixupsCount = (uint32_t)fixups.protocolReferenceFixups.count();
- allocator.zeroFill(fixups.protocolReferenceFixups.count() * sizeof(PrebuiltLoader::BindTargetRef));
- BumpAllocatorPtr<uint8_t> protocolsArray(allocator, serializationStart + protocolsArrayOff);
- memcpy(protocolsArray.get(), fixups.protocolReferenceFixups.data(), (size_t)(fixups.protocolReferenceFixups.count() * sizeof(PrebuiltLoader::BindTargetRef)));
- }
-
- return serializationStart;
-}
-
-} // namespace dyld4
-#endif // SUPPORT_PREBUILTLOADERS || BUILDING_UNIT_TESTS || BUILDING_CACHE_BUILDER_UNIT_TESTS
-
-#endif // !TARGET_OS_EXCLAVEKIT
-