mirror of
https://github.com/Karmaz95/Snake_Apple.git
synced 2026-03-30 14:00:16 +02:00
3250 lines
154 KiB
C++
3250 lines
154 KiB
C++
// Source (dyld-1125.5) : https://github.com/apple-oss-distributions/dyld/blob/main/dyld/Loader.cpp
|
|
/*
|
|
* Copyright (c) 2019 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 <unistd.h>
|
|
#include <fcntl.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#include <sys/errno.h>
|
|
#include <sys/mman.h>
|
|
#endif
|
|
|
|
#include <assert.h>
|
|
#include <mach-o/nlist.h>
|
|
|
|
#include "Defines.h"
|
|
#include "MachOAnalyzer.h"
|
|
#include "Loader.h"
|
|
#include "JustInTimeLoader.h"
|
|
#include "PrebuiltLoader.h"
|
|
#include "PremappedLoader.h"
|
|
#include "DyldRuntimeState.h"
|
|
#include "DyldProcessConfig.h"
|
|
#include "StringUtils.h"
|
|
#if BUILDING_DYLD && SUPPORT_ROSETTA
|
|
#include "RosettaSupport.h"
|
|
#endif
|
|
#include "Tracing.h"
|
|
#include "Utils.h"
|
|
|
|
#ifndef VM_PROT_TPRO
|
|
#define VM_PROT_TPRO 0x200
|
|
#endif
|
|
|
|
#if !TARGET_OS_EXCLAVEKIT
|
|
#if __has_include(<System/mach/dyld_pager.h>)
|
|
#include <System/mach/dyld_pager.h>
|
|
// this #define can be removed when rdar://92861504 is fixed
|
|
#ifndef MWL_MAX_REGION_COUNT
|
|
#define MWL_MAX_REGION_COUNT 5
|
|
#endif
|
|
#else
|
|
struct mwl_region {
|
|
int mwlr_fd; /* fd of file file to over map */
|
|
vm_prot_t mwlr_protections;/* protections for new overmapping */
|
|
off_t mwlr_file_offset;/* offset in file of start of mapping */
|
|
mach_vm_address_t mwlr_address; /* start address of existing region */
|
|
mach_vm_size_t mwlr_size; /* size of existing region */
|
|
};
|
|
|
|
#define MWL_INFO_VERS 7
|
|
struct mwl_info_hdr {
|
|
uint32_t mwli_version; /* version of info blob, currently 7 */
|
|
uint16_t mwli_page_size; /* 0x1000 or 0x4000 (for sanity checking) */
|
|
uint16_t mwli_pointer_format; /* DYLD_CHAINED_PTR_* value */
|
|
uint32_t mwli_binds_offset; /* offset within this blob of bind pointers table */
|
|
uint32_t mwli_binds_count; /* number of pointers in bind pointers table (for range checks) */
|
|
uint32_t mwli_chains_offset; /* offset within this blob of dyld_chained_starts_in_image */
|
|
uint32_t mwli_chains_size; /* size of dyld_chained_starts_in_image */
|
|
uint64_t mwli_slide; /* slide to add to rebased pointers */
|
|
uint64_t mwli_image_address; /* add this to rebase offsets includes any slide */
|
|
/* followed by the binds pointers and dyld_chained_starts_in_image */
|
|
};
|
|
#define MWL_MAX_REGION_COUNT 5
|
|
extern int __map_with_linking_np(const struct mwl_region regions[], uint32_t regionCount, const struct mwl_info_hdr* blob, uint32_t blobSize);
|
|
#endif
|
|
#endif // !TARGET_OS_EXCLAVEKIT
|
|
|
|
extern struct mach_header __dso_handle;
|
|
|
|
// If a root is used that overrides a dylib in the dyld cache, dyld patches all uses of the dylib in the cache
|
|
// to point to the new dylib. But if that dylib is missing some symbol, dyld will patch other clients to point
|
|
// to BAD_ROOT_ADDRESS instead. That will cause a crash and the crash will be easy to identify in crash logs.
|
|
#define BAD_ROOT_ADDRESS 0xbad4007
|
|
|
|
|
|
using dyld3::MachOAnalyzer;
|
|
using dyld3::MachOFile;
|
|
using dyld3::Platform;
|
|
|
|
namespace dyld4 {
|
|
|
|
Loader::InitialOptions::InitialOptions()
|
|
: inDyldCache(false)
|
|
, hasObjc(false)
|
|
, mayHavePlusLoad(false)
|
|
, roData(false)
|
|
, neverUnloaded(false)
|
|
, leaveMapped(false)
|
|
, roObjC(false)
|
|
, pre2022Binary(false)
|
|
{
|
|
}
|
|
|
|
Loader::InitialOptions::InitialOptions(const Loader& other)
|
|
: inDyldCache(other.dylibInDyldCache)
|
|
, hasObjc(other.hasObjC)
|
|
, mayHavePlusLoad(other.mayHavePlusLoad)
|
|
, roData(other.hasReadOnlyData)
|
|
, neverUnloaded(other.neverUnload)
|
|
, leaveMapped(other.leaveMapped)
|
|
, roObjC(other.hasReadOnlyObjC)
|
|
, pre2022Binary(other.pre2022Binary)
|
|
{
|
|
}
|
|
|
|
const char* Loader::path() const
|
|
{
|
|
assert(this->magic == kMagic);
|
|
#if SUPPORT_CREATING_PREMAPPEDLOADERS
|
|
assert(this->isPremapped);
|
|
return ((PremappedLoader*)this)->path();
|
|
#else
|
|
#if SUPPORT_CREATING_PREBUILTLOADERS
|
|
if ( this->isPrebuilt )
|
|
return ((PrebuiltLoader*)this)->path();
|
|
else
|
|
#endif // SUPPORT_CREATING_PREBUILTLOADERS
|
|
return ((JustInTimeLoader*)this)->path();
|
|
#endif // SUPPORT_CREATING_PREMAPPEDLOADERS
|
|
}
|
|
|
|
const MachOFile* Loader::mf(RuntimeState& state) const
|
|
{
|
|
assert(this->magic == kMagic);
|
|
#if SUPPORT_CREATING_PREMAPPEDLOADERS
|
|
assert(this->isPremapped);
|
|
return ((PremappedLoader*)this)->mf(state);
|
|
#else
|
|
#if SUPPORT_CREATING_PREBUILTLOADERS
|
|
if ( this->isPrebuilt )
|
|
return ((PrebuiltLoader*)this)->mf(state);
|
|
else
|
|
#endif // SUPPORT_CREATING_PREBUILTLOADERS
|
|
return ((JustInTimeLoader*)this)->mf(state);
|
|
#endif // SUPPORT_CREATING_PREMAPPEDLOADERS
|
|
}
|
|
|
|
#if SUPPORT_VM_LAYOUT
|
|
const MachOLoaded* Loader::loadAddress(RuntimeState& state) const
|
|
{
|
|
assert(this->magic == kMagic);
|
|
#if SUPPORT_CREATING_PREMAPPEDLOADERS
|
|
assert(this->isPremapped);
|
|
return ((PremappedLoader*)this)->loadAddress(state);
|
|
#else
|
|
#if SUPPORT_CREATING_PREBUILTLOADERS
|
|
if ( this->isPrebuilt )
|
|
return ((PrebuiltLoader*)this)->loadAddress(state);
|
|
else
|
|
#endif // SUPPORT_CREATING_PREBUILTLOADERS
|
|
return ((JustInTimeLoader*)this)->loadAddress(state);
|
|
#endif // SUPPORT_CREATING_PREMAPPEDLOADERS
|
|
}
|
|
#endif
|
|
|
|
#if SUPPORT_VM_LAYOUT
|
|
bool Loader::contains(RuntimeState& state, const void* addr, const void** segAddr, uint64_t* segSize, uint8_t* segPerms) const
|
|
{
|
|
assert(this->magic == kMagic);
|
|
#if SUPPORT_CREATING_PREMAPPEDLOADERS
|
|
assert(this->isPremapped);
|
|
return ((PremappedLoader*)this)->contains(state, addr, segAddr, segSize, segPerms);
|
|
#else
|
|
#if SUPPORT_CREATING_PREBUILTLOADERS
|
|
if ( this->isPrebuilt )
|
|
return ((PrebuiltLoader*)this)->contains(state, addr, segAddr, segSize, segPerms);
|
|
else
|
|
#endif // SUPPORT_CREATING_PREBUILTLOADERS
|
|
return ((JustInTimeLoader*)this)->contains(state, addr, segAddr, segSize, segPerms);
|
|
#endif // SUPPORT_CREATING_PREMAPPEDLOADERS
|
|
}
|
|
#endif
|
|
|
|
bool Loader::matchesPath(const char* path) const
|
|
{
|
|
assert(this->magic == kMagic);
|
|
#if SUPPORT_CREATING_PREMAPPEDLOADERS
|
|
assert(this->isPremapped);
|
|
return ((PremappedLoader*)this)->matchesPath(path);
|
|
#else
|
|
#if SUPPORT_CREATING_PREBUILTLOADERS
|
|
if ( this->isPrebuilt )
|
|
return ((PrebuiltLoader*)this)->matchesPath(path);
|
|
else
|
|
#endif // SUPPORT_CREATING_PREBUILTLOADERS
|
|
return ((JustInTimeLoader*)this)->matchesPath(path);
|
|
#endif // SUPPORT_CREATING_PREMAPPEDLOADERS
|
|
}
|
|
|
|
#if !SUPPORT_CREATING_PREMAPPEDLOADERS
|
|
FileID Loader::fileID(const RuntimeState& state) const
|
|
{
|
|
assert(this->magic == kMagic);
|
|
#if SUPPORT_CREATING_PREBUILTLOADERS
|
|
if ( this->isPrebuilt )
|
|
return ((PrebuiltLoader*)this)->fileID(state);
|
|
else
|
|
#endif // SUPPORT_CREATING_PREBUILTLOADERS
|
|
return ((JustInTimeLoader*)this)->fileID(state);
|
|
}
|
|
#endif // !SUPPORT_CREATING_PREMAPPEDLOADERS
|
|
|
|
uint32_t Loader::dependentCount() const
|
|
{
|
|
assert(this->magic == kMagic);
|
|
#if SUPPORT_CREATING_PREMAPPEDLOADERS
|
|
assert(this->isPremapped);
|
|
return ((PremappedLoader*)this)->dependentCount();
|
|
#else
|
|
#if SUPPORT_CREATING_PREBUILTLOADERS
|
|
if ( this->isPrebuilt )
|
|
return ((PrebuiltLoader*)this)->dependentCount();
|
|
else
|
|
#endif // SUPPORT_CREATING_PREBUILTLOADERS
|
|
return ((JustInTimeLoader*)this)->dependentCount();
|
|
#endif // SUPPORT_CREATING_PREMAPPEDLOADERS
|
|
}
|
|
|
|
Loader* Loader::dependent(const RuntimeState& state, uint32_t depIndex, DependentKind* kind) const
|
|
{
|
|
assert(this->magic == kMagic);
|
|
#if SUPPORT_CREATING_PREMAPPEDLOADERS
|
|
assert(this->isPremapped);
|
|
return ((PremappedLoader*)this)->dependent(state, depIndex, kind);
|
|
#else
|
|
#if SUPPORT_CREATING_PREBUILTLOADERS
|
|
if ( this->isPrebuilt )
|
|
return ((PrebuiltLoader*)this)->dependent(state, depIndex, kind);
|
|
else
|
|
#endif // SUPPORT_CREATING_PREBUILTLOADERS
|
|
return ((JustInTimeLoader*)this)->dependent(state, depIndex, kind);
|
|
#endif // SUPPORT_CREATING_PREMAPPEDLOADERS
|
|
}
|
|
|
|
void Loader::loadDependents(Diagnostics& diag, RuntimeState& state, const LoadOptions& options)
|
|
{
|
|
assert(this->magic == kMagic);
|
|
#if SUPPORT_CREATING_PREMAPPEDLOADERS
|
|
assert(this->isPremapped);
|
|
return ((PremappedLoader*)this)->loadDependents(diag, state, options);
|
|
#else
|
|
#if SUPPORT_CREATING_PREBUILTLOADERS
|
|
if ( this->isPrebuilt )
|
|
return ((PrebuiltLoader*)this)->loadDependents(diag, state, options);
|
|
else
|
|
#endif // SUPPORT_CREATING_PREBUILTLOADERS
|
|
return ((JustInTimeLoader*)this)->loadDependents(diag, state, options);
|
|
#endif // SUPPORT_CREATING_PREMAPPEDLOADERS
|
|
}
|
|
|
|
bool Loader::getExportsTrie(uint64_t& runtimeOffset, uint32_t& size) const
|
|
{
|
|
assert(this->magic == kMagic);
|
|
#if SUPPORT_CREATING_PREMAPPEDLOADERS
|
|
assert(this->isPremapped);
|
|
return ((PremappedLoader*)this)->getExportsTrie(runtimeOffset, size);
|
|
#else
|
|
#if SUPPORT_CREATING_PREBUILTLOADERS
|
|
if ( this->isPrebuilt )
|
|
return ((PrebuiltLoader*)this)->getExportsTrie(runtimeOffset, size);
|
|
else
|
|
#endif // SUPPORT_CREATING_PREBUILTLOADERS
|
|
return ((JustInTimeLoader*)this)->getExportsTrie(runtimeOffset, size);
|
|
#endif // SUPPORT_CREATING_PREMAPPEDLOADERS
|
|
}
|
|
|
|
bool Loader::hiddenFromFlat(bool forceGlobal) const
|
|
{
|
|
assert(this->magic == kMagic);
|
|
#if SUPPORT_CREATING_PREMAPPEDLOADERS
|
|
assert(this->isPremapped);
|
|
return ((PremappedLoader*)this)->hiddenFromFlat(forceGlobal);
|
|
#else
|
|
#if SUPPORT_CREATING_PREBUILTLOADERS
|
|
if ( this->isPrebuilt )
|
|
return ((PrebuiltLoader*)this)->hiddenFromFlat(forceGlobal);
|
|
else
|
|
#endif // SUPPORT_CREATING_PREBUILTLOADERS
|
|
return ((JustInTimeLoader*)this)->hiddenFromFlat(forceGlobal);
|
|
#endif // SUPPORT_CREATING_PREMAPPEDLOADERS
|
|
}
|
|
|
|
#if !SUPPORT_CREATING_PREMAPPEDLOADERS
|
|
bool Loader::representsCachedDylibIndex(uint16_t dylibIndex) const
|
|
{
|
|
assert(this->magic == kMagic);
|
|
#if SUPPORT_CREATING_PREBUILTLOADERS
|
|
if ( this->isPrebuilt )
|
|
return ((PrebuiltLoader*)this)->representsCachedDylibIndex(dylibIndex);
|
|
else
|
|
#endif // SUPPORT_CREATING_PREBUILTLOADERS
|
|
return ((JustInTimeLoader*)this)->representsCachedDylibIndex(dylibIndex);
|
|
}
|
|
|
|
bool Loader::overridesDylibInCache(const DylibPatch*& patchTable, uint16_t& cacheDylibOverriddenIndex) const
|
|
{
|
|
assert(this->magic == kMagic);
|
|
#if SUPPORT_CREATING_PREBUILTLOADERS
|
|
if ( this->isPrebuilt )
|
|
return ((PrebuiltLoader*)this)->overridesDylibInCache(patchTable, cacheDylibOverriddenIndex);
|
|
else
|
|
#endif // SUPPORT_CREATING_PREBUILTLOADERS
|
|
return ((JustInTimeLoader*)this)->overridesDylibInCache(patchTable, cacheDylibOverriddenIndex);
|
|
}
|
|
|
|
#endif // !SUPPORT_CREATING_PREMAPPEDLOADERS
|
|
|
|
#if BUILDING_DYLD || BUILDING_UNIT_TESTS
|
|
void Loader::applyFixups(Diagnostics& diag, RuntimeState& state, DyldCacheDataConstLazyScopedWriter& dataConst, bool allowLazyBinds) const
|
|
{
|
|
assert(this->magic == kMagic);
|
|
#if SUPPORT_CREATING_PREMAPPEDLOADERS
|
|
assert(this->isPremapped);
|
|
((PremappedLoader*)this)->applyFixups(diag, state, dataConst, allowLazyBinds);
|
|
#else
|
|
#if SUPPORT_CREATING_PREBUILTLOADERS
|
|
if ( this->isPrebuilt )
|
|
((PrebuiltLoader*)this)->applyFixups(diag, state, dataConst, allowLazyBinds);
|
|
else
|
|
#endif // SUPPORT_CREATING_PREBUILTLOADERS
|
|
((JustInTimeLoader*)this)->applyFixups(diag, state, dataConst, allowLazyBinds);
|
|
#endif // SUPPORT_CREATING_PREMAPPEDLOADERS
|
|
}
|
|
#endif // BUILDING_DYLD || BUILDING_UNIT_TESTS
|
|
|
|
void Loader::withLayout(Diagnostics &diag, RuntimeState& state, void (^callback)(const mach_o::Layout &layout)) const
|
|
{
|
|
assert(this->magic == kMagic);
|
|
#if SUPPORT_CREATING_PREMAPPEDLOADERS
|
|
assert(this->isPremapped);
|
|
((PremappedLoader*)this)->withLayout(diag, state, callback);
|
|
#else
|
|
#if SUPPORT_CREATING_PREBUILTLOADERS
|
|
if ( this->isPrebuilt )
|
|
return ((PrebuiltLoader*)this)->withLayout(diag, state, callback);
|
|
else
|
|
#endif // SUPPORT_CREATING_PREBUILTLOADERS
|
|
return ((JustInTimeLoader*)this)->withLayout(diag, state, callback);
|
|
#endif // SUPPORT_CREATING_PREMAPPEDLOADERS
|
|
}
|
|
|
|
bool Loader::dyldDoesObjCFixups() const
|
|
{
|
|
assert(this->magic == kMagic);
|
|
#if SUPPORT_CREATING_PREMAPPEDLOADERS
|
|
assert(this->isPremapped);
|
|
return ((PremappedLoader*)this)->dyldDoesObjCFixups();
|
|
#else
|
|
#if SUPPORT_CREATING_PREBUILTLOADERS
|
|
if ( this->isPrebuilt )
|
|
return ((PrebuiltLoader*)this)->dyldDoesObjCFixups();
|
|
else
|
|
#endif // SUPPORT_CREATING_PREBUILTLOADERS
|
|
return ((JustInTimeLoader*)this)->dyldDoesObjCFixups();
|
|
#endif // SUPPORT_CREATING_PREMAPPEDLOADERS
|
|
}
|
|
|
|
const SectionLocations* Loader::getSectionLocations() const
|
|
{
|
|
assert(this->magic == kMagic);
|
|
#if SUPPORT_CREATING_PREBUILTLOADERS
|
|
if ( this->isPrebuilt )
|
|
return ((PrebuiltLoader*)this)->getSectionLocations();
|
|
else
|
|
#endif // SUPPORT_CREATING_PREBUILTLOADERS
|
|
return ((JustInTimeLoader*)this)->getSectionLocations();
|
|
}
|
|
|
|
#if SUPPORT_IMAGE_UNLOADING
|
|
void Loader::unmap(RuntimeState& state, bool force) const
|
|
{
|
|
assert(this->magic == kMagic);
|
|
#if SUPPORT_CREATING_PREBUILTLOADERS
|
|
if ( this->isPrebuilt )
|
|
return ((PrebuiltLoader*)this)->unmap(state, force);
|
|
else
|
|
#endif // SUPPORT_CREATING_PREBUILTLOADERS
|
|
return ((JustInTimeLoader*)this)->unmap(state, force);
|
|
}
|
|
#endif
|
|
|
|
|
|
bool Loader::hasBeenFixedUp(RuntimeState& state) const
|
|
{
|
|
assert(this->magic == kMagic);
|
|
#if SUPPORT_CREATING_PREMAPPEDLOADERS
|
|
assert(this->isPremapped);
|
|
return ((PremappedLoader*)this)->hasBeenFixedUp(state);
|
|
#else
|
|
#if SUPPORT_CREATING_PREBUILTLOADERS
|
|
if ( this->isPrebuilt )
|
|
return ((PrebuiltLoader*)this)->hasBeenFixedUp(state);
|
|
else
|
|
#endif // SUPPORT_CREATING_PREBUILTLOADERS
|
|
return ((JustInTimeLoader*)this)->hasBeenFixedUp(state);
|
|
#endif // SUPPORT_CREATING_PREMAPPEDLOADERS
|
|
}
|
|
|
|
|
|
bool Loader::beginInitializers(RuntimeState& state)
|
|
{
|
|
assert(this->magic == kMagic);
|
|
#if SUPPORT_CREATING_PREMAPPEDLOADERS
|
|
assert(this->isPremapped);
|
|
return ((PremappedLoader*)this)->beginInitializers(state);
|
|
#else
|
|
#if SUPPORT_CREATING_PREBUILTLOADERS
|
|
if ( this->isPrebuilt )
|
|
return ((PrebuiltLoader*)this)->beginInitializers(state);
|
|
else
|
|
#endif // SUPPORT_CREATING_PREBUILTLOADERS
|
|
return ((JustInTimeLoader*)this)->beginInitializers(state);
|
|
#endif // SUPPORT_CREATING_PREMAPPEDLOADERS
|
|
}
|
|
|
|
#if BUILDING_DYLD || BUILDING_UNIT_TESTS
|
|
void Loader::runInitializers(RuntimeState& state) const
|
|
{
|
|
assert(this->magic == kMagic);
|
|
#if SUPPORT_CREATING_PREMAPPEDLOADERS
|
|
assert(this->isPremapped);
|
|
((PremappedLoader*)this)->runInitializers(state);
|
|
#else
|
|
#if SUPPORT_CREATING_PREBUILTLOADERS
|
|
if ( this->isPrebuilt )
|
|
((PrebuiltLoader*)this)->runInitializers(state);
|
|
else
|
|
#endif // SUPPORT_CREATING_PREBUILTLOADERS
|
|
((JustInTimeLoader*)this)->runInitializers(state);
|
|
#endif // SUPPORT_CREATING_PREMAPPEDLOADERS
|
|
}
|
|
#endif
|
|
|
|
const PrebuiltLoader* Loader::LoaderRef::loader(const RuntimeState& state) const
|
|
{
|
|
if ( this->app )
|
|
return state.processPrebuiltLoaderSet()->atIndex(this->index);
|
|
else
|
|
return state.cachedDylibsPrebuiltLoaderSet()->atIndex(this->index);
|
|
}
|
|
|
|
const char* Loader::leafName(const char* path)
|
|
{
|
|
if ( const char* lastSlash = strrchr(path, '/') )
|
|
return lastSlash + 1;
|
|
else
|
|
return path;
|
|
}
|
|
|
|
const char* Loader::leafName() const
|
|
{
|
|
return leafName(path());
|
|
}
|
|
|
|
#if SUPPORT_VM_LAYOUT
|
|
const MachOAnalyzer* Loader::analyzer(RuntimeState& state) const
|
|
{
|
|
return (MachOAnalyzer*)loadAddress(state);
|
|
}
|
|
#endif
|
|
|
|
bool Loader::hasMagic() const
|
|
{
|
|
return (this->magic == kMagic);
|
|
}
|
|
|
|
void Loader::appendHexNibble(uint8_t value, char*& p)
|
|
{
|
|
if ( value < 10 )
|
|
*p++ = '0' + value;
|
|
else
|
|
*p++ = 'A' + value - 10;
|
|
}
|
|
|
|
void Loader::appendHexByte(uint8_t value, char*& p)
|
|
{
|
|
value &= 0xFF;
|
|
appendHexNibble(value >> 4, p);
|
|
appendHexNibble(value & 0x0F, p);
|
|
}
|
|
|
|
void Loader::uuidToStr(uuid_t uuid, char uuidStr[64])
|
|
{
|
|
char* p = uuidStr;
|
|
appendHexByte(uuid[0], p);
|
|
appendHexByte(uuid[1], p);
|
|
appendHexByte(uuid[2], p);
|
|
appendHexByte(uuid[3], p);
|
|
*p++ = '-';
|
|
appendHexByte(uuid[4], p);
|
|
appendHexByte(uuid[5], p);
|
|
*p++ = '-';
|
|
appendHexByte(uuid[6], p);
|
|
appendHexByte(uuid[7], p);
|
|
*p++ = '-';
|
|
appendHexByte(uuid[8], p);
|
|
appendHexByte(uuid[9], p);
|
|
*p++ = '-';
|
|
appendHexByte(uuid[10], p);
|
|
appendHexByte(uuid[11], p);
|
|
appendHexByte(uuid[12], p);
|
|
appendHexByte(uuid[13], p);
|
|
appendHexByte(uuid[14], p);
|
|
appendHexByte(uuid[15], p);
|
|
*p = '\0';
|
|
}
|
|
|
|
void Loader::getUuidStr(RuntimeState& state, char uuidStr[64]) const
|
|
{
|
|
uuid_t uuid;
|
|
if ( this->mf(state)->getUuid(uuid) ) {
|
|
uuidToStr(uuid, uuidStr);
|
|
}
|
|
else {
|
|
strlcpy(uuidStr, "no uuid", 64);
|
|
}
|
|
}
|
|
|
|
void Loader::logLoad(RuntimeState& state, const char* path) const
|
|
{
|
|
char uuidStr[64];
|
|
this->getUuidStr(state, uuidStr);
|
|
state.log("<%s> %s\n", uuidStr, path);
|
|
}
|
|
|
|
#if TARGET_OS_EXCLAVEKIT
|
|
const Loader* Loader::makePremappedLoader(Diagnostics& diag, RuntimeState& state, const char* path, const LoadOptions& options, const mach_o::Layout* layout)
|
|
{
|
|
return PremappedLoader::makePremappedLoader(diag, state, path, options, layout);
|
|
}
|
|
#endif // !TARGET_OS_EXCLAVEKIT
|
|
|
|
#if !TARGET_OS_EXCLAVEKIT
|
|
const Loader* Loader::makeDiskLoader(Diagnostics& diag, RuntimeState& state, const char* path, const LoadOptions& options,
|
|
bool overridesDyldCache, uint32_t dylibIndex,
|
|
const mach_o::Layout* layout)
|
|
{
|
|
// never create a new loader in RTLD_NOLOAD mode
|
|
if ( options.rtldNoLoad )
|
|
return nullptr;
|
|
|
|
// don't use PrebuiltLoaders for simulator because the paths will be wrong (missing SIMROOT prefix)
|
|
#if SUPPORT_CREATING_PREBUILTLOADERS
|
|
// first check for a PrebuiltLoader
|
|
const Loader* result = (Loader*)state.findPrebuiltLoader(path);
|
|
if ( result != nullptr )
|
|
return result;
|
|
#endif // SUPPORT_CREATING_PREBUILTLOADERS
|
|
|
|
// The dylibIndex for a catalyst root might be wrong. This can happen if the dylib is found via its macOS path (ie from a zippered dylib)
|
|
// but getLoader() found the root in the /System/iOSSupport path
|
|
// In this case, we want to rewrite the dylib index to be to the catalyst unzippered twin, not the macOS one
|
|
if ( overridesDyldCache && state.config.process.catalystRuntime ) {
|
|
uint32_t dylibInCacheIndex;
|
|
if ( state.config.dyldCache.indexOfPath(path, dylibInCacheIndex) )
|
|
dylibIndex = dylibInCacheIndex;
|
|
}
|
|
|
|
// try building a JustInTime Loader
|
|
return JustInTimeLoader::makeJustInTimeLoaderDisk(diag, state, path, options, overridesDyldCache, dylibIndex, layout);
|
|
}
|
|
|
|
const Loader* Loader::makeDyldCacheLoader(Diagnostics& diag, RuntimeState& state, const char* path, const LoadOptions& options, uint32_t dylibIndex,
|
|
const mach_o::Layout* layout)
|
|
{
|
|
// never create a new loader in RTLD_NOLOAD mode
|
|
if ( options.rtldNoLoad )
|
|
return nullptr;
|
|
|
|
#if SUPPORT_CREATING_PREBUILTLOADERS
|
|
// first check for a PrebuiltLoader with compatible platform
|
|
// rdar://76406035 (simulator cache paths need prefix)
|
|
const PrebuiltLoader* result = state.findPrebuiltLoader(path);
|
|
if ( result != nullptr ) {
|
|
if ( result->mf(state)->loadableIntoProcess(state.config.process.platform, path, state.config.security.internalInstall) ) {
|
|
return result;
|
|
}
|
|
}
|
|
#endif // SUPPORT_CREATING_PREBUILTLOADERS
|
|
|
|
// try building a JustInTime Loader
|
|
return JustInTimeLoader::makeJustInTimeLoaderDyldCache(diag, state, path, options, dylibIndex, layout);
|
|
}
|
|
|
|
const Loader* Loader::makePseudoDylibLoader(Diagnostics& diag, RuntimeState &state, const char* path, const LoadOptions& options, const PseudoDylib* pd) {
|
|
return JustInTimeLoader::makePseudoDylibLoader(diag, state, path, options, pd);
|
|
}
|
|
|
|
static bool isFileRelativePath(const char* path)
|
|
{
|
|
if ( path[0] == '/' )
|
|
return false;
|
|
if ( (path[0] == '.') && (path[1] == '/') )
|
|
return true;
|
|
if ( (path[0] == '.') && (path[1] == '.') && (path[2] == '/') )
|
|
return true;
|
|
return (path[0] != '@');
|
|
}
|
|
|
|
static bool mightBeInSharedCache(const char* dylibName) {
|
|
return ( (strncmp(dylibName, "/usr/lib/", 9) == 0)
|
|
|| (strncmp(dylibName, "/System/Library/", 16) == 0)
|
|
|| (strncmp(dylibName, "/System/iOSSupport/usr/lib/", 27) == 0)
|
|
|| (strncmp(dylibName, "/System/iOSSupport/System/Library/", 34) == 0)
|
|
|| (strncmp(dylibName, "/System/DriverKit/", 18) == 0) );
|
|
}
|
|
|
|
|
|
// This composes DyldProcessConfig::forEachPathVariant() with Loader::forEachResolvedAtPathVar()
|
|
// They are separate layers because DyldProcessConfig handles DYLD_ env vars and Loader handle @ paths
|
|
void Loader::forEachPath(Diagnostics& diag, RuntimeState& state, const char* loadPath, const LoadOptions& options,
|
|
void (^handler)(const char* possiblePath, ProcessConfig::PathOverrides::Type type, bool&))
|
|
{
|
|
__block bool stop = false;
|
|
const ProcessConfig::PathOverrides& po = state.config.pathOverrides;
|
|
// <rdar://5951327> (DYLD_FALLBACK_LIBRARY_PATH should only apply to dlopen() of leaf names)
|
|
bool skipFallbacks = !options.staticLinkage && (strchr(loadPath, '/') != nullptr) && (state.config.pathOverrides.getFrameworkPartialPath(loadPath) == nullptr);
|
|
po.forEachPathVariant(loadPath, state.config.process.platform, options.requestorNeedsFallbacks, skipFallbacks, stop,
|
|
^(const char* possibleVariantPath, ProcessConfig::PathOverrides::Type type, bool&) {
|
|
#if !TARGET_OS_EXCLAVEKIT
|
|
// relative name to dlopen() has special behavior
|
|
if ( !options.staticLinkage && (type == ProcessConfig::PathOverrides::Type::rawPath) && (loadPath[0] != '/') ) {
|
|
// if relative path, turn into implicit @rpath
|
|
if ( (loadPath[0] != '@') ) {
|
|
char implicitRPath[PATH_MAX];
|
|
strlcpy(implicitRPath, "@rpath/", sizeof(implicitRPath));
|
|
strlcat(implicitRPath, possibleVariantPath, sizeof(implicitRPath));
|
|
Loader::forEachResolvedAtPathVar(state, implicitRPath, options, ProcessConfig::PathOverrides::Type::implictRpathExpansion, stop, handler);
|
|
if ( stop )
|
|
return;
|
|
// <rdar://47682983> always look in /usr/lib for leaf names
|
|
char implicitPath[PATH_MAX];
|
|
strlcpy(implicitPath, "/usr/lib/", sizeof(implicitRPath));
|
|
strlcat(implicitPath, loadPath, sizeof(implicitPath));
|
|
handler(implicitPath, ProcessConfig::PathOverrides::Type::standardFallback, stop);
|
|
if ( stop )
|
|
return;
|
|
// only try cwd relative if afmi allows
|
|
if ( state.config.security.allowAtPaths ) {
|
|
handler(loadPath, type, stop);
|
|
}
|
|
// don't try anything else for dlopen of non-absolute paths
|
|
return;
|
|
}
|
|
}
|
|
// expand @ paths
|
|
Loader::forEachResolvedAtPathVar(state, possibleVariantPath, options, type, stop, handler);
|
|
#else
|
|
handler(possibleVariantPath, type, stop);
|
|
#endif // !TARGET_OS_EXCLAVEKIT
|
|
});
|
|
}
|
|
#endif // !TARGET_OS_EXCLAVEKIT
|
|
|
|
//
|
|
// Use PathOverrides class to walk possible paths, for each, look on disk, then in cache.
|
|
// Special case customer caches to look in cache first, to avoid stat() when result will be disgarded.
|
|
// For dylibs loaded from disk, we need to know if they override something in the cache in order to patch it in.
|
|
// It is considered an override if the initial path or path found is in the dyld cache
|
|
//
|
|
const Loader* Loader::getLoader(Diagnostics& diag, RuntimeState& state, const char* loadPath, const LoadOptions& options)
|
|
{
|
|
#if TARGET_OS_EXCLAVEKIT
|
|
__block const Loader* result = nullptr;
|
|
// check if this path already in use by a Loader
|
|
for ( const Loader* ldr : state.loaded ) {
|
|
if ( !ldr->matchesPath(loadPath) )
|
|
continue;
|
|
result = ldr;
|
|
if ( state.config.log.searching )
|
|
state.log(" found: already-loaded-by-path: \"%s\"\n", loadPath);
|
|
}
|
|
|
|
if ( result == nullptr )
|
|
result = makePremappedLoader(diag, state, loadPath, options, nullptr);
|
|
|
|
if ( (result == nullptr) && options.canBeMissing ) {
|
|
diag.clearError();
|
|
}
|
|
|
|
return result;
|
|
#else
|
|
__block const Loader* result = nullptr;
|
|
const DyldSharedCache* cache = state.config.dyldCache.addr;
|
|
const bool customerCache = (cache != nullptr) && !state.config.dyldCache.development;
|
|
if ( state.config.log.searching )
|
|
state.log("find path \"%s\"\n", loadPath);
|
|
|
|
const bool loadPathIsRPath = (::strncmp(loadPath, "@rpath/", 7) == 0);
|
|
const bool loadPathIsFileRelativePath = isFileRelativePath(loadPath);
|
|
|
|
// for @rpath paths, first check if already loaded as rpath
|
|
if ( loadPathIsRPath ) {
|
|
for ( const Loader* ldr : state.loaded ) {
|
|
if ( ldr->matchesPath(loadPath) ) {
|
|
if ( state.config.log.searching )
|
|
state.log(" found: already-loaded-by-rpath: %s\n", ldr->path());
|
|
return ldr;
|
|
}
|
|
}
|
|
}
|
|
else if ( !options.staticLinkage && (loadPath[0] != '@') && (loadPath[0] != '/') && (strchr(loadPath, '/') == nullptr) ) {
|
|
// handle dlopen("xxx") to mean "@rpath/xxx" when it is already loaded
|
|
char implicitRPath[strlen(loadPath)+8];
|
|
strlcpy(implicitRPath, "@rpath/", sizeof(implicitRPath));
|
|
strlcat(implicitRPath, loadPath, sizeof(implicitRPath));
|
|
for ( const Loader* ldr : state.loaded ) {
|
|
if ( ldr->matchesPath(implicitRPath) ) {
|
|
if ( state.config.log.searching )
|
|
state.log(" found: already-loaded-by-rpath: %s\n", ldr->path());
|
|
return ldr;
|
|
}
|
|
}
|
|
}
|
|
|
|
// canonicalize shared cache paths
|
|
if ( const char* canonicalPathInCache = state.config.canonicalDylibPathInCache(loadPath) ) {
|
|
if ( strcmp(canonicalPathInCache, loadPath) != 0 ) {
|
|
loadPath = canonicalPathInCache;
|
|
if ( state.config.log.searching )
|
|
state.log(" switch to canonical cache path: %s\n", loadPath);
|
|
}
|
|
}
|
|
|
|
// get info about original path
|
|
__block uint32_t dylibInCacheIndex;
|
|
const bool originalPathIsInDyldCache = state.config.dyldCache.indexOfPath(loadPath, dylibInCacheIndex);
|
|
|
|
#if BUILDING_DYLD && TARGET_OS_OSX
|
|
// On macOS, we need to support unzippered twins, which look like roots. So if the original path is in the cache, it may
|
|
// still be overridable by an unzippered twin which is also in the cache
|
|
const bool originalPathIsOverridableInDyldCache = originalPathIsInDyldCache;
|
|
#else
|
|
const bool originalPathIsOverridableInDyldCache = originalPathIsInDyldCache && state.config.dyldCache.isOverridablePath(loadPath);
|
|
#endif
|
|
|
|
// search all locations
|
|
Loader::forEachPath(diag, state, loadPath, options,
|
|
^(const char* possiblePath, ProcessConfig::PathOverrides::Type type, bool& stop) {
|
|
// On customer dyld caches, if loaded a path in cache, don't look for overrides
|
|
if ( customerCache && originalPathIsInDyldCache && !originalPathIsOverridableInDyldCache && (possiblePath != loadPath) )
|
|
return;
|
|
if ( state.config.log.searching )
|
|
state.log(" possible path(%s): \"%s\"\n", ProcessConfig::PathOverrides::typeName(type), possiblePath);
|
|
|
|
// check if this path already in use by a Loader
|
|
for ( const Loader* ldr : state.loaded ) {
|
|
if ( ldr->matchesPath(possiblePath) ) {
|
|
result = ldr;
|
|
stop = true;
|
|
diag.clearError(); // found dylib, so clear any errors from previous paths tried
|
|
if ( state.config.log.searching )
|
|
state.log(" found: already-loaded-by-path: \"%s\"\n", possiblePath);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// <rdar://problem/47682983> don't allow file system relative paths in hardened programs
|
|
// (type == ProcessConfig::PathOverrides::Type::implictRpathExpansion)
|
|
if ( !state.config.security.allowEnvVarsPath && isFileRelativePath(possiblePath) ) {
|
|
if ( diag.noError() )
|
|
diag.error("tried: '%s' (relative path not allowed in hardened program)", possiblePath);
|
|
else
|
|
diag.appendError(", '%s' (relative path not allowed in hardened program)", possiblePath);
|
|
return;
|
|
}
|
|
|
|
// check dyld cache trie to see if this is an alias to a cached dylib
|
|
uint32_t possibleCacheIndex;
|
|
if ( state.config.dyldCache.indexOfPath(possiblePath, possibleCacheIndex) ) {
|
|
for ( const Loader* ldr : state.loaded ) {
|
|
if ( ldr->representsCachedDylibIndex(possibleCacheIndex) ) {
|
|
result = ldr;
|
|
stop = true;
|
|
diag.clearError(); // found dylib, so clear any errors from previous paths tried
|
|
if ( state.config.log.searching )
|
|
state.log(" found: already-loaded-by-dylib-index: \"%s\" -> %s\n", possiblePath, ldr->path());
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// RTLD_NOLOAD used and this possible path not already in use, so skip to next
|
|
if ( options.rtldNoLoad ) {
|
|
return;
|
|
}
|
|
|
|
// Check for PseduoDylibs
|
|
if (!state.pseudoDylibs.empty()) {
|
|
// FIXME: Should all of this be in its own function?
|
|
if ( state.config.log.searching )
|
|
state.log("searching %lu pseudo-dylibs:\n", state.pseudoDylibs.size());
|
|
for (auto &pd : state.pseudoDylibs) {
|
|
if (strcmp(pd->getIdentifier(), possiblePath) == 0) {
|
|
if ( state.config.log.searching )
|
|
state.log(" found: pseduo-dylib: \"%s\"\n", possiblePath);
|
|
Diagnostics possiblePathDiag;
|
|
result = makePseudoDylibLoader(possiblePathDiag, state, possiblePath, options, &*pd);
|
|
if ( possiblePathDiag.hasError() ) {
|
|
// Report error if pseudo-dylib failed to load.
|
|
if ( diag.noError() )
|
|
diag.error("tried: '%s' (%s)", possiblePath, possiblePathDiag.errorMessageCStr());
|
|
else
|
|
diag.appendError(", '%s' (%s)", possiblePath, possiblePathDiag.errorMessageCStr());
|
|
|
|
if ( state.config.log.searching )
|
|
state.log(" found: pseudo-dylib-error: \"%s\" => \"%s\"\n", possiblePath, possiblePathDiag.errorMessageCStr());
|
|
}
|
|
if (result) {
|
|
diag.clearError();
|
|
stop = true;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
if ( state.config.log.searching && !result)
|
|
state.log("no pseudo-dylibs matched\n");
|
|
} else if ( state.config.log.searching )
|
|
state.log("no pseudo-dylibs to search\n");
|
|
|
|
// see if this path is on disk or in dyld cache
|
|
int possiblePathOnDiskErrNo = 0;
|
|
bool possiblePathHasFileOnDisk = false;
|
|
bool possiblePathIsInDyldCache = false;
|
|
bool possiblePathOverridesCache = false;
|
|
FileID possiblePathFileID = FileID::none();
|
|
if ( customerCache ) {
|
|
// for customer cache, check cache first and only stat() if overridable
|
|
if ( !ProcessConfig::PathOverrides::isOnDiskOnlyType(type) )
|
|
possiblePathIsInDyldCache = state.config.dyldCache.indexOfPath(possiblePath, dylibInCacheIndex);
|
|
if ( possiblePathIsInDyldCache ) {
|
|
if ( state.config.dyldCache.isOverridablePath(possiblePath) ) {
|
|
// see if there is a root installed that overrides one of few overridable dylibs in the cache
|
|
possiblePathHasFileOnDisk = state.config.fileExists(possiblePath, &possiblePathFileID, &possiblePathOnDiskErrNo);
|
|
possiblePathOverridesCache = possiblePathHasFileOnDisk;
|
|
}
|
|
}
|
|
else {
|
|
possiblePathHasFileOnDisk = state.config.fileExists(possiblePath, &possiblePathFileID, &possiblePathOnDiskErrNo);
|
|
possiblePathOverridesCache = possiblePathHasFileOnDisk && originalPathIsOverridableInDyldCache;
|
|
}
|
|
}
|
|
else {
|
|
// for dev caches, always stat() and check cache
|
|
possiblePathHasFileOnDisk = state.config.fileExists(possiblePath, &possiblePathFileID, &possiblePathOnDiskErrNo);
|
|
if ( !ProcessConfig::PathOverrides::isOnDiskOnlyType(type) )
|
|
possiblePathIsInDyldCache = state.config.dyldCache.indexOfPath(possiblePath, dylibInCacheIndex);
|
|
possiblePathOverridesCache = possiblePathHasFileOnDisk && (originalPathIsInDyldCache || possiblePathIsInDyldCache);
|
|
}
|
|
|
|
// see if this possible path was already loaded via a symlink or hardlink by checking inode
|
|
if ( possiblePathHasFileOnDisk && possiblePathFileID.valid() ) {
|
|
for ( const Loader* ldr : state.loaded ) {
|
|
FileID ldrFileID = ldr->fileID(state);
|
|
if ( ldrFileID.valid() && (possiblePathFileID == ldrFileID) ) {
|
|
result = ldr;
|
|
stop = true;
|
|
diag.clearError(); // found dylib, so clear any errors from previous paths tried
|
|
if ( state.config.log.searching )
|
|
state.log(" found: already-loaded-by-inode-mtime: \"%s\"\n", ldr->path());
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
#if TARGET_OS_SIMULATOR
|
|
// rdar://76406035 (load simulator dylibs from cache)
|
|
if ( (state.config.dyldCache.addr != nullptr) && state.config.dyldCache.addr->header.dylibsExpectedOnDisk ) {
|
|
if ( const char* simRoot = state.config.pathOverrides.simRootPath() ) {
|
|
size_t simRootLen = strlen(simRoot);
|
|
// compare inode/mtime of dylib now vs when cache was built
|
|
const char* possiblePathInSimDyldCache = nullptr;
|
|
if ( strncmp(possiblePath, simRoot, simRootLen) == 0 ) {
|
|
// looks like a dylib in the sim Runtime root, see if partial path is in the dyld cache
|
|
possiblePathInSimDyldCache = &possiblePath[simRootLen];
|
|
}
|
|
else if ( strncmp(possiblePath, "/usr/lib/system/", 16) == 0 ) {
|
|
// could be one of the magic host dylibs that got incorporated into the dyld cache
|
|
possiblePathInSimDyldCache = possiblePath;
|
|
}
|
|
if ( possiblePathInSimDyldCache != nullptr ) {
|
|
if ( state.config.dyldCache.indexOfPath(possiblePathInSimDyldCache, dylibInCacheIndex) ) {
|
|
uint64_t expectedMTime;
|
|
uint64_t expectedInode;
|
|
state.config.dyldCache.addr->getIndexedImageEntry(dylibInCacheIndex, expectedMTime, expectedInode);
|
|
FileID expectedID(expectedInode, state.config.process.dyldSimFSID, expectedMTime, true);
|
|
if ( possiblePathFileID == expectedID ) {
|
|
// inode/mtime matches when sim dyld cache was built, so use dylib from dyld cache and ignore file on disk
|
|
possiblePathHasFileOnDisk = false;
|
|
possiblePathIsInDyldCache = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
// if possiblePath not a file and not in dyld cache, skip to next possible path
|
|
if ( !possiblePathHasFileOnDisk && !possiblePathIsInDyldCache ) {
|
|
if ( options.pathNotFoundHandler && !ProcessConfig::PathOverrides::isOnDiskOnlyType(type) )
|
|
options.pathNotFoundHandler(possiblePath);
|
|
// append each path tried to diag
|
|
if ( diag.noError() )
|
|
diag.error("tried: ");
|
|
else
|
|
diag.appendError(", ");
|
|
const char* sharedCacheMsg = "";
|
|
if ( !ProcessConfig::PathOverrides::isOnDiskOnlyType(type) && mightBeInSharedCache(possiblePath) )
|
|
sharedCacheMsg = (state.config.dyldCache.addr != nullptr) ? ", not in dyld cache" : ", no dyld cache";
|
|
if ( possiblePathOnDiskErrNo == ENOENT )
|
|
diag.appendError("'%s' (no such file%s)", possiblePath, sharedCacheMsg);
|
|
else if ( possiblePathOnDiskErrNo == ENOTAFILE_NP )
|
|
diag.appendError("'%s' (not a file%s)", possiblePath, sharedCacheMsg);
|
|
else
|
|
diag.appendError("'%s' (errno=%d%s)", possiblePath, possiblePathOnDiskErrNo, sharedCacheMsg);
|
|
return;
|
|
}
|
|
|
|
// try to build Loader from possiblePath
|
|
Diagnostics possiblePathDiag;
|
|
if ( possiblePathHasFileOnDisk ) {
|
|
if ( possiblePathOverridesCache ) {
|
|
// use dylib on disk to override dyld cache
|
|
if ( state.config.log.searching )
|
|
state.log(" found: dylib-from-disk-to-override-cache: \"%s\"\n", possiblePath);
|
|
result = makeDiskLoader(possiblePathDiag, state, possiblePath, options, true, dylibInCacheIndex, nullptr);
|
|
if ( state.config.log.searching && possiblePathDiag.hasError() )
|
|
state.log(" found: dylib-from-disk-to-override-cache-error: \"%s\" => \"%s\"\n", possiblePath, possiblePathDiag.errorMessageCStr());
|
|
}
|
|
else {
|
|
// load from disk, nothing to do with dyld cache
|
|
if ( state.config.log.searching )
|
|
state.log(" found: dylib-from-disk: \"%s\"\n", possiblePath);
|
|
result = makeDiskLoader(possiblePathDiag, state, possiblePath, options, false, 0, nullptr);
|
|
if ( state.config.log.searching && possiblePathDiag.hasError() )
|
|
state.log(" found: dylib-from-disk-error: \"%s\" => \"%s\"\n", possiblePath, possiblePathDiag.errorMessageCStr());
|
|
}
|
|
}
|
|
else if ( possiblePathIsInDyldCache ) {
|
|
// can use dylib in dyld cache
|
|
if ( state.config.log.searching )
|
|
state.log(" found: dylib-from-cache: (0x%04X) \"%s\"\n", dylibInCacheIndex, possiblePath);
|
|
result = makeDyldCacheLoader(possiblePathDiag, state, possiblePath, options, dylibInCacheIndex, nullptr);
|
|
if ( state.config.log.searching && possiblePathDiag.hasError() )
|
|
state.log(" found: dylib-from-cache-error: \"%s\" => \"%s\"\n", possiblePath, possiblePathDiag.errorMessageCStr());
|
|
}
|
|
if ( result != nullptr ) {
|
|
stop = true;
|
|
diag.clearError(); // found dylib, so clear any errors from previous paths tried
|
|
}
|
|
else {
|
|
// set diag to be contain all errors from all paths tried
|
|
if ( diag.noError() )
|
|
diag.error("tried: '%s' (%s)", possiblePath, possiblePathDiag.errorMessageCStr());
|
|
else
|
|
diag.appendError(", '%s' (%s)", possiblePath, possiblePathDiag.errorMessageCStr());
|
|
}
|
|
});
|
|
|
|
// The last possibility is that the path provided has ../ or // in it,
|
|
// or is a symlink to a dylib which is in the cache and no longer on disk.
|
|
// Use realpath() and try getLoader() again.
|
|
// Do this last and only if it would fail anyways so as to not slow down correct paths
|
|
if ( result == nullptr ) {
|
|
if ( !state.config.security.allowEnvVarsPath && loadPathIsFileRelativePath ) {
|
|
// don't realpath() relative paths in hardened programs
|
|
// but do check if path matches install name of something already loaded
|
|
for ( const Loader* ldr : state.loaded ) {
|
|
if ( ldr->matchesPath(loadPath) ) {
|
|
if ( state.config.log.searching )
|
|
state.log(" found existing image by install name: \"%s\"\n", ldr->path());
|
|
result = ldr;
|
|
diag.clearError();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else if ( !options.staticLinkage && (strchr(loadPath, '/') == nullptr) ) {
|
|
// don't realpath() leaf names to dlopen(), they have already been handled
|
|
}
|
|
else {
|
|
char canonicalPath[PATH_MAX];
|
|
if ( (loadPath[0] != '@') && state.config.syscall.realpath(loadPath, canonicalPath) ) {
|
|
// only call getLoader() again if the realpath is different to prevent recursion
|
|
// don't call getLoader() again if the realpath is a just the loadPath cut back, because that means some dir was not found
|
|
if ( ::strncmp(loadPath, canonicalPath, strlen(canonicalPath)) != 0 ) {
|
|
if ( state.config.log.searching )
|
|
state.log(" switch to realpath: \"%s\"\n", canonicalPath);
|
|
result = getLoader(diag, state, canonicalPath, options);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( state.config.log.searching && (result == nullptr) )
|
|
state.log(" not found: \"%s\"\n", loadPath);
|
|
|
|
// if the load failed due to security policy, leave a hint in dlerror() or crash log messages
|
|
if ( (result == nullptr) && (loadPath[0] == '@') && !state.config.security.allowAtPaths ) {
|
|
diag.appendError(", (security policy does not allow @ path expansion)");
|
|
}
|
|
|
|
// if dylib could not be found, but is not required, clear error message
|
|
if ( result == nullptr ) {
|
|
if ( (options.canBeMissing || options.rtldNoLoad) )
|
|
diag.clearError();
|
|
else if ( diag.noError() ) {
|
|
bool isRPath = (strncmp(loadPath, "@rpath/", 7) == 0);
|
|
if ( isRPath ) {
|
|
__block bool hasRPath = false;
|
|
for ( const LoadChain* link = options.rpathStack; (link != nullptr) && !hasRPath; link = link->previous ) {
|
|
const MachOFile* mf = link->image->mf(state);
|
|
mf->forEachRPath(^(const char* rPath, bool& innerStop) {
|
|
hasRPath = true;
|
|
innerStop = true;
|
|
});
|
|
}
|
|
if ( !hasRPath ) {
|
|
diag.error("no LC_RPATH's found");
|
|
} else {
|
|
// FIXME: Is there an error we can give if we can even get here?
|
|
}
|
|
} else {
|
|
// FIXME: Is there an error we can give if we can even get here?
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
#endif // !TARGET_OS_EXCLAVEKIT
|
|
}
|
|
|
|
#if !TARGET_OS_EXCLAVEKIT
|
|
bool Loader::expandAtLoaderPath(RuntimeState& state, const char* loadPath, const LoadOptions& options, const Loader* ldr, bool fromLCRPATH, char fixedPath[])
|
|
{
|
|
// only do something if path starts with @loader_path
|
|
if ( strncmp(loadPath, "@loader_path", 12) != 0 )
|
|
return false;
|
|
if ( (loadPath[12] != '/') && (loadPath[12] != '\0') )
|
|
return false;
|
|
|
|
// don't support @loader_path in DYLD_INSERT_LIBRARIES
|
|
if ( options.insertedDylib ) {
|
|
if ( state.config.log.searching )
|
|
state.log(" @loader_path not allowed in DYLD_INSERT_LIBRARIES\n");
|
|
return false;
|
|
}
|
|
|
|
// don't expand if security does not allow
|
|
if ( !state.config.security.allowAtPaths && fromLCRPATH && (ldr == state.mainExecutableLoader) ) {
|
|
// <rdar://42360708> but allow @loader_path in LC_LOAD_DYLIB during dlopen()
|
|
if ( state.config.log.searching )
|
|
state.log(" @loader_path in LC_RPATH from main executable not expanded due to security policy\n");
|
|
return false;
|
|
}
|
|
|
|
strlcpy(fixedPath, ldr->path(), PATH_MAX);
|
|
char* lastSlash = strrchr(fixedPath, '/');
|
|
if ( lastSlash != nullptr ) {
|
|
strlcpy(lastSlash, &loadPath[12], PATH_MAX);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool Loader::expandAndNormalizeAtExecutablePath(const char* mainPath, const char* pathWithAtExecutable, char fixedPath[PATH_MAX])
|
|
{
|
|
// only do something if path starts with "@executable_path/" or is ""@executable_path"
|
|
if ( strncmp(pathWithAtExecutable, "@executable_path", 16) != 0 )
|
|
return false;
|
|
if ( (pathWithAtExecutable[16] != '/') && (pathWithAtExecutable[16] != '\0') )
|
|
return false;
|
|
|
|
strlcpy(fixedPath, mainPath, PATH_MAX);
|
|
char* mainPathDirStart = strrchr(fixedPath, '/');
|
|
if ( mainPathDirStart == nullptr )
|
|
return false; // no slash in mainPath ??
|
|
|
|
const char* trailingLoadPath = &pathWithAtExecutable[16];
|
|
if ( *trailingLoadPath == '/' ) {
|
|
// main executable path is already a real path, so we can remove ../ by chopping back path
|
|
// Ex: @executable_path/../Foo (when mainPath=/Applications/XZY.app/XZY)
|
|
// optimize /Applications/XZY.app/../Foo to /Applications/Foo
|
|
while ( strncmp(trailingLoadPath, "/..", 3) == 0 ) {
|
|
char* newLastSlash = mainPathDirStart-1;
|
|
while ( (newLastSlash > fixedPath) && (*newLastSlash != '/') )
|
|
--newLastSlash;
|
|
if ( newLastSlash != fixedPath ) {
|
|
trailingLoadPath += 3;
|
|
mainPathDirStart = newLastSlash;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
++mainPathDirStart;
|
|
}
|
|
strlcpy(mainPathDirStart, trailingLoadPath, PATH_MAX);
|
|
return true;
|
|
}
|
|
|
|
bool Loader::expandAtExecutablePath(RuntimeState& state, const char* loadPath, const LoadOptions& options, bool fromLCRPATH, char fixedPath[])
|
|
{
|
|
// only do something if path starts with @executable_path
|
|
if ( strncmp(loadPath, "@executable_path", 16) != 0 )
|
|
return false;
|
|
if ( (loadPath[16] != '/') && (loadPath[16] != '\0') )
|
|
return false;
|
|
|
|
// don't expand if security does not allow
|
|
if ( !state.config.security.allowAtPaths ) {
|
|
if ( state.config.log.searching )
|
|
state.log(" @executable_path not expanded due to security policy\n");
|
|
return false;
|
|
}
|
|
|
|
return expandAndNormalizeAtExecutablePath(state.config.process.mainExecutablePath, loadPath, fixedPath);
|
|
}
|
|
|
|
void Loader::forEachResolvedAtPathVar(RuntimeState& state, const char* loadPath, const LoadOptions& options, ProcessConfig::PathOverrides::Type type, bool& stop,
|
|
void (^handler)(const char* possiblePath, ProcessConfig::PathOverrides::Type type, bool& stop))
|
|
{
|
|
// don't expand @rpath in DYLD_INSERT_LIBRARIES
|
|
bool isRPath = (strncmp(loadPath, "@rpath/", 7) == 0);
|
|
if ( isRPath && options.insertedDylib ) {
|
|
handler(loadPath, type, stop);
|
|
return;
|
|
}
|
|
|
|
// expand @loader_path
|
|
BLOCK_ACCCESSIBLE_ARRAY(char, tempPath, PATH_MAX);
|
|
if ( expandAtLoaderPath(state, loadPath, options, options.rpathStack->image, false, tempPath) ) {
|
|
handler(tempPath, ProcessConfig::PathOverrides::Type::loaderPathExpansion, stop);
|
|
#if BUILDING_DYLD && TARGET_OS_OSX
|
|
if ( !stop ) {
|
|
// using @loader_path, but what it expanded to did not work ('stop' not set)
|
|
// maybe this is an old binary with an install name missing the /Versions/A/ part
|
|
const Loader* orgLoader = options.rpathStack->image;
|
|
const MachOAnalyzer* orgMA = orgLoader->analyzer(state);
|
|
if ( orgMA->isDylib() && !orgMA->enforceFormat(MachOAnalyzer::Malformed::loaderPathsAreReal) ) {
|
|
const char* fullPath = orgLoader->path();
|
|
const char* installPath = orgMA->installName();
|
|
if ( const char* installLeaf = strrchr(installPath, '/') ) {
|
|
size_t leafLen = strlen(installLeaf);
|
|
size_t fullLen = strlen(fullPath);
|
|
if ( fullLen > (leafLen+11) ) {
|
|
const char* fullWhereVersionMayBe = &fullPath[fullLen-leafLen-11];
|
|
if ( strncmp(fullWhereVersionMayBe, "/Versions/", 10) == 0 ) {
|
|
// try expanding @loader_path to this framework's path that is missing /Versions/A part
|
|
strlcpy(tempPath, fullPath, PATH_MAX);
|
|
tempPath[fullLen-leafLen-11] = '\0';
|
|
strlcat(tempPath, &loadPath[12], PATH_MAX);
|
|
handler(tempPath, ProcessConfig::PathOverrides::Type::loaderPathExpansion, stop);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
// expand @executable_path
|
|
if ( expandAtExecutablePath(state, loadPath, options, false, tempPath) ) {
|
|
handler(tempPath, ProcessConfig::PathOverrides::Type::executablePathExpansion, stop);
|
|
return;
|
|
}
|
|
|
|
// expand @rpath
|
|
if ( isRPath ) {
|
|
// note: rpathTail starts with '/'
|
|
const char* rpathTail = &loadPath[6];
|
|
// keep track if this is an explict @rpath or implicit
|
|
ProcessConfig::PathOverrides::Type expandType = ProcessConfig::PathOverrides::Type::rpathExpansion;
|
|
if ( type == ProcessConfig::PathOverrides::Type::implictRpathExpansion )
|
|
expandType = type;
|
|
// rpath is expansion is a stack of rpath dirs built starting with main executable and pushing
|
|
// LC_RPATHS from each dylib as they are recursively loaded. options.rpathStack is a linnked list of that stack.
|
|
for ( const LoadChain* link = options.rpathStack; (link != nullptr) && !stop; link = link->previous ) {
|
|
const MachOFile* mf = link->image->mf(state);
|
|
mf->forEachRPath(^(const char* rPath, bool& innerStop) {
|
|
if ( state.config.log.searching )
|
|
state.log(" LC_RPATH '%s' from '%s'\n", rPath, link->image->path());
|
|
if ( expandAtLoaderPath(state, rPath, options, link->image, true, tempPath) || expandAtExecutablePath(state, rPath, options, true, tempPath) ) {
|
|
Utils::concatenatePaths(tempPath, rpathTail, PATH_MAX);
|
|
handler(tempPath, expandType, innerStop);
|
|
}
|
|
else if ( rPath[0] == '/' ) {
|
|
#if BUILDING_DYLD && TARGET_OS_OSX && __arm64__ // FIXME: this should be a runtime check to enable unit testing
|
|
// if LC_RPATH is to absolute path like /usr/lib/swift, but this iOS app running on macOS, we really need /System/iOSSupport/usr/lib/swift
|
|
if ( state.config.process.platform == dyld3::Platform::iOS ) {
|
|
strlcpy(tempPath, "/System/iOSSupport", PATH_MAX);
|
|
strlcat(tempPath, rPath, PATH_MAX);
|
|
Utils::concatenatePaths(tempPath, rpathTail, PATH_MAX);
|
|
handler(tempPath, expandType, innerStop);
|
|
if ( innerStop ) {
|
|
stop = true;
|
|
return;
|
|
}
|
|
}
|
|
// fall through
|
|
#endif
|
|
#if TARGET_OS_SIMULATOR
|
|
// <rdar://problem/5869973> DYLD_ROOT_PATH should apply to LC_RPATH rpaths
|
|
if ( const char* simRoot = state.config.pathOverrides.simRootPath() ) {
|
|
strlcpy(tempPath, simRoot, PATH_MAX);
|
|
strlcat(tempPath, rPath, PATH_MAX);
|
|
Utils::concatenatePaths(tempPath, rpathTail, PATH_MAX);
|
|
handler(tempPath, expandType, innerStop);
|
|
if ( innerStop ) {
|
|
stop = true;
|
|
return;
|
|
}
|
|
}
|
|
// <rdar://problem/49576123> Even if DYLD_ROOT_PATH exists, LC_RPATH should add raw path to rpaths
|
|
// so fall through
|
|
#endif
|
|
|
|
// LC_RPATH is an absolute path, not blocked by AtPath::none
|
|
strlcpy(tempPath, rPath, PATH_MAX);
|
|
Utils::concatenatePaths(tempPath, rpathTail, PATH_MAX);
|
|
handler(tempPath, expandType, innerStop);
|
|
if ( innerStop ) {
|
|
stop = true;
|
|
return;
|
|
}
|
|
|
|
// Note this is after the above call due to:
|
|
// rdar://91027811 (dyld should search for dylib overrides in / before /System/Cryptexes/OS)
|
|
// <rdar://problem/5869973> DYLD_ROOT_PATH should apply to LC_RPATH rpaths
|
|
if ( const char* cryptexRoot = state.config.pathOverrides.cryptexRootPath() ) {
|
|
strlcpy(tempPath, cryptexRoot, PATH_MAX);
|
|
strlcat(tempPath, rPath, PATH_MAX);
|
|
Utils::concatenatePaths(tempPath, rpathTail, PATH_MAX);
|
|
handler(tempPath, expandType, innerStop);
|
|
if ( innerStop ) {
|
|
stop = true;
|
|
return;
|
|
}
|
|
}
|
|
} else {
|
|
#if BUILDING_DYLD && TARGET_OS_OSX // FIXME: this should be a runtime check to enable unit testing
|
|
// <rdar://81909581>
|
|
// Relative paths. Only allow these if security supports them
|
|
if ( state.config.security.allowAtPaths ) {
|
|
strlcpy(tempPath, rPath, PATH_MAX);
|
|
Utils::concatenatePaths(tempPath, rpathTail, PATH_MAX);
|
|
handler(tempPath, expandType, innerStop);
|
|
}
|
|
#endif
|
|
}
|
|
if ( innerStop )
|
|
stop = true;
|
|
});
|
|
}
|
|
if ( stop )
|
|
return;
|
|
}
|
|
|
|
// only call with origin path if it did not start with @
|
|
if ( loadPath[0] != '@' ) {
|
|
handler(loadPath, type, stop);
|
|
}
|
|
}
|
|
#endif // !TARGET_OS_EXCLAVEKIT
|
|
|
|
|
|
#if (BUILDING_DYLD || BUILDING_CLOSURE_UTIL || BUILDING_UNIT_TESTS) && !TARGET_OS_EXCLAVEKIT
|
|
uint64_t Loader::validateFile(Diagnostics& diag, const RuntimeState& state, int fd, const char* path,
|
|
const CodeSignatureInFile& codeSignature, const Loader::FileValidationInfo& fileValidation)
|
|
{
|
|
// get file info
|
|
struct stat statBuf;
|
|
if ( state.config.syscall.fstat(fd, &statBuf) != 0 ) {
|
|
int statErr = errno;
|
|
if ( (statErr == EPERM) && state.config.syscall.sandboxBlockedStat(path) )
|
|
diag.error("file system sandbox blocked stat(\"%s\")", path);
|
|
else if ( statErr == ENOENT )
|
|
diag.error("no such file");
|
|
else
|
|
diag.error("stat(\"%s\") failed with errno=%d", path, statErr);
|
|
return -1;
|
|
}
|
|
|
|
#if !__LP64__
|
|
statBuf.st_ino = (statBuf.st_ino & 0xFFFFFFFF);
|
|
#endif
|
|
|
|
// if inode/mtime was recorded, check that
|
|
if ( fileValidation.checkInodeMtime ) {
|
|
if ( statBuf.st_ino != fileValidation.inode ) {
|
|
diag.error("file inode changed from 0x%llX to 0x%llX since PrebuiltLoader was built for '%s'", fileValidation.inode, statBuf.st_ino, path);
|
|
return -1;
|
|
}
|
|
if ( (uint64_t)statBuf.st_mtime != fileValidation.mtime ) {
|
|
diag.error("file mtime changed from 0x%llX to 0x%lX since PrebuiltLoader was built for '%s'", fileValidation.mtime, statBuf.st_mtime, path);
|
|
return -1;
|
|
}
|
|
// sanity check slice offset
|
|
if ( (uint64_t)statBuf.st_size < fileValidation.sliceOffset ) {
|
|
diag.error("file too small for slice offset '%s'", path);
|
|
return -1;
|
|
}
|
|
return fileValidation.sliceOffset;
|
|
}
|
|
else if ( codeSignature.size != 0 ) {
|
|
#if !TARGET_OS_SIMULATOR // some hashing functions not available in .a files
|
|
// otherwise compare cdHash
|
|
void* mappedFile = state.config.syscall.mmap(nullptr, (size_t)statBuf.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
|
|
if ( mappedFile == MAP_FAILED ) {
|
|
diag.error("could not mmap() '%s'", path);
|
|
return -1;
|
|
}
|
|
uint64_t sliceOffset = -1;
|
|
bool isOSBinary = false; // FIXME
|
|
if ( const MachOFile* mf = MachOFile::compatibleSlice(diag, mappedFile, (size_t)statBuf.st_size, path, state.config.process.platform, isOSBinary, *state.config.process.archs, state.config.security.internalInstall) ) {
|
|
const MachOLoaded* ml = (MachOLoaded*)mf;
|
|
__block bool cdHashMatches = false;
|
|
// Note, file is not mapped with zero fill so cannot use forEachCdHash()
|
|
// need to use lower level forEachCDHashOfCodeSignature() which takes pointer to code blob
|
|
ml->forEachCDHashOfCodeSignature((uint8_t*)mf + codeSignature.fileOffset, codeSignature.size, ^(const uint8_t cdHash[20]) {
|
|
if ( ::memcmp((void*)cdHash, (void*)fileValidation.cdHash, 20) == 0 )
|
|
cdHashMatches = true;
|
|
});
|
|
if ( cdHashMatches )
|
|
sliceOffset = (uint8_t*)mf - (uint8_t*)mappedFile;
|
|
else
|
|
diag.error("file cdHash not as expected '%s'", path);
|
|
}
|
|
state.config.syscall.munmap(mappedFile, (size_t)fileValidation.sliceOffset);
|
|
return sliceOffset;
|
|
#endif
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
#if BUILDING_DYLD
|
|
static bool getUuidFromFd(RuntimeState& state, int fd, uint64_t sliceOffset, char uuidStr[64])
|
|
{
|
|
strlcpy(uuidStr, "no uuid", 64);
|
|
mach_header mh;
|
|
if ( state.config.syscall.pread(fd, &mh, sizeof(mh), (size_t)sliceOffset) == sizeof(mh) ) {
|
|
if ( ((MachOFile*)&mh)->hasMachOMagic() ) {
|
|
size_t headerAndLoadCommandsSize = mh.sizeofcmds+sizeof(mach_header_64);
|
|
uint8_t buffer[headerAndLoadCommandsSize];
|
|
if ( state.config.syscall.pread(fd, buffer, sizeof(buffer), (size_t)sliceOffset) == headerAndLoadCommandsSize ) {
|
|
uuid_t uuid;
|
|
if ( ((MachOFile*)buffer)->getUuid(uuid) ) {
|
|
Loader::uuidToStr(uuid, uuidStr);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
const MachOAnalyzer* Loader::mapSegments(Diagnostics& diag, RuntimeState& state, const char* path, uint64_t vmSpace,
|
|
const CodeSignatureInFile& codeSignature, bool hasCodeSignature,
|
|
const Array<Region>& regions, bool neverUnloads, bool prebuilt, const FileValidationInfo& fileValidation)
|
|
{
|
|
#if BUILDING_DYLD
|
|
dyld3::ScopedTimer timer(DBG_DYLD_TIMING_MAP_IMAGE, path, 0, 0);
|
|
#endif
|
|
// open file
|
|
int fd = state.config.syscall.open(path, O_RDONLY, 0);
|
|
if ( fd == -1 ) {
|
|
int openErr = errno;
|
|
if ( (openErr == EPERM) && state.config.syscall.sandboxBlockedOpen(path) )
|
|
diag.error("file system sandbox blocked open(\"%s\", O_RDONLY)", path);
|
|
else if ( openErr == ENOENT )
|
|
diag.error("no such file");
|
|
else
|
|
diag.error("open(\"%s\", O_RDONLY) failed with errno=%d", path, openErr);
|
|
return nullptr;
|
|
}
|
|
|
|
// validated this file has not changed (since PrebuiltLoader was made)
|
|
uint64_t sliceOffset = fileValidation.sliceOffset;
|
|
if ( prebuilt ) {
|
|
sliceOffset = validateFile(diag, state, fd, path, codeSignature, fileValidation);
|
|
if ( diag.hasError() ) {
|
|
state.config.syscall.close(fd);
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
#if BUILDING_DYLD
|
|
// register code signature
|
|
uint64_t coveredCodeLength = UINT64_MAX;
|
|
if ( hasCodeSignature && codeSignature.size != 0 ) {
|
|
dyld3::ScopedTimer codeSigTimer(DBG_DYLD_TIMING_ATTACH_CODESIGNATURE, 0, 0, 0);
|
|
fsignatures_t siginfo;
|
|
siginfo.fs_file_start = sliceOffset; // start of mach-o slice in fat file
|
|
siginfo.fs_blob_start = (void*)(long)(codeSignature.fileOffset); // start of CD in mach-o file
|
|
siginfo.fs_blob_size = codeSignature.size; // size of CD
|
|
int result = state.config.syscall.fcntl(fd, F_ADDFILESIGS_RETURN, &siginfo);
|
|
if ( result == -1 ) {
|
|
char uuidStr[64];
|
|
getUuidFromFd(state, fd, sliceOffset, uuidStr);
|
|
int errnoCopy = errno;
|
|
if ( (errnoCopy == EPERM) || (errnoCopy == EBADEXEC) ) {
|
|
diag.error("code signature invalid in <%s> '%s' (errno=%d) sliceOffset=0x%08llX, codeBlobOffset=0x%08X, codeBlobSize=0x%08X",
|
|
uuidStr, path, errnoCopy, sliceOffset, codeSignature.fileOffset, codeSignature.size);
|
|
}
|
|
else {
|
|
diag.error("fcntl(fd, F_ADDFILESIGS_RETURN) failed with errno=%d in <%s> '%s', sliceOffset=0x%08llX, codeBlobOffset=0x%08X, codeBlobSize=0x%08X",
|
|
errnoCopy, uuidStr, path, sliceOffset, codeSignature.fileOffset, codeSignature.size);
|
|
}
|
|
state.config.syscall.close(fd);
|
|
return nullptr;
|
|
}
|
|
coveredCodeLength = siginfo.fs_file_start;
|
|
if ( coveredCodeLength < codeSignature.fileOffset ) {
|
|
char uuidStr[64];
|
|
getUuidFromFd(state, fd, sliceOffset, uuidStr);
|
|
diag.error("code signature does not cover entire file up to signature in <%s> '%s' (signed 0x%08llX, expected 0x%08X) for '%s'",
|
|
uuidStr, path, coveredCodeLength, codeSignature.fileOffset, path);
|
|
state.config.syscall.close(fd);
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
// <rdar://problem/41015217> dyld should use F_CHECK_LV even on unsigned binaries
|
|
{
|
|
// <rdar://problem/32684903> always call F_CHECK_LV to preflight
|
|
fchecklv checkInfo;
|
|
char messageBuffer[512];
|
|
messageBuffer[0] = '\0';
|
|
checkInfo.lv_file_start = sliceOffset;
|
|
checkInfo.lv_error_message_size = sizeof(messageBuffer);
|
|
checkInfo.lv_error_message = messageBuffer;
|
|
int res = state.config.syscall.fcntl(fd, F_CHECK_LV, &checkInfo);
|
|
if ( res == -1 ) {
|
|
// rdar://79796526 (include uuid of mis-signed binary to help debug)
|
|
char uuidStr[64];
|
|
getUuidFromFd(state, fd, sliceOffset, uuidStr);
|
|
diag.error("code signature in <%s> '%s' not valid for use in process: %s", uuidStr, path, messageBuffer);
|
|
state.config.syscall.close(fd);
|
|
return nullptr;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#if BUILDING_DYLD && SUPPORT_ROSETTA
|
|
// if translated, need to add in translated code segment
|
|
char aotPath[PATH_MAX];
|
|
uint64_t extraAllocSize = 0;
|
|
if ( state.config.process.isTranslated ) {
|
|
int ret = aot_get_extra_mapping_info(fd, path, extraAllocSize, aotPath, sizeof(aotPath));
|
|
if ( ret == 0 ) {
|
|
vmSpace += extraAllocSize;
|
|
}
|
|
else {
|
|
extraAllocSize = 0;
|
|
aotPath[0] = '\0';
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// reserve address range
|
|
vm_address_t loadAddress = 0;
|
|
kern_return_t r = ::vm_allocate(mach_task_self(), &loadAddress, (vm_size_t)vmSpace, VM_FLAGS_ANYWHERE);
|
|
if ( r != KERN_SUCCESS ) {
|
|
diag.error("vm_allocate(size=0x%0llX) failed with result=%d", vmSpace, r);
|
|
state.config.syscall.close(fd);
|
|
return nullptr;
|
|
}
|
|
|
|
#if BUILDING_DYLD
|
|
if ( state.config.log.segments ) {
|
|
if ( sliceOffset != 0 )
|
|
state.log("Mapping %s (slice offset=0x%llX)\n", path, sliceOffset);
|
|
else
|
|
state.log("Mapping %s\n", path);
|
|
}
|
|
#endif
|
|
|
|
// map each segment
|
|
bool mmapFailure = false;
|
|
const bool enableTpro = state.config.process.enableTproDataConst;
|
|
__block uint32_t segIndex = 0;
|
|
for ( const Region& region : regions ) {
|
|
// <rdar://problem/32363581> Mapping zero filled regions fails with mmap of size 0
|
|
if ( region.isZeroFill || (region.fileSize == 0) )
|
|
continue;
|
|
if ( (region.vmOffset == 0) && (segIndex > 0) )
|
|
continue;
|
|
int perms = VM_PROT_READ;
|
|
int flags = MAP_FIXED | MAP_PRIVATE;
|
|
|
|
#if BUILDING_DYLD
|
|
perms = region.perms;
|
|
#endif
|
|
if (enableTpro && region.readOnlyData) {
|
|
flags |= MAP_TPRO;
|
|
}
|
|
void* segAddress = state.config.syscall.mmap((void*)(loadAddress + region.vmOffset), (size_t)region.fileSize, perms,
|
|
flags, fd, (size_t)(sliceOffset + region.fileOffset));
|
|
int mmapErr = errno;
|
|
if ( segAddress == MAP_FAILED ) {
|
|
if ( mmapErr == EPERM ) {
|
|
if ( state.config.syscall.sandboxBlockedMmap(path) )
|
|
diag.error("file system sandbox blocked mmap() of '%s'", path);
|
|
else
|
|
diag.error("code signing blocked mmap() of '%s'", path);
|
|
}
|
|
else {
|
|
diag.error("mmap(addr=0x%0llX, size=0x%08X) failed with errno=%d for %s", loadAddress + region.vmOffset,
|
|
region.fileSize, mmapErr, path);
|
|
}
|
|
mmapFailure = true;
|
|
break;
|
|
}
|
|
|
|
// sanity check first segment is mach-o header
|
|
if ( !mmapFailure && (segIndex == 0) ) {
|
|
const MachOAnalyzer* ma = (MachOAnalyzer*)segAddress;
|
|
if ( !ma->isMachO(diag, region.fileSize) ) {
|
|
mmapFailure = true;
|
|
break;
|
|
}
|
|
}
|
|
if ( !mmapFailure ) {
|
|
#if BUILDING_DYLD
|
|
uintptr_t mappedSize = round_page((uintptr_t)region.fileSize);
|
|
uintptr_t mappedStart = (uintptr_t)segAddress;
|
|
uintptr_t mappedEnd = mappedStart + mappedSize;
|
|
if ( state.config.log.segments ) {
|
|
const MachOLoaded* lmo = (MachOLoaded*)loadAddress;
|
|
state.log("%14s (%c%c%c) 0x%012lX->0x%012lX\n", lmo->segmentName(segIndex),
|
|
(region.perms & PROT_READ) ? 'r' : '.', (region.perms & PROT_WRITE) ? 'w' : '.', (region.perms & PROT_EXEC) ? 'x' : '.',
|
|
mappedStart, mappedEnd);
|
|
}
|
|
#endif
|
|
}
|
|
++segIndex;
|
|
}
|
|
|
|
#if BUILDING_DYLD && !TARGET_OS_SIMULATOR && (__arm64__ || __arm__)
|
|
if ( !mmapFailure ) {
|
|
// tell kernel about fairplay encrypted regions
|
|
uint32_t fpTextOffset;
|
|
uint32_t fpSize;
|
|
const MachOAnalyzer* ma = (const MachOAnalyzer*)loadAddress;
|
|
// FIXME: record if FP info in PrebuiltLoader
|
|
if ( ma->isFairPlayEncrypted(fpTextOffset, fpSize) ) {
|
|
int result = state.config.syscall.mremap_encrypted((void*)(loadAddress + fpTextOffset), fpSize, 1, ma->cputype, ma->cpusubtype);
|
|
if ( result != 0 ) {
|
|
diag.error("could not register fairplay decryption, mremap_encrypted() => %d", result);
|
|
mmapFailure = true;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if ( mmapFailure ) {
|
|
::vm_deallocate(mach_task_self(), loadAddress, (vm_size_t)vmSpace);
|
|
state.config.syscall.close(fd);
|
|
return nullptr;
|
|
}
|
|
|
|
#if BUILDING_DYLD && SUPPORT_ROSETTA
|
|
if ( state.config.process.isTranslated && (extraAllocSize != 0) ) {
|
|
// map in translated code at end of mapped segments
|
|
dyld_aot_image_info aotInfo;
|
|
uint64_t extraSpaceAddr = (long)loadAddress + vmSpace - extraAllocSize;
|
|
int ret = aot_map_extra(path, (mach_header*)loadAddress, (void*)extraSpaceAddr, aotInfo.aotLoadAddress, aotInfo.aotImageSize, aotInfo.aotImageKey);
|
|
if ( ret == 0 ) {
|
|
// fill in the load address, at this point the Rosetta trap has filled in the other fields
|
|
aotInfo.x86LoadAddress = (mach_header*)loadAddress;
|
|
#if HAS_EXTERNAL_STATE
|
|
std::span<const dyld_aot_image_info> aots(&aotInfo, 1);
|
|
// dyld automatically adds an entry to the image list when loading the dylib.
|
|
// Add an entry for the aot info but pass an empty std::span for the dyld image info
|
|
std::span<const dyld_image_info> infos;
|
|
state.externallyViewable.addRosettaImages(aots, infos);
|
|
#endif
|
|
if ( state.config.log.segments ) {
|
|
state.log("%14s (r.x) 0x%012llX->0x%012llX\n", "ROSETTA", extraSpaceAddr, extraSpaceAddr + extraAllocSize);
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
// close file
|
|
state.config.syscall.close(fd);
|
|
return (MachOAnalyzer*)loadAddress;
|
|
}
|
|
#endif // BUILDING_DYLD || BUILDING_CLOSURE_UTIL || BUILDING_UNIT_TESTS
|
|
|
|
#if BUILDING_DYLD || BUILDING_UNIT_TESTS
|
|
|
|
// can't do page-in linking with simulator until will know host OS will support it
|
|
#if !TARGET_OS_SIMULATOR && !TARGET_OS_EXCLAVEKIT
|
|
|
|
static void fixupPage64(void* pageContent, const mwl_info_hdr* blob, const dyld_chained_starts_in_segment* segInfo, uint32_t pageIndex, bool offsetBased)
|
|
{
|
|
const uint64_t* bindsArray = (uint64_t*)((uint8_t*)blob + blob->mwli_binds_offset);
|
|
uint16_t firstStartOffset = segInfo->page_start[pageIndex];
|
|
// check marker for no fixups on the page
|
|
if ( firstStartOffset == DYLD_CHAINED_PTR_START_NONE )
|
|
return;
|
|
uint64_t* chain = (uint64_t*)((uint8_t*)pageContent + firstStartOffset);
|
|
// walk chain
|
|
const uint64_t targetAdjust = (offsetBased ? blob->mwli_image_address : blob->mwli_slide);
|
|
uint64_t delta = 0;
|
|
do {
|
|
uint64_t value = *chain;
|
|
bool isBind = (value & 0x8000000000000000ULL);
|
|
delta = (value >> 51) & 0xFFF;
|
|
//fprintf(stderr, " offset=0x%08lX, chain=%p, value=0x%016llX, delta=%lld\n", (long)chain - (long)header, chain, value, delta);
|
|
if ( isBind ) {
|
|
// is bind
|
|
uint32_t bindOrdinal = value & 0x00FFFFFF;
|
|
if ( bindOrdinal >= blob->mwli_binds_count ) {
|
|
fprintf(stderr, "out of range bind ordinal %u (max %u)", bindOrdinal, blob->mwli_binds_count);
|
|
break;
|
|
}
|
|
else {
|
|
uint32_t addend = (value >> 24) & 0xFF;
|
|
*chain = bindsArray[bindOrdinal] + addend;
|
|
}
|
|
}
|
|
else {
|
|
// is rebase
|
|
uint64_t target = value & 0xFFFFFFFFFULL;
|
|
uint64_t high8 = (value >> 36) & 0xFF;
|
|
*chain = target + targetAdjust + (high8 << 56);
|
|
}
|
|
chain = (uint64_t*)((uint8_t*)chain + (delta*4)); // 4-byte stride
|
|
} while ( delta != 0 );
|
|
}
|
|
|
|
|
|
static void fixupChain32(uint32_t* chain, const mwl_info_hdr* blob, const dyld_chained_starts_in_segment* segInfo, const uint32_t bindsArray[])
|
|
{
|
|
//fprintf(stderr, "fixupChain32(%p)\n", chain);
|
|
uint32_t delta = 0;
|
|
do {
|
|
uint32_t value = *chain;
|
|
delta = (value >> 26) & 0x1F;
|
|
//fprintf(stderr, " chain=%p, value=0x%08X, delta=%u\n", chain, value, delta);
|
|
if ( value & 0x80000000 ) {
|
|
// is bind
|
|
uint32_t bindOrdinal = value & 0x000FFFFF;
|
|
if ( bindOrdinal >= blob->mwli_binds_count ) {
|
|
fprintf(stderr, "out of range bind ordinal %u (max %u)", bindOrdinal, blob->mwli_binds_count);
|
|
break;
|
|
}
|
|
else {
|
|
uint32_t addend = (value >> 20) & 0x3F;
|
|
*chain = bindsArray[bindOrdinal] + addend;
|
|
}
|
|
}
|
|
else {
|
|
// is rebase
|
|
uint32_t target = value & 0x03FFFFFF;
|
|
if ( target > segInfo->max_valid_pointer ) {
|
|
// handle non-pointers in chain
|
|
uint32_t bias = (0x04000000 + segInfo->max_valid_pointer)/2;
|
|
*chain = target - bias;
|
|
}
|
|
else {
|
|
*chain = target + (uint32_t)blob->mwli_slide;
|
|
}
|
|
}
|
|
chain += delta;
|
|
} while ( delta != 0 );
|
|
}
|
|
|
|
|
|
static void fixupPage32(void* pageContent, const mwl_info_hdr* blob, const dyld_chained_starts_in_segment* segInfo, uint32_t pageIndex)
|
|
{
|
|
const uint32_t* bindsArray = (uint32_t*)((uint8_t*)blob + blob->mwli_binds_offset);
|
|
uint16_t startOffset = segInfo->page_start[pageIndex];
|
|
if ( startOffset == DYLD_CHAINED_PTR_START_NONE )
|
|
return;
|
|
if ( startOffset & DYLD_CHAINED_PTR_START_MULTI ) {
|
|
// some fixups in the page are too far apart, so page has multiple starts
|
|
uint32_t overflowIndex = startOffset & ~DYLD_CHAINED_PTR_START_MULTI;
|
|
bool chainEnd = false;
|
|
while ( !chainEnd ) {
|
|
chainEnd = (segInfo->page_start[overflowIndex] & DYLD_CHAINED_PTR_START_LAST);
|
|
startOffset = (segInfo->page_start[overflowIndex] & ~DYLD_CHAINED_PTR_START_LAST);
|
|
uint32_t* chain = (uint32_t*)((uint8_t*)pageContent + startOffset);
|
|
fixupChain32(chain, blob, segInfo, bindsArray);
|
|
++overflowIndex;
|
|
}
|
|
}
|
|
else {
|
|
uint32_t* chain = (uint32_t*)((uint8_t*)pageContent + startOffset);
|
|
fixupChain32(chain, blob, segInfo, bindsArray);
|
|
}
|
|
}
|
|
|
|
#if __has_feature(ptrauth_calls)
|
|
static uint64_t signPointer(uint64_t unsignedAddr, void* loc, bool addrDiv, uint16_t diversity, ptrauth_key key)
|
|
{
|
|
// don't sign NULL
|
|
if ( unsignedAddr == 0 )
|
|
return 0;
|
|
|
|
uint64_t extendedDiscriminator = diversity;
|
|
if ( addrDiv )
|
|
extendedDiscriminator = __builtin_ptrauth_blend_discriminator(loc, extendedDiscriminator);
|
|
switch ( key ) {
|
|
case ptrauth_key_asia:
|
|
return (uint64_t)__builtin_ptrauth_sign_unauthenticated((void*)unsignedAddr, 0, extendedDiscriminator);
|
|
case ptrauth_key_asib:
|
|
return (uint64_t)__builtin_ptrauth_sign_unauthenticated((void*)unsignedAddr, 1, extendedDiscriminator);
|
|
case ptrauth_key_asda:
|
|
return (uint64_t)__builtin_ptrauth_sign_unauthenticated((void*)unsignedAddr, 2, extendedDiscriminator);
|
|
case ptrauth_key_asdb:
|
|
return (uint64_t)__builtin_ptrauth_sign_unauthenticated((void*)unsignedAddr, 3, extendedDiscriminator);
|
|
default:
|
|
assert(0 && "invalid signing key");
|
|
}
|
|
}
|
|
|
|
static void fixupPageAuth64(void* pageContent, const mwl_info_hdr* blob, const dyld_chained_starts_in_segment* segInfo, uint32_t pageIndex, bool offsetBased)
|
|
{
|
|
//fprintf(stderr, "fixupPageAuth64(): pageContent=%p, blob=%p, segInfo=%p, pageIndex=%u\n", pageContent, blob, segInfo, pageIndex);
|
|
const uint64_t* bindsArray = (uint64_t*)((uint8_t*)blob + blob->mwli_binds_offset);
|
|
uint16_t firstStartOffset = segInfo->page_start[pageIndex];
|
|
// check marker for no fixups on the page
|
|
if ( firstStartOffset == DYLD_CHAINED_PTR_START_NONE )
|
|
return;
|
|
uint64_t* chain = (uint64_t*)((uint8_t*)pageContent + firstStartOffset);
|
|
// walk chain
|
|
const uint64_t targetAdjust = (offsetBased ? blob->mwli_image_address : blob->mwli_slide);
|
|
uint64_t delta = 0;
|
|
do {
|
|
uint64_t value = *chain;
|
|
delta = (value >> 51) & 0x7FF;
|
|
//fprintf(stderr, " chain=%p, value=0x%08llX, delta=%llu\n", chain, value, delta);
|
|
bool isAuth = (value & 0x8000000000000000ULL);
|
|
bool isBind = (value & 0x4000000000000000ULL);
|
|
if ( isAuth ) {
|
|
ptrauth_key key = (ptrauth_key)((value >> 49) & 0x3);
|
|
bool addrDiv = ((value & (1ULL << 48)) != 0);
|
|
uint16_t diversity = (uint16_t)((value >> 32) & 0xFFFF);
|
|
if ( isBind ) {
|
|
uint32_t bindOrdinal = value & 0x00FFFFFF;
|
|
if ( bindOrdinal >= blob->mwli_binds_count ) {
|
|
fprintf(stderr, "out of range bind ordinal %u (max %u)", bindOrdinal, blob->mwli_binds_count);
|
|
break;
|
|
}
|
|
else {
|
|
*chain = signPointer(bindsArray[bindOrdinal], chain, addrDiv, diversity, key);
|
|
}
|
|
}
|
|
else {
|
|
/* note: in auth rebases only have 32-bits, so target is always offset - never vmaddr */
|
|
uint64_t target = (value & 0xFFFFFFFF) + blob->mwli_image_address;
|
|
*chain = signPointer(target, chain, addrDiv, diversity, key);
|
|
}
|
|
}
|
|
else {
|
|
if ( isBind ) {
|
|
uint32_t bindOrdinal = value & 0x00FFFFFF;
|
|
if ( bindOrdinal >= blob->mwli_binds_count ) {
|
|
fprintf(stderr, "out of range bind ordinal %u (max %u)", bindOrdinal, blob->mwli_binds_count);
|
|
break;
|
|
}
|
|
else {
|
|
uint64_t addend19 = (value >> 32) & 0x0007FFFF;
|
|
if ( addend19 & 0x40000 )
|
|
addend19 |= 0xFFFFFFFFFFFC0000ULL;
|
|
*chain = bindsArray[bindOrdinal] + addend19;
|
|
}
|
|
}
|
|
else {
|
|
uint64_t target = (value & 0x7FFFFFFFFFFULL);
|
|
uint64_t high8 = (value << 13) & 0xFF00000000000000ULL;
|
|
*chain = target + targetAdjust + high8;
|
|
}
|
|
}
|
|
chain += delta;
|
|
} while ( delta != 0 );
|
|
}
|
|
#endif // __has_feature(ptrauth_calls)
|
|
|
|
|
|
static void fixupPage(void* pageContent, uint64_t userlandAddress, const mwl_info_hdr* blob)
|
|
{
|
|
// find seg info and page within segment
|
|
const dyld_chained_starts_in_segment* segInfo = nullptr;
|
|
uint32_t pageIndex = 0;
|
|
const dyld_chained_starts_in_image* startsInfo = (dyld_chained_starts_in_image*)((uint8_t*)blob + blob->mwli_chains_offset);
|
|
for (uint32_t segIndex=0; segIndex < startsInfo->seg_count; ++segIndex) {
|
|
const dyld_chained_starts_in_segment* seg = (dyld_chained_starts_in_segment*)((uint8_t*)startsInfo + startsInfo->seg_info_offset[segIndex]);
|
|
uint64_t segStartAddress = (blob->mwli_image_address + seg->segment_offset);
|
|
uint64_t segEndAddress = segStartAddress + seg->page_count * seg->page_size;
|
|
if ( (segStartAddress <= userlandAddress) && (userlandAddress < segEndAddress) ) {
|
|
segInfo = seg;
|
|
pageIndex = (uint32_t)((userlandAddress-segStartAddress)/(seg->page_size));
|
|
break;
|
|
}
|
|
}
|
|
//fprintf(stderr, "fixupPage(%p), blob=%p, pageIndex=%d, segInfo=%p\n", pageContent, blob, pageIndex, segInfo);
|
|
assert(segInfo != nullptr);
|
|
|
|
switch (blob->mwli_pointer_format) {
|
|
#if __has_feature(ptrauth_calls)
|
|
case DYLD_CHAINED_PTR_ARM64E:
|
|
fixupPageAuth64(pageContent, blob, segInfo, pageIndex, false);
|
|
break;
|
|
case DYLD_CHAINED_PTR_ARM64E_USERLAND:
|
|
case DYLD_CHAINED_PTR_ARM64E_USERLAND24:
|
|
fixupPageAuth64(pageContent, blob, segInfo, pageIndex, true);
|
|
break;
|
|
#endif
|
|
case DYLD_CHAINED_PTR_64:
|
|
fixupPage64(pageContent, blob, segInfo, pageIndex, false);
|
|
break;
|
|
case DYLD_CHAINED_PTR_64_OFFSET:
|
|
fixupPage64(pageContent, blob, segInfo, pageIndex, true);
|
|
break;
|
|
case DYLD_CHAINED_PTR_32:
|
|
fixupPage32(pageContent, blob, segInfo, pageIndex);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// implement __map_with_linking_np() in userland
|
|
static int dyld_map_with_linking_np(const mwl_region regions[], uint32_t regionCount, const mwl_info_hdr* blob, uint32_t blobSize)
|
|
{
|
|
// sanity check
|
|
if ( blob->mwli_version != 7 )
|
|
return -1;
|
|
uint32_t pointerSize = (blob->mwli_pointer_format == DYLD_CHAINED_PTR_32) ? 4 : 8;
|
|
if ( (blob->mwli_binds_offset + pointerSize*blob->mwli_binds_count) > blobSize ) {
|
|
fprintf(stderr, "bind table extends past blob, blobSize=%d, offset=%d, count=%d\n", blobSize, blob->mwli_binds_offset, blob->mwli_binds_count);
|
|
return -1;
|
|
}
|
|
if ( (blob->mwli_chains_offset + blob->mwli_chains_size) > blobSize )
|
|
return -1;
|
|
|
|
// apply fixups to each page in each page
|
|
const dyld_chained_starts_in_image* startsInfo = (dyld_chained_starts_in_image*)((uint8_t*)blob + blob->mwli_chains_offset);
|
|
//fprintf(stderr, "dyld_map_with_linking_np(), startsInfo=%p, seg_count=%d\n", startsInfo, startsInfo->seg_count);
|
|
for (uint32_t s=0; s < startsInfo->seg_count; ++s) {
|
|
if ( uint32_t segOffset = startsInfo->seg_info_offset[s] ) {
|
|
const dyld_chained_starts_in_segment* segInfo = (dyld_chained_starts_in_segment*)((uint8_t*)startsInfo + segOffset);
|
|
uint8_t* segStartAddress = (uint8_t*)(blob->mwli_image_address + segInfo->segment_offset);
|
|
//fprintf(stderr, "dyld_map_with_linking_np(), segStartAddress=%p, page_count=%d\n", segStartAddress, segInfo->page_count);
|
|
for (uint32_t i=0; i < segInfo->page_count; ++i) {
|
|
void* content = (void*)(uintptr_t)(segStartAddress + i*blob->mwli_page_size);
|
|
fixupPage(content, (uintptr_t)content, blob);
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
struct PageInLinkingRange { mwl_region region; const char* segName; const dyld_chained_starts_in_segment* chainInfo; };
|
|
|
|
// Note: disable tail call optimization, otherwise tailcall may remove stack allocated blob
|
|
[[clang::disable_tail_calls]] static
|
|
int setUpPageInLinkingRegions(RuntimeState& state, const Loader* ldr, uintptr_t slide, uint16_t pointer_format, uint16_t pageSize,
|
|
bool forceDyldBinding, const Array<PageInLinkingRange>& ranges, const Array<const void*>& bindTargets)
|
|
{
|
|
// create blob on the stack
|
|
uint32_t chainInfoSize = (uint32_t)offsetof(dyld_chained_starts_in_image, seg_info_offset[ranges.count()]);
|
|
for (const PageInLinkingRange& range : ranges) {
|
|
chainInfoSize += range.chainInfo->size;
|
|
chainInfoSize = (chainInfoSize + 3) & (-4); // size should always be 4-byte aligned
|
|
}
|
|
uint32_t pointerSize = (pointer_format == DYLD_CHAINED_PTR_32) ? 4 : 8;
|
|
uint32_t bindsOffset = (sizeof(mwl_info_hdr) + chainInfoSize + 7) & (-8); // 8-byte align
|
|
size_t blobAllocationSize = bindsOffset + pointerSize*bindTargets.count();
|
|
uint8_t buffer[blobAllocationSize];
|
|
bzero(buffer,blobAllocationSize);
|
|
mwl_info_hdr* blob = (mwl_info_hdr*)buffer;
|
|
blob->mwli_version = 7;
|
|
blob->mwli_page_size = pageSize;
|
|
blob->mwli_pointer_format = pointer_format;
|
|
blob->mwli_binds_offset = bindsOffset;
|
|
blob->mwli_binds_count = (uint32_t)bindTargets.count();
|
|
blob->mwli_chains_offset = sizeof(mwl_info_hdr);
|
|
blob->mwli_chains_size = chainInfoSize;
|
|
blob->mwli_slide = slide;
|
|
blob->mwli_image_address = (uintptr_t)ldr->loadAddress(state);
|
|
::memcpy(&buffer[blob->mwli_binds_offset], bindTargets.begin(), pointerSize * blob->mwli_binds_count);
|
|
uint32_t offsetInChainInfo = (uint32_t)offsetof(dyld_chained_starts_in_image, seg_info_offset[ranges.count()]);
|
|
uint32_t rangeIndex = 0;
|
|
dyld_chained_starts_in_image* starts = (dyld_chained_starts_in_image*)((uint8_t*)blob + blob->mwli_chains_offset);
|
|
starts->seg_count = (uint32_t)ranges.count();
|
|
for (const PageInLinkingRange& range : ranges) {
|
|
starts->seg_info_offset[rangeIndex] = offsetInChainInfo;
|
|
::memcpy(&buffer[blob->mwli_chains_offset + offsetInChainInfo], range.chainInfo, range.chainInfo->size);
|
|
++rangeIndex;
|
|
offsetInChainInfo += range.chainInfo->size;
|
|
}
|
|
STACK_ALLOC_ARRAY(mwl_region, regions, ranges.count());
|
|
for (const PageInLinkingRange& range : ranges) {
|
|
regions.push_back(range.region);
|
|
}
|
|
|
|
int result = 0;
|
|
if ( forceDyldBinding ) {
|
|
result = dyld_map_with_linking_np(regions.begin(), (uint32_t)regions.count(), blob, (uint32_t)blobAllocationSize);
|
|
}
|
|
else {
|
|
if ( state.config.log.fixups || state.config.log.segments ) {
|
|
state.log("Setting up kernel page-in linking for %s\n", ldr->path());
|
|
for (const PageInLinkingRange& range : ranges) {
|
|
state.log("%14s (%c%c%c) 0x%012llX->0x%012llX (fileOffset=0x%0llX, size=%lluKB)\n", range.segName,
|
|
((range.region.mwlr_protections & 1) ? 'r' : '.'), ((range.region.mwlr_protections & 2) ? 'w' : '.'), ((range.region.mwlr_protections & 4) ? 'x' : '.'),
|
|
range.region.mwlr_address, range.region.mwlr_address + range.region.mwlr_size, range.region.mwlr_file_offset, range.region.mwlr_size/1024);
|
|
}
|
|
}
|
|
#if BUILDING_DYLD
|
|
result = __map_with_linking_np(regions.begin(), (uint32_t)regions.count(), blob, (uint32_t)blobAllocationSize);
|
|
if ( result != 0 ) {
|
|
// kernel backed page-in linking failed, manually do fixups in-process
|
|
if ( state.config.log.fixups || state.config.log.segments )
|
|
state.log("__map_with_linking_np(%s) failed, falling back to linking in-process\n", ldr->path());
|
|
result = dyld_map_with_linking_np(regions.begin(), (uint32_t)regions.count(), blob, (uint32_t)blobAllocationSize);
|
|
}
|
|
#endif
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
void Loader::setUpPageInLinking(Diagnostics& diag, RuntimeState& state, uintptr_t slide, uint64_t sliceOffset, const Array<const void*>& bindTargets) const
|
|
{
|
|
int fd = state.config.syscall.open(this->path(), O_RDONLY, 0);
|
|
if ( fd == -1 ) {
|
|
diag.error("open(\"%s\", O_RDONLY) failed with errno=%d", this->path(), errno);
|
|
return;
|
|
}
|
|
// don't use page-in linking after libSystem is initialized
|
|
// don't use page-in linking if process has a sandbox that disables syscall
|
|
bool canUsePageInLinkingSyscall = (state.config.process.pageInLinkingMode >= 2) && (state.libSystemHelpers == nullptr) && !state.config.syscall.sandboxBlockedPageInLinking();
|
|
const MachOAnalyzer* ma = (MachOAnalyzer*)this->loadAddress(state);
|
|
const bool enableTpro = state.config.process.enableTproDataConst;
|
|
__block uint16_t format = 0;
|
|
__block uint16_t pageSize = 0;
|
|
STACK_ALLOC_OVERFLOW_SAFE_ARRAY(PageInLinkingRange, kernelPageInRegionInfo, 8);
|
|
STACK_ALLOC_OVERFLOW_SAFE_ARRAY(PageInLinkingRange, dyldPageInRegionInfo, 8);
|
|
ma->withChainStarts(diag, ma->chainStartsOffset(), ^(const dyld_chained_starts_in_image* startsInfo) {
|
|
// build mwl_region array and compute page starts size
|
|
__block const dyld_chained_starts_in_segment* lastSegChainInfo = nullptr;
|
|
ma->forEachSegment(^(const MachOAnalyzer::SegmentInfo& segInfo, bool& stop) {
|
|
if ( segInfo.segIndex < startsInfo->seg_count ) {
|
|
if ( startsInfo->seg_info_offset[segInfo.segIndex] == 0 ) {
|
|
return;
|
|
}
|
|
const dyld_chained_starts_in_segment* segChainInfo = (dyld_chained_starts_in_segment*)((uint8_t*)startsInfo + startsInfo->seg_info_offset[segInfo.segIndex]);
|
|
if ( format == 0 ) {
|
|
format = segChainInfo->pointer_format;
|
|
}
|
|
else if ( format != segChainInfo->pointer_format ) {
|
|
diag.error("pointer_format is different in different segments");
|
|
stop = true;
|
|
}
|
|
if ( pageSize == 0 ) {
|
|
pageSize = segChainInfo->page_size;
|
|
}
|
|
else if ( pageSize != segChainInfo->page_size ) {
|
|
diag.error("page_size is different in different segments");
|
|
stop = true;
|
|
}
|
|
PageInLinkingRange rangeInfo;
|
|
rangeInfo.region.mwlr_fd = fd;
|
|
rangeInfo.region.mwlr_protections = segInfo.protections; // Note: DATA_CONST is r/w at this point, so objc can do its fixups
|
|
rangeInfo.region.mwlr_file_offset = segInfo.fileOffset + sliceOffset;
|
|
rangeInfo.region.mwlr_address = segInfo.vmAddr + slide;
|
|
rangeInfo.region.mwlr_size = pageSize * segChainInfo->page_count; // in case some pages don't have fixups, don't use segment size
|
|
rangeInfo.segName = segInfo.segName;
|
|
rangeInfo.chainInfo = segChainInfo;
|
|
if ( canUsePageInLinkingSyscall ) {
|
|
// this is where we tune which fixups are done by the kernel
|
|
// currently only single page DATA segments are done by dyld
|
|
// the kernel only supports 5 regions per syscall, so any segments past that are fixed up by dyld
|
|
if ( (segInfo.readOnlyData || (segChainInfo->page_count > 1)) && (kernelPageInRegionInfo.count() < MWL_MAX_REGION_COUNT) ) {
|
|
if (enableTpro && segInfo.readOnlyData) {
|
|
rangeInfo.region.mwlr_protections |= VM_PROT_TPRO;
|
|
}
|
|
kernelPageInRegionInfo.push_back(rangeInfo);
|
|
}
|
|
else
|
|
dyldPageInRegionInfo.push_back(rangeInfo);
|
|
}
|
|
else {
|
|
dyldPageInRegionInfo.push_back(rangeInfo);
|
|
}
|
|
lastSegChainInfo = segChainInfo;
|
|
}
|
|
});
|
|
// image has not DATA pages to page-in link, so do nothing
|
|
if ( lastSegChainInfo == nullptr )
|
|
return;
|
|
|
|
if ( !kernelPageInRegionInfo.empty() ) {
|
|
int kernResult = setUpPageInLinkingRegions(state, this, slide, format, pageSize, (state.config.process.pageInLinkingMode == 1), kernelPageInRegionInfo, bindTargets);
|
|
// if kernel can't do page in linking, then have dyld do the fixups
|
|
if ( kernResult != 0 )
|
|
setUpPageInLinkingRegions(state, this, slide, format, pageSize, true, kernelPageInRegionInfo, bindTargets);
|
|
}
|
|
if ( !dyldPageInRegionInfo.empty() )
|
|
setUpPageInLinkingRegions(state, this, slide, format, pageSize, true, dyldPageInRegionInfo, bindTargets);
|
|
});
|
|
|
|
state.config.syscall.close(fd);
|
|
}
|
|
#endif // !TARGET_OS_SIMULATOR && !TARGET_OS_EXCLAVEKIT
|
|
|
|
void Loader::applyFixupsGeneric(Diagnostics& diag, RuntimeState& state, uint64_t sliceOffset, const Array<const void*>& bindTargets,
|
|
const Array<const void*>& overrideBindTargets, bool laziesMustBind,
|
|
const Array<MissingFlatLazySymbol>& missingFlatLazySymbols) const
|
|
{
|
|
const MachOAnalyzer* ma = (MachOAnalyzer*)this->loadAddress(state);
|
|
const uintptr_t slide = ma->getSlide();
|
|
if ( ma->hasChainedFixups() ) {
|
|
bool applyFixupsNow = true;
|
|
#if !TARGET_OS_SIMULATOR && !TARGET_OS_EXCLAVEKIT
|
|
// only do page in linking, if binary has standard chained fixups, config allows, and not so many targets that is wastes wired memory
|
|
if ( (state.config.process.pageInLinkingMode != 0) && ma->hasChainedFixupsLoadCommand() && (bindTargets.count() < 10000) ) {
|
|
this->setUpPageInLinking(diag, state, slide, sliceOffset, bindTargets);
|
|
// if we cannot do page-in-linking, then do fixups now
|
|
applyFixupsNow = diag.hasError();
|
|
diag.clearError();
|
|
}
|
|
#endif // !TARGET_OS_SIMULATOR && !TARGET_OS_EXCLAVEKIT
|
|
if ( applyFixupsNow ) {
|
|
// walk all chains
|
|
ma->withChainStarts(diag, ma->chainStartsOffset(), ^(const dyld_chained_starts_in_image* startsInfo) {
|
|
ma->fixupAllChainedFixups(diag, startsInfo, slide, bindTargets, ^(void* loc, void* newValue) {
|
|
if ( state.config.log.fixups )
|
|
state.log("fixup: *0x%012lX = 0x%012lX\n", (uintptr_t)loc, (uintptr_t)newValue);
|
|
*((uintptr_t*)loc) = (uintptr_t)newValue;
|
|
});
|
|
});
|
|
}
|
|
}
|
|
else if ( ma->hasOpcodeFixups() ) {
|
|
// process all rebase opcodes
|
|
ma->forEachRebaseLocation_Opcodes(diag, ^(uint64_t runtimeOffset, bool& stop) {
|
|
uintptr_t* loc = (uintptr_t*)((uint8_t*)ma + runtimeOffset);
|
|
uintptr_t locValue = *loc;
|
|
uintptr_t newValue = locValue + slide;
|
|
if ( state.config.log.fixups )
|
|
state.log("fixup: *0x%012lX = 0x%012lX <rebase>\n", (uintptr_t)loc, (uintptr_t)newValue);
|
|
*loc = newValue;
|
|
});
|
|
if ( diag.hasError() )
|
|
return;
|
|
|
|
// process all bind opcodes
|
|
ma->forEachBindLocation_Opcodes(diag, ^(uint64_t runtimeOffset, unsigned targetIndex, bool& stop) {
|
|
uintptr_t* loc = (uintptr_t*)((uint8_t*)ma + runtimeOffset);
|
|
uintptr_t newValue = (uintptr_t)(bindTargets[targetIndex]);
|
|
|
|
if ( state.config.log.fixups )
|
|
state.log("fixup: *0x%012lX = 0x%012lX <%s/bind#%u>\n", (uintptr_t)loc, (uintptr_t)newValue, this->leafName(), targetIndex);
|
|
*loc = newValue;
|
|
|
|
#if !TARGET_OS_EXCLAVEKIT
|
|
// Record missing lazy symbols
|
|
if ( newValue == (uintptr_t)state.libdyldMissingSymbol ) {
|
|
for (const MissingFlatLazySymbol& missingSymbol : missingFlatLazySymbols) {
|
|
if ( missingSymbol.bindTargetIndex == targetIndex ) {
|
|
state.addMissingFlatLazySymbol(this, missingSymbol.symbolName, loc);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
#endif // !TARGET_OS_EXCLAVEKIT
|
|
}, ^(uint64_t runtimeOffset, unsigned overrideBindTargetIndex, bool& stop) {
|
|
uintptr_t* loc = (uintptr_t*)((uint8_t*)ma + runtimeOffset);
|
|
uintptr_t newValue = (uintptr_t)(overrideBindTargets[overrideBindTargetIndex]);
|
|
|
|
// Skip missing weak binds
|
|
if ( newValue == UINTPTR_MAX ) {
|
|
if ( state.config.log.fixups )
|
|
state.log("fixup: *0x%012lX (skipping missing weak bind) <%s/weak-bind#%u>\n", (uintptr_t)loc, this->leafName(), overrideBindTargetIndex);
|
|
return;
|
|
}
|
|
|
|
if ( state.config.log.fixups )
|
|
state.log("fixup: *0x%012lX = 0x%012lX <%s/weak-bind#%u>\n", (uintptr_t)loc, (uintptr_t)newValue, this->leafName(), overrideBindTargetIndex);
|
|
*loc = newValue;
|
|
});
|
|
}
|
|
#if SUPPORT_CLASSIC_RELOCS
|
|
else {
|
|
// process internal relocations
|
|
ma->forEachRebaseLocation_Relocations(diag, ^(uint64_t runtimeOffset, bool& stop) {
|
|
uintptr_t* loc = (uintptr_t*)((uint8_t*)ma + runtimeOffset);
|
|
uintptr_t locValue = *loc;
|
|
uintptr_t newValue = locValue + slide;
|
|
if ( state.config.log.fixups )
|
|
state.log("fixup: *0x%012lX = 0x%012lX <rebase>\n", (uintptr_t)loc, (uintptr_t)newValue);
|
|
*loc = newValue;
|
|
});
|
|
if ( diag.hasError() )
|
|
return;
|
|
|
|
// process external relocations
|
|
ma->forEachBindLocation_Relocations(diag, ^(uint64_t runtimeOffset, unsigned targetIndex, bool& stop) {
|
|
uintptr_t* loc = (uintptr_t*)((uint8_t*)ma + runtimeOffset);
|
|
uintptr_t newValue = (uintptr_t)(bindTargets[targetIndex]);
|
|
if ( state.config.log.fixups )
|
|
state.log("fixup: *0x%012lX = 0x%012lX <%s/bind#%u>\n", (uintptr_t)loc, (uintptr_t)newValue, this->leafName(), targetIndex);
|
|
*loc = newValue;
|
|
});
|
|
}
|
|
#endif // SUPPORT_CLASSIC_RELOCS
|
|
}
|
|
|
|
void Loader::findAndRunAllInitializers(RuntimeState& state) const
|
|
{
|
|
Diagnostics diag;
|
|
const MachOAnalyzer* ma = this->analyzer(state);
|
|
dyld3::MachOAnalyzer::VMAddrConverter vmAddrConverter = ma->makeVMAddrConverter(true);
|
|
state.memoryManager.withReadOnlyMemory([&]{
|
|
ma->forEachInitializer(diag, vmAddrConverter, ^(uint32_t offset) {
|
|
void *func = (void *)((uint8_t*)ma + offset);
|
|
if ( state.config.log.initializers )
|
|
state.log("running initializer %p in %s\n", func, this->path());
|
|
#if __has_feature(ptrauth_calls)
|
|
func = __builtin_ptrauth_sign_unauthenticated(func, ptrauth_key_asia, 0);
|
|
#endif
|
|
dyld3::ScopedTimer timer(DBG_DYLD_TIMING_STATIC_INITIALIZER, (uint64_t)ma, (uint64_t)func, 0);
|
|
((Initializer)func)(state.config.process.argc, state.config.process.argv, state.config.process.envp, state.config.process.apple, state.vars);
|
|
});
|
|
});
|
|
|
|
#if !TARGET_OS_EXCLAVEKIT
|
|
// don't support static terminators in arm64e binaries
|
|
if ( ma->isArch("arm64e") )
|
|
return;
|
|
// register static terminators in old binaries, if any
|
|
typedef void (*Terminator)(void*);
|
|
ma->forEachTerminator(diag, vmAddrConverter, ^(uint32_t offset) {
|
|
Terminator func = (Terminator)((uint8_t*)ma + offset);
|
|
state.libSystemHelpers->__cxa_atexit(func, nullptr, (void*)ma);
|
|
if ( state.config.log.initializers )
|
|
state.log("registering old style destructor %p for %s\n", func, this->path());
|
|
});
|
|
#endif // !TARGET_OS_EXCLAVEKIT
|
|
}
|
|
|
|
void Loader::runInitializersBottomUp(RuntimeState& state, Array<const Loader*>& danglingUpwards) const
|
|
{
|
|
// do nothing if already initializers already run
|
|
if ( (const_cast<Loader*>(this))->beginInitializers(state) )
|
|
return;
|
|
|
|
//state.log("runInitializersBottomUp(%s)\n", this->path());
|
|
|
|
// make sure everything below this image is initialized before running my initializers
|
|
const uint32_t depCount = this->dependentCount();
|
|
for ( uint32_t i = 0; i < depCount; ++i ) {
|
|
DependentKind childKind;
|
|
if ( Loader* child = this->dependent(state, i, &childKind) ) {
|
|
if ( childKind == DependentKind::upward ) {
|
|
// add upwards to list to process later
|
|
if ( !danglingUpwards.contains(child) )
|
|
danglingUpwards.push_back(child);
|
|
}
|
|
else {
|
|
child->runInitializersBottomUp(state, danglingUpwards);
|
|
}
|
|
}
|
|
}
|
|
|
|
// tell objc to run any +load methods in this image (done before C++ initializers)
|
|
state.notifyObjCInit(this);
|
|
|
|
// run initializers for this image
|
|
this->runInitializers(state);
|
|
}
|
|
|
|
void Loader::runInitializersBottomUpPlusUpwardLinks(RuntimeState& state) const
|
|
{
|
|
//state.log("runInitializersBottomUpPlusUpwardLinks() %s\n", this->path());
|
|
state.memoryManager.withWritableMemory([&]{
|
|
// recursively run all initializers
|
|
STACK_ALLOC_ARRAY(const Loader*, danglingUpwards, state.loaded.size());
|
|
this->runInitializersBottomUp(state, danglingUpwards);
|
|
|
|
//state.log("runInitializersBottomUpPlusUpwardLinks(%s), found %d dangling upwards\n", this->path(), danglingUpwards.count());
|
|
|
|
// go back over all images that were upward linked, and recheck they were initialized (might be danglers)
|
|
STACK_ALLOC_ARRAY(const Loader*, extraDanglingUpwards, state.loaded.size());
|
|
for ( const Loader* ldr : danglingUpwards ) {
|
|
//state.log("running initializers for dangling upward link %s\n", ldr->path());
|
|
ldr->runInitializersBottomUp(state, extraDanglingUpwards);
|
|
}
|
|
if ( !extraDanglingUpwards.empty() ) {
|
|
// in case of double upward dangling images, check initializers again
|
|
danglingUpwards.resize(0);
|
|
for ( const Loader* ldr : extraDanglingUpwards ) {
|
|
//state.log("running initializers for dangling upward link %s\n", ldr->path());
|
|
ldr->runInitializersBottomUp(state, danglingUpwards);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
#endif // BUILDING_DYLD || BUILDING_UNIT_TESTS
|
|
|
|
// Used to build prebound targets in PrebuiltLoader.
|
|
void Loader::forEachBindTarget(Diagnostics& diag, RuntimeState& state, CacheWeakDefOverride cacheWeakDefFixup, bool allowLazyBinds,
|
|
void (^callback)(const ResolvedSymbol& target, bool& stop),
|
|
void (^overrideBindCallback)(const ResolvedSymbol& target, bool& stop)) const
|
|
{
|
|
this->withLayout(diag, state, ^(const mach_o::Layout &layout) {
|
|
mach_o::Fixups fixups(layout);
|
|
|
|
__block unsigned targetIndex = 0;
|
|
__block unsigned overrideBindTargetIndex = 0;
|
|
#if SUPPORT_PRIVATE_EXTERNS_WORKAROUND
|
|
intptr_t slide = this->analyzer(state)->getSlide();
|
|
#else
|
|
intptr_t slide = 0;
|
|
#endif
|
|
fixups.forEachBindTarget(diag, allowLazyBinds, slide, ^(const mach_o::Fixups::BindTargetInfo& info, bool& stop) {
|
|
// Regular and lazy binds
|
|
assert(targetIndex == info.targetIndex);
|
|
ResolvedSymbol targetInfo = this->resolveSymbol(diag, state, info.libOrdinal, info.symbolName, info.weakImport, info.lazyBind, cacheWeakDefFixup);
|
|
targetInfo.targetRuntimeOffset += info.addend;
|
|
callback(targetInfo, stop);
|
|
if ( diag.hasError() )
|
|
stop = true;
|
|
++targetIndex;
|
|
}, ^(const mach_o::Fixups::BindTargetInfo& info, bool& stop) {
|
|
// Opcode based weak binds
|
|
assert(overrideBindTargetIndex == info.targetIndex);
|
|
Diagnostics weakBindDiag; // failures aren't fatal here
|
|
ResolvedSymbol targetInfo = this->resolveSymbol(weakBindDiag, state, info.libOrdinal, info.symbolName, info.weakImport, info.lazyBind, cacheWeakDefFixup);
|
|
if ( weakBindDiag.hasError() ) {
|
|
// In dyld2, it was also ok for a weak bind to be missing. Then we would let the bind/rebase on this
|
|
// address handle it
|
|
targetInfo.targetLoader = nullptr;
|
|
targetInfo.targetRuntimeOffset = 0;
|
|
targetInfo.kind = ResolvedSymbol::Kind::bindToImage;
|
|
targetInfo.isCode = false;
|
|
targetInfo.isWeakDef = false;
|
|
targetInfo.isMissingFlatLazy = false;
|
|
} else {
|
|
targetInfo.targetRuntimeOffset += info.addend;
|
|
}
|
|
overrideBindCallback(targetInfo, stop);
|
|
++overrideBindTargetIndex;
|
|
});
|
|
});
|
|
}
|
|
|
|
bool Loader::hasConstantSegmentsToProtect() const
|
|
{
|
|
return this->hasReadOnlyData && !this->dylibInDyldCache;
|
|
}
|
|
|
|
#if BUILDING_DYLD || BUILDING_UNIT_TESTS
|
|
void Loader::makeSegmentsReadOnly(RuntimeState& state) const
|
|
{
|
|
const MachOAnalyzer* ma = this->analyzer(state);
|
|
uintptr_t slide = ma->getSlide();
|
|
ma->forEachSegment(^(const MachOAnalyzer::SegmentInfo& segInfo, bool& stop) {
|
|
if ( segInfo.readOnlyData ) {
|
|
#if TARGET_OS_EXCLAVEKIT
|
|
//TODO: EXCLAVES
|
|
(void)slide;
|
|
#else
|
|
const uint8_t* start = (uint8_t*)(segInfo.vmAddr + slide);
|
|
size_t size = (size_t)segInfo.vmSize;
|
|
state.config.syscall.mprotect((void*)start, size, PROT_READ);
|
|
if ( state.config.log.segments )
|
|
state.log("mprotect 0x%012lX->0x%012lX to read-only\n", (long)start, (long)start + size);
|
|
#endif
|
|
}
|
|
});
|
|
}
|
|
|
|
void Loader::makeSegmentsReadWrite(RuntimeState& state) const
|
|
{
|
|
const MachOAnalyzer* ma = this->analyzer(state);
|
|
uintptr_t slide = ma->getSlide();
|
|
ma->forEachSegment(^(const MachOAnalyzer::SegmentInfo& segInfo, bool& stop) {
|
|
if ( segInfo.readOnlyData ) {
|
|
#if TARGET_OS_EXCLAVEKIT
|
|
//TODO: EXCLAVES
|
|
(void)slide;
|
|
#else
|
|
const uint8_t* start = (uint8_t*)(segInfo.vmAddr + slide);
|
|
size_t size = (size_t)segInfo.vmSize;
|
|
state.config.syscall.mprotect((void*)start, size, PROT_READ | PROT_WRITE);
|
|
if ( state.config.log.segments )
|
|
state.log("mprotect 0x%012lX->0x%012lX to read-write\n", (long)start, (long)start + size);
|
|
#endif
|
|
}
|
|
});
|
|
}
|
|
|
|
void Loader::logSegmentsFromSharedCache(RuntimeState& state) const
|
|
{
|
|
state.log("Using mapping in dyld cache for %s\n", this->path());
|
|
uint64_t cacheSlide = state.config.dyldCache.slide;
|
|
this->loadAddress(state)->forEachSegment(^(const MachOLoaded::SegmentInfo& info, bool& stop) {
|
|
state.log("%14s (%c%c%c) 0x%012llX->0x%012llX \n", info.segName,
|
|
(info.readable() ? 'r' : '.'), (info.writable() ? 'w' : '.'), (info.executable() ? 'x' : '.'),
|
|
info.vmAddr + cacheSlide, info.vmAddr + cacheSlide + info.vmSize);
|
|
});
|
|
}
|
|
|
|
// FIXME: This only handles weak-defs and does not look for non-weaks that override weak-defs
|
|
void Loader::addWeakDefsToMap(RuntimeState& state, const std::span<const Loader*>& newLoaders)
|
|
{
|
|
for (const Loader* ldr : newLoaders) {
|
|
const MachOAnalyzer* ma = ldr->analyzer(state);
|
|
if ( (ma->flags & MH_WEAK_DEFINES) == 0 )
|
|
continue;
|
|
if ( ldr->hiddenFromFlat() )
|
|
continue;
|
|
|
|
// NOTE: using the nlist is faster to scan for weak-def exports, than iterating the exports trie
|
|
Diagnostics diag;
|
|
uint64_t baseAddress = ma->preferredLoadAddress();
|
|
ma->forEachGlobalSymbol(diag, ^(const char* symbolName, uint64_t n_value, uint8_t n_type, uint8_t n_sect, uint16_t n_desc, bool& stop) {
|
|
if ( (n_desc & N_WEAK_DEF) != 0 ) {
|
|
// only add if not already in map
|
|
const auto& pos = state.weakDefMap->find(symbolName);
|
|
if ( pos == state.weakDefMap->end() ) {
|
|
WeakDefMapValue mapEntry;
|
|
mapEntry.targetLoader = ldr;
|
|
mapEntry.targetRuntimeOffset = n_value - baseAddress;
|
|
mapEntry.isCode = false; // unused
|
|
mapEntry.isWeakDef = true;
|
|
state.weakDefMap->operator[](symbolName) = mapEntry;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
#endif // BUILDING_DYLD || BUILDING_UNIT_TESTS
|
|
|
|
Loader::ResolvedSymbol Loader::resolveSymbol(Diagnostics& diag, RuntimeState& state, int libOrdinal, const char* symbolName,
|
|
bool weakImport, bool lazyBind, CacheWeakDefOverride patcher, bool buildingCache) const
|
|
{
|
|
__block ResolvedSymbol result = { nullptr, symbolName, 0, ResolvedSymbol::Kind::bindAbsolute, false, false };
|
|
if ( (libOrdinal > 0) && ((unsigned)libOrdinal <= this->dependentCount()) ) {
|
|
result.targetLoader = dependent(state, libOrdinal - 1);
|
|
}
|
|
else if ( libOrdinal == BIND_SPECIAL_DYLIB_SELF ) {
|
|
result.targetLoader = this;
|
|
}
|
|
else if ( libOrdinal == BIND_SPECIAL_DYLIB_MAIN_EXECUTABLE ) {
|
|
result.targetLoader = state.mainExecutableLoader;
|
|
}
|
|
else if ( libOrdinal == BIND_SPECIAL_DYLIB_FLAT_LOOKUP ) {
|
|
__block bool found = false;
|
|
state.locks.withLoadersReadLock(^{
|
|
for ( const Loader* ldr : state.loaded ) {
|
|
// flat lookup can look in self, even if hidden
|
|
if ( ldr->hiddenFromFlat() && (ldr != this) )
|
|
continue;
|
|
if ( ldr->hasExportedSymbol(diag, state, symbolName, Loader::shallow, &result) ) {
|
|
found = true;
|
|
return;
|
|
}
|
|
}
|
|
});
|
|
if ( found ) {
|
|
// record the dynamic dependency so the symbol we found does not get unloaded from under us
|
|
if ( result.targetLoader != this )
|
|
state.addDynamicReference(this, result.targetLoader);
|
|
}
|
|
else {
|
|
if ( weakImport ) {
|
|
// ok to be missing, bind to NULL
|
|
result.kind = ResolvedSymbol::Kind::bindAbsolute;
|
|
result.targetRuntimeOffset = 0;
|
|
}
|
|
else if ( lazyBind && (state.libdyldMissingSymbolRuntimeOffset != 0) ) {
|
|
// lazy bound symbols can be bound to __dyld_missing_symbol_abort
|
|
result.targetLoader = state.libdyldLoader;
|
|
result.targetSymbolName = symbolName;
|
|
result.targetRuntimeOffset = (uintptr_t)state.libdyldMissingSymbolRuntimeOffset;
|
|
result.kind = ResolvedSymbol::Kind::bindToImage;
|
|
result.isCode = false; // only used for arm64e which uses trie not nlist
|
|
result.isWeakDef = false;
|
|
result.isMissingFlatLazy = true;
|
|
}
|
|
else {
|
|
// missing symbol, but not weak-import or lazy-bound, so error
|
|
diag.error("symbol not found in flat namespace '%s'", symbolName);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
else if ( libOrdinal == BIND_SPECIAL_DYLIB_WEAK_LOOKUP ) {
|
|
const bool verboseWeak = false;
|
|
__block bool foundFirst = false;
|
|
#if BUILDING_CACHE_BUILDER
|
|
if ( buildingCache ) {
|
|
// when dylibs in cache are build, we don't have real load order, so do weak binding differently
|
|
if ( verboseWeak )
|
|
state.log("looking for weak-def symbol %s\n", symbolName);
|
|
|
|
// look first in /usr/lib/libc++, most will be here
|
|
for ( const Loader* ldr : state.loaded ) {
|
|
ResolvedSymbol libcppResult;
|
|
if ( ldr->mf(state)->hasWeakDefs() && (strncmp(ldr->path(), "/usr/lib/libc++.", 16) == 0) ) {
|
|
if ( ldr->hasExportedSymbol(diag, state, symbolName, Loader::shallow, &libcppResult) ) {
|
|
if ( verboseWeak )
|
|
state.log(" using %s from libc++.dylib\n", symbolName);
|
|
return libcppResult;
|
|
}
|
|
}
|
|
}
|
|
|
|
// if not found, try looking in the images itself, most custom weak-def symbols have a copy in the image itself
|
|
ResolvedSymbol selfResult;
|
|
if ( this->hasExportedSymbol(diag, state, symbolName, Loader::shallow, &selfResult) ) {
|
|
if ( verboseWeak )
|
|
state.log(" using %s from self %s\n", symbolName, this->path());
|
|
return selfResult;
|
|
}
|
|
|
|
// if this image directly links with something that also defines this weak-def, use that because we know it will be loaded
|
|
const uint32_t depCount = this->dependentCount();
|
|
for ( uint32_t i = 0; i < depCount; ++i ) {
|
|
Loader::DependentKind depKind;
|
|
if ( Loader* depLoader = this->dependent(state, i, &depKind) ) {
|
|
if ( depKind != Loader::DependentKind::upward ) {
|
|
ResolvedSymbol depResult;
|
|
if ( depLoader->hasExportedSymbol(diag, state, symbolName, Loader::staticLink, &depResult) ) {
|
|
if ( verboseWeak )
|
|
state.log(" using %s from dependent %s\n", symbolName, depLoader->path());
|
|
return depResult;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// no impl??
|
|
diag.error("weak-def symbol (%s) not found in dyld cache", symbolName);
|
|
return result;
|
|
}
|
|
else // fall into app launch case
|
|
#endif
|
|
state.locks.withLoadersReadLock(^{
|
|
if ( verboseWeak )
|
|
state.log("looking for weak-def symbol %s\n", symbolName);
|
|
state.weakDefResolveSymbolCount++;
|
|
// 5000 is a guess that "this is a large C++ app" and could use a map to speed up coalescing
|
|
if ( (state.weakDefResolveSymbolCount > 5000) && (state.weakDefMap == nullptr) ) {
|
|
state.weakDefMap = new (state.persistentAllocator.malloc(sizeof(WeakDefMap))) WeakDefMap();
|
|
}
|
|
if ( state.weakDefMap != nullptr ) {
|
|
const auto& pos = state.weakDefMap->find(symbolName);
|
|
if ( (pos != state.weakDefMap->end()) && (pos->second.targetLoader != nullptr) ) {
|
|
//state.log("resolveSymbol(%s) found in map\n", symbolName);
|
|
result.targetLoader = pos->second.targetLoader;
|
|
result.targetSymbolName = symbolName;
|
|
result.targetRuntimeOffset = pos->second.targetRuntimeOffset;
|
|
result.kind = ResolvedSymbol::Kind::bindToImage;
|
|
result.isCode = pos->second.isCode;
|
|
result.isWeakDef = pos->second.isWeakDef;
|
|
result.isMissingFlatLazy = false;
|
|
if ( verboseWeak )
|
|
state.log(" found %s in map, using impl from %s\n", symbolName, result.targetLoader->path());
|
|
foundFirst = true;
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Keep track of results from the cache to be processed at the end, once
|
|
// we've chosen a canonical definition
|
|
struct CacheLookupResult {
|
|
const Loader* targetLoader = nullptr;
|
|
uint64_t targetRuntimeOffset = 0;
|
|
};
|
|
STACK_ALLOC_ARRAY(CacheLookupResult, cacheResults, state.loaded.size());
|
|
|
|
bool weakBindOpcodeClient = !this->dylibInDyldCache && this->mf(state)->hasOpcodeFixups();
|
|
for ( const Loader* ldr : state.loaded ) {
|
|
if ( ldr->mf(state)->hasWeakDefs() ) {
|
|
ResolvedSymbol thisResult;
|
|
// weak coalescing ignores hidden images
|
|
if ( ldr->hiddenFromFlat() )
|
|
continue;
|
|
if ( ldr->hasExportedSymbol(diag, state, symbolName, Loader::shallow, &thisResult) ) {
|
|
if ( weakBindOpcodeClient && !thisResult.isWeakDef && ldr->dylibInDyldCache ) {
|
|
// rdar://75956202 ignore non-weak symbols in shared cache when opcode based binary is looking for symbols to coalesce
|
|
continue;
|
|
}
|
|
if ( thisResult.targetLoader->dylibInDyldCache && !ldr->hasBeenFixedUp(state) )
|
|
cacheResults.push_back({ thisResult.targetLoader, thisResult.targetRuntimeOffset });
|
|
|
|
// record first implementation found, but keep searching
|
|
if ( !foundFirst ) {
|
|
foundFirst = true;
|
|
result = thisResult;
|
|
if ( verboseWeak )
|
|
state.log(" using %s in %s\n", symbolName, thisResult.targetLoader->path());
|
|
}
|
|
if ( !thisResult.isWeakDef && result.isWeakDef ) {
|
|
// non-weak wins over previous weak-def
|
|
// we don't stop search because we need to see if this overrides anything in the dyld cache
|
|
result = thisResult;
|
|
if ( verboseWeak )
|
|
state.log(" using non-weak %s in %s\n", symbolName, thisResult.targetLoader->path());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// if not found anywhere else and this image is hidden, try looking in itself
|
|
if ( !foundFirst && this->hiddenFromFlat() ) {
|
|
if ( verboseWeak )
|
|
state.log(" did not find unhidden %s, trying self (%s)\n", symbolName, this->leafName());
|
|
ResolvedSymbol thisResult;
|
|
if ( this->hasExportedSymbol(diag, state, symbolName, Loader::shallow, &thisResult) ) {
|
|
foundFirst = true;
|
|
result = thisResult;
|
|
}
|
|
}
|
|
|
|
// Patch the cache if we chose a definition which overrides it
|
|
if ( foundFirst && !cacheResults.empty() && !result.targetLoader->dylibInDyldCache && (patcher != nullptr) ) {
|
|
uint64_t patchedCacheOffset = 0;
|
|
for ( const CacheLookupResult& cacheResult : cacheResults ) {
|
|
// We have already found the impl which we want all clients to use.
|
|
// But, later in load order we see something in the dyld cache that also implements
|
|
// this symbol, so we need to change all caches uses of that to use the found one instead.
|
|
const MachOFile* cacheMF = cacheResult.targetLoader->mf(state);
|
|
uint32_t cachedOverriddenDylibIndex;
|
|
if ( state.config.dyldCache.findMachHeaderImageIndex(cacheMF, cachedOverriddenDylibIndex) ) {
|
|
// Use VMAddr's as the cache may not exist if we are in the builder
|
|
uint64_t cacheOverriddenExportVMAddr = cacheMF->preferredLoadAddress() + cacheResult.targetRuntimeOffset;
|
|
uint64_t cacheOverriddenExportOffset = cacheOverriddenExportVMAddr - state.config.dyldCache.unslidLoadAddress;
|
|
if ( cacheOverriddenExportOffset != patchedCacheOffset ) {
|
|
// because of re-exports, same cacheOffset shows up in multiple dylibs. Only call patcher once per
|
|
if ( verboseWeak )
|
|
state.log(" found use of %s in cache, need to override: %s\n", symbolName, cacheResult.targetLoader->path());
|
|
patcher(cachedOverriddenDylibIndex, (uint32_t)cacheResult.targetRuntimeOffset, result);
|
|
patchedCacheOffset = cacheOverriddenExportOffset;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
if ( foundFirst ) {
|
|
// if a c++ dylib weak-def binds to another dylibs, record the dynamic dependency
|
|
if ( result.targetLoader != this )
|
|
state.addDynamicReference(this, result.targetLoader);
|
|
// if we are using a map to cache weak-def resolution, add to map
|
|
if ( (state.weakDefMap != nullptr) && !result.targetLoader->hiddenFromFlat() ) {
|
|
WeakDefMapValue mapEntry;
|
|
mapEntry.targetLoader = result.targetLoader;
|
|
mapEntry.targetRuntimeOffset = result.targetRuntimeOffset;
|
|
mapEntry.isCode = result.isCode;
|
|
mapEntry.isWeakDef = result.isWeakDef;
|
|
state.weakDefMap->operator[](symbolName) = mapEntry;
|
|
}
|
|
}
|
|
else {
|
|
if ( weakImport ) {
|
|
// ok to be missing, bind to NULL
|
|
result.kind = ResolvedSymbol::Kind::bindAbsolute;
|
|
result.targetRuntimeOffset = 0;
|
|
}
|
|
else {
|
|
diag.error("weak-def symbol not found '%s'", symbolName);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
else {
|
|
diag.error("unknown library ordinal %d in %s when binding '%s'", libOrdinal, path(), symbolName);
|
|
return result;
|
|
}
|
|
if ( result.targetLoader != nullptr ) {
|
|
STACK_ALLOC_ARRAY(const Loader*, alreadySearched, state.loaded.size());
|
|
if ( result.targetLoader->hasExportedSymbol(diag, state, symbolName, Loader::staticLink, &result, &alreadySearched) ) {
|
|
return result;
|
|
}
|
|
}
|
|
if ( weakImport ) {
|
|
// ok to be missing, bind to NULL
|
|
result.kind = ResolvedSymbol::Kind::bindAbsolute;
|
|
result.targetRuntimeOffset = 0;
|
|
}
|
|
else if ( lazyBind && (state.libdyldMissingSymbolRuntimeOffset != 0) ) {
|
|
// missing lazy binds are bound to abort
|
|
result.targetLoader = state.libdyldLoader;
|
|
result.targetSymbolName = symbolName;
|
|
result.targetRuntimeOffset = (uintptr_t)state.libdyldMissingSymbolRuntimeOffset;
|
|
result.kind = ResolvedSymbol::Kind::bindToImage;
|
|
result.isCode = false; // only used for arm64e which uses trie not nlist
|
|
result.isWeakDef = false;
|
|
result.isMissingFlatLazy = false;
|
|
}
|
|
else {
|
|
// if libSystem.dylib has not been initialized yet, then the missing symbol is during launch and need to save that info
|
|
const char* expectedInDylib = "unknown";
|
|
if ( result.targetLoader != nullptr )
|
|
expectedInDylib = result.targetLoader->path();
|
|
#if BUILDING_DYLD && !TARGET_OS_EXCLAVEKIT
|
|
if ( !state.libSystemInitialized() ) {
|
|
state.setLaunchMissingSymbol(symbolName, expectedInDylib, this->path());
|
|
}
|
|
#endif
|
|
// rdar://79796526 add UUID to error message
|
|
char fromUuidStr[64];
|
|
this->getUuidStr(state, fromUuidStr);
|
|
char expectedUuidStr[64];
|
|
if ( result.targetLoader != nullptr )
|
|
result.targetLoader->getUuidStr(state, expectedUuidStr);
|
|
else
|
|
strlcpy(expectedUuidStr, "no uuid", sizeof(expectedUuidStr));
|
|
|
|
// rdar://15648948 (On fatal errors, check binary's min-OS version and note if from the future)
|
|
Diagnostics tooNewBinaryDiag;
|
|
this->tooNewErrorAddendum(tooNewBinaryDiag, state);
|
|
|
|
diag.error("Symbol not found: %s\n Referenced from: <%s> %s%s\n Expected in: <%s> %s",
|
|
symbolName, fromUuidStr, this->path(), tooNewBinaryDiag.errorMessageCStr(), expectedUuidStr, expectedInDylib);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// if the binary for this Loader is newer than dyld, then we are trying to run a too new binary
|
|
void Loader::tooNewErrorAddendum(Diagnostics& diag, RuntimeState& state) const
|
|
{
|
|
__block Platform dyldPlatform = Platform::unknown;
|
|
__block uint32_t dyldMinOS = 0;
|
|
((MachOFile*)(&__dso_handle))->forEachSupportedPlatform(^(Platform plat, uint32_t minOS, uint32_t sdk) {
|
|
dyldPlatform = plat;
|
|
dyldMinOS = minOS;
|
|
});
|
|
this->mf(state)->forEachSupportedPlatform(^(Platform plat, uint32_t minOS, uint32_t sdk) {
|
|
if ( (plat == dyldPlatform) && (minOS > dyldMinOS) ) {
|
|
char versionString[32];
|
|
MachOFile::packedVersionToString(minOS, versionString);
|
|
diag.error(" (built for %s %s which is newer than running OS)",
|
|
MachOFile::platformName(dyldPlatform), versionString);
|
|
}
|
|
});
|
|
}
|
|
|
|
bool Loader::hasExportedSymbol(Diagnostics& diag, RuntimeState& state, const char* symbolName, ExportedSymbolMode mode, ResolvedSymbol* result, dyld3::Array<const Loader*>* alreadySearched) const
|
|
{
|
|
// don't search twice
|
|
if ( alreadySearched != nullptr ) {
|
|
for ( const Loader* im : *alreadySearched ) {
|
|
if ( im == this )
|
|
return false;
|
|
}
|
|
alreadySearched->push_back(this);
|
|
}
|
|
bool canSearchDependents;
|
|
bool searchNonReExports;
|
|
bool searchSelf;
|
|
ExportedSymbolMode depsMode;
|
|
switch ( mode ) {
|
|
case staticLink:
|
|
canSearchDependents = true;
|
|
searchNonReExports = false;
|
|
searchSelf = true;
|
|
depsMode = staticLink;
|
|
break;
|
|
case shallow:
|
|
canSearchDependents = false;
|
|
searchNonReExports = false;
|
|
searchSelf = true;
|
|
depsMode = shallow;
|
|
break;
|
|
case dlsymNext:
|
|
canSearchDependents = true;
|
|
searchNonReExports = true;
|
|
searchSelf = false;
|
|
depsMode = dlsymSelf;
|
|
break;
|
|
case dlsymSelf:
|
|
canSearchDependents = true;
|
|
searchNonReExports = true;
|
|
searchSelf = true;
|
|
depsMode = dlsymSelf;
|
|
break;
|
|
}
|
|
|
|
// The cache builder can't use runtimeOffset's to get the exports trie. Instead use the layout from
|
|
// the builder
|
|
__block const uint8_t* trieStart = nullptr;
|
|
__block const uint8_t* trieEnd = nullptr;
|
|
__block bool hasTrie = false;
|
|
#if SUPPORT_VM_LAYOUT
|
|
const MachOLoaded* ml = this->loadAddress(state);
|
|
//state.log("Loader::hasExportedSymbol(%s) this=%s\n", symbolName, this->path());
|
|
uint64_t trieRuntimeOffset;
|
|
uint32_t trieSize;
|
|
if ( this->getExportsTrie(trieRuntimeOffset, trieSize) ) {
|
|
trieStart = (uint8_t*)ml + trieRuntimeOffset;
|
|
trieEnd = trieStart + trieSize;
|
|
hasTrie = true;
|
|
}
|
|
#else
|
|
this->withLayout(diag, state, ^(const mach_o::Layout &layout) {
|
|
if ( layout.linkedit.exportsTrie.hasValue() ) {
|
|
trieStart = layout.linkedit.exportsTrie.buffer;
|
|
trieEnd = trieStart + layout.linkedit.exportsTrie.bufferSize;
|
|
hasTrie = true;
|
|
}
|
|
});
|
|
#endif
|
|
|
|
if ( hasTrie ) {
|
|
const uint8_t* node = MachOLoaded::trieWalk(diag, trieStart, trieEnd, symbolName);
|
|
//state.log(" trieStart=%p, trieSize=0x%08X, node=%p, error=%s\n", trieStart, trieSize, node, diag.errorMessage());
|
|
if ( (node != nullptr) && searchSelf ) {
|
|
const uint8_t* p = node;
|
|
const uint64_t flags = MachOLoaded::read_uleb128(diag, p, trieEnd);
|
|
if ( flags & EXPORT_SYMBOL_FLAGS_REEXPORT ) {
|
|
// re-export from another dylib, lookup there
|
|
const uint64_t ordinal = MachOLoaded::read_uleb128(diag, p, trieEnd);
|
|
const char* importedName = (char*)p;
|
|
bool nameChanged = false;
|
|
if ( importedName[0] == '\0' ) {
|
|
importedName = symbolName;
|
|
} else if ( strcmp(importedName, symbolName) != 0 ) {
|
|
nameChanged = true;
|
|
}
|
|
if ( (ordinal == 0) || (ordinal > this->dependentCount()) ) {
|
|
diag.error("re-export ordinal %lld in %s out of range for %s", ordinal, this->path(), symbolName);
|
|
return false;
|
|
}
|
|
uint32_t depIndex = (uint32_t)(ordinal - 1);
|
|
DependentKind depKind;
|
|
if ( Loader* depLoader = this->dependent(state, depIndex, &depKind) ) {
|
|
// <rdar://91326465> Explicitly promote to a ::staticLink
|
|
// resolution when looking for a reexported symbol in ::shallow mode.
|
|
// The symbol might be located in one of the reexported libraries
|
|
// of the dependent. If the caller checks all loaders with
|
|
// ::shallow mode it won't be able to find an aliased symbol,
|
|
// because it will only look for the original name.
|
|
if ( nameChanged && mode == Loader::shallow )
|
|
mode = Loader::staticLink;
|
|
if ( nameChanged && alreadySearched ) {
|
|
// As we are changing the symbol name we are looking for, use a new alreadySearched. The existnig
|
|
// alreadySearched may include loaders we have searched before for the old name, but not the new one,
|
|
// and we want to check them again
|
|
STACK_ALLOC_ARRAY(const Loader*, nameChangedAlreadySearched, state.loaded.size());
|
|
return depLoader->hasExportedSymbol(diag, state, importedName, mode, result, &nameChangedAlreadySearched);
|
|
}
|
|
return depLoader->hasExportedSymbol(diag, state, importedName, mode, result, alreadySearched);
|
|
}
|
|
return false; // re-exported symbol from weak-linked dependent which is missing
|
|
}
|
|
else {
|
|
if ( diag.hasError() )
|
|
return false;
|
|
bool isAbsoluteSymbol = ((flags & EXPORT_SYMBOL_FLAGS_KIND_MASK) == EXPORT_SYMBOL_FLAGS_KIND_ABSOLUTE);
|
|
result->targetLoader = this;
|
|
result->targetSymbolName = symbolName;
|
|
result->targetRuntimeOffset = (uintptr_t)MachOLoaded::read_uleb128(diag, p, trieEnd);
|
|
result->kind = isAbsoluteSymbol ? ResolvedSymbol::Kind::bindAbsolute : ResolvedSymbol::Kind::bindToImage;
|
|
result->isCode = this->mf(state)->inCodeSection((uint32_t)(result->targetRuntimeOffset));
|
|
result->isWeakDef = (flags & EXPORT_SYMBOL_FLAGS_WEAK_DEFINITION);
|
|
result->isMissingFlatLazy = false;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
// try old slow way
|
|
const mach_o::MachOFileRef fileRef = this->mf(state);
|
|
__block bool found = false;
|
|
this->withLayout(diag, state, ^(const mach_o::Layout& layout) {
|
|
mach_o::SymbolTable symbolTable(layout);
|
|
|
|
symbolTable.forEachGlobalSymbol(diag, ^(const char* n_name, uint64_t n_value, uint8_t n_type,
|
|
uint8_t n_sect, uint16_t n_desc, bool& stop) {
|
|
if ( ((n_type & N_TYPE) == N_SECT) && ((n_type & N_EXT) != 0) ) {
|
|
if ( strcmp(n_name, symbolName) == 0 ) {
|
|
result->targetLoader = this;
|
|
result->targetSymbolName = symbolName;
|
|
result->targetRuntimeOffset = (uintptr_t)(n_value - fileRef->preferredLoadAddress());
|
|
result->kind = ResolvedSymbol::Kind::bindToImage;
|
|
result->isCode = false; // only used for arm64e which uses trie not nlist
|
|
result->isWeakDef = (n_desc & N_WEAK_DEF);
|
|
result->isMissingFlatLazy = false;
|
|
stop = true;
|
|
found = true;
|
|
}
|
|
}
|
|
});
|
|
});
|
|
if ( found )
|
|
return true;
|
|
}
|
|
|
|
if ( const JustInTimeLoader* jitThis = this->isJustInTimeLoader() ) {
|
|
if ( const PseudoDylib *pd = jitThis->pseudoDylib() ) {
|
|
const char *symbolNames[] = { symbolName };
|
|
void *addrs[1] = { nullptr };
|
|
_dyld_pseudodylib_symbol_flags flags[1] = { DYLD_PSEUDODYLIB_SYMBOL_FLAGS_NONE };
|
|
if (char *errMsg = pd->lookupSymbols(symbolNames, addrs, flags)) {
|
|
diag.error("pseudo-dylib lookup error: %s", errMsg);
|
|
pd->disposeErrorMessage(errMsg);
|
|
return false;
|
|
}
|
|
if ( flags[0] & DYLD_PSEUDODYLIB_SYMBOL_FLAGS_FOUND ) {
|
|
result->targetLoader = this;
|
|
result->targetSymbolName = symbolName;
|
|
result->targetRuntimeOffset = (uintptr_t)addrs[0] - (uintptr_t)this->mf(state);
|
|
result->kind = ResolvedSymbol::Kind::bindToImage;
|
|
result->isCode = flags[0] & DYLD_PSEUDODYLIB_SYMBOL_FLAGS_CALLABLE;
|
|
result->isWeakDef = flags[0] & DYLD_PSEUDODYLIB_SYMBOL_FLAGS_WEAK_DEF;
|
|
result->isMissingFlatLazy = false;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( canSearchDependents ) {
|
|
// Search re-exported dylibs
|
|
const uint32_t depCount = this->dependentCount();
|
|
for ( uint32_t i = 0; i < depCount; ++i ) {
|
|
Loader::DependentKind depKind;
|
|
if ( Loader* depLoader = this->dependent(state, i, &depKind) ) {
|
|
//state.log("dep #%d of %p is %d %p (%s %s)\n", i, this, (int)depKind, depLoader, this->path(), depLoader->path());
|
|
if ( (depKind == Loader::DependentKind::reexport) || (searchNonReExports && (depKind != Loader::DependentKind::upward)) ) {
|
|
if ( depLoader->hasExportedSymbol(diag, state, symbolName, depsMode, result, alreadySearched) )
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
#if BUILDING_DYLD || BUILDING_UNIT_TESTS
|
|
uintptr_t Loader::resolvedAddress(RuntimeState& state, const ResolvedSymbol& symbol)
|
|
{
|
|
switch ( symbol.kind ) {
|
|
case ResolvedSymbol::Kind::rebase:
|
|
case ResolvedSymbol::Kind::bindToImage:
|
|
return (uintptr_t)symbol.targetLoader->loadAddress(state) + (uintptr_t)symbol.targetRuntimeOffset;
|
|
case ResolvedSymbol::Kind::bindAbsolute:
|
|
return (uintptr_t)symbol.targetRuntimeOffset;
|
|
}
|
|
}
|
|
#endif // BUILDING_DYLD || BUILDING_UNIT_TESTS
|
|
|
|
|
|
|
|
uintptr_t Loader::interpose(RuntimeState& state, uintptr_t value, const Loader* forLoader)
|
|
{
|
|
// <rdar://problem/25686570> ignore interposing on a weak function that does not exist
|
|
if ( value == 0 )
|
|
return 0;
|
|
|
|
// Always start with objc patching. This is required every when AMFI may not permit other interposing
|
|
for ( const InterposeTupleAll& tuple : state.patchedObjCClasses ) {
|
|
if ( tuple.replacee == value ) {
|
|
if ( state.config.log.interposing )
|
|
state.log(" interpose replaced 0x%08lX with 0x%08lX in %s\n", value, tuple.replacement, forLoader ? forLoader->path() : "dlsym");
|
|
return tuple.replacement;
|
|
}
|
|
}
|
|
|
|
// Next singleton patching, which also may happen without other interposing
|
|
for ( const InterposeTupleAll& tuple : state.patchedSingletons ) {
|
|
if ( tuple.replacee == value ) {
|
|
if ( state.config.log.interposing )
|
|
state.log(" interpose replaced 0x%08lX with 0x%08lX in %s\n", value, tuple.replacement, forLoader ? forLoader->path() : "dlsym");
|
|
return tuple.replacement;
|
|
}
|
|
}
|
|
|
|
#if !TARGET_OS_EXCLAVEKIT
|
|
// AMFI can ban interposing
|
|
// Note we check this here just in case someone tried to substitute a fake interposing tuples array in the state
|
|
if ( !state.config.security.allowInterposing )
|
|
return value;
|
|
#endif
|
|
|
|
// look for image specific interposing (needed for multiple interpositions on the same function)
|
|
for ( const InterposeTupleSpecific& tuple : state.interposingTuplesSpecific ) {
|
|
if ( (tuple.replacee == value) && (tuple.onlyImage == forLoader) ) {
|
|
if ( state.config.log.interposing )
|
|
state.log(" interpose replaced 0x%08lX with 0x%08lX in %s\n", value, tuple.replacement, forLoader->path());
|
|
return tuple.replacement;
|
|
}
|
|
}
|
|
|
|
// no image specific interpose, so look for generic interpose
|
|
for ( const InterposeTupleAll& tuple : state.interposingTuplesAll ) {
|
|
if ( tuple.replacee == value ) {
|
|
if ( state.config.log.interposing )
|
|
state.log(" interpose replaced 0x%08lX with 0x%08lX in %s\n", value, tuple.replacement, forLoader ? forLoader->path() : "dlsym");
|
|
return tuple.replacement;
|
|
}
|
|
}
|
|
return value;
|
|
}
|
|
|
|
#if (BUILDING_DYLD || BUILDING_UNIT_TESTS) && !TARGET_OS_EXCLAVEKIT
|
|
void Loader::applyInterposingToDyldCache(RuntimeState& state)
|
|
{
|
|
const DyldSharedCache* dyldCache = state.config.dyldCache.addr;
|
|
if ( dyldCache == nullptr )
|
|
return; // no dyld cache to interpose
|
|
if ( state.interposingTuplesAll.empty() )
|
|
return; // no interposing tuples
|
|
|
|
// make the cache writable for this block
|
|
DyldCacheDataConstScopedWriter patcher(state);
|
|
|
|
state.setVMAccountingSuspending(true);
|
|
for ( const InterposeTupleAll& tuple : state.interposingTuplesAll ) {
|
|
uint32_t imageIndex;
|
|
uintptr_t cacheOffsetOfReplacee = tuple.replacee - (uintptr_t)dyldCache;
|
|
if ( !dyldCache->addressInText(cacheOffsetOfReplacee, &imageIndex) )
|
|
continue;
|
|
|
|
// Convert from a cache offset to an image offset
|
|
uint64_t mTime;
|
|
uint64_t inode;
|
|
const dyld3::MachOAnalyzer* imageMA = (dyld3::MachOAnalyzer*)(dyldCache->getIndexedImageEntry(imageIndex, mTime, inode));
|
|
if ( imageMA == nullptr )
|
|
continue;
|
|
|
|
uint32_t dylibOffsetOfReplacee = (uint32_t)((dyldCache->unslidLoadAddress() + cacheOffsetOfReplacee) - imageMA->preferredLoadAddress());
|
|
|
|
dyldCache->forEachPatchableExport(imageIndex, ^(uint32_t dylibVMOffsetOfImpl, const char* exportName,
|
|
PatchKind patchKind) {
|
|
// Skip patching anything other than this symbol
|
|
if ( dylibVMOffsetOfImpl != dylibOffsetOfReplacee )
|
|
return;
|
|
uintptr_t newLoc = tuple.replacement;
|
|
dyldCache->forEachPatchableUseOfExport(imageIndex, dylibVMOffsetOfImpl,
|
|
^(uint64_t cacheVMOffset, MachOLoaded::PointerMetaData pmd, uint64_t addend,
|
|
bool isWeakImport) {
|
|
uintptr_t* loc = (uintptr_t*)((uintptr_t)dyldCache + cacheVMOffset);
|
|
uintptr_t newValue = newLoc + (uintptr_t)addend;
|
|
#if __has_feature(ptrauth_calls)
|
|
if ( pmd.authenticated ) {
|
|
newValue = dyld3::MachOLoaded::ChainedFixupPointerOnDisk::Arm64e::signPointer(newValue, loc, pmd.usesAddrDiversity, pmd.diversity, pmd.key);
|
|
*loc = newValue;
|
|
if ( state.config.log.interposing )
|
|
state.log("interpose: *%p = %p (JOP: diversity 0x%04X, addr-div=%d, key=%s)\n",
|
|
loc, (void*)newValue, pmd.diversity, pmd.usesAddrDiversity, MachOLoaded::ChainedFixupPointerOnDisk::Arm64e::keyName(pmd.key));
|
|
return;
|
|
}
|
|
#endif
|
|
if ( state.config.log.interposing )
|
|
state.log("interpose: *%p = 0x%0llX (dyld cache patch) to %s\n", loc, newLoc + addend, exportName);
|
|
*loc = newValue;
|
|
});
|
|
});
|
|
}
|
|
state.setVMAccountingSuspending(false);
|
|
}
|
|
|
|
|
|
void Loader::applyCachePatchesToOverride(RuntimeState& state, const Loader* dylibToPatch,
|
|
uint16_t overriddenDylibIndex, const DylibPatch* patches,
|
|
DyldCacheDataConstLazyScopedWriter& cacheDataConst) const
|
|
{
|
|
const DyldSharedCache* dyldCache = state.config.dyldCache.addr;
|
|
const MachOAnalyzer* dylibToPatchMA = dylibToPatch->analyzer(state);
|
|
uint32_t dylibToPatchIndex = dylibToPatch->ref.index;
|
|
|
|
// Early return if we have no exports used in the client dylib. Then we don't need to walk every export
|
|
if ( !dyldCache->shouldPatchClientOfImage(overriddenDylibIndex, dylibToPatchIndex) )
|
|
return;
|
|
|
|
uint32_t patchVersion = dyldCache->patchInfoVersion();
|
|
assert((patchVersion == 2) || (patchVersion == 3) || (patchVersion == 4));
|
|
__block bool suspended = false;
|
|
__block const DylibPatch* cachePatch = patches;
|
|
dyldCache->forEachPatchableExport(overriddenDylibIndex, ^(uint32_t dylibVMOffsetOfImpl, const char* exportName,
|
|
PatchKind patchKind) {
|
|
const DylibPatch* patch = cachePatch;
|
|
++cachePatch;
|
|
|
|
// Skip patching objc classes and singletons. We'll handle those another way
|
|
switch ( patchKind ) {
|
|
case PatchKind::regular:
|
|
break;
|
|
case PatchKind::cfObj2:
|
|
if ( patch->overrideOffsetOfImpl == DylibPatch::singleton )
|
|
return;
|
|
break;
|
|
case PatchKind::objcClass:
|
|
if ( patch->overrideOffsetOfImpl == DylibPatch::objcClass )
|
|
return;
|
|
break;
|
|
}
|
|
|
|
uintptr_t targetRuntimeAddress = BAD_ROOT_ADDRESS; // magic value to cause a unique crash is missing symbol in root is used
|
|
if ( patch->overrideOffsetOfImpl != DylibPatch::missingSymbol )
|
|
targetRuntimeAddress = (uintptr_t)(this->loadAddress(state)) + ((intptr_t)patch->overrideOffsetOfImpl);
|
|
|
|
dyldCache->forEachPatchableUseOfExportInImage(overriddenDylibIndex, dylibVMOffsetOfImpl, dylibToPatchIndex,
|
|
^(uint32_t userVMOffset,
|
|
dyld3::MachOLoaded::PointerMetaData pmd, uint64_t addend,
|
|
bool isWeakImport) {
|
|
// ensure dyld cache __DATA_CONST is writeable
|
|
cacheDataConst.makeWriteable();
|
|
|
|
// overridden dylib may not effect this dylib, so only suspend when we find it does effect it
|
|
if ( !suspended ) {
|
|
state.setVMAccountingSuspending(true);
|
|
suspended = true;
|
|
}
|
|
|
|
uintptr_t* loc = (uintptr_t*)((uint8_t*)dylibToPatchMA + userVMOffset);
|
|
uintptr_t newValue = targetRuntimeAddress + (uintptr_t)addend;
|
|
|
|
// if client in dyld cache is ok with symbol being missing, set its use to NULL instead of bad-missing-value
|
|
if ( isWeakImport && (targetRuntimeAddress == BAD_ROOT_ADDRESS) )
|
|
newValue = 0;
|
|
|
|
// if overridden dylib is also interposed, use interposing
|
|
for ( const InterposeTupleAll& tuple : state.interposingTuplesAll ) {
|
|
if ( tuple.replacee == newValue ) {
|
|
newValue = tuple.replacement;
|
|
}
|
|
}
|
|
#if __has_feature(ptrauth_calls)
|
|
if ( pmd.authenticated && (newValue != 0) ) {
|
|
newValue = dyld3::MachOLoaded::ChainedFixupPointerOnDisk::Arm64e::signPointer(newValue, loc, pmd.usesAddrDiversity, pmd.diversity, pmd.key);
|
|
if ( *loc != newValue ) {
|
|
*loc = newValue;
|
|
if ( state.config.log.fixups ) {
|
|
state.log("cache fixup: *0x%012lX = 0x%012lX (*%s+0x%012lX = %s+0x%012lX) (JOP: diversity=0x%04X, addr-div=%d, key=%s)\n",
|
|
(long)loc, newValue,
|
|
dylibToPatch->leafName(), (long)userVMOffset,
|
|
this->leafName(), (long)patch->overrideOffsetOfImpl,
|
|
pmd.diversity, pmd.usesAddrDiversity, MachOLoaded::ChainedFixupPointerOnDisk::Arm64e::keyName(pmd.key));
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
#endif
|
|
if ( *loc != newValue ) {
|
|
*loc = newValue;
|
|
if ( state.config.log.fixups )
|
|
state.log("cache fixup: *0x%012lX = 0x%012lX (*%s+0x%012lX = %s+0x%012lX)\n",
|
|
(long)loc, (long)newValue,
|
|
dylibToPatch->leafName(), (long)userVMOffset,
|
|
this->leafName(), (long)patch->overrideOffsetOfImpl);
|
|
}
|
|
});
|
|
});
|
|
// Ensure the end marker is as expected
|
|
assert(cachePatch->overrideOffsetOfImpl == DylibPatch::endOfPatchTable);
|
|
|
|
if ( suspended )
|
|
state.setVMAccountingSuspending(false);
|
|
}
|
|
|
|
|
|
void Loader::applyCachePatchesTo(RuntimeState& state, const Loader* dylibToPatch, DyldCacheDataConstLazyScopedWriter& cacheDataConst) const
|
|
{
|
|
// do nothing if this dylib does not override something in the dyld cache
|
|
uint16_t overriddenDylibIndex;
|
|
const DylibPatch* patches;
|
|
if ( !this->overridesDylibInCache(patches, overriddenDylibIndex) )
|
|
return;
|
|
if ( patches != nullptr )
|
|
this->applyCachePatchesToOverride(state, dylibToPatch, overriddenDylibIndex, patches, cacheDataConst);
|
|
|
|
// The override here may be a root of an iOSMac dylib, in which case we should also try patch uses of the macOS unzippered twin
|
|
if ( !this->isPrebuilt && state.config.process.catalystRuntime ) {
|
|
if ( const JustInTimeLoader* jitThis = this->isJustInTimeLoader() ) {
|
|
if ( const DylibPatch* patches2 = jitThis->getCatalystMacTwinPatches() ) {
|
|
uint16_t macOSTwinIndex = Loader::indexOfUnzipperedTwin(state, overriddenDylibIndex);
|
|
if ( macOSTwinIndex != kNoUnzipperedTwin )
|
|
this->applyCachePatchesToOverride(state, dylibToPatch, macOSTwinIndex, patches2, cacheDataConst);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Loader::applyCachePatches(RuntimeState& state, DyldCacheDataConstLazyScopedWriter& cacheDataConst) const
|
|
{
|
|
// do nothing if this dylib does not override something in the dyld cache
|
|
uint16_t overriddenDylibIndex;
|
|
const DylibPatch* patches;
|
|
if ( !this->overridesDylibInCache(patches, overriddenDylibIndex) )
|
|
return;
|
|
|
|
if ( patches == nullptr )
|
|
return;
|
|
|
|
const DyldSharedCache* dyldCache = state.config.dyldCache.addr;
|
|
|
|
__block bool suspended = false;
|
|
__block const DylibPatch* cachePatch = patches;
|
|
dyldCache->forEachPatchableExport(overriddenDylibIndex, ^(uint32_t dylibVMOffsetOfImpl, const char* exportName,
|
|
PatchKind patchKind) {
|
|
const DylibPatch* patch = cachePatch;
|
|
++cachePatch;
|
|
|
|
// Skip patching objc classes and singletons. We'll handle those another way
|
|
switch ( patchKind ) {
|
|
case PatchKind::regular:
|
|
break;
|
|
case PatchKind::cfObj2:
|
|
if ( patch->overrideOffsetOfImpl == DylibPatch::singleton )
|
|
return;
|
|
break;
|
|
case PatchKind::objcClass:
|
|
if ( patch->overrideOffsetOfImpl == DylibPatch::objcClass )
|
|
return;
|
|
break;
|
|
}
|
|
|
|
uintptr_t targetRuntimeAddress = BAD_ROOT_ADDRESS; // magic value to cause a unique crash is missing symbol in root is used
|
|
if ( patch->overrideOffsetOfImpl != DylibPatch::missingSymbol )
|
|
targetRuntimeAddress = (uintptr_t)(this->loadAddress(state)) + ((intptr_t)patch->overrideOffsetOfImpl);
|
|
|
|
dyldCache->forEachPatchableGOTUseOfExport(overriddenDylibIndex, dylibVMOffsetOfImpl,
|
|
^(uint64_t cacheVMOffset, dyld3::MachOLoaded::PointerMetaData pmd, uint64_t addend,
|
|
bool isWeakImport) {
|
|
// ensure dyld cache __DATA_CONST is writeable
|
|
cacheDataConst.makeWriteable();
|
|
|
|
// overridden dylib may not effect this dylib, so only suspend when we find it does effect it
|
|
if ( !suspended ) {
|
|
state.setVMAccountingSuspending(true);
|
|
suspended = true;
|
|
}
|
|
uintptr_t* loc = (uintptr_t*)((uint8_t*)dyldCache + cacheVMOffset);
|
|
uintptr_t newValue = targetRuntimeAddress + (uintptr_t)addend;
|
|
|
|
// if client in dyld cache is ok with symbol being missing, set its use to NULL instead of bad-missing-value
|
|
if ( isWeakImport && (targetRuntimeAddress == BAD_ROOT_ADDRESS) )
|
|
newValue = 0;
|
|
|
|
#if __has_feature(ptrauth_calls)
|
|
if ( pmd.authenticated && (newValue != 0) ) {
|
|
newValue = dyld3::MachOLoaded::ChainedFixupPointerOnDisk::Arm64e::signPointer(newValue, loc, pmd.usesAddrDiversity, pmd.diversity, pmd.key);
|
|
if ( *loc != newValue ) {
|
|
*loc = newValue;
|
|
if ( state.config.log.fixups ) {
|
|
state.log("cache GOT fixup: *0x%012lX = 0x%012lX (*cache+0x%012lX = %s+0x%012lX) (JOP: diversity=0x%04X, addr-div=%d, key=%s)\n",
|
|
(long)loc, newValue, (long)cacheVMOffset,
|
|
this->leafName(), (long)patch->overrideOffsetOfImpl,
|
|
pmd.diversity, pmd.usesAddrDiversity,
|
|
MachOLoaded::ChainedFixupPointerOnDisk::Arm64e::keyName(pmd.key));
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
#endif
|
|
if ( *loc != newValue ) {
|
|
*loc = newValue;
|
|
if ( state.config.log.fixups )
|
|
state.log("cache GOT fixup: *0x%012lX = 0x%012lX (*cache+0x%012lX = %s+0x%012lX)\n",
|
|
(long)loc, (long)newValue,
|
|
(long)cacheVMOffset,
|
|
this->leafName(), (long)patch->overrideOffsetOfImpl);
|
|
}
|
|
});
|
|
});
|
|
// Ensure the end marker is as expected
|
|
assert(cachePatch->overrideOffsetOfImpl == DylibPatch::endOfPatchTable);
|
|
|
|
if ( suspended )
|
|
state.setVMAccountingSuspending(false);
|
|
}
|
|
|
|
#endif // BUILDING_DYLD || BUILDING_UNIT_TESTS
|
|
|
|
uint16_t Loader::indexOfUnzipperedTwin(const RuntimeState& state, uint16_t overrideIndex)
|
|
{
|
|
if ( state.config.process.catalystRuntime ) {
|
|
// Find the macOS twin overridden index
|
|
if ( const PrebuiltLoaderSet* cachePBLS = state.cachedDylibsPrebuiltLoaderSet() ) {
|
|
const Loader* overridenDylibLdr = cachePBLS->atIndex(overrideIndex);
|
|
if ( const PrebuiltLoader* overridenDylibPBLdr = overridenDylibLdr->isPrebuiltLoader() ) {
|
|
if ( overridenDylibPBLdr->supportsCatalyst )
|
|
return overridenDylibPBLdr->indexOfTwin;
|
|
}
|
|
} else {
|
|
// We might be running with an invalid version, so can't use Prebuilt loaders
|
|
const char* catalystInstallName = state.config.dyldCache.getIndexedImagePath(overrideIndex);
|
|
if ( strncmp(catalystInstallName, "/System/iOSSupport/", 19) == 0 ) {
|
|
const char* macTwinPath = &catalystInstallName[18];
|
|
uint32_t macDylibCacheIndex;
|
|
if ( state.config.dyldCache.indexOfPath(macTwinPath, macDylibCacheIndex) )
|
|
return macDylibCacheIndex;
|
|
}
|
|
}
|
|
}
|
|
|
|
return kNoUnzipperedTwin;
|
|
}
|
|
|
|
#if !TARGET_OS_EXCLAVEKIT
|
|
uint64_t Loader::getOnDiskBinarySliceOffset(RuntimeState& state, const MachOAnalyzer* ma, const char* path)
|
|
{
|
|
#if BUILDING_DYLD
|
|
#if TARGET_OS_OSX && __arm64__
|
|
// these are always thin and sanboxing blocks open()ing them
|
|
if ( strncmp(path, "/usr/libexec/rosetta/", 21) == 0 )
|
|
return 0;
|
|
#endif
|
|
__block Diagnostics diag;
|
|
__block uint64_t sliceOffset = 0;
|
|
state.config.syscall.withReadOnlyMappedFile(diag, path, false, ^(const void* mapping, size_t mappedSize, bool isOSBinary, const FileID& fileID, const char* realPath) {
|
|
if ( const dyld3::FatFile* ff = dyld3::FatFile::isFatFile(mapping) ) {
|
|
ff->forEachSlice(diag, mappedSize, ^(uint32_t sliceCpuType, uint32_t sliceCpuSubType, const void* sliceStart, uint64_t sliceSize, bool& stop) {
|
|
if ( memcmp(ma, sliceStart, 64) == 0 ) {
|
|
sliceOffset = (uint8_t*)sliceStart - (uint8_t*)mapping;
|
|
stop = true;
|
|
}
|
|
});
|
|
}
|
|
});
|
|
return sliceOffset;
|
|
#else
|
|
// don't record a sliceOffset when the dyld cache builder is run in Mastering because the file may be thinned later
|
|
return 0;
|
|
#endif
|
|
}
|
|
#endif // !TARGET_OS_EXCLAVEKIT
|
|
|
|
} // namespace
|