mirror of
https://github.com/Karmaz95/Snake_Apple.git
synced 2026-03-30 14:00:16 +02:00
4095 lines
162 KiB
C++
4095 lines
162 KiB
C++
// Source (dyld-1122.1) https://github.com/apple-oss-distributions/dyld/blob/18d3cb0f6b46707fee6d315cccccf7af8a8dbe57/common/MachOFile.cpp#L1213C25-L1213C25
|
||
/*
|
||
* Copyright (c) 2017 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 <stdlib.h>
|
||
#include <assert.h>
|
||
#include <string.h>
|
||
#include <strings.h>
|
||
#include <stdio.h>
|
||
#include <TargetConditionals.h>
|
||
#include "Defines.h"
|
||
#if TARGET_OS_EXCLAVEKIT
|
||
#define OSSwapBigToHostInt32 __builtin_bswap32
|
||
#define OSSwapBigToHostInt64 __builtin_bswap64
|
||
#define htonl __builtin_bswap32
|
||
#else
|
||
#include <sys/stat.h>
|
||
#include <sys/types.h>
|
||
#include <sys/errno.h>
|
||
#include <sys/fcntl.h>
|
||
#include <unistd.h>
|
||
#include <mach/host_info.h>
|
||
#include <mach/mach.h>
|
||
#include <mach/mach_host.h>
|
||
#if SUPPORT_CLASSIC_RELOCS
|
||
#include <mach-o/reloc.h>
|
||
#include <mach-o/x86_64/reloc.h>
|
||
#endif
|
||
extern "C" {
|
||
#include <corecrypto/ccdigest.h>
|
||
#include <corecrypto/ccsha1.h>
|
||
#include <corecrypto/ccsha2.h>
|
||
}
|
||
#endif
|
||
|
||
#include "Defines.h"
|
||
|
||
#include <mach-o/nlist.h>
|
||
|
||
#include "Array.h"
|
||
#include "MachOFile.h"
|
||
#include "SupportedArchs.h"
|
||
#include "CodeSigningTypes.h"
|
||
|
||
#if (BUILDING_DYLD || BUILDING_LIBDYLD) && !TARGET_OS_EXCLAVEKIT
|
||
#include <subsystem.h>
|
||
#endif
|
||
|
||
namespace dyld3 {
|
||
|
||
#if !TARGET_OS_EXCLAVEKIT
|
||
|
||
//////////////////////////// posix wrappers ////////////////////////////////////////
|
||
|
||
// <rdar://problem/10111032> wrap calls to stat() with check for EAGAIN
|
||
int stat(const char* path, struct stat* buf)
|
||
{
|
||
int result;
|
||
do {
|
||
#if BUILDING_DYLD
|
||
result = ::stat_with_subsystem(path, buf);
|
||
#else
|
||
result = ::stat(path, buf);
|
||
#endif
|
||
} while ((result == -1) && ((errno == EAGAIN) || (errno == EINTR)));
|
||
|
||
return result;
|
||
}
|
||
|
||
// <rdar://problem/10111032> wrap calls to stat() with check for EAGAIN
|
||
int fstatat(int fd, const char *path, struct stat *buf, int flag)
|
||
{
|
||
int result;
|
||
do {
|
||
result = ::fstatat(fd, path, buf, flag);
|
||
} while ((result == -1) && ((errno == EAGAIN) || (errno == EINTR)));
|
||
|
||
return result;
|
||
}
|
||
|
||
// <rdar://problem/13805025> dyld should retry open() if it gets an EGAIN
|
||
int open(const char* path, int flag, int other)
|
||
{
|
||
int result;
|
||
do {
|
||
#if BUILDING_DYLD
|
||
if (flag & O_CREAT)
|
||
result = ::open(path, flag, other);
|
||
else
|
||
result = ::open_with_subsystem(path, flag);
|
||
#else
|
||
result = ::open(path, flag, other);
|
||
#endif
|
||
} while ((result == -1) && ((errno == EAGAIN) || (errno == EINTR)));
|
||
|
||
return result;
|
||
}
|
||
#endif // !TARGET_OS_EXCLAVEKIT
|
||
|
||
|
||
//////////////////////////// FatFile ////////////////////////////////////////
|
||
|
||
const FatFile* FatFile::isFatFile(const void* fileStart)
|
||
{
|
||
const FatFile* fileStartAsFat = (FatFile*)fileStart;
|
||
if ( (fileStartAsFat->magic == OSSwapBigToHostInt32(FAT_MAGIC)) || (fileStartAsFat->magic == OSSwapBigToHostInt32(FAT_MAGIC_64)) )
|
||
return fileStartAsFat;
|
||
else
|
||
return nullptr;
|
||
}
|
||
|
||
bool FatFile::isValidSlice(Diagnostics& diag, uint64_t fileLen, uint32_t sliceIndex,
|
||
uint32_t sliceCpuType, uint32_t sliceCpuSubType, uint64_t sliceOffset, uint64_t sliceLen) const {
|
||
if ( greaterThanAddOrOverflow(sliceOffset, sliceLen, fileLen) ) {
|
||
diag.error("slice %d extends beyond end of file", sliceIndex);
|
||
return false;
|
||
}
|
||
const dyld3::MachOFile* mf = (const dyld3::MachOFile*)((uint8_t*)this+sliceOffset);
|
||
if (!mf->isMachO(diag, sliceLen))
|
||
return false;
|
||
if ( mf->cputype != (cpu_type_t)sliceCpuType ) {
|
||
diag.error("cpu type in slice (0x%08X) does not match fat header (0x%08X)", mf->cputype, sliceCpuType);
|
||
return false;
|
||
}
|
||
else if ( (mf->cpusubtype & ~CPU_SUBTYPE_MASK) != (sliceCpuSubType & ~CPU_SUBTYPE_MASK) ) {
|
||
diag.error("cpu subtype in slice (0x%08X) does not match fat header (0x%08X)", mf->cpusubtype, sliceCpuSubType);
|
||
return false;
|
||
}
|
||
uint32_t pageSizeMask = mf->uses16KPages() ? 0x3FFF : 0xFFF;
|
||
if ( (sliceOffset & pageSizeMask) != 0 ) {
|
||
// slice not page aligned
|
||
if ( strncmp((char*)this+sliceOffset, "!<arch>", 7) == 0 )
|
||
diag.error("file is static library");
|
||
else
|
||
diag.error("slice is not page aligned");
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
void FatFile::forEachSlice(Diagnostics& diag, uint64_t fileLen, bool validate,
|
||
void (^callback)(uint32_t sliceCpuType, uint32_t sliceCpuSubType, const void* sliceStart, uint64_t sliceSize, bool& stop)) const
|
||
{
|
||
if ( this->magic == OSSwapBigToHostInt32(FAT_MAGIC) ) {
|
||
const uint64_t maxArchs = ((4096 - sizeof(fat_header)) / sizeof(fat_arch));
|
||
const uint32_t numArchs = OSSwapBigToHostInt32(nfat_arch);
|
||
if ( numArchs > maxArchs ) {
|
||
diag.error("fat header too large: %u entries", numArchs);
|
||
return;
|
||
}
|
||
// <rdar://90700132> make sure architectures list doesn't exceed the file size
|
||
// We can’t overflow due to maxArch check
|
||
// Check numArchs+1 to cover the extra read after the loop
|
||
if ( (sizeof(fat_header) + ((numArchs + 1) * sizeof(fat_arch))) > fileLen ) {
|
||
diag.error("fat header malformed, architecture slices extend beyond end of file");
|
||
return;
|
||
}
|
||
bool stop = false;
|
||
const fat_arch* const archs = (fat_arch*)(((char*)this)+sizeof(fat_header));
|
||
for (uint32_t i=0; i < numArchs; ++i) {
|
||
uint32_t cpuType = OSSwapBigToHostInt32(archs[i].cputype);
|
||
uint32_t cpuSubType = OSSwapBigToHostInt32(archs[i].cpusubtype);
|
||
uint32_t offset = OSSwapBigToHostInt32(archs[i].offset);
|
||
uint32_t len = OSSwapBigToHostInt32(archs[i].size);
|
||
Diagnostics sliceDiag;
|
||
if ( !validate || isValidSlice(sliceDiag, fileLen, i, cpuType, cpuSubType, offset, len) )
|
||
callback(cpuType, cpuSubType, (uint8_t*)this+offset, len, stop);
|
||
if ( stop )
|
||
break;
|
||
if ( sliceDiag.hasError() )
|
||
diag.appendError("%s, ", sliceDiag.errorMessageCStr());
|
||
}
|
||
|
||
// Look for one more slice
|
||
if ( numArchs != maxArchs ) {
|
||
uint32_t cpuType = OSSwapBigToHostInt32(archs[numArchs].cputype);
|
||
uint32_t cpuSubType = OSSwapBigToHostInt32(archs[numArchs].cpusubtype);
|
||
uint32_t offset = OSSwapBigToHostInt32(archs[numArchs].offset);
|
||
uint32_t len = OSSwapBigToHostInt32(archs[numArchs].size);
|
||
if ((cpuType == CPU_TYPE_ARM64) && ((cpuSubType == CPU_SUBTYPE_ARM64_ALL || cpuSubType == CPU_SUBTYPE_ARM64_V8))) {
|
||
if ( !validate || isValidSlice(diag, fileLen, numArchs, cpuType, cpuSubType, offset, len) )
|
||
callback(cpuType, cpuSubType, (uint8_t*)this+offset, len, stop);
|
||
}
|
||
}
|
||
}
|
||
else if ( this->magic == OSSwapBigToHostInt32(FAT_MAGIC_64) ) {
|
||
const uint32_t numArchs = OSSwapBigToHostInt32(nfat_arch);
|
||
if ( numArchs > ((4096 - sizeof(fat_header)) / sizeof(fat_arch_64)) ) {
|
||
diag.error("fat header too large: %u entries", OSSwapBigToHostInt32(nfat_arch));
|
||
return;
|
||
}
|
||
// <rdar://90700132> make sure architectures list doesn't exceed the file size
|
||
// We can’t overflow due to maxArch check
|
||
if ( (sizeof(fat_header) + (numArchs * sizeof(fat_arch_64))) > fileLen ) {
|
||
diag.error("fat header malformed, architecture slices extend beyond end of file");
|
||
return;
|
||
}
|
||
bool stop = false;
|
||
const fat_arch_64* const archs = (fat_arch_64*)(((char*)this)+sizeof(fat_header));
|
||
for (uint32_t i=0; i < numArchs; ++i) {
|
||
uint32_t cpuType = OSSwapBigToHostInt32(archs[i].cputype);
|
||
uint32_t cpuSubType = OSSwapBigToHostInt32(archs[i].cpusubtype);
|
||
uint64_t offset = OSSwapBigToHostInt64(archs[i].offset);
|
||
uint64_t len = OSSwapBigToHostInt64(archs[i].size);
|
||
if ( !validate || isValidSlice(diag, fileLen, i, cpuType, cpuSubType, offset, len) )
|
||
callback(cpuType, cpuSubType, (uint8_t*)this+offset, len, stop);
|
||
if ( stop )
|
||
break;
|
||
}
|
||
}
|
||
else {
|
||
diag.error("not a fat file");
|
||
}
|
||
}
|
||
|
||
void FatFile::forEachSlice(Diagnostics& diag, uint64_t fileLen, void (^callback)(uint32_t sliceCpuType, uint32_t sliceCpuSubType, const void* sliceStart, uint64_t sliceSize, bool& stop)) const
|
||
{
|
||
forEachSlice(diag, fileLen, true, callback);
|
||
}
|
||
|
||
const char* FatFile::archNames(char strBuf[256], uint64_t fileLen) const
|
||
{
|
||
strBuf[0] = '\0';
|
||
Diagnostics diag;
|
||
__block bool needComma = false;
|
||
this->forEachSlice(diag, fileLen, false, ^(uint32_t sliceCpuType, uint32_t sliceCpuSubType, const void* sliceStart, uint64_t sliceSize, bool& stop) {
|
||
if ( needComma )
|
||
strlcat(strBuf, ",", 256);
|
||
strlcat(strBuf, MachOFile::archName(sliceCpuType, sliceCpuSubType), 256);
|
||
needComma = true;
|
||
});
|
||
return strBuf;
|
||
}
|
||
|
||
bool FatFile::isFatFileWithSlice(Diagnostics& diag, uint64_t fileLen, const GradedArchs& archs, bool isOSBinary,
|
||
uint64_t& sliceOffset, uint64_t& sliceLen, bool& missingSlice) const
|
||
{
|
||
missingSlice = false;
|
||
if ( (this->magic != OSSwapBigToHostInt32(FAT_MAGIC)) && (this->magic != OSSwapBigToHostInt32(FAT_MAGIC_64)) )
|
||
return false;
|
||
|
||
__block int bestGrade = 0;
|
||
forEachSlice(diag, fileLen, ^(uint32_t sliceCpuType, uint32_t sliceCpuSubType, const void* sliceStart, uint64_t sliceSize, bool& stop) {
|
||
if (int sliceGrade = archs.grade(sliceCpuType, sliceCpuSubType, isOSBinary)) {
|
||
if ( sliceGrade > bestGrade ) {
|
||
sliceOffset = (char*)sliceStart - (char*)this;
|
||
sliceLen = sliceSize;
|
||
bestGrade = sliceGrade;
|
||
}
|
||
}
|
||
});
|
||
if ( diag.hasError() )
|
||
return false;
|
||
|
||
if ( bestGrade == 0 )
|
||
missingSlice = true;
|
||
|
||
return (bestGrade != 0);
|
||
}
|
||
|
||
|
||
//////////////////////////// GradedArchs ////////////////////////////////////////
|
||
|
||
|
||
#define GRADE_i386 CPU_TYPE_I386, CPU_SUBTYPE_I386_ALL, false
|
||
#define GRADE_x86_64 CPU_TYPE_X86_64, CPU_SUBTYPE_X86_64_ALL, false
|
||
#define GRADE_x86_64h CPU_TYPE_X86_64, CPU_SUBTYPE_X86_64_H, false
|
||
#define GRADE_armv7 CPU_TYPE_ARM, CPU_SUBTYPE_ARM_V7, false
|
||
#define GRADE_armv7s CPU_TYPE_ARM, CPU_SUBTYPE_ARM_V7S, false
|
||
#define GRADE_armv7k CPU_TYPE_ARM, CPU_SUBTYPE_ARM_V7K, false
|
||
#define GRADE_armv6m CPU_TYPE_ARM, CPU_SUBTYPE_ARM_V6M, false
|
||
#define GRADE_armv7m CPU_TYPE_ARM, CPU_SUBTYPE_ARM_V7M, false
|
||
#define GRADE_armv7em CPU_TYPE_ARM, CPU_SUBTYPE_ARM_V7EM, false
|
||
#define GRADE_armv8m CPU_TYPE_ARM, CPU_SUBTYPE_ARM_V8M, false
|
||
#define GRADE_arm64 CPU_TYPE_ARM64, CPU_SUBTYPE_ARM64_ALL, false
|
||
#define GRADE_arm64e CPU_TYPE_ARM64, CPU_SUBTYPE_ARM64E, false
|
||
#define GRADE_arm64e_pb CPU_TYPE_ARM64, CPU_SUBTYPE_ARM64E, true
|
||
#define GRADE_arm64_32 CPU_TYPE_ARM64_32, CPU_SUBTYPE_ARM64_32_V8, false
|
||
|
||
const GradedArchs GradedArchs::i386 = GradedArchs({GRADE_i386, 1});
|
||
const GradedArchs GradedArchs::x86_64 = GradedArchs({GRADE_x86_64, 1});
|
||
const GradedArchs GradedArchs::x86_64h = GradedArchs({GRADE_x86_64h, 2}, {GRADE_x86_64, 1});
|
||
const GradedArchs GradedArchs::arm64 = GradedArchs({GRADE_arm64, 1});
|
||
#if SUPPORT_ARCH_arm64e
|
||
const GradedArchs GradedArchs::arm64e_keysoff = GradedArchs({GRADE_arm64e, 2}, {GRADE_arm64, 1});
|
||
const GradedArchs GradedArchs::arm64e_keysoff_pb = GradedArchs({GRADE_arm64e_pb, 2}, {GRADE_arm64, 1});
|
||
const GradedArchs GradedArchs::arm64e = GradedArchs({GRADE_arm64e, 1});
|
||
const GradedArchs GradedArchs::arm64e_pb = GradedArchs({GRADE_arm64e_pb, 1});
|
||
#endif
|
||
const GradedArchs GradedArchs::armv7 = GradedArchs({GRADE_armv7, 1});
|
||
const GradedArchs GradedArchs::armv7s = GradedArchs({GRADE_armv7s, 2}, {GRADE_armv7, 1});
|
||
const GradedArchs GradedArchs::armv7k = GradedArchs({GRADE_armv7k, 1});
|
||
const GradedArchs GradedArchs::armv7m = GradedArchs({GRADE_armv7m, 1});
|
||
const GradedArchs GradedArchs::armv7em = GradedArchs({GRADE_armv7em, 1});
|
||
|
||
|
||
#if SUPPORT_ARCH_arm64_32
|
||
const GradedArchs GradedArchs::arm64_32 = GradedArchs({GRADE_arm64_32, 1});
|
||
#endif
|
||
#if BUILDING_LIBDYLD || BUILDING_UNIT_TESTS
|
||
const GradedArchs GradedArchs::launch_AS = GradedArchs({GRADE_arm64e, 3}, {GRADE_arm64, 2}, {GRADE_x86_64, 1});
|
||
const GradedArchs GradedArchs::launch_AS_Sim = GradedArchs({GRADE_arm64, 2}, {GRADE_x86_64, 1});
|
||
const GradedArchs GradedArchs::launch_Intel_h = GradedArchs({GRADE_x86_64h, 3}, {GRADE_x86_64, 2}, {GRADE_i386, 1});
|
||
const GradedArchs GradedArchs::launch_Intel = GradedArchs({GRADE_x86_64, 2}, {GRADE_i386, 1});
|
||
const GradedArchs GradedArchs::launch_Intel_Sim = GradedArchs({GRADE_x86_64, 2}, {GRADE_i386, 1});
|
||
#endif
|
||
|
||
int GradedArchs::grade(uint32_t cputype, uint32_t cpusubtype, bool isOSBinary) const
|
||
{
|
||
for (const auto& p : _orderedCpuTypes) {
|
||
if (p.type == 0) { break; }
|
||
if ( (p.type == cputype) && (p.subtype == (cpusubtype & ~CPU_SUBTYPE_MASK)) ) {
|
||
if ( p.osBinary ) {
|
||
if ( isOSBinary )
|
||
return p.grade;
|
||
}
|
||
else {
|
||
return p.grade;
|
||
}
|
||
}
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
const char* GradedArchs::name() const
|
||
{
|
||
return MachOFile::archName(_orderedCpuTypes[0].type, _orderedCpuTypes[0].subtype);
|
||
}
|
||
|
||
void GradedArchs::forEachArch(bool platformBinariesOnly, void (^handler)(const char*)) const
|
||
{
|
||
for (const auto& p : _orderedCpuTypes) {
|
||
if (p.type == 0)
|
||
break;
|
||
if ( p.osBinary && !platformBinariesOnly )
|
||
continue;
|
||
handler(MachOFile::archName(p.type, p.subtype));
|
||
}
|
||
}
|
||
|
||
bool GradedArchs::checksOSBinary() const
|
||
{
|
||
for (const auto& p : _orderedCpuTypes) {
|
||
if (p.type == 0) { return false; }
|
||
if ( p.osBinary ) { return true; }
|
||
}
|
||
__builtin_unreachable();
|
||
}
|
||
|
||
bool GradedArchs::supports64() const
|
||
{
|
||
return (_orderedCpuTypes.front().type & CPU_ARCH_ABI64) != 0;
|
||
}
|
||
|
||
#if __x86_64__
|
||
static bool isHaswell()
|
||
{
|
||
// FIXME: figure out a commpage way to check this
|
||
struct host_basic_info info;
|
||
mach_msg_type_number_t count = HOST_BASIC_INFO_COUNT;
|
||
mach_port_t hostPort = mach_host_self();
|
||
kern_return_t result = host_info(hostPort, HOST_BASIC_INFO, (host_info_t)&info, &count);
|
||
mach_port_deallocate(mach_task_self(), hostPort);
|
||
return (result == KERN_SUCCESS) && (info.cpu_subtype == CPU_SUBTYPE_X86_64_H);
|
||
}
|
||
#endif
|
||
|
||
const GradedArchs& GradedArchs::forCurrentOS(bool keysOff, bool osBinariesOnly)
|
||
{
|
||
#if __arm64e__
|
||
if ( osBinariesOnly )
|
||
return (keysOff ? arm64e_keysoff_pb : arm64e_pb);
|
||
else
|
||
return (keysOff ? arm64e_keysoff : arm64e);
|
||
#elif __ARM64_ARCH_8_32__
|
||
return arm64_32;
|
||
#elif __arm64__
|
||
return arm64;
|
||
#elif __ARM_ARCH_7K__
|
||
return armv7k;
|
||
#elif __ARM_ARCH_7S__
|
||
return armv7s;
|
||
#elif __ARM_ARCH_7A__
|
||
return armv7;
|
||
#elif __x86_64__
|
||
#if TARGET_OS_SIMULATOR
|
||
return x86_64;
|
||
#else
|
||
return isHaswell() ? x86_64h : x86_64;
|
||
#endif
|
||
#elif __i386__
|
||
return i386;
|
||
#else
|
||
#error unknown platform
|
||
#endif
|
||
}
|
||
|
||
#if BUILDING_LIBDYLD || BUILDING_UNIT_TESTS
|
||
const GradedArchs& GradedArchs::launchCurrentOS(const char* simArches)
|
||
{
|
||
#if TARGET_OS_SIMULATOR
|
||
// on Apple Silicon, there is both an arm64 and an x86_64 (under rosetta) simulators
|
||
// You cannot tell if you are running under rosetta, so CoreSimulator sets SIMULATOR_ARCHS
|
||
if ( strcmp(simArches, "arm64 x86_64") == 0 )
|
||
return launch_AS_Sim;
|
||
else
|
||
return x86_64;
|
||
#elif TARGET_OS_OSX
|
||
#if __arm64__
|
||
return launch_AS;
|
||
#else
|
||
return isHaswell() ? launch_Intel_h : launch_Intel;
|
||
#endif
|
||
#else
|
||
// all other platforms use same grading for executables as dylibs
|
||
return forCurrentOS(true, false);
|
||
#endif
|
||
}
|
||
#endif // BUILDING_LIBDYLD
|
||
|
||
const GradedArchs& GradedArchs::forName(const char* archName, bool keysOff)
|
||
{
|
||
if (strcmp(archName, "x86_64h") == 0 )
|
||
return x86_64h;
|
||
else if (strcmp(archName, "x86_64") == 0 )
|
||
return x86_64;
|
||
#if SUPPORT_ARCH_arm64e
|
||
else if (strcmp(archName, "arm64e") == 0 )
|
||
return keysOff ? arm64e_keysoff : arm64e;
|
||
#endif
|
||
else if (strcmp(archName, "arm64") == 0 )
|
||
return arm64;
|
||
else if (strcmp(archName, "armv7k") == 0 )
|
||
return armv7k;
|
||
else if (strcmp(archName, "armv7s") == 0 )
|
||
return armv7s;
|
||
else if (strcmp(archName, "armv7") == 0 )
|
||
return armv7;
|
||
else if (strcmp(archName, "armv7m") == 0 )
|
||
return armv7m;
|
||
else if (strcmp(archName, "armv7em") == 0 )
|
||
return armv7em;
|
||
#if SUPPORT_ARCH_arm64_32
|
||
else if (strcmp(archName, "arm64_32") == 0 )
|
||
return arm64_32;
|
||
#endif
|
||
else if (strcmp(archName, "i386") == 0 )
|
||
return i386;
|
||
assert(0 && "unknown arch name");
|
||
}
|
||
|
||
|
||
|
||
//////////////////////////// MachOFile ////////////////////////////////////////
|
||
|
||
|
||
const MachOFile::ArchInfo MachOFile::_s_archInfos[] = {
|
||
{ "x86_64", CPU_TYPE_X86_64, CPU_SUBTYPE_X86_64_ALL },
|
||
{ "x86_64h", CPU_TYPE_X86_64, CPU_SUBTYPE_X86_64_H },
|
||
{ "i386", CPU_TYPE_I386, CPU_SUBTYPE_I386_ALL },
|
||
{ "arm64", CPU_TYPE_ARM64, CPU_SUBTYPE_ARM64_ALL },
|
||
#if SUPPORT_ARCH_arm64e
|
||
{ "arm64e", CPU_TYPE_ARM64, CPU_SUBTYPE_ARM64E },
|
||
#endif
|
||
#if SUPPORT_ARCH_arm64_32
|
||
{ "arm64_32", CPU_TYPE_ARM64_32, CPU_SUBTYPE_ARM64_32_V8 },
|
||
#endif
|
||
{ "armv7k", CPU_TYPE_ARM, CPU_SUBTYPE_ARM_V7K },
|
||
{ "armv7s", CPU_TYPE_ARM, CPU_SUBTYPE_ARM_V7S },
|
||
{ "armv7", CPU_TYPE_ARM, CPU_SUBTYPE_ARM_V7 },
|
||
{ "armv6m", CPU_TYPE_ARM, CPU_SUBTYPE_ARM_V6M },
|
||
{ "armv7m", CPU_TYPE_ARM, CPU_SUBTYPE_ARM_V7M },
|
||
{ "armv7em", CPU_TYPE_ARM, CPU_SUBTYPE_ARM_V7EM },
|
||
{ "armv8m", CPU_TYPE_ARM, CPU_SUBTYPE_ARM_V8M },
|
||
};
|
||
|
||
const MachOFile::PlatformInfo MachOFile::_s_platformInfos[] = {
|
||
{ "macOS", Platform::macOS, LC_VERSION_MIN_MACOSX },
|
||
{ "iOS", Platform::iOS, LC_VERSION_MIN_IPHONEOS },
|
||
{ "tvOS", Platform::tvOS, LC_VERSION_MIN_TVOS },
|
||
{ "watchOS", Platform::watchOS, LC_VERSION_MIN_WATCHOS },
|
||
{ "bridgeOS", Platform::bridgeOS, LC_BUILD_VERSION },
|
||
{ "MacCatalyst", Platform::iOSMac, LC_BUILD_VERSION },
|
||
{ "iOS-sim", Platform::iOS_simulator, LC_BUILD_VERSION },
|
||
{ "tvOS-sim", Platform::tvOS_simulator, LC_BUILD_VERSION },
|
||
{ "watchOS-sim", Platform::watchOS_simulator, LC_BUILD_VERSION },
|
||
{ "driverKit", Platform::driverKit, LC_BUILD_VERSION },
|
||
};
|
||
|
||
|
||
|
||
bool MachOFile::is64() const
|
||
{
|
||
return (this->magic == MH_MAGIC_64);
|
||
}
|
||
|
||
size_t MachOFile::machHeaderSize() const
|
||
{
|
||
return is64() ? sizeof(mach_header_64) : sizeof(mach_header);
|
||
}
|
||
|
||
uint32_t MachOFile::maskedCpuSubtype() const
|
||
{
|
||
return (this->cpusubtype & ~CPU_SUBTYPE_MASK);
|
||
}
|
||
|
||
uint32_t MachOFile::pointerSize() const
|
||
{
|
||
if (this->magic == MH_MAGIC_64)
|
||
return 8;
|
||
else
|
||
return 4;
|
||
}
|
||
|
||
bool MachOFile::uses16KPages() const
|
||
{
|
||
switch (this->cputype) {
|
||
case CPU_TYPE_ARM64:
|
||
case CPU_TYPE_ARM64_32:
|
||
return true;
|
||
case CPU_TYPE_ARM:
|
||
// iOS is 16k aligned for armv7/armv7s and watchOS armv7k is 16k aligned
|
||
// HACK: Pretend armv7k kexts are 4k aligned
|
||
if ( this->isKextBundle() )
|
||
return false;
|
||
return this->cpusubtype == CPU_SUBTYPE_ARM_V7K;
|
||
default:
|
||
return false;
|
||
}
|
||
}
|
||
|
||
bool MachOFile::isArch(const char* aName) const
|
||
{
|
||
return (strcmp(aName, archName(this->cputype, this->cpusubtype)) == 0);
|
||
}
|
||
|
||
const char* MachOFile::archName(uint32_t cputype, uint32_t cpusubtype)
|
||
{
|
||
for (const ArchInfo& info : _s_archInfos) {
|
||
if ( (cputype == info.cputype) && ((cpusubtype & ~CPU_SUBTYPE_MASK) == info.cpusubtype) ) {
|
||
return info.name;
|
||
}
|
||
}
|
||
return "unknown";
|
||
}
|
||
|
||
bool MachOFile::cpuTypeFromArchName(const char* archName, cpu_type_t* cputype, cpu_subtype_t* cpusubtype)
|
||
{
|
||
for (const ArchInfo& info : _s_archInfos) {
|
||
if ( strcmp(archName, info.name) == 0 ) {
|
||
*cputype = info.cputype;
|
||
*cpusubtype = info.cpusubtype;
|
||
return true;
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
const char* MachOFile::archName() const
|
||
{
|
||
return archName(this->cputype, this->cpusubtype);
|
||
}
|
||
|
||
static void appendDigit(char*& s, unsigned& num, unsigned place, bool& startedPrinting)
|
||
{
|
||
if ( num >= place ) {
|
||
unsigned dig = (num/place);
|
||
*s++ = '0' + dig;
|
||
num -= (dig*place);
|
||
startedPrinting = true;
|
||
}
|
||
else if ( startedPrinting ) {
|
||
*s++ = '0';
|
||
}
|
||
}
|
||
|
||
static void appendNumber(char*& s, unsigned num)
|
||
{
|
||
assert(num < 99999);
|
||
bool startedPrinting = false;
|
||
appendDigit(s, num, 10000, startedPrinting);
|
||
appendDigit(s, num, 1000, startedPrinting);
|
||
appendDigit(s, num, 100, startedPrinting);
|
||
appendDigit(s, num, 10, startedPrinting);
|
||
appendDigit(s, num, 1, startedPrinting);
|
||
if ( !startedPrinting )
|
||
*s++ = '0';
|
||
}
|
||
|
||
void MachOFile::packedVersionToString(uint32_t packedVersion, char versionString[32])
|
||
{
|
||
// sprintf(versionString, "%d.%d.%d", (packedVersion >> 16), ((packedVersion >> 8) & 0xFF), (packedVersion & 0xFF));
|
||
char* s = versionString;
|
||
appendNumber(s, (packedVersion >> 16));
|
||
*s++ = '.';
|
||
appendNumber(s, (packedVersion >> 8) & 0xFF);
|
||
if ( (packedVersion & 0xFF) != 0 ) {
|
||
*s++ = '.';
|
||
appendNumber(s, (packedVersion & 0xFF));
|
||
}
|
||
*s++ = '\0';
|
||
}
|
||
|
||
bool MachOFile::builtForPlatform(Platform reqPlatform, bool onlyOnePlatform) const
|
||
{
|
||
__block bool foundRequestedPlatform = false;
|
||
__block bool foundOtherPlatform = false;
|
||
forEachSupportedPlatform(^(Platform platform, uint32_t minOS, uint32_t sdk) {
|
||
if ( platform == reqPlatform )
|
||
foundRequestedPlatform = true;
|
||
else
|
||
foundOtherPlatform = true;
|
||
});
|
||
// if checking that this binary is built for exactly one platform, fail if more
|
||
if ( foundOtherPlatform && onlyOnePlatform )
|
||
return false;
|
||
if ( foundRequestedPlatform )
|
||
return true;
|
||
|
||
// binary has no explict load command to mark platform
|
||
// could be an old macOS binary, look at arch
|
||
if ( !foundOtherPlatform && (reqPlatform == Platform::macOS) ) {
|
||
if ( this->cputype == CPU_TYPE_X86_64 )
|
||
return true;
|
||
if ( this->cputype == CPU_TYPE_I386 )
|
||
return true;
|
||
}
|
||
|
||
#if BUILDING_DYLDINFO
|
||
// Allow offline tools to analyze binaries dyld doesn't load, ie, those with platforms
|
||
if ( !foundOtherPlatform && (reqPlatform == Platform::unknown) )
|
||
return true;
|
||
#endif
|
||
|
||
return false;
|
||
}
|
||
|
||
bool MachOFile::loadableIntoProcess(Platform processPlatform, const char* path, bool internalInstall) const
|
||
{
|
||
if ( this->builtForPlatform(processPlatform) )
|
||
return true;
|
||
|
||
// Some host macOS dylibs can be loaded into simulator processes
|
||
if ( MachOFile::isSimulatorPlatform(processPlatform) && this->builtForPlatform(Platform::macOS)) {
|
||
static const char* const macOSHost[] = {
|
||
"/usr/lib/system/libsystem_kernel.dylib",
|
||
"/usr/lib/system/libsystem_platform.dylib",
|
||
"/usr/lib/system/libsystem_pthread.dylib",
|
||
"/usr/lib/system/libsystem_platform_debug.dylib",
|
||
"/usr/lib/system/libsystem_pthread_debug.dylib",
|
||
"/usr/lib/system/host/liblaunch_sim.dylib",
|
||
};
|
||
for (const char* libPath : macOSHost) {
|
||
if (strcmp(libPath, path) == 0)
|
||
return true;
|
||
}
|
||
}
|
||
|
||
// If this is being called on main executable where we expect a macOS program, Catalyst programs are also runnable
|
||
if ( (this->filetype == MH_EXECUTE) && (processPlatform == Platform::macOS) && this->builtForPlatform(Platform::iOSMac, true) )
|
||
return true;
|
||
#if (TARGET_OS_OSX && TARGET_CPU_ARM64)
|
||
if ( (this->filetype == MH_EXECUTE) && (processPlatform == Platform::macOS) && this->builtForPlatform(Platform::iOS, true) )
|
||
return true;
|
||
#endif
|
||
|
||
|
||
bool iOSonMac = (processPlatform == Platform::iOSMac);
|
||
#if (TARGET_OS_OSX && TARGET_CPU_ARM64)
|
||
// allow iOS binaries in iOSApp
|
||
if ( processPlatform == Platform::iOS ) {
|
||
// can load Catalyst binaries into iOS process
|
||
if ( this->builtForPlatform(Platform::iOSMac) )
|
||
return true;
|
||
iOSonMac = true;
|
||
}
|
||
#endif
|
||
// macOS dylibs can be loaded into iOSMac processes
|
||
if ( (iOSonMac) && this->builtForPlatform(Platform::macOS, true) )
|
||
return true;
|
||
|
||
|
||
return false;
|
||
}
|
||
|
||
bool MachOFile::isZippered() const
|
||
{
|
||
__block bool macOS = false;
|
||
__block bool iOSMac = false;
|
||
forEachSupportedPlatform(^(Platform platform, uint32_t minOS, uint32_t sdk) {
|
||
if ( platform == Platform::macOS )
|
||
macOS = true;
|
||
else if ( platform == Platform::iOSMac )
|
||
iOSMac = true;
|
||
});
|
||
return macOS && iOSMac;
|
||
}
|
||
|
||
bool MachOFile::inDyldCache() const {
|
||
return (this->flags & MH_DYLIB_IN_CACHE);
|
||
}
|
||
|
||
Platform MachOFile::currentPlatform()
|
||
{
|
||
#if TARGET_OS_SIMULATOR
|
||
#if TARGET_OS_WATCH
|
||
return Platform::watchOS_simulator;
|
||
#elif TARGET_OS_TV
|
||
return Platform::tvOS_simulator;
|
||
#else
|
||
return Platform::iOS_simulator;
|
||
#endif
|
||
#elif TARGET_OS_BRIDGE
|
||
return Platform::bridgeOS;
|
||
#elif TARGET_OS_WATCH
|
||
return Platform::watchOS;
|
||
#elif TARGET_OS_TV
|
||
return Platform::tvOS;
|
||
#elif TARGET_OS_IOS
|
||
return Platform::iOS;
|
||
#elif TARGET_OS_OSX
|
||
return Platform::macOS;
|
||
#elif TARGET_OS_DRIVERKIT
|
||
return Platform::driverKit;
|
||
#else
|
||
#error unknown platform
|
||
#endif
|
||
}
|
||
|
||
Platform MachOFile::basePlatform(dyld3::Platform reqPlatform) {
|
||
switch(reqPlatform) {
|
||
case Platform::unknown: return Platform::unknown;
|
||
case Platform::macOS: return Platform::macOS;
|
||
case Platform::iOS: return Platform::iOS;
|
||
case Platform::tvOS: return Platform::tvOS;
|
||
case Platform::watchOS: return Platform::watchOS;
|
||
case Platform::bridgeOS: return Platform::bridgeOS;
|
||
case Platform::iOSMac: return Platform::iOS;
|
||
case Platform::iOS_simulator: return Platform::iOS;
|
||
case Platform::tvOS_simulator: return Platform::tvOS;
|
||
case Platform::watchOS_simulator: return Platform::watchOS;
|
||
case Platform::driverKit: return Platform::driverKit;
|
||
default: return Platform::unknown;
|
||
}
|
||
}
|
||
|
||
|
||
const char* MachOFile::currentArchName()
|
||
{
|
||
#if __ARM_ARCH_7K__
|
||
return "armv7k";
|
||
#elif __ARM_ARCH_7A__
|
||
return "armv7";
|
||
#elif __ARM_ARCH_7S__
|
||
return "armv7s";
|
||
#elif __arm64e__
|
||
return "arm64e";
|
||
#elif __arm64__
|
||
#if __LP64__
|
||
return "arm64";
|
||
#else
|
||
return "arm64_32";
|
||
#endif
|
||
#elif __x86_64__
|
||
return isHaswell() ? "x86_64h" : "x86_64";
|
||
#elif __i386__
|
||
return "i386";
|
||
#else
|
||
#error unknown arch
|
||
#endif
|
||
}
|
||
|
||
bool MachOFile::isSimulatorPlatform(Platform platform, Platform* basePlatform)
|
||
{
|
||
switch ( platform ) {
|
||
case Platform::iOS_simulator:
|
||
if ( basePlatform )
|
||
*basePlatform = Platform::iOS;
|
||
return true;
|
||
case Platform::watchOS_simulator:
|
||
if ( basePlatform )
|
||
*basePlatform = Platform::watchOS;
|
||
return true;
|
||
case Platform::tvOS_simulator:
|
||
if ( basePlatform )
|
||
*basePlatform = Platform::tvOS;
|
||
return true;
|
||
default:
|
||
return false;
|
||
}
|
||
}
|
||
|
||
bool MachOFile::isBuiltForSimulator() const
|
||
{
|
||
__block bool result = false;
|
||
this->forEachSupportedPlatform(^(Platform platform, uint32_t minOS, uint32_t sdk) {
|
||
switch ( platform ) {
|
||
case Platform::iOS_simulator:
|
||
case Platform::watchOS_simulator:
|
||
case Platform::tvOS_simulator:
|
||
result = true;
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
});
|
||
return result;
|
||
}
|
||
|
||
bool MachOFile::isDyld() const
|
||
{
|
||
return (this->filetype == MH_DYLINKER);
|
||
}
|
||
|
||
bool MachOFile::isDyldManaged() const {
|
||
switch ( this->filetype ) {
|
||
case MH_BUNDLE:
|
||
case MH_EXECUTE:
|
||
case MH_DYLIB:
|
||
return true;
|
||
default:
|
||
break;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
bool MachOFile::isDylib() const
|
||
{
|
||
return (this->filetype == MH_DYLIB);
|
||
}
|
||
|
||
bool MachOFile::isBundle() const
|
||
{
|
||
return (this->filetype == MH_BUNDLE);
|
||
}
|
||
|
||
bool MachOFile::isMainExecutable() const
|
||
{
|
||
return (this->filetype == MH_EXECUTE);
|
||
}
|
||
|
||
bool MachOFile::isDynamicExecutable() const
|
||
{
|
||
if ( this->filetype != MH_EXECUTE )
|
||
return false;
|
||
|
||
// static executables do not have dyld load command
|
||
return hasLoadCommand(LC_LOAD_DYLINKER);
|
||
}
|
||
|
||
bool MachOFile::isStaticExecutable() const
|
||
{
|
||
if ( this->filetype != MH_EXECUTE )
|
||
return false;
|
||
|
||
// static executables do not have dyld load command
|
||
return !hasLoadCommand(LC_LOAD_DYLINKER);
|
||
}
|
||
|
||
bool MachOFile::isKextBundle() const
|
||
{
|
||
return (this->filetype == MH_KEXT_BUNDLE);
|
||
}
|
||
|
||
bool MachOFile::isFileSet() const
|
||
{
|
||
return (this->filetype == MH_FILESET);
|
||
}
|
||
|
||
bool MachOFile::isPIE() const
|
||
{
|
||
return (this->flags & MH_PIE);
|
||
}
|
||
|
||
bool MachOFile::isPreload() const
|
||
{
|
||
return (this->filetype == MH_PRELOAD);
|
||
}
|
||
|
||
const char* MachOFile::platformName(Platform reqPlatform)
|
||
{
|
||
for (const PlatformInfo& info : _s_platformInfos) {
|
||
if ( info.platform == reqPlatform )
|
||
return info.name;
|
||
}
|
||
return "unknown";
|
||
}
|
||
|
||
void MachOFile::forEachSupportedPlatform(void (^handler)(Platform platform, uint32_t minOS, uint32_t sdk)) const
|
||
{
|
||
Diagnostics diag;
|
||
__block bool foundPlatform = false;
|
||
forEachLoadCommand(diag, ^(const load_command* cmd, bool& stop) {
|
||
const build_version_command* buildCmd = (build_version_command *)cmd;
|
||
const version_min_command* versCmd = (version_min_command*)cmd;
|
||
uint32_t sdk;
|
||
switch ( cmd->cmd ) {
|
||
case LC_BUILD_VERSION:
|
||
handler((Platform)(buildCmd->platform), buildCmd->minos, buildCmd->sdk);
|
||
foundPlatform = true;
|
||
break;
|
||
case LC_VERSION_MIN_MACOSX:
|
||
sdk = versCmd->sdk;
|
||
// The original LC_VERSION_MIN_MACOSX did not have an sdk field, assume sdk is same as minOS for those old binaries
|
||
if ( sdk == 0 )
|
||
sdk = versCmd->version;
|
||
handler(Platform::macOS, versCmd->version, sdk);
|
||
foundPlatform = true;
|
||
break;
|
||
case LC_VERSION_MIN_IPHONEOS:
|
||
if ( (this->cputype == CPU_TYPE_X86_64) || (this->cputype == CPU_TYPE_I386) )
|
||
handler(Platform::iOS_simulator, versCmd->version, versCmd->sdk); // old sim binary
|
||
else
|
||
handler(Platform::iOS, versCmd->version, versCmd->sdk);
|
||
foundPlatform = true;
|
||
break;
|
||
case LC_VERSION_MIN_TVOS:
|
||
if ( this->cputype == CPU_TYPE_X86_64 )
|
||
handler(Platform::tvOS_simulator, versCmd->version, versCmd->sdk); // old sim binary
|
||
else
|
||
handler(Platform::tvOS, versCmd->version, versCmd->sdk);
|
||
foundPlatform = true;
|
||
break;
|
||
case LC_VERSION_MIN_WATCHOS:
|
||
if ( (this->cputype == CPU_TYPE_X86_64) || (this->cputype == CPU_TYPE_I386) )
|
||
handler(Platform::watchOS_simulator, versCmd->version, versCmd->sdk); // old sim binary
|
||
else
|
||
handler(Platform::watchOS, versCmd->version, versCmd->sdk);
|
||
foundPlatform = true;
|
||
break;
|
||
}
|
||
});
|
||
if ( !foundPlatform ) {
|
||
// old binary with no explicit platform
|
||
#if (BUILDING_DYLD || BUILDING_CLOSURE_UTIL) && TARGET_OS_OSX
|
||
if ( this->cputype == CPU_TYPE_X86_64 )
|
||
handler(Platform::macOS, 0x000A0500, 0x000A0500); // guess it is a macOS 10.5 binary
|
||
// <rdar://problem/75343399>
|
||
// The Go linker emits non-standard binaries without a platform and we have to live with it.
|
||
if ( this->cputype == CPU_TYPE_ARM64 )
|
||
handler(Platform::macOS, 0x000B0000, 0x000B0000); // guess it is a macOS 11.0 binary
|
||
#endif
|
||
}
|
||
diag.assertNoError(); // any malformations in the file should have been caught by earlier validate() call
|
||
}
|
||
|
||
void MachOFile::forEachSupportedBuildTool(void (^handler)(Platform platform, uint32_t tool, uint32_t version)) const
|
||
{
|
||
Diagnostics diag;
|
||
forEachLoadCommand(diag, ^(const load_command* cmd, bool& stop) {
|
||
switch ( cmd->cmd ) {
|
||
case LC_BUILD_VERSION: {
|
||
const build_version_command* buildCmd = (build_version_command *)cmd;
|
||
for ( uint32_t i = 0; i != buildCmd->ntools; ++i ) {
|
||
uint32_t offset = sizeof(build_version_command) + (i * sizeof(build_tool_version));
|
||
if ( offset >= cmd->cmdsize )
|
||
break;
|
||
|
||
const build_tool_version* firstTool = (const build_tool_version*)(&buildCmd[1]);
|
||
handler((Platform)(buildCmd->platform), firstTool[i].tool, firstTool[i].version);
|
||
}
|
||
}
|
||
}
|
||
});
|
||
diag.assertNoError(); // any malformations in the file should have been caught by earlier validate() call
|
||
}
|
||
|
||
|
||
bool MachOFile::isMachO(Diagnostics& diag, uint64_t fileSize) const
|
||
{
|
||
if ( fileSize < sizeof(mach_header) ) {
|
||
diag.error("MachO header exceeds file length");
|
||
return false;
|
||
}
|
||
|
||
if ( !hasMachOMagic() ) {
|
||
// old PPC slices are not currently valid "mach-o" but should not cause an error
|
||
if ( !hasMachOBigEndianMagic() )
|
||
diag.error("file does not start with MH_MAGIC[_64]");
|
||
return false;
|
||
}
|
||
if ( this->sizeofcmds + machHeaderSize() > fileSize ) {
|
||
diag.error("load commands exceed length of first segment");
|
||
return false;
|
||
}
|
||
forEachLoadCommand(diag, ^(const load_command* cmd, bool& stop) { });
|
||
return diag.noError();
|
||
}
|
||
|
||
|
||
const MachOFile* MachOFile::isMachO(const void* content)
|
||
{
|
||
const MachOFile* mf = (MachOFile*)content;
|
||
if ( mf->hasMachOMagic() )
|
||
return mf;
|
||
return nullptr;
|
||
}
|
||
|
||
bool MachOFile::hasMachOMagic() const
|
||
{
|
||
return ( (this->magic == MH_MAGIC) || (this->magic == MH_MAGIC_64) );
|
||
}
|
||
|
||
bool MachOFile::hasMachOBigEndianMagic() const
|
||
{
|
||
return ( (this->magic == MH_CIGAM) || (this->magic == MH_CIGAM_64) );
|
||
}
|
||
|
||
|
||
void MachOFile::forEachLoadCommand(Diagnostics& diag, void (^callback)(const load_command* cmd, bool& stop)) const
|
||
{
|
||
bool stop = false;
|
||
const load_command* startCmds = nullptr;
|
||
if ( this->magic == MH_MAGIC_64 )
|
||
startCmds = (load_command*)((char *)this + sizeof(mach_header_64));
|
||
else if ( this->magic == MH_MAGIC )
|
||
startCmds = (load_command*)((char *)this + sizeof(mach_header));
|
||
else if ( hasMachOBigEndianMagic() )
|
||
return; // can't process big endian mach-o
|
||
else {
|
||
const uint32_t* h = (uint32_t*)this;
|
||
diag.error("file does not start with MH_MAGIC[_64]: 0x%08X 0x%08X", h[0], h [1]);
|
||
return; // not a mach-o file
|
||
}
|
||
if ( this->filetype > 12 ) {
|
||
diag.error("unknown mach-o filetype (%u)", this->filetype);
|
||
return;
|
||
}
|
||
const load_command* const cmdsEnd = (load_command*)((char*)startCmds + this->sizeofcmds);
|
||
const load_command* const cmdsLast = (load_command*)((char*)startCmds + this->sizeofcmds - sizeof(load_command));
|
||
const load_command* cmd = startCmds;
|
||
for (uint32_t i = 0; i < this->ncmds; ++i) {
|
||
if ( cmd > cmdsLast ) {
|
||
diag.error("malformed load command #%u of %u at %p with mh=%p, extends past sizeofcmds", i, this->ncmds, cmd, this);
|
||
return;
|
||
}
|
||
uint32_t cmdsize = cmd->cmdsize;
|
||
if ( cmdsize < 8 ) {
|
||
diag.error("malformed load command #%u of %u at %p with mh=%p, size (0x%X) too small", i, this->ncmds, cmd, this, cmd->cmdsize);
|
||
return;
|
||
}
|
||
if ( (cmdsize % 4) != 0 ) {
|
||
// FIXME: on 64-bit mach-o, should be 8-byte aligned, (might reveal bin-compat issues)
|
||
diag.error("malformed load command #%u of %u at %p with mh=%p, size (0x%X) not multiple of 4", i, this->ncmds, cmd, this, cmd->cmdsize);
|
||
return;
|
||
}
|
||
const load_command* nextCmd = (load_command*)((char *)cmd + cmdsize);
|
||
if ( (nextCmd > cmdsEnd) || (nextCmd < startCmds) ) {
|
||
diag.error("malformed load command #%u of %u at %p with mh=%p, size (0x%X) is too large, load commands end at %p", i, this->ncmds, cmd, this, cmd->cmdsize, cmdsEnd);
|
||
return;
|
||
}
|
||
callback(cmd, stop);
|
||
if ( stop )
|
||
return;
|
||
cmd = nextCmd;
|
||
}
|
||
}
|
||
|
||
void MachOFile::removeLoadCommand(Diagnostics& diag, void (^callback)(const load_command* cmd, bool& remove, bool& stop))
|
||
{
|
||
bool stop = false;
|
||
const load_command* startCmds = nullptr;
|
||
if ( this->magic == MH_MAGIC_64 )
|
||
startCmds = (load_command*)((char *)this + sizeof(mach_header_64));
|
||
else if ( this->magic == MH_MAGIC )
|
||
startCmds = (load_command*)((char *)this + sizeof(mach_header));
|
||
else if ( hasMachOBigEndianMagic() )
|
||
return; // can't process big endian mach-o
|
||
else {
|
||
const uint32_t* h = (uint32_t*)this;
|
||
diag.error("file does not start with MH_MAGIC[_64]: 0x%08X 0x%08X", h[0], h [1]);
|
||
return; // not a mach-o file
|
||
}
|
||
const load_command* const cmdsEnd = (load_command*)((char*)startCmds + this->sizeofcmds);
|
||
auto cmd = (load_command*)startCmds;
|
||
const uint32_t origNcmds = this->ncmds;
|
||
unsigned bytesRemaining = this->sizeofcmds;
|
||
for (uint32_t i = 0; i < origNcmds; ++i) {
|
||
bool remove = false;
|
||
auto nextCmd = (load_command*)((char *)cmd + cmd->cmdsize);
|
||
if ( cmd->cmdsize < 8 ) {
|
||
diag.error("malformed load command #%d of %d at %p with mh=%p, size (0x%X) too small", i, this->ncmds, cmd, this, cmd->cmdsize);
|
||
return;
|
||
}
|
||
if ( (nextCmd > cmdsEnd) || (nextCmd < startCmds) ) {
|
||
diag.error("malformed load command #%d of %d at %p with mh=%p, size (0x%X) is too large, load commands end at %p", i, this->ncmds, cmd, this, cmd->cmdsize, cmdsEnd);
|
||
return;
|
||
}
|
||
callback(cmd, remove, stop);
|
||
if ( remove ) {
|
||
this->sizeofcmds -= cmd->cmdsize;
|
||
::memmove((void*)cmd, (void*)nextCmd, bytesRemaining);
|
||
this->ncmds--;
|
||
} else {
|
||
bytesRemaining -= cmd->cmdsize;
|
||
cmd = nextCmd;
|
||
}
|
||
if ( stop )
|
||
break;
|
||
}
|
||
if ( cmd )
|
||
::bzero(cmd, bytesRemaining);
|
||
}
|
||
|
||
|
||
bool MachOFile::hasObjC() const
|
||
{
|
||
__block bool result = false;
|
||
forEachSection(^(const SectionInfo& info, bool malformedSectionRange, bool& stop) {
|
||
if ( (strcmp(info.sectName, "__objc_imageinfo") == 0) && (strncmp(info.segInfo.segName, "__DATA", 6) == 0) ) {
|
||
result = true;
|
||
stop = true;
|
||
}
|
||
if ( (this->cputype == CPU_TYPE_I386) && (strcmp(info.sectName, "__image_info") == 0) && (strcmp(info.segInfo.segName, "__OBJC") == 0) ) {
|
||
result = true;
|
||
stop = true;
|
||
}
|
||
});
|
||
return result;
|
||
}
|
||
|
||
bool MachOFile::hasSection(const char* segName, const char* sectName) const
|
||
{
|
||
__block bool result = false;
|
||
forEachSection(^(const SectionInfo& info, bool malformedSectionRange, bool& stop) {
|
||
if ( (strcmp(info.segInfo.segName, segName) == 0) && (strcmp(info.sectName, sectName) == 0) ) {
|
||
result = true;
|
||
stop = true;
|
||
}
|
||
});
|
||
return result;
|
||
}
|
||
|
||
const char* MachOFile::installName() const
|
||
{
|
||
const char* name;
|
||
uint32_t compatVersion;
|
||
uint32_t currentVersion;
|
||
if ( getDylibInstallName(&name, &compatVersion, ¤tVersion) )
|
||
return name;
|
||
return nullptr;
|
||
}
|
||
|
||
bool MachOFile::getDylibInstallName(const char** installName, uint32_t* compatVersion, uint32_t* currentVersion) const
|
||
{
|
||
Diagnostics diag;
|
||
__block bool found = false;
|
||
forEachLoadCommand(diag, ^(const load_command* cmd, bool& stop) {
|
||
if ( (cmd->cmd == LC_ID_DYLIB) || (cmd->cmd == LC_ID_DYLINKER) ) {
|
||
const dylib_command* dylibCmd = (dylib_command*)cmd;
|
||
*compatVersion = dylibCmd->dylib.compatibility_version;
|
||
*currentVersion = dylibCmd->dylib.current_version;
|
||
*installName = (char*)dylibCmd + dylibCmd->dylib.name.offset;
|
||
found = true;
|
||
stop = true;
|
||
}
|
||
});
|
||
diag.assertNoError(); // any malformations in the file should have been caught by earlier validate() call
|
||
return found;
|
||
}
|
||
|
||
bool MachOFile::getUuid(uuid_t uuid) const
|
||
{
|
||
Diagnostics diag;
|
||
__block bool found = false;
|
||
forEachLoadCommand(diag, ^(const load_command* cmd, bool& stop) {
|
||
if ( cmd->cmd == LC_UUID ) {
|
||
const uuid_command* uc = (const uuid_command*)cmd;
|
||
memcpy(uuid, uc->uuid, sizeof(uuid_t));
|
||
found = true;
|
||
stop = true;
|
||
}
|
||
});
|
||
diag.assertNoError(); // any malformations in the file should have been caught by earlier validate() call
|
||
if ( !found )
|
||
bzero(uuid, sizeof(uuid_t));
|
||
return found;
|
||
}
|
||
|
||
UUID MachOFile::uuid() const {
|
||
Diagnostics diag;
|
||
__block UUID result;
|
||
forEachLoadCommand(diag, ^(const load_command* cmd, bool& stop) {
|
||
if ( cmd->cmd == LC_UUID ) {
|
||
const uuid_command* uc = (const uuid_command*)cmd;
|
||
result = UUID(uc->uuid);
|
||
stop = true;
|
||
}
|
||
});
|
||
diag.assertNoError();
|
||
return result;
|
||
}
|
||
|
||
void MachOFile::forEachDependentDylib(void (^callback)(const char* loadPath, bool isWeak, bool isReExport, bool isUpward, uint32_t compatVersion, uint32_t curVersion, bool& stop)) const
|
||
{
|
||
Diagnostics diag;
|
||
__block unsigned count = 0;
|
||
__block bool stopped = false;
|
||
forEachLoadCommand(diag, ^(const load_command* cmd, bool& stop) {
|
||
switch ( cmd->cmd ) {
|
||
case LC_LOAD_DYLIB:
|
||
case LC_LOAD_WEAK_DYLIB:
|
||
case LC_REEXPORT_DYLIB:
|
||
case LC_LOAD_UPWARD_DYLIB: {
|
||
const dylib_command* dylibCmd = (dylib_command*)cmd;
|
||
const char* loadPath = (char*)dylibCmd + dylibCmd->dylib.name.offset;
|
||
callback(loadPath, (cmd->cmd == LC_LOAD_WEAK_DYLIB), (cmd->cmd == LC_REEXPORT_DYLIB), (cmd->cmd == LC_LOAD_UPWARD_DYLIB),
|
||
dylibCmd->dylib.compatibility_version, dylibCmd->dylib.current_version, stop);
|
||
++count;
|
||
if ( stop )
|
||
stopped = true;
|
||
}
|
||
break;
|
||
}
|
||
});
|
||
#if !BUILDING_SHARED_CACHE_UTIL && !BUILDING_DYLDINFO && !BUILDING_UNIT_TESTS
|
||
// everything must link with something
|
||
if ( (count == 0) && !stopped ) {
|
||
// The dylibs that make up libSystem can link with nothing
|
||
// except for dylibs in libSystem.dylib which are ok to link with nothing (they are on bottom)
|
||
#if TARGET_OS_EXCLAVEKIT
|
||
if ( !this->isDylib() || (strncmp(this->installName(), "/System/ExclaveKit/usr/lib/system/", 34) != 0) )
|
||
callback("/System/ExclaveKit/usr/lib/libSystem.dylib", false, false, false, 0x00010000, 0x00010000, stopped);
|
||
#else
|
||
if ( this->builtForPlatform(Platform::driverKit, true) ) {
|
||
if ( !this->isDylib() || (strncmp(this->installName(), "/System/DriverKit/usr/lib/system/", 33) != 0) )
|
||
callback("/System/DriverKit/usr/lib/libSystem.B.dylib", false, false, false, 0x00010000, 0x00010000, stopped);
|
||
}
|
||
else {
|
||
if ( !this->isDylib() || (strncmp(this->installName(), "/usr/lib/system/", 16) != 0) )
|
||
callback("/usr/lib/libSystem.B.dylib", false, false, false, 0x00010000, 0x00010000, stopped);
|
||
}
|
||
#endif // TARGET_OS_EXCLAVEKIT
|
||
}
|
||
#endif // !BUILDING_SHARED_CACHE_UTIL && !BUILDING_DYLDINFO && !BUILDING_UNIT_TESTS
|
||
diag.assertNoError(); // any malformations in the file should have been caught by earlier validate() call
|
||
}
|
||
|
||
void MachOFile::forDyldEnv(void (^callback)(const char* envVar, bool& stop)) const
|
||
{
|
||
Diagnostics diag;
|
||
forEachLoadCommand(diag, ^(const load_command* cmd, bool& stop) {
|
||
if ( cmd->cmd == LC_DYLD_ENVIRONMENT ) {
|
||
const dylinker_command* envCmd = (dylinker_command*)cmd;
|
||
const char* keyEqualsValue = (char*)envCmd + envCmd->name.offset;
|
||
// only process variables that start with DYLD_ and end in _PATH
|
||
if ( (strncmp(keyEqualsValue, "DYLD_", 5) == 0) ) {
|
||
const char* equals = strchr(keyEqualsValue, '=');
|
||
if ( equals != NULL ) {
|
||
if ( strncmp(&equals[-5], "_PATH", 5) == 0 ) {
|
||
callback(keyEqualsValue, stop);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
});
|
||
diag.assertNoError(); // any malformations in the file should have been caught by earlier validate() call
|
||
}
|
||
|
||
bool MachOFile::enforceCompatVersion() const
|
||
{
|
||
__block bool result = true;
|
||
forEachSupportedPlatform(^(Platform platform, uint32_t minOS, uint32_t sdk) {
|
||
switch ( platform ) {
|
||
case Platform::macOS:
|
||
if ( minOS >= 0x000A0E00 ) // macOS 10.14
|
||
result = false;
|
||
break;
|
||
case Platform::iOS:
|
||
case Platform::tvOS:
|
||
case Platform::iOS_simulator:
|
||
case Platform::tvOS_simulator:
|
||
if ( minOS >= 0x000C0000 ) // iOS 12.0
|
||
result = false;
|
||
break;
|
||
case Platform::watchOS:
|
||
case Platform::watchOS_simulator:
|
||
if ( minOS >= 0x00050000 ) // watchOS 5.0
|
||
result = false;
|
||
break;
|
||
case Platform::bridgeOS:
|
||
if ( minOS >= 0x00030000 ) // bridgeOS 3.0
|
||
result = false;
|
||
break;
|
||
case Platform::driverKit:
|
||
case Platform::iOSMac:
|
||
result = false;
|
||
break;
|
||
case Platform::unknown:
|
||
break;
|
||
}
|
||
});
|
||
return result;
|
||
}
|
||
|
||
const thread_command* MachOFile::unixThreadLoadCommand() const {
|
||
Diagnostics diag;
|
||
__block const thread_command* command = nullptr;
|
||
forEachLoadCommand(diag, ^(const load_command* cmd, bool& stop) {
|
||
if ( cmd->cmd == LC_UNIXTHREAD ) {
|
||
command = (const thread_command*)cmd;
|
||
stop = true;
|
||
}
|
||
});
|
||
return command;
|
||
}
|
||
|
||
const linkedit_data_command* MachOFile::chainedFixupsCmd() const {
|
||
Diagnostics diag;
|
||
__block const linkedit_data_command* command = nullptr;
|
||
forEachLoadCommand(diag, ^(const load_command* cmd, bool& stop) {
|
||
if ( cmd->cmd == LC_DYLD_CHAINED_FIXUPS ) {
|
||
command = (const linkedit_data_command*)cmd;
|
||
stop = true;
|
||
}
|
||
});
|
||
return command;
|
||
}
|
||
|
||
|
||
uint32_t MachOFile::entryAddrRegisterIndexForThreadCmd() const
|
||
{
|
||
switch ( this->cputype ) {
|
||
case CPU_TYPE_I386:
|
||
return 10; // i386_thread_state_t.eip
|
||
case CPU_TYPE_X86_64:
|
||
return 16; // x86_thread_state64_t.rip
|
||
case CPU_TYPE_ARM:
|
||
return 15; // arm_thread_state_t.pc
|
||
case CPU_TYPE_ARM64:
|
||
case CPU_TYPE_ARM64_32:
|
||
return 32; // arm_thread_state64_t.__pc
|
||
}
|
||
return ~0U;
|
||
}
|
||
|
||
bool MachOFile::use64BitEntryRegs() const
|
||
{
|
||
return is64() || isArch("arm64_32");
|
||
}
|
||
|
||
uint64_t MachOFile::entryAddrFromThreadCmd(const thread_command* cmd) const
|
||
{
|
||
assert(cmd->cmd == LC_UNIXTHREAD);
|
||
const uint32_t* regs32 = (uint32_t*)(((char*)cmd) + 16);
|
||
const uint64_t* regs64 = (uint64_t*)(((char*)cmd) + 16);
|
||
|
||
uint32_t index = entryAddrRegisterIndexForThreadCmd();
|
||
if (index == ~0U)
|
||
return 0;
|
||
|
||
return use64BitEntryRegs() ? regs64[index] : regs32[index];
|
||
}
|
||
|
||
|
||
bool MachOFile::getEntry(uint64_t& offset, bool& usesCRT) const
|
||
{
|
||
Diagnostics diag;
|
||
offset = 0;
|
||
forEachLoadCommand(diag, ^(const load_command* cmd, bool& stop) {
|
||
if ( cmd->cmd == LC_MAIN ) {
|
||
entry_point_command* mainCmd = (entry_point_command*)cmd;
|
||
usesCRT = false;
|
||
offset = mainCmd->entryoff;
|
||
stop = true;
|
||
}
|
||
else if ( cmd->cmd == LC_UNIXTHREAD ) {
|
||
stop = true;
|
||
usesCRT = true;
|
||
uint64_t startAddress = entryAddrFromThreadCmd((thread_command*)cmd);
|
||
offset = startAddress - preferredLoadAddress();
|
||
}
|
||
});
|
||
return (offset != 0);
|
||
}
|
||
|
||
|
||
void MachOFile::forEachSegment(void (^callback)(const SegmentInfo& info, bool& stop)) const
|
||
{
|
||
Diagnostics diag;
|
||
const bool intel32 = (this->cputype == CPU_TYPE_I386);
|
||
__block uint32_t segIndex = 0;
|
||
forEachLoadCommand(diag, ^(const load_command* cmd, bool& stop) {
|
||
if ( cmd->cmd == LC_SEGMENT_64 ) {
|
||
const segment_command_64* segCmd = (segment_command_64*)cmd;
|
||
uint64_t sizeOfSections = segCmd->vmsize;
|
||
uint8_t p2align = 0;
|
||
const section_64* const sectionsStart = (section_64*)((char*)segCmd + sizeof(struct segment_command_64));
|
||
const section_64* const sectionsEnd = §ionsStart[segCmd->nsects];
|
||
for (const section_64* sect=sectionsStart; sect < sectionsEnd; ++sect) {
|
||
sizeOfSections = sect->addr + sect->size - segCmd->vmaddr;
|
||
if ( sect->align > p2align )
|
||
p2align = sect->align;
|
||
}
|
||
SegmentInfo info;
|
||
info.fileOffset = segCmd->fileoff;
|
||
info.fileSize = segCmd->filesize;
|
||
info.vmAddr = segCmd->vmaddr;
|
||
info.vmSize = segCmd->vmsize;
|
||
info.sizeOfSections = sizeOfSections;
|
||
info.segName = segCmd->segname;
|
||
info.loadCommandOffset = (uint32_t)((uint8_t*)segCmd - (uint8_t*)this);
|
||
info.protections = segCmd->initprot;
|
||
info.textRelocs = false;
|
||
info.readOnlyData = ((segCmd->flags & SG_READ_ONLY) != 0);
|
||
info.isProtected = (segCmd->flags & SG_PROTECTED_VERSION_1) ? 1 : 0;
|
||
info.hasZeroFill = (segCmd->initprot == 3) && (segCmd->filesize < segCmd->vmsize);
|
||
info.p2align = p2align;
|
||
info.segIndex = segIndex;
|
||
callback(info, stop);
|
||
++segIndex;
|
||
}
|
||
else if ( cmd->cmd == LC_SEGMENT ) {
|
||
const segment_command* segCmd = (segment_command*)cmd;
|
||
uint64_t sizeOfSections = segCmd->vmsize;
|
||
uint8_t p2align = 0;
|
||
bool hasTextRelocs = false;
|
||
const section* const sectionsStart = (section*)((char*)segCmd + sizeof(struct segment_command));
|
||
const section* const sectionsEnd = §ionsStart[segCmd->nsects];
|
||
for (const section* sect=sectionsStart; sect < sectionsEnd; ++sect) {
|
||
sizeOfSections = sect->addr + sect->size - segCmd->vmaddr;
|
||
if ( sect->align > p2align )
|
||
p2align = sect->align;
|
||
if ( sect->flags & (S_ATTR_EXT_RELOC|S_ATTR_LOC_RELOC) )
|
||
hasTextRelocs = true;
|
||
}
|
||
SegmentInfo info;
|
||
info.fileOffset = segCmd->fileoff;
|
||
info.fileSize = segCmd->filesize;
|
||
info.vmAddr = segCmd->vmaddr;
|
||
info.vmSize = segCmd->vmsize;
|
||
info.sizeOfSections = sizeOfSections;
|
||
info.segName = segCmd->segname;
|
||
info.loadCommandOffset = (uint32_t)((uint8_t*)segCmd - (uint8_t*)this);
|
||
info.protections = segCmd->initprot;
|
||
info.textRelocs = intel32 && !info.writable() && hasTextRelocs;
|
||
info.readOnlyData = ((segCmd->flags & SG_READ_ONLY) != 0);
|
||
info.isProtected = (segCmd->flags & SG_PROTECTED_VERSION_1) ? 1 : 0;
|
||
info.hasZeroFill = (segCmd->initprot == 3) && (segCmd->filesize < segCmd->vmsize);
|
||
info.p2align = p2align;
|
||
info.segIndex = segIndex;
|
||
callback(info, stop);
|
||
++segIndex;
|
||
}
|
||
});
|
||
diag.assertNoError(); // any malformations in the file should have been caught by earlier validate() call
|
||
}
|
||
|
||
uint64_t MachOFile::preferredLoadAddress() const
|
||
{
|
||
__block uint64_t textVmAddr = 0;
|
||
forEachSegment(^(const SegmentInfo& info, bool& stop) {
|
||
if ( strcmp(info.segName, "__TEXT") == 0 ) {
|
||
textVmAddr = info.vmAddr;
|
||
stop = true;
|
||
}
|
||
});
|
||
return textVmAddr;
|
||
}
|
||
|
||
void MachOFile::forEachSection(void (^callback)(const SectionInfo& sectInfo, bool malformedSectionRange, bool& stop)) const
|
||
{
|
||
Diagnostics diag;
|
||
BLOCK_ACCCESSIBLE_ARRAY(char, sectNameCopy, 20); // read as: char sectNameCopy[20];
|
||
const bool intel32 = (this->cputype == CPU_TYPE_I386);
|
||
__block uint32_t segIndex = 0;
|
||
forEachLoadCommand(diag, ^(const load_command* cmd, bool& stop) {
|
||
SectionInfo sectInfo;
|
||
if ( cmd->cmd == LC_SEGMENT_64 ) {
|
||
const segment_command_64* segCmd = (segment_command_64*)cmd;
|
||
uint64_t sizeOfSections = segCmd->vmsize;
|
||
uint8_t p2align = 0;
|
||
const section_64* const sectionsStart = (section_64*)((char*)segCmd + sizeof(struct segment_command_64));
|
||
const section_64* const sectionsEnd = §ionsStart[segCmd->nsects];
|
||
for (const section_64* sect=sectionsStart; sect < sectionsEnd; ++sect) {
|
||
sizeOfSections = sect->addr + sect->size - segCmd->vmaddr;
|
||
if ( sect->align > p2align )
|
||
p2align = sect->align;
|
||
}
|
||
sectInfo.segInfo.fileOffset = segCmd->fileoff;
|
||
sectInfo.segInfo.fileSize = segCmd->filesize;
|
||
sectInfo.segInfo.vmAddr = segCmd->vmaddr;
|
||
sectInfo.segInfo.vmSize = segCmd->vmsize;
|
||
sectInfo.segInfo.sizeOfSections = sizeOfSections;
|
||
sectInfo.segInfo.segName = segCmd->segname;
|
||
sectInfo.segInfo.loadCommandOffset = (uint32_t)((uint8_t*)segCmd - (uint8_t*)this);
|
||
sectInfo.segInfo.protections = segCmd->initprot;
|
||
sectInfo.segInfo.textRelocs = false;
|
||
sectInfo.segInfo.readOnlyData = ((segCmd->flags & SG_READ_ONLY) != 0);
|
||
sectInfo.segInfo.isProtected = (segCmd->flags & SG_PROTECTED_VERSION_1) ? 1 : 0;
|
||
sectInfo.segInfo.p2align = p2align;
|
||
sectInfo.segInfo.segIndex = segIndex;
|
||
for (const section_64* sect=sectionsStart; !stop && (sect < sectionsEnd); ++sect) {
|
||
const char* sectName = sect->sectname;
|
||
if ( sectName[15] != '\0' ) {
|
||
strlcpy(sectNameCopy, sectName, 17);
|
||
sectName = sectNameCopy;
|
||
}
|
||
bool malformedSectionRange = (sect->addr < segCmd->vmaddr) || greaterThanAddOrOverflow(sect->addr, sect->size, segCmd->vmaddr + segCmd->filesize);
|
||
sectInfo.sectName = sectName;
|
||
sectInfo.sectFileOffset = sect->offset;
|
||
sectInfo.sectFlags = sect->flags;
|
||
sectInfo.sectAddr = sect->addr;
|
||
sectInfo.sectSize = sect->size;
|
||
sectInfo.sectAlignP2 = sect->align;
|
||
sectInfo.reserved1 = sect->reserved1;
|
||
sectInfo.reserved2 = sect->reserved2;
|
||
callback(sectInfo, malformedSectionRange, stop);
|
||
}
|
||
++segIndex;
|
||
}
|
||
else if ( cmd->cmd == LC_SEGMENT ) {
|
||
const segment_command* segCmd = (segment_command*)cmd;
|
||
uint64_t sizeOfSections = segCmd->vmsize;
|
||
uint8_t p2align = 0;
|
||
bool hasTextRelocs = false;
|
||
const section* const sectionsStart = (section*)((char*)segCmd + sizeof(struct segment_command));
|
||
const section* const sectionsEnd = §ionsStart[segCmd->nsects];
|
||
for (const section* sect=sectionsStart; sect < sectionsEnd; ++sect) {
|
||
sizeOfSections = sect->addr + sect->size - segCmd->vmaddr;
|
||
if ( sect->align > p2align )
|
||
p2align = sect->align;
|
||
if ( sect->flags & (S_ATTR_EXT_RELOC|S_ATTR_LOC_RELOC) )
|
||
hasTextRelocs = true;
|
||
}
|
||
sectInfo.segInfo.fileOffset = segCmd->fileoff;
|
||
sectInfo.segInfo.fileSize = segCmd->filesize;
|
||
sectInfo.segInfo.vmAddr = segCmd->vmaddr;
|
||
sectInfo.segInfo.vmSize = segCmd->vmsize;
|
||
sectInfo.segInfo.sizeOfSections = sizeOfSections;
|
||
sectInfo.segInfo.segName = segCmd->segname;
|
||
sectInfo.segInfo.loadCommandOffset = (uint32_t)((uint8_t*)segCmd - (uint8_t*)this);
|
||
sectInfo.segInfo.protections = segCmd->initprot;
|
||
sectInfo.segInfo.textRelocs = intel32 && !sectInfo.segInfo.writable() && hasTextRelocs;
|
||
sectInfo.segInfo.readOnlyData = ((segCmd->flags & SG_READ_ONLY) != 0);
|
||
sectInfo.segInfo.isProtected = (segCmd->flags & SG_PROTECTED_VERSION_1) ? 1 : 0;
|
||
sectInfo.segInfo.p2align = p2align;
|
||
sectInfo.segInfo.segIndex = segIndex;
|
||
for (const section* sect=sectionsStart; !stop && (sect < sectionsEnd); ++sect) {
|
||
const char* sectName = sect->sectname;
|
||
if ( sectName[15] != '\0' ) {
|
||
strlcpy(sectNameCopy, sectName, 17);
|
||
sectName = sectNameCopy;
|
||
}
|
||
bool malformedSectionRange = (sect->addr < segCmd->vmaddr) || greaterThanAddOrOverflow(sect->addr, sect->size, segCmd->vmaddr + segCmd->filesize);
|
||
sectInfo.sectName = sectName;
|
||
sectInfo.sectFileOffset = sect->offset;
|
||
sectInfo.sectFlags = sect->flags;
|
||
sectInfo.sectAddr = sect->addr;
|
||
sectInfo.sectSize = sect->size;
|
||
sectInfo.sectAlignP2 = sect->align;
|
||
sectInfo.reserved1 = sect->reserved1;
|
||
sectInfo.reserved2 = sect->reserved2;
|
||
callback(sectInfo, malformedSectionRange, stop);
|
||
}
|
||
++segIndex;
|
||
}
|
||
});
|
||
diag.assertNoError(); // any malformations in the file should have been caught by earlier validate() call
|
||
}
|
||
|
||
void MachOFile::forEachInterposingSection(Diagnostics& diag, void (^handler)(uint64_t vmOffset, uint64_t vmSize, bool& stop)) const
|
||
{
|
||
const unsigned ptrSize = pointerSize();
|
||
const unsigned entrySize = 2 * ptrSize;
|
||
forEachSection(^(const MachOFile::SectionInfo& info, bool malformedSectionRange, bool &stop) {
|
||
if ( ((info.sectFlags & SECTION_TYPE) == S_INTERPOSING) || ((strcmp(info.sectName, "__interpose") == 0) && ((strncmp(info.segInfo.segName, "__DATA", 6) == 0) || strncmp(info.segInfo.segName, "__AUTH", 6) == 0)) ) {
|
||
if ( info.sectSize % entrySize != 0 ) {
|
||
diag.error("interposing section %s/%s has bad size", info.segInfo.segName, info.sectName);
|
||
stop = true;
|
||
return;
|
||
}
|
||
if ( malformedSectionRange ) {
|
||
diag.error("interposing section %s/%s extends beyond the end of the segment", info.segInfo.segName, info.sectName);
|
||
stop = true;
|
||
return;
|
||
}
|
||
if ( (info.sectAddr % ptrSize) != 0 ) {
|
||
diag.error("interposing section %s/%s is not pointer aligned", info.segInfo.segName, info.sectName);
|
||
stop = true;
|
||
return;
|
||
}
|
||
handler(info.sectAddr - preferredLoadAddress(), info.sectSize, stop);
|
||
}
|
||
});
|
||
}
|
||
|
||
bool MachOFile::isRestricted() const
|
||
{
|
||
__block bool result = false;
|
||
forEachSection(^(const MachOFile::SectionInfo& info, bool malformedSectionRange, bool &stop) {
|
||
if ( (strcmp(info.segInfo.segName, "__RESTRICT") == 0) && (strcmp(info.sectName, "__restrict") == 0) ) {
|
||
result = true;
|
||
stop = true;
|
||
}
|
||
});
|
||
return result;
|
||
}
|
||
|
||
bool MachOFile::hasWeakDefs() const
|
||
{
|
||
return (this->flags & MH_WEAK_DEFINES);
|
||
}
|
||
|
||
bool MachOFile::usesWeakDefs() const
|
||
{
|
||
return (this->flags & MH_BINDS_TO_WEAK);
|
||
}
|
||
|
||
bool MachOFile::hasThreadLocalVariables() const
|
||
{
|
||
return (this->flags & MH_HAS_TLV_DESCRIPTORS);
|
||
}
|
||
|
||
#if BUILDING_CACHE_BUILDER || BUILDING_CACHE_BUILDER_UNIT_TESTS
|
||
static bool endsWith(const char* str, const char* suffix)
|
||
{
|
||
size_t strLen = strlen(str);
|
||
size_t suffixLen = strlen(suffix);
|
||
if ( strLen < suffixLen )
|
||
return false;
|
||
return (strcmp(&str[strLen-suffixLen], suffix) == 0);
|
||
}
|
||
|
||
bool MachOFile::isSharedCacheEligiblePath(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, "/Library/Apple/usr/lib/", 23) == 0)
|
||
|| (strncmp(dylibName, "/Library/Apple/System/Library/", 30) == 0)
|
||
|| (strncmp(dylibName, "/System/DriverKit/", 18) == 0)
|
||
|| (strncmp(dylibName, "/System/Cryptexes/OS/usr/lib/", 29) == 0)
|
||
|| (strncmp(dylibName, "/System/Cryptexes/OS/System/Library/", 36) == 0)
|
||
|| (strncmp(dylibName, "/System/Cryptexes/OS/System/iOSSupport/usr/lib/", 47) == 0)
|
||
|| (strncmp(dylibName, "/System/Cryptexes/OS/System/iOSSupport/System/Library/", 54) == 0));
|
||
}
|
||
|
||
static bool startsWith(const char* buffer, const char* valueToFind) {
|
||
return strncmp(buffer, valueToFind, strlen(valueToFind)) == 0;
|
||
}
|
||
|
||
static bool platformExcludesSharedCache_macOS(const char* installName) {
|
||
// Note: This function basically matches dontCache() from update dyld shared cache
|
||
|
||
if ( startsWith(installName, "/usr/lib/system/introspection/") )
|
||
return true;
|
||
if ( startsWith(installName, "/System/Library/QuickTime/") )
|
||
return true;
|
||
if ( startsWith(installName, "/System/Library/Tcl/") )
|
||
return true;
|
||
if ( startsWith(installName, "/System/Library/Perl/") )
|
||
return true;
|
||
if ( startsWith(installName, "/System/Library/MonitorPanels/") )
|
||
return true;
|
||
if ( startsWith(installName, "/System/Library/Accessibility/") )
|
||
return true;
|
||
if ( startsWith(installName, "/usr/local/") )
|
||
return true;
|
||
if ( startsWith(installName, "/usr/lib/pam/") )
|
||
return true;
|
||
// We no longer support ROSP, so skip all paths which start with the special prefix
|
||
if ( startsWith(installName, "/System/Library/Templates/Data/") )
|
||
return true;
|
||
|
||
// anything inside a .app bundle is specific to app, so should not be in shared cache
|
||
if ( strstr(installName, ".app/") != NULL )
|
||
return true;
|
||
|
||
// Depends on UHASHelloExtensionPoint-macOS which is not always cache eligible
|
||
if ( !strcmp(installName, "/System/Library/PrivateFrameworks/HelloWorldMacHelper.framework/Versions/A/HelloWorldMacHelper") )
|
||
return true;
|
||
|
||
return false;
|
||
}
|
||
|
||
static bool platformExcludesSharedCache_iOS(const char* installName) {
|
||
if ( strcmp(installName, "/System/Library/Caches/com.apple.xpc/sdk.dylib") == 0 )
|
||
return true;
|
||
if ( strcmp(installName, "/System/Library/Caches/com.apple.xpcd/xpcd_cache.dylib") == 0 )
|
||
return true;
|
||
return false;
|
||
}
|
||
|
||
// HACK: Remove this function. Its only here until we can handle cache overflow
|
||
static bool platformExcludesSharedCache_sim(const char* installName) {
|
||
if ( startsWith(installName, "/System/Library/PrivateFrameworks/iWorkImport.framework/") )
|
||
return true;
|
||
if ( startsWith(installName, "/System/Library/PrivateFrameworks/News") )
|
||
return true;
|
||
if ( strcmp(installName, "/System/Library/PrivateFrameworks/StocksUI.framework/StocksUI") == 0 )
|
||
return true;
|
||
if ( strcmp(installName, "/System/Library/PrivateFrameworks/NewsUI.framework/NewsUI") == 0 )
|
||
return true;
|
||
if ( strcmp(installName, "/System/Library/PrivateFrameworks/CompassUI.framework/CompassUI") == 0 )
|
||
return true;
|
||
if ( strcmp(installName, "/System/Library/PrivateFrameworks/WeatherUI.framework/WeatherUI") == 0 )
|
||
return true;
|
||
if ( strcmp(installName, "/System/Library/PrivateFrameworks/NewsUI2.framework/NewsUI2") == 0 )
|
||
return true;
|
||
if ( strcmp(installName, "/System/Library/PrivateFrameworks/MLCompilerOS.framework/MLCompilerOS") == 0 )
|
||
return true;
|
||
if ( strcmp(installName, "/System/Library/PrivateFrameworks/HomeKitDaemon.framework/HomeKitDaemon") == 0 )
|
||
return true;
|
||
if ( strcmp(installName, "/System/Library/PrivateFrameworks/HomeKitDaemonLegacy.framework/HomeKitDaemonLegacy") == 0 )
|
||
return true;
|
||
return false;
|
||
}
|
||
|
||
// Returns true if the current platform requires that this install name be excluded from the shared cache
|
||
// Note that this overrides any exclusion from anywhere else.
|
||
static bool platformExcludesSharedCache(Platform platform, const char* installName) {
|
||
if ( MachOFile::isSimulatorPlatform(platform) )
|
||
return platformExcludesSharedCache_sim(installName);
|
||
if ( (platform == dyld3::Platform::macOS) || (platform == dyld3::Platform::iOSMac) )
|
||
return platformExcludesSharedCache_macOS(installName);
|
||
// Everything else is based on iOS so just use that value
|
||
return platformExcludesSharedCache_iOS(installName);
|
||
}
|
||
|
||
bool MachOFile::canBePlacedInDyldCache(const char* path, void (^failureReason)(const char*)) const
|
||
{
|
||
if ( !isSharedCacheEligiblePath(path) ) {
|
||
// Dont spam the user with an error about paths when we know these are never eligible.
|
||
return false;
|
||
}
|
||
|
||
// only dylibs can go in cache
|
||
if ( !this->isDylib() && !this->isDyld() ) {
|
||
failureReason("Not MH_DYLIB");
|
||
return false; // cannot continue, installName() will assert() if not a dylib
|
||
}
|
||
|
||
|
||
const char* dylibName = installName();
|
||
if ( dylibName[0] != '/' ) {
|
||
failureReason("install name not an absolute path");
|
||
// Don't continue as we don't want to spam the log with errors we don't need.
|
||
return false;
|
||
}
|
||
else if ( strcmp(dylibName, path) != 0 ) {
|
||
failureReason("install path does not match install name");
|
||
return false;
|
||
}
|
||
else if ( strstr(dylibName, "//") != 0 ) {
|
||
failureReason("install name should not include //");
|
||
return false;
|
||
}
|
||
else if ( strstr(dylibName, "./") != 0 ) {
|
||
failureReason("install name should not include ./");
|
||
return false;
|
||
}
|
||
|
||
__block bool platformExcludedFile = false;
|
||
forEachSupportedPlatform(^(Platform platform, uint32_t minOS, uint32_t sdk) {
|
||
if ( platformExcludedFile )
|
||
return;
|
||
if ( platformExcludesSharedCache(platform, dylibName) ) {
|
||
platformExcludedFile = true;
|
||
return;
|
||
}
|
||
});
|
||
if ( platformExcludedFile ) {
|
||
failureReason("install name is not shared cache eligible on platform");
|
||
return false;
|
||
}
|
||
|
||
// flat namespace files cannot go in cache
|
||
if ( (this->flags & MH_TWOLEVEL) == 0 ) {
|
||
failureReason("Not built with two level namespaces");
|
||
return false;
|
||
}
|
||
|
||
// don't put debug variants into dyld cache
|
||
if ( endsWith(path, "_profile.dylib") || endsWith(path, "_debug.dylib") || endsWith(path, "_asan.dylib")
|
||
|| endsWith(path, "_profile") || endsWith(path, "_debug") || endsWith(path, "/CoreADI") ) {
|
||
failureReason("Variant image");
|
||
return false;
|
||
}
|
||
|
||
// dylib must have extra info for moving DATA and TEXT segments apart
|
||
__block bool hasExtraInfo = false;
|
||
__block bool hasDyldInfo = false;
|
||
__block bool hasExportTrie = false;
|
||
__block Diagnostics diag;
|
||
forEachLoadCommand(diag, ^(const load_command* cmd, bool& stop) {
|
||
if ( cmd->cmd == LC_SEGMENT_SPLIT_INFO )
|
||
hasExtraInfo = true;
|
||
if ( cmd->cmd == LC_DYLD_INFO_ONLY )
|
||
hasDyldInfo = true;
|
||
if ( cmd->cmd == LC_DYLD_EXPORTS_TRIE )
|
||
hasExportTrie = true;
|
||
});
|
||
if ( !hasExtraInfo ) {
|
||
std::string_view ignorePaths[] = {
|
||
"/usr/lib/libobjc-trampolines.dylib",
|
||
"/usr/lib/libffi-trampolines.dylib"
|
||
};
|
||
for ( std::string_view ignorePath : ignorePaths ) {
|
||
if ( ignorePath == path )
|
||
return false;
|
||
}
|
||
failureReason("Missing split seg info");
|
||
return false;
|
||
}
|
||
if ( !hasDyldInfo && !hasExportTrie ) {
|
||
failureReason("Old binary, missing dyld info or export trie");
|
||
return false;
|
||
}
|
||
|
||
// dylib can only depend on other dylibs in the shared cache
|
||
__block bool allDepPathsAreGood = true;
|
||
forEachDependentDylib(^(const char* loadPath, bool isWeak, bool isReExport, bool isUpward, uint32_t compatVersion, uint32_t curVersion, bool& stop) {
|
||
// Skip weak links. They are allowed to be missing
|
||
if ( isWeak )
|
||
return;
|
||
if ( !isSharedCacheEligiblePath(loadPath) ) {
|
||
allDepPathsAreGood = false;
|
||
stop = true;
|
||
}
|
||
});
|
||
if ( !allDepPathsAreGood ) {
|
||
failureReason("Depends on dylibs ineligable for dyld cache");
|
||
return false;
|
||
}
|
||
|
||
// dylibs with interposing info cannot be in cache
|
||
if ( hasInterposingTuples() ) {
|
||
failureReason("Has interposing tuples");
|
||
return false;
|
||
}
|
||
|
||
// Temporarily kick out swift binaries out of dyld cache on watchOS simulators as they have missing split seg
|
||
if ( (this->cputype == CPU_TYPE_I386) && builtForPlatform(Platform::watchOS_simulator) ) {
|
||
if ( strncmp(dylibName, "/usr/lib/swift/", 15) == 0 ) {
|
||
failureReason("i386 swift binary");
|
||
return false;
|
||
}
|
||
}
|
||
|
||
// These used to be in MachOAnalyzer
|
||
__block bool passedLinkeditChecks = false;
|
||
this->withFileLayout(diag, ^(const mach_o::Layout &layout) {
|
||
|
||
mach_o::SplitSeg splitSeg(layout);
|
||
mach_o::Fixups fixups(layout);
|
||
|
||
// arm64e requires split seg v2 as the split seg code can't handle chained fixups for split seg v1
|
||
if ( isArch("arm64e") ) {
|
||
if ( !splitSeg.isV2() ) {
|
||
failureReason("chained fixups requires split seg v2");
|
||
return;
|
||
}
|
||
}
|
||
|
||
// evict swift dylibs with split seg v1 info
|
||
if ( layout.isSwiftLibrary() && splitSeg.isV1() )
|
||
return;
|
||
|
||
if ( splitSeg.isV1() ) {
|
||
// Split seg v1 can only support 1 __DATA, and no other writable segments
|
||
__block bool foundBadSegment = false;
|
||
forEachSegment(^(const SegmentInfo& info, bool& stop) {
|
||
if ( info.protections == (VM_PROT_READ | VM_PROT_WRITE) ) {
|
||
if ( strcmp(info.segName, "__DATA") == 0 )
|
||
return;
|
||
|
||
failureReason("RW segments other than __DATA requires split seg v2");
|
||
foundBadSegment = true;
|
||
stop = true;
|
||
}
|
||
});
|
||
|
||
if ( foundBadSegment )
|
||
return;
|
||
}
|
||
|
||
// <rdar://problem/57769033> dyld_cache_patchable_location only supports addend in range 0..31
|
||
// rdar://96164956 (dyld needs to support arbitrary addends in cache patch table)
|
||
const bool is64bit = is64();
|
||
__block bool addendTooLarge = false;
|
||
const uint64_t tooLargeRegularAddend = 1 << 23;
|
||
const uint64_t tooLargeAuthAddend = 1 << 5;
|
||
if ( this->hasChainedFixups() ) {
|
||
|
||
// with chained fixups, addends can be in the import table or embedded in a bind pointer
|
||
__block std::vector<uint64_t> targetAddends;
|
||
fixups.forEachChainedFixupTarget(diag, ^(int libOrdinal, const char* symbolName, uint64_t addend, bool weakImport, bool& stop) {
|
||
if ( is64bit )
|
||
addend &= 0x00FFFFFFFFFFFFFF; // ignore TBI
|
||
targetAddends.push_back(addend);
|
||
});
|
||
// check each pointer for embedded addend
|
||
fixups.withChainStarts(diag, ^(const dyld_chained_starts_in_image* starts) {
|
||
fixups.forEachFixupInAllChains(diag, starts, false, ^(mach_o::ChainedFixupPointerOnDisk* fixupLoc, uint64_t fixupSegmentOffset, const dyld_chained_starts_in_segment* segInfo, bool& stop) {
|
||
switch (segInfo->pointer_format) {
|
||
case DYLD_CHAINED_PTR_ARM64E:
|
||
case DYLD_CHAINED_PTR_ARM64E_USERLAND:
|
||
if ( fixupLoc->arm64e.bind.bind ) {
|
||
uint64_t ordinal = fixupLoc->arm64e.bind.ordinal;
|
||
uint64_t addend = (ordinal < targetAddends.size()) ? targetAddends[ordinal] : 0;
|
||
if ( fixupLoc->arm64e.bind.auth ) {
|
||
if ( addend >= tooLargeAuthAddend ) {
|
||
addendTooLarge = true;
|
||
stop = true;
|
||
}
|
||
} else {
|
||
addend += fixupLoc->arm64e.signExtendedAddend();
|
||
if ( addend >= tooLargeRegularAddend ) {
|
||
addendTooLarge = true;
|
||
stop = true;
|
||
}
|
||
}
|
||
}
|
||
break;
|
||
case DYLD_CHAINED_PTR_ARM64E_USERLAND24:
|
||
if ( fixupLoc->arm64e.bind24.bind ) {
|
||
uint64_t ordinal = fixupLoc->arm64e.bind24.ordinal;
|
||
uint64_t addend = (ordinal < targetAddends.size()) ? targetAddends[ordinal] : 0;
|
||
if ( fixupLoc->arm64e.bind24.auth ) {
|
||
if ( addend >= tooLargeAuthAddend ) {
|
||
addendTooLarge = true;
|
||
stop = true;
|
||
}
|
||
} else {
|
||
addend += fixupLoc->arm64e.signExtendedAddend();
|
||
if ( addend >= tooLargeRegularAddend ) {
|
||
addendTooLarge = true;
|
||
stop = true;
|
||
}
|
||
}
|
||
}
|
||
break;
|
||
case DYLD_CHAINED_PTR_64:
|
||
case DYLD_CHAINED_PTR_64_OFFSET: {
|
||
if ( fixupLoc->generic64.rebase.bind ) {
|
||
uint64_t ordinal = fixupLoc->generic64.bind.ordinal;
|
||
uint64_t addend = (ordinal < targetAddends.size()) ? targetAddends[ordinal] : 0;
|
||
addend += fixupLoc->generic64.bind.addend;
|
||
if ( addend >= tooLargeRegularAddend ) {
|
||
addendTooLarge = true;
|
||
stop = true;
|
||
}
|
||
}
|
||
break;
|
||
}
|
||
case DYLD_CHAINED_PTR_32:
|
||
if ( fixupLoc->generic32.bind.bind ) {
|
||
uint64_t ordinal = fixupLoc->generic32.bind.ordinal;
|
||
uint64_t addend = (ordinal < targetAddends.size()) ? targetAddends[ordinal] : 0;
|
||
addend += fixupLoc->generic32.bind.addend;
|
||
if ( addend >= tooLargeRegularAddend ) {
|
||
addendTooLarge = true;
|
||
stop = true;
|
||
}
|
||
}
|
||
break;
|
||
}
|
||
});
|
||
});
|
||
}
|
||
else {
|
||
// scan bind opcodes for large addend
|
||
auto handler = ^(const mach_o::Fixups::BindTargetInfo &info, bool &stop) {
|
||
uint64_t addend = info.addend;
|
||
if ( is64bit )
|
||
addend &= 0x00FFFFFFFFFFFFFF; // ignore TBI
|
||
if ( addend >= tooLargeRegularAddend ) {
|
||
addendTooLarge = true;
|
||
stop = true;
|
||
}
|
||
};
|
||
fixups.forEachBindTarget_Opcodes(diag, true, handler, handler);
|
||
}
|
||
if ( addendTooLarge ) {
|
||
failureReason("bind addend too large");
|
||
return;
|
||
}
|
||
|
||
if ( (isArch("x86_64") || isArch("x86_64h")) ) {
|
||
__block bool rebasesOk = true;
|
||
uint64_t startVMAddr = preferredLoadAddress();
|
||
uint64_t endVMAddr = startVMAddr + mappedSize();
|
||
fixups.forEachRebase(diag, ^(uint64_t runtimeOffset, uint64_t rebasedValue, bool &stop) {
|
||
// We allow TBI for x86_64 dylibs, but then require that the remainder of the offset
|
||
// is a 32-bit offset from the mach-header.
|
||
rebasedValue &= 0x00FFFFFFFFFFFFFFULL;
|
||
if ( (rebasedValue < startVMAddr) || (rebasedValue >= endVMAddr) ) {
|
||
failureReason("rebase value out of range of dylib");
|
||
rebasesOk = false;
|
||
stop = true;
|
||
return;
|
||
}
|
||
|
||
// Also error if the rebase location is anything other than 4/8 byte aligned
|
||
if ( (runtimeOffset & 0x3) != 0 ) {
|
||
failureReason("rebase value is not 4-byte aligned");
|
||
rebasesOk = false;
|
||
stop = true;
|
||
return;
|
||
}
|
||
|
||
// Error if the fixup will cross a page
|
||
if ( (runtimeOffset & 0xFFF) == 0xFFC ) {
|
||
failureReason("rebase value crosses page boundary");
|
||
rebasesOk = false;
|
||
stop = true;
|
||
return;
|
||
}
|
||
});
|
||
|
||
if ( !rebasesOk )
|
||
return;
|
||
|
||
if ( this->hasChainedFixups() ) {
|
||
fixups.withChainStarts(diag, ^(const dyld_chained_starts_in_image* starts) {
|
||
fixups.forEachFixupInAllChains(diag, starts, false, ^(mach_o::ChainedFixupPointerOnDisk* fixupLoc, uint64_t fixupSegmentOffset, const dyld_chained_starts_in_segment* segInfo, bool& stop) {
|
||
if ( (fixupSegmentOffset & 0xFFF) == 0xFFC ) {
|
||
failureReason("chained fixup crosses page boundary");
|
||
rebasesOk = false;
|
||
stop = true;
|
||
return;
|
||
}
|
||
});
|
||
});
|
||
}
|
||
|
||
if ( !rebasesOk )
|
||
return;
|
||
}
|
||
|
||
// Check that shared cache dylibs don't use undefined lookup
|
||
{
|
||
__block bool bindsOk = true;
|
||
|
||
auto checkBind = ^(int libOrdinal, bool& stop) {
|
||
if ( libOrdinal == BIND_SPECIAL_DYLIB_FLAT_LOOKUP ) {
|
||
failureReason("has dynamic_lookup binds");
|
||
bindsOk = false;
|
||
stop = true;
|
||
}
|
||
};
|
||
|
||
if (hasChainedFixups()) {
|
||
fixups.forEachChainedFixupTarget(diag, ^(int libOrdinal, const char* symbolName, uint64_t addend, bool weakImport, bool& stop) {
|
||
checkBind(libOrdinal, stop);
|
||
});
|
||
} else {
|
||
auto handler = ^(const mach_o::Fixups::BindTargetInfo &info, bool &stop) {
|
||
checkBind(info.libOrdinal, stop);
|
||
};
|
||
fixups.forEachBindTarget_Opcodes(diag, true, handler, handler);
|
||
}
|
||
|
||
if ( !bindsOk )
|
||
return;
|
||
}
|
||
|
||
passedLinkeditChecks = true;
|
||
});
|
||
|
||
return passedLinkeditChecks;
|
||
}
|
||
|
||
// Returns true if the executable path is eligible for a PrebuiltLoader on the given platform.
|
||
bool MachOFile::canHavePrebuiltExecutableLoader(dyld3::Platform platform, const std::string_view& path,
|
||
void (^failureReason)(const char*)) const
|
||
{
|
||
// For now we can't build prebuilt loaders for the simulator
|
||
if ( isSimulatorPlatform(platform) ) {
|
||
// Don't spam with tons of messages about executables
|
||
return false;
|
||
}
|
||
|
||
if ( (platform == dyld3::Platform::macOS) || (platform == dyld3::Platform::iOSMac) ) {
|
||
// We no longer support ROSP, so skip all paths which start with the special prefix
|
||
if ( path.starts_with("/System/Library/Templates/Data/") ) {
|
||
// Dont spam the user with an error about paths when we know these are never eligible.
|
||
return false;
|
||
}
|
||
|
||
static const char* sAllowedPrefixes[] = {
|
||
"/bin/",
|
||
"/sbin/",
|
||
"/usr/",
|
||
"/System/",
|
||
"/Library/Apple/System/",
|
||
"/Library/Apple/usr/",
|
||
"/System/Applications/Safari.app/",
|
||
"/Library/CoreMediaIO/Plug-Ins/DAL/" // temp until plugins moved or closured working
|
||
};
|
||
|
||
bool inSearchDir = false;
|
||
for ( const char* searchDir : sAllowedPrefixes ) {
|
||
if ( path.starts_with(searchDir) ) {
|
||
inSearchDir = true;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if ( !inSearchDir ) {
|
||
failureReason("path not eligible");
|
||
return false;
|
||
}
|
||
} else {
|
||
// On embedded, only staged apps are excluded. They will run from a different location at runtime
|
||
if ( path.find("/staged_system_apps/") != std::string::npos ) {
|
||
// Dont spam the user with an error about paths when we know these are never eligible.
|
||
return false;
|
||
}
|
||
}
|
||
|
||
if ( !hasCodeSignature() ) {
|
||
failureReason("missing code signature");
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
#endif
|
||
|
||
#if BUILDING_APP_CACHE_UTIL
|
||
bool MachOFile::canBePlacedInKernelCollection(const char* path, void (^failureReason)(const char*)) const
|
||
{
|
||
// only dylibs and the kernel itself can go in cache
|
||
if ( this->filetype == MH_EXECUTE ) {
|
||
// xnu
|
||
} else if ( this->isKextBundle() ) {
|
||
// kext's
|
||
} else {
|
||
failureReason("Not MH_KEXT_BUNDLE");
|
||
return false;
|
||
}
|
||
|
||
if ( this->filetype == MH_EXECUTE ) {
|
||
// xnu
|
||
|
||
// two-level namespace binaries cannot go in cache
|
||
if ( (this->flags & MH_TWOLEVEL) != 0 ) {
|
||
failureReason("Built with two level namespaces");
|
||
return false;
|
||
}
|
||
|
||
// xnu kernel cannot have a page zero
|
||
__block bool foundPageZero = false;
|
||
forEachSegment(^(const SegmentInfo &segmentInfo, bool &stop) {
|
||
if ( strcmp(segmentInfo.segName, "__PAGEZERO") == 0 ) {
|
||
foundPageZero = true;
|
||
stop = true;
|
||
}
|
||
});
|
||
if (foundPageZero) {
|
||
failureReason("Has __PAGEZERO");
|
||
return false;
|
||
}
|
||
|
||
// xnu must have an LC_UNIXTHREAD to point to the entry point
|
||
__block bool foundMainLC = false;
|
||
__block bool foundUnixThreadLC = false;
|
||
Diagnostics diag;
|
||
forEachLoadCommand(diag, ^(const load_command* cmd, bool& stop) {
|
||
if ( cmd->cmd == LC_MAIN ) {
|
||
foundMainLC = true;
|
||
stop = true;
|
||
}
|
||
else if ( cmd->cmd == LC_UNIXTHREAD ) {
|
||
foundUnixThreadLC = true;
|
||
}
|
||
});
|
||
if (foundMainLC) {
|
||
failureReason("Found LC_MAIN");
|
||
return false;
|
||
}
|
||
if (!foundUnixThreadLC) {
|
||
failureReason("Expected LC_UNIXTHREAD");
|
||
return false;
|
||
}
|
||
|
||
if (diag.hasError()) {
|
||
failureReason("Error parsing load commands");
|
||
return false;
|
||
}
|
||
|
||
// The kernel should be a static executable, not a dynamic one
|
||
if ( !isStaticExecutable() ) {
|
||
failureReason("Expected static executable");
|
||
return false;
|
||
}
|
||
|
||
// The kernel must be built with -pie
|
||
if ( !isPIE() ) {
|
||
failureReason("Expected pie");
|
||
return false;
|
||
}
|
||
}
|
||
|
||
if ( isArch("arm64e") && isKextBundle() && !hasChainedFixups() ) {
|
||
failureReason("Missing fixup information");
|
||
return false;
|
||
}
|
||
|
||
// dylibs with interposing info cannot be in cache
|
||
if ( hasInterposingTuples() ) {
|
||
failureReason("Has interposing tuples");
|
||
return false;
|
||
}
|
||
|
||
// Only x86_64 is allowed to have RWX segments
|
||
if ( !isArch("x86_64") && !isArch("x86_64h") ) {
|
||
__block bool foundBadSegment = false;
|
||
forEachSegment(^(const SegmentInfo &info, bool &stop) {
|
||
if ( (info.protections & (VM_PROT_WRITE | VM_PROT_EXECUTE)) == (VM_PROT_WRITE | VM_PROT_EXECUTE) ) {
|
||
failureReason("Segments are not allowed to be both writable and executable");
|
||
foundBadSegment = true;
|
||
stop = true;
|
||
}
|
||
});
|
||
if ( foundBadSegment )
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
bool MachOFile::usesClassicRelocationsInKernelCollection() const {
|
||
// The xnu x86_64 static executable needs to do the i386->x86_64 transition
|
||
// so will be emitted with classic relocations
|
||
if ( isArch("x86_64") || isArch("x86_64h") ) {
|
||
return isStaticExecutable() || isFileSet();
|
||
}
|
||
return false;
|
||
}
|
||
#endif
|
||
|
||
#if BUILDING_CACHE_BUILDER || BUILDING_CACHE_BUILDER_UNIT_TESTS
|
||
static bool platformExcludesPrebuiltClosure_macOS(const char* path) {
|
||
// We no longer support ROSP, so skip all paths which start with the special prefix
|
||
if ( startsWith(path, "/System/Library/Templates/Data/") )
|
||
return true;
|
||
|
||
// anything inside a .app bundle is specific to app, so should not get a prebuilt closure
|
||
if ( strstr(path, ".app/") != NULL )
|
||
return true;
|
||
|
||
return false;
|
||
}
|
||
|
||
static bool platformExcludesPrebuiltClosure_iOS(const char* path) {
|
||
if ( strcmp(path, "/System/Library/Caches/com.apple.xpc/sdk.dylib") == 0 )
|
||
return true;
|
||
if ( strcmp(path, "/System/Library/Caches/com.apple.xpcd/xpcd_cache.dylib") == 0 )
|
||
return true;
|
||
return false;
|
||
}
|
||
|
||
// Returns true if the current platform requires that this install name be excluded from the shared cache
|
||
// Note that this overrides any exclusion from anywhere else.
|
||
static bool platformExcludesPrebuiltClosure(Platform platform, const char* path) {
|
||
if ( MachOFile::isSimulatorPlatform(platform) )
|
||
return false;
|
||
if ( (platform == dyld3::Platform::macOS) || (platform == dyld3::Platform::iOSMac) )
|
||
return platformExcludesPrebuiltClosure_macOS(path);
|
||
// Everything else is based on iOS so just use that value
|
||
return platformExcludesPrebuiltClosure_iOS(path);
|
||
}
|
||
|
||
bool MachOFile::canHavePrecomputedDlopenClosure(const char* path, void (^failureReason)(const char*)) const
|
||
{
|
||
__block bool retval = true;
|
||
|
||
// only dylibs can go in cache
|
||
if ( (this->filetype != MH_DYLIB) && (this->filetype != MH_BUNDLE) ) {
|
||
retval = false;
|
||
failureReason("not MH_DYLIB or MH_BUNDLE");
|
||
}
|
||
|
||
// flat namespace files cannot go in cache
|
||
if ( (this->flags & MH_TWOLEVEL) == 0 ) {
|
||
retval = false;
|
||
failureReason("not built with two level namespaces");
|
||
}
|
||
|
||
// can only depend on other dylibs with absolute paths
|
||
__block bool allDepPathsAreGood = true;
|
||
forEachDependentDylib(^(const char* loadPath, bool isWeak, bool isReExport, bool isUpward, uint32_t compatVersion, uint32_t curVersion, bool& stop) {
|
||
if ( loadPath[0] != '/' ) {
|
||
allDepPathsAreGood = false;
|
||
stop = true;
|
||
}
|
||
});
|
||
if ( !allDepPathsAreGood ) {
|
||
retval = false;
|
||
failureReason("depends on dylibs that are not absolute paths");
|
||
}
|
||
|
||
__block bool platformExcludedFile = false;
|
||
forEachSupportedPlatform(^(Platform platform, uint32_t minOS, uint32_t sdk) {
|
||
if ( platformExcludedFile )
|
||
return;
|
||
if ( platformExcludesPrebuiltClosure(platform, path) ) {
|
||
platformExcludedFile = true;
|
||
return;
|
||
}
|
||
});
|
||
if ( platformExcludedFile ) {
|
||
failureReason("file cannot get a prebuilt closure on this platform");
|
||
return false;
|
||
}
|
||
|
||
// dylibs with interposing info cannot have dlopen closure pre-computed
|
||
if ( hasInterposingTuples() ) {
|
||
retval = false;
|
||
failureReason("has interposing tuples");
|
||
}
|
||
|
||
// special system dylib overrides cannot have closure pre-computed
|
||
if ( strncmp(path, "/usr/lib/system/introspection/", 30) == 0 ) {
|
||
retval = false;
|
||
failureReason("override of OS dylib");
|
||
}
|
||
|
||
return retval;
|
||
}
|
||
#endif
|
||
|
||
bool MachOFile::hasInterposingTuples() const
|
||
{
|
||
__block bool hasInterposing = false;
|
||
Diagnostics diag;
|
||
forEachInterposingSection(diag, ^(uint64_t vmOffset, uint64_t vmSize, bool &stop) {
|
||
hasInterposing = true;
|
||
stop = true;
|
||
});
|
||
return hasInterposing;
|
||
}
|
||
|
||
bool MachOFile::isFairPlayEncrypted(uint32_t& textOffset, uint32_t& size) const
|
||
{
|
||
if ( const encryption_info_command* encCmd = findFairPlayEncryptionLoadCommand() ) {
|
||
if ( encCmd->cryptid == 1 ) {
|
||
// Note: cryptid is 0 in just-built apps. The AppStore sets cryptid to 1
|
||
textOffset = encCmd->cryptoff;
|
||
size = encCmd->cryptsize;
|
||
return true;
|
||
}
|
||
}
|
||
textOffset = 0;
|
||
size = 0;
|
||
return false;
|
||
}
|
||
|
||
bool MachOFile::canBeFairPlayEncrypted() const
|
||
{
|
||
return (findFairPlayEncryptionLoadCommand() != nullptr);
|
||
}
|
||
|
||
const encryption_info_command* MachOFile::findFairPlayEncryptionLoadCommand() const
|
||
{
|
||
__block const encryption_info_command* result = nullptr;
|
||
Diagnostics diag;
|
||
forEachLoadCommand(diag, ^(const load_command* cmd, bool& stop) {
|
||
if ( (cmd->cmd == LC_ENCRYPTION_INFO) || (cmd->cmd == LC_ENCRYPTION_INFO_64) ) {
|
||
result = (encryption_info_command*)cmd;
|
||
stop = true;
|
||
}
|
||
});
|
||
if ( diag.noError() )
|
||
return result;
|
||
else
|
||
return nullptr;
|
||
}
|
||
|
||
|
||
bool MachOFile::hasLoadCommand(uint32_t cmdNum) const
|
||
{
|
||
__block bool hasLC = false;
|
||
Diagnostics diag;
|
||
forEachLoadCommand(diag, ^(const load_command* cmd, bool& stop) {
|
||
if ( cmd->cmd == cmdNum ) {
|
||
hasLC = true;
|
||
stop = true;
|
||
}
|
||
});
|
||
return hasLC;
|
||
}
|
||
|
||
bool MachOFile::allowsAlternatePlatform() const
|
||
{
|
||
__block bool result = false;
|
||
forEachSection(^(const SectionInfo& info, bool malformedSectionRange, bool& stop) {
|
||
if ( (strcmp(info.sectName, "__allow_alt_plat") == 0) && (strncmp(info.segInfo.segName, "__DATA", 6) == 0) ) {
|
||
result = true;
|
||
stop = true;
|
||
}
|
||
});
|
||
return result;
|
||
}
|
||
|
||
bool MachOFile::hasChainedFixups() const
|
||
{
|
||
#if SUPPORT_ARCH_arm64e
|
||
// arm64e always uses chained fixups
|
||
if ( (this->cputype == CPU_TYPE_ARM64) && (this->maskedCpuSubtype() == CPU_SUBTYPE_ARM64E) ) {
|
||
// Not all binaries have fixups at all so check for the load commands
|
||
return hasLoadCommand(LC_DYLD_INFO_ONLY) || hasLoadCommand(LC_DYLD_CHAINED_FIXUPS);
|
||
}
|
||
#endif
|
||
return hasLoadCommand(LC_DYLD_CHAINED_FIXUPS);
|
||
}
|
||
|
||
bool MachOFile::hasChainedFixupsLoadCommand() const
|
||
{
|
||
return hasLoadCommand(LC_DYLD_CHAINED_FIXUPS);
|
||
}
|
||
|
||
bool MachOFile::hasOpcodeFixups() const
|
||
{
|
||
return hasLoadCommand(LC_DYLD_INFO_ONLY) || hasLoadCommand(LC_DYLD_INFO) ;
|
||
}
|
||
|
||
uint16_t MachOFile::chainedPointerFormat(const dyld_chained_fixups_header* header)
|
||
{
|
||
const dyld_chained_starts_in_image* startsInfo = (dyld_chained_starts_in_image*)((uint8_t*)header + header->starts_offset);
|
||
for (uint32_t i=0; i < startsInfo->seg_count; ++i) {
|
||
uint32_t segInfoOffset = startsInfo->seg_info_offset[i];
|
||
// 0 offset means this segment has no fixups
|
||
if ( segInfoOffset == 0 )
|
||
continue;
|
||
const dyld_chained_starts_in_segment* segInfo = (dyld_chained_starts_in_segment*)((uint8_t*)startsInfo + segInfoOffset);
|
||
if ( segInfo->page_count != 0 )
|
||
return segInfo->pointer_format;
|
||
}
|
||
return 0; // no chains (perhaps no __DATA segment)
|
||
}
|
||
|
||
// find dyld_chained_starts_in_image* in image
|
||
// if old arm64e binary, synthesize dyld_chained_starts_in_image*
|
||
void MachOFile::withChainStarts(Diagnostics& diag, const dyld_chained_fixups_header* chainHeader, void (^callback)(const dyld_chained_starts_in_image*))
|
||
{
|
||
if ( chainHeader == nullptr ) {
|
||
diag.error("Must pass in a chain header");
|
||
return;
|
||
}
|
||
// we have a pre-computed offset into LINKEDIT for dyld_chained_starts_in_image
|
||
callback((dyld_chained_starts_in_image*)((uint8_t*)chainHeader + chainHeader->starts_offset));
|
||
}
|
||
|
||
void MachOFile::forEachFixupChainSegment(Diagnostics& diag, const dyld_chained_starts_in_image* starts,
|
||
void (^handler)(const dyld_chained_starts_in_segment* segInfo, uint32_t segIndex, bool& stop))
|
||
{
|
||
bool stopped = false;
|
||
for (uint32_t segIndex=0; segIndex < starts->seg_count && !stopped; ++segIndex) {
|
||
if ( starts->seg_info_offset[segIndex] == 0 )
|
||
continue;
|
||
const dyld_chained_starts_in_segment* segInfo = (dyld_chained_starts_in_segment*)((uint8_t*)starts + starts->seg_info_offset[segIndex]);
|
||
handler(segInfo, segIndex, stopped);
|
||
}
|
||
}
|
||
|
||
|
||
bool MachOFile::walkChain(Diagnostics& diag, ChainedFixupPointerOnDisk* chain, uint16_t pointer_format, bool notifyNonPointers, uint32_t max_valid_pointer,
|
||
void (^handler)(ChainedFixupPointerOnDisk* fixupLocation, bool& stop))
|
||
{
|
||
const unsigned stride = ChainedFixupPointerOnDisk::strideSize(pointer_format);
|
||
bool stop = false;
|
||
bool chainEnd = false;
|
||
while (!stop && !chainEnd) {
|
||
// copy chain content, in case handler modifies location to final value
|
||
ChainedFixupPointerOnDisk chainContent = *chain;
|
||
handler(chain, stop);
|
||
|
||
if ( !stop ) {
|
||
switch (pointer_format) {
|
||
case DYLD_CHAINED_PTR_ARM64E:
|
||
case DYLD_CHAINED_PTR_ARM64E_KERNEL:
|
||
case DYLD_CHAINED_PTR_ARM64E_USERLAND:
|
||
case DYLD_CHAINED_PTR_ARM64E_USERLAND24:
|
||
case DYLD_CHAINED_PTR_ARM64E_FIRMWARE:
|
||
if ( chainContent.arm64e.rebase.next == 0 )
|
||
chainEnd = true;
|
||
else
|
||
chain = (ChainedFixupPointerOnDisk*)((uint8_t*)chain + chainContent.arm64e.rebase.next*stride);
|
||
break;
|
||
case DYLD_CHAINED_PTR_64:
|
||
case DYLD_CHAINED_PTR_64_OFFSET:
|
||
if ( chainContent.generic64.rebase.next == 0 )
|
||
chainEnd = true;
|
||
else
|
||
chain = (ChainedFixupPointerOnDisk*)((uint8_t*)chain + chainContent.generic64.rebase.next*4);
|
||
break;
|
||
case DYLD_CHAINED_PTR_32:
|
||
if ( chainContent.generic32.rebase.next == 0 )
|
||
chainEnd = true;
|
||
else {
|
||
chain = (ChainedFixupPointerOnDisk*)((uint8_t*)chain + chainContent.generic32.rebase.next*4);
|
||
if ( !notifyNonPointers ) {
|
||
while ( (chain->generic32.rebase.bind == 0) && (chain->generic32.rebase.target > max_valid_pointer) ) {
|
||
// not a real pointer, but a non-pointer co-opted into chain
|
||
chain = (ChainedFixupPointerOnDisk*)((uint8_t*)chain + chain->generic32.rebase.next*4);
|
||
}
|
||
}
|
||
}
|
||
break;
|
||
case DYLD_CHAINED_PTR_64_KERNEL_CACHE:
|
||
case DYLD_CHAINED_PTR_X86_64_KERNEL_CACHE:
|
||
if ( chainContent.kernel64.next == 0 )
|
||
chainEnd = true;
|
||
else
|
||
chain = (ChainedFixupPointerOnDisk*)((uint8_t*)chain + chainContent.kernel64.next*stride);
|
||
break;
|
||
case DYLD_CHAINED_PTR_32_FIRMWARE:
|
||
if ( chainContent.firmware32.next == 0 )
|
||
chainEnd = true;
|
||
else
|
||
chain = (ChainedFixupPointerOnDisk*)((uint8_t*)chain + chainContent.firmware32.next*4);
|
||
break;
|
||
default:
|
||
diag.error("unknown pointer format 0x%04X", pointer_format);
|
||
stop = true;
|
||
}
|
||
}
|
||
}
|
||
return stop;
|
||
}
|
||
|
||
void MachOFile::forEachFixupInSegmentChains(Diagnostics& diag, const dyld_chained_starts_in_segment* segInfo,
|
||
bool notifyNonPointers, uint8_t* segmentContent,
|
||
void (^handler)(ChainedFixupPointerOnDisk* fixupLocation, bool& stop))
|
||
{
|
||
bool stopped = false;
|
||
for (uint32_t pageIndex=0; pageIndex < segInfo->page_count && !stopped; ++pageIndex) {
|
||
uint16_t offsetInPage = segInfo->page_start[pageIndex];
|
||
if ( offsetInPage == DYLD_CHAINED_PTR_START_NONE )
|
||
continue;
|
||
if ( offsetInPage & DYLD_CHAINED_PTR_START_MULTI ) {
|
||
// 32-bit chains which may need multiple starts per page
|
||
uint32_t overflowIndex = offsetInPage & ~DYLD_CHAINED_PTR_START_MULTI;
|
||
bool chainEnd = false;
|
||
while (!stopped && !chainEnd) {
|
||
chainEnd = (segInfo->page_start[overflowIndex] & DYLD_CHAINED_PTR_START_LAST);
|
||
offsetInPage = (segInfo->page_start[overflowIndex] & ~DYLD_CHAINED_PTR_START_LAST);
|
||
uint8_t* pageContentStart = segmentContent + (pageIndex * segInfo->page_size);
|
||
ChainedFixupPointerOnDisk* chain = (ChainedFixupPointerOnDisk*)(pageContentStart+offsetInPage);
|
||
stopped = walkChain(diag, chain, segInfo->pointer_format, notifyNonPointers, segInfo->max_valid_pointer, handler);
|
||
++overflowIndex;
|
||
}
|
||
}
|
||
else {
|
||
// one chain per page
|
||
uint8_t* pageContentStart = segmentContent + (pageIndex * segInfo->page_size);
|
||
ChainedFixupPointerOnDisk* chain = (ChainedFixupPointerOnDisk*)(pageContentStart+offsetInPage);
|
||
stopped = walkChain(diag, chain, segInfo->pointer_format, notifyNonPointers, segInfo->max_valid_pointer, handler);
|
||
}
|
||
}
|
||
}
|
||
|
||
void MachOFile::forEachChainedFixupTarget(Diagnostics& diag, const dyld_chained_fixups_header* header,
|
||
const linkedit_data_command* chainedFixups,
|
||
void (^callback)(int libOrdinal, const char* symbolName, uint64_t addend, bool weakImport, bool& stop))
|
||
{
|
||
if ( (header->imports_offset > chainedFixups->datasize) || (header->symbols_offset > chainedFixups->datasize) ) {
|
||
diag.error("malformed import table");
|
||
return;
|
||
}
|
||
|
||
bool stop = false;
|
||
|
||
const dyld_chained_import* imports;
|
||
const dyld_chained_import_addend* importsA32;
|
||
const dyld_chained_import_addend64* importsA64;
|
||
const char* symbolsPool = (char*)header + header->symbols_offset;
|
||
uint32_t maxSymbolOffset = chainedFixups->datasize - header->symbols_offset;
|
||
int libOrdinal;
|
||
switch (header->imports_format) {
|
||
case DYLD_CHAINED_IMPORT:
|
||
imports = (dyld_chained_import*)((uint8_t*)header + header->imports_offset);
|
||
for (uint32_t i=0; i < header->imports_count && !stop; ++i) {
|
||
const char* symbolName = &symbolsPool[imports[i].name_offset];
|
||
if ( imports[i].name_offset > maxSymbolOffset ) {
|
||
diag.error("malformed import table, string overflow");
|
||
return;
|
||
}
|
||
uint8_t libVal = imports[i].lib_ordinal;
|
||
if ( libVal > 0xF0 )
|
||
libOrdinal = (int8_t)libVal;
|
||
else
|
||
libOrdinal = libVal;
|
||
callback(libOrdinal, symbolName, 0, imports[i].weak_import, stop);
|
||
if ( stop )
|
||
return;
|
||
}
|
||
break;
|
||
case DYLD_CHAINED_IMPORT_ADDEND:
|
||
importsA32 = (dyld_chained_import_addend*)((uint8_t*)header + header->imports_offset);
|
||
for (uint32_t i=0; i < header->imports_count && !stop; ++i) {
|
||
const char* symbolName = &symbolsPool[importsA32[i].name_offset];
|
||
if ( importsA32[i].name_offset > maxSymbolOffset ) {
|
||
diag.error("malformed import table, string overflow");
|
||
return;
|
||
}
|
||
uint8_t libVal = importsA32[i].lib_ordinal;
|
||
if ( libVal > 0xF0 )
|
||
libOrdinal = (int8_t)libVal;
|
||
else
|
||
libOrdinal = libVal;
|
||
callback(libOrdinal, symbolName, importsA32[i].addend, importsA32[i].weak_import, stop);
|
||
if ( stop )
|
||
return;
|
||
}
|
||
break;
|
||
case DYLD_CHAINED_IMPORT_ADDEND64:
|
||
importsA64 = (dyld_chained_import_addend64*)((uint8_t*)header + header->imports_offset);
|
||
for (uint32_t i=0; i < header->imports_count && !stop; ++i) {
|
||
const char* symbolName = &symbolsPool[importsA64[i].name_offset];
|
||
if ( importsA64[i].name_offset > maxSymbolOffset ) {
|
||
diag.error("malformed import table, string overflow");
|
||
return;
|
||
}
|
||
uint16_t libVal = importsA64[i].lib_ordinal;
|
||
if ( libVal > 0xFFF0 )
|
||
libOrdinal = (int16_t)libVal;
|
||
else
|
||
libOrdinal = libVal;
|
||
callback(libOrdinal, symbolName, importsA64[i].addend, importsA64[i].weak_import, stop);
|
||
if ( stop )
|
||
return;
|
||
}
|
||
break;
|
||
default:
|
||
diag.error("unknown imports format");
|
||
return;
|
||
}
|
||
}
|
||
|
||
uint64_t MachOFile::read_uleb128(Diagnostics& diag, const uint8_t*& p, const uint8_t* end)
|
||
{
|
||
uint64_t result = 0;
|
||
int bit = 0;
|
||
do {
|
||
if ( p == end ) {
|
||
diag.error("malformed uleb128");
|
||
break;
|
||
}
|
||
uint64_t slice = *p & 0x7f;
|
||
|
||
if ( bit > 63 ) {
|
||
diag.error("uleb128 too big for uint64");
|
||
break;
|
||
}
|
||
else {
|
||
result |= (slice << bit);
|
||
bit += 7;
|
||
}
|
||
}
|
||
while (*p++ & 0x80);
|
||
return result;
|
||
}
|
||
|
||
|
||
int64_t MachOFile::read_sleb128(Diagnostics& diag, const uint8_t*& p, const uint8_t* end)
|
||
{
|
||
int64_t result = 0;
|
||
int bit = 0;
|
||
uint8_t byte = 0;
|
||
do {
|
||
if ( p == end ) {
|
||
diag.error("malformed sleb128");
|
||
break;
|
||
}
|
||
byte = *p++;
|
||
result |= (((int64_t)(byte & 0x7f)) << bit);
|
||
bit += 7;
|
||
} while (byte & 0x80);
|
||
// sign extend negative numbers
|
||
if ( ((byte & 0x40) != 0) && (bit < 64) )
|
||
result |= (~0ULL) << bit;
|
||
return result;
|
||
}
|
||
|
||
static void getArchNames(const GradedArchs& archs, bool isOSBinary, char buffer[256])
|
||
{
|
||
buffer[0] = '\0';
|
||
archs.forEachArch(isOSBinary, ^(const char* archName) {
|
||
if ( buffer[0] != '\0' )
|
||
strlcat(buffer, "' or '", 256);
|
||
strlcat(buffer, archName, 256);
|
||
});
|
||
}
|
||
|
||
const MachOFile* MachOFile::compatibleSlice(Diagnostics& diag, const void* fileContent, size_t contentSize, const char* path, Platform platform, bool isOSBinary, const GradedArchs& archs, bool internalInstall)
|
||
{
|
||
const MachOFile* mf = nullptr;
|
||
if ( const dyld3::FatFile* ff = dyld3::FatFile::isFatFile(fileContent) ) {
|
||
uint64_t sliceOffset;
|
||
uint64_t sliceLen;
|
||
bool missingSlice;
|
||
if ( ff->isFatFileWithSlice(diag, contentSize, archs, isOSBinary, sliceOffset, sliceLen, missingSlice) ) {
|
||
mf = (MachOFile*)((long)fileContent + sliceOffset);
|
||
}
|
||
else {
|
||
BLOCK_ACCCESSIBLE_ARRAY(char, gradedArchsBuf, 256);
|
||
getArchNames(archs, isOSBinary, gradedArchsBuf);
|
||
|
||
char strBuf[256];
|
||
diag.error("fat file, but missing compatible architecture (have '%s', need '%s')", ff->archNames(strBuf, contentSize), gradedArchsBuf);
|
||
return nullptr;
|
||
}
|
||
}
|
||
else {
|
||
mf = (MachOFile*)fileContent;
|
||
}
|
||
|
||
if ( !mf->hasMachOMagic() || !mf->isMachO(diag, contentSize) ) {
|
||
if ( diag.noError() )
|
||
diag.error("not a mach-o file");
|
||
return nullptr;
|
||
}
|
||
|
||
if ( archs.grade(mf->cputype, mf->cpusubtype, isOSBinary) == 0 ) {
|
||
BLOCK_ACCCESSIBLE_ARRAY(char, gradedArchsBuf, 256);
|
||
getArchNames(archs, isOSBinary, gradedArchsBuf);
|
||
diag.error("mach-o file, but is an incompatible architecture (have '%s', need '%s')", mf->archName(), gradedArchsBuf);
|
||
return nullptr;
|
||
}
|
||
|
||
if ( !mf->loadableIntoProcess(platform, path, internalInstall) ) {
|
||
__block Platform havePlatform = Platform::unknown;
|
||
mf->forEachSupportedPlatform(^(Platform aPlat, uint32_t minOS, uint32_t sdk) {
|
||
havePlatform = aPlat;
|
||
});
|
||
diag.error("mach-o file (%s), but incompatible platform (have '%s', need '%s')", path, MachOFile::platformName(havePlatform), MachOFile::platformName(platform));
|
||
return nullptr;
|
||
}
|
||
|
||
return mf;
|
||
}
|
||
|
||
const uint8_t* MachOFile::trieWalk(Diagnostics& diag, const uint8_t* start, const uint8_t* end, const char* symbol)
|
||
{
|
||
STACK_ALLOC_OVERFLOW_SAFE_ARRAY(uint32_t, visitedNodeOffsets, 128);
|
||
visitedNodeOffsets.push_back(0);
|
||
const uint8_t* p = start;
|
||
while ( p < end ) {
|
||
uint64_t terminalSize = *p++;
|
||
if ( terminalSize > 127 ) {
|
||
// except for re-export-with-rename, all terminal sizes fit in one byte
|
||
--p;
|
||
terminalSize = read_uleb128(diag, p, end);
|
||
if ( diag.hasError() )
|
||
return nullptr;
|
||
}
|
||
if ( (*symbol == '\0') && (terminalSize != 0) ) {
|
||
return p;
|
||
}
|
||
const uint8_t* children = p + terminalSize;
|
||
if ( children > end ) {
|
||
//diag.error("malformed trie node, terminalSize=0x%llX extends past end of trie\n", terminalSize);
|
||
return nullptr;
|
||
}
|
||
uint8_t childrenRemaining = *children++;
|
||
p = children;
|
||
uint64_t nodeOffset = 0;
|
||
for (; childrenRemaining > 0; --childrenRemaining) {
|
||
const char* ss = symbol;
|
||
bool wrongEdge = false;
|
||
// scan whole edge to get to next edge
|
||
// if edge is longer than target symbol name, don't read past end of symbol name
|
||
char c = *p;
|
||
while ( c != '\0' ) {
|
||
if ( !wrongEdge ) {
|
||
if ( c != *ss )
|
||
wrongEdge = true;
|
||
++ss;
|
||
}
|
||
++p;
|
||
c = *p;
|
||
}
|
||
if ( wrongEdge ) {
|
||
// advance to next child
|
||
++p; // skip over zero terminator
|
||
// skip over uleb128 until last byte is found
|
||
while ( (*p & 0x80) != 0 )
|
||
++p;
|
||
++p; // skip over last byte of uleb128
|
||
if ( p > end ) {
|
||
diag.error("malformed trie node, child node extends past end of trie\n");
|
||
return nullptr;
|
||
}
|
||
}
|
||
else {
|
||
// the symbol so far matches this edge (child)
|
||
// so advance to the child's node
|
||
++p;
|
||
nodeOffset = read_uleb128(diag, p, end);
|
||
if ( diag.hasError() )
|
||
return nullptr;
|
||
if ( (nodeOffset == 0) || ( &start[nodeOffset] > end) ) {
|
||
diag.error("malformed trie child, nodeOffset=0x%llX out of range\n", nodeOffset);
|
||
return nullptr;
|
||
}
|
||
symbol = ss;
|
||
break;
|
||
}
|
||
}
|
||
if ( nodeOffset != 0 ) {
|
||
if ( nodeOffset > (uint64_t)(end-start) ) {
|
||
diag.error("malformed trie child, nodeOffset=0x%llX out of range\n", nodeOffset);
|
||
return nullptr;
|
||
}
|
||
// check for cycles
|
||
for (uint32_t aVisitedNodeOffset : visitedNodeOffsets) {
|
||
if ( aVisitedNodeOffset == nodeOffset ) {
|
||
diag.error("malformed trie child, cycle to nodeOffset=0x%llX\n", nodeOffset);
|
||
return nullptr;
|
||
}
|
||
}
|
||
visitedNodeOffsets.push_back((uint32_t)nodeOffset);
|
||
p = &start[nodeOffset];
|
||
}
|
||
else
|
||
p = end;
|
||
}
|
||
return nullptr;
|
||
}
|
||
|
||
void MachOFile::forEachRPath(void (^callback)(const char* rPath, bool& stop)) const
|
||
{
|
||
Diagnostics diag;
|
||
forEachLoadCommand(diag, ^(const load_command* cmd, bool& stop) {
|
||
if ( cmd->cmd == LC_RPATH ) {
|
||
const char* rpath = (char*)cmd + ((struct rpath_command*)cmd)->path.offset;
|
||
callback(rpath, stop);
|
||
}
|
||
});
|
||
diag.assertNoError(); // any malformations in the file should have been caught by earlier validate() call
|
||
}
|
||
|
||
|
||
bool MachOFile::inCodeSection(uint32_t runtimeOffset) const
|
||
{
|
||
// only needed for arm64e code to know to sign pointers
|
||
if ( (this->cputype != CPU_TYPE_ARM64) || (this->maskedCpuSubtype() != CPU_SUBTYPE_ARM64E) )
|
||
return false;
|
||
|
||
__block bool result = false;
|
||
uint64_t baseAddress = this->preferredLoadAddress();
|
||
this->forEachSection(^(const SectionInfo& sectInfo, bool malformedSectionRange, bool& stop) {
|
||
if ( ((sectInfo.sectAddr-baseAddress) <= runtimeOffset) && (runtimeOffset < (sectInfo.sectAddr+sectInfo.sectSize-baseAddress)) ) {
|
||
result = ( (sectInfo.sectFlags & S_ATTR_PURE_INSTRUCTIONS) || (sectInfo.sectFlags & S_ATTR_SOME_INSTRUCTIONS) );
|
||
stop = true;
|
||
}
|
||
});
|
||
return result;
|
||
}
|
||
|
||
uint32_t MachOFile::dependentDylibCount(bool* allDepsAreNormalPtr) const
|
||
{
|
||
__block uint32_t count = 0;
|
||
__block bool allDepsAreNormal = true;
|
||
forEachDependentDylib(^(const char* loadPath, bool isWeak, bool isReExport, bool isUpward, uint32_t compatVersion, uint32_t curVersion, bool& stop) {
|
||
++count;
|
||
if ( isWeak || isReExport || isUpward )
|
||
allDepsAreNormal = false;
|
||
});
|
||
|
||
if ( allDepsAreNormalPtr != nullptr )
|
||
*allDepsAreNormalPtr = allDepsAreNormal;
|
||
return count;
|
||
}
|
||
|
||
bool MachOFile::hasPlusLoadMethod(Diagnostics& diag) const
|
||
{
|
||
__block bool result = false;
|
||
|
||
// in new objc runtime compiler puts classes/categories with +load method in specical section
|
||
forEachSection(^(const SectionInfo& info, bool malformedSectionRange, bool& stop) {
|
||
if ( strncmp(info.segInfo.segName, "__DATA", 6) != 0 )
|
||
return;
|
||
if ( (strcmp(info.sectName, "__objc_nlclslist") == 0) || (strcmp(info.sectName, "__objc_nlcatlist") == 0)) {
|
||
result = true;
|
||
stop = true;
|
||
}
|
||
});
|
||
return result;
|
||
}
|
||
|
||
uint32_t MachOFile::getFixupsLoadCommandFileOffset() const
|
||
{
|
||
Diagnostics diag;
|
||
__block uint32_t fileOffset = 0;
|
||
this->forEachLoadCommand(diag, ^(const load_command* cmd, bool& stop) {
|
||
switch ( cmd->cmd ) {
|
||
case LC_DYLD_INFO:
|
||
case LC_DYLD_INFO_ONLY:
|
||
fileOffset = (uint32_t)( (uint8_t*)cmd - (uint8_t*)this );
|
||
break;
|
||
case LC_DYLD_CHAINED_FIXUPS:
|
||
fileOffset = (uint32_t)( (uint8_t*)cmd - (uint8_t*)this );
|
||
break;
|
||
}
|
||
});
|
||
if ( diag.hasError() )
|
||
return 0;
|
||
|
||
return fileOffset;
|
||
}
|
||
|
||
bool MachOFile::hasInitializer(Diagnostics& diag) const
|
||
{
|
||
__block bool result = false;
|
||
|
||
// if dylib linked with -init linker option, that initializer is first
|
||
forEachLoadCommand(diag, ^(const load_command* cmd, bool& stop) {
|
||
if ( (cmd->cmd == LC_ROUTINES) || (cmd->cmd == LC_ROUTINES_64) ) {
|
||
result = true;
|
||
stop = true;
|
||
}
|
||
});
|
||
|
||
if ( result )
|
||
return true;
|
||
|
||
// next any function pointers in mod-init section
|
||
forEachInitializerPointerSection(diag, ^(uint32_t sectionOffset, uint32_t sectionSize, bool& stop) {
|
||
result = true;
|
||
stop = true;
|
||
});
|
||
|
||
if ( result )
|
||
return true;
|
||
|
||
forEachSection(^(const SectionInfo& info, bool malformedSectionRange, bool& stop) {
|
||
if ( (info.sectFlags & SECTION_TYPE) != S_INIT_FUNC_OFFSETS )
|
||
return;
|
||
result = true;
|
||
stop = true;
|
||
});
|
||
|
||
return result;
|
||
}
|
||
|
||
void MachOFile::forEachInitializerPointerSection(Diagnostics& diag, void (^callback)(uint32_t sectionOffset, uint32_t sectionSize, bool& stop)) const
|
||
{
|
||
const unsigned ptrSize = pointerSize();
|
||
const uint64_t baseAddress = preferredLoadAddress();
|
||
forEachSection(^(const SectionInfo& info, bool malformedSectionRange, bool& sectStop) {
|
||
if ( (info.sectFlags & SECTION_TYPE) == S_MOD_INIT_FUNC_POINTERS ) {
|
||
if ( (info.sectSize % ptrSize) != 0 ) {
|
||
diag.error("initializer section %s/%s has bad size", info.segInfo.segName, info.sectName);
|
||
sectStop = true;
|
||
return;
|
||
}
|
||
if ( malformedSectionRange ) {
|
||
diag.error("initializer section %s/%s extends beyond its segment", info.segInfo.segName, info.sectName);
|
||
sectStop = true;
|
||
return;
|
||
}
|
||
if ( (info.sectAddr % ptrSize) != 0 ) {
|
||
diag.error("initializer section %s/%s is not pointer aligned", info.segInfo.segName, info.sectName);
|
||
sectStop = true;
|
||
return;
|
||
}
|
||
callback((uint32_t)(info.sectAddr - baseAddress), (uint32_t)info.sectSize, sectStop);
|
||
}
|
||
});
|
||
}
|
||
|
||
bool MachOFile::hasCodeSignature() const
|
||
{
|
||
return this->hasLoadCommand(LC_CODE_SIGNATURE);
|
||
}
|
||
|
||
bool MachOFile::hasCodeSignature(uint32_t& fileOffset, uint32_t& size) const
|
||
{
|
||
fileOffset = 0;
|
||
size = 0;
|
||
|
||
Diagnostics diag;
|
||
forEachLoadCommand(diag, ^(const load_command* cmd, bool& stop) {
|
||
if ( cmd->cmd == LC_CODE_SIGNATURE ) {
|
||
const linkedit_data_command* sigCmd = (linkedit_data_command*)cmd;
|
||
fileOffset = sigCmd->dataoff;
|
||
size = sigCmd->datasize;
|
||
stop = true;
|
||
}
|
||
});
|
||
diag.assertNoError(); // any malformations in the file should have been caught by earlier validate() call
|
||
|
||
// early exist if no LC_CODE_SIGNATURE
|
||
if ( fileOffset == 0 )
|
||
return false;
|
||
|
||
// <rdar://problem/13622786> ignore code signatures in macOS binaries built with pre-10.9 tools
|
||
if ( (this->cputype == CPU_TYPE_X86_64) || (this->cputype == CPU_TYPE_I386) ) {
|
||
__block bool foundPlatform = false;
|
||
__block bool badSignature = false;
|
||
forEachSupportedPlatform(^(Platform platform, uint32_t minOS, uint32_t sdk) {
|
||
foundPlatform = true;
|
||
if ( (platform == Platform::macOS) && (sdk < 0x000A0900) )
|
||
badSignature = true;
|
||
});
|
||
return foundPlatform && !badSignature;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
uint64_t MachOFile::mappedSize() const
|
||
{
|
||
uint64_t vmSpace;
|
||
bool hasZeroFill;
|
||
analyzeSegmentsLayout(vmSpace, hasZeroFill);
|
||
return vmSpace;
|
||
}
|
||
|
||
void MachOFile::analyzeSegmentsLayout(uint64_t& vmSpace, bool& hasZeroFill) const
|
||
{
|
||
__block bool writeExpansion = false;
|
||
__block uint64_t lowestVmAddr = 0xFFFFFFFFFFFFFFFFULL;
|
||
__block uint64_t highestVmAddr = 0;
|
||
__block uint64_t sumVmSizes = 0;
|
||
forEachSegment(^(const SegmentInfo& segmentInfo, bool& stop) {
|
||
if ( strcmp(segmentInfo.segName, "__PAGEZERO") == 0 )
|
||
return;
|
||
if ( segmentInfo.writable() && (segmentInfo.fileSize != segmentInfo.vmSize) )
|
||
writeExpansion = true; // zerofill at end of __DATA
|
||
if ( segmentInfo.vmSize == 0 ) {
|
||
// Always zero fill if we have zero-sized segments
|
||
writeExpansion = true;
|
||
}
|
||
if ( segmentInfo.vmAddr < lowestVmAddr )
|
||
lowestVmAddr = segmentInfo.vmAddr;
|
||
if ( segmentInfo.vmAddr+segmentInfo.vmSize > highestVmAddr )
|
||
highestVmAddr = segmentInfo.vmAddr+segmentInfo.vmSize;
|
||
sumVmSizes += segmentInfo.vmSize;
|
||
});
|
||
uint64_t totalVmSpace = (highestVmAddr - lowestVmAddr);
|
||
// LINKEDIT vmSize is not required to be a multiple of page size. Round up if that is the case
|
||
const uint64_t pageSize = uses16KPages() ? 0x4000 : 0x1000;
|
||
totalVmSpace = (totalVmSpace + (pageSize - 1)) & ~(pageSize - 1);
|
||
bool hasHole = (totalVmSpace != sumVmSizes); // segments not contiguous
|
||
|
||
// The aux KC may have __DATA first, in which case we always want to vm_copy to the right place
|
||
bool hasOutOfOrderSegments = false;
|
||
#if BUILDING_APP_CACHE_UTIL
|
||
uint64_t textSegVMAddr = preferredLoadAddress();
|
||
hasOutOfOrderSegments = textSegVMAddr != lowestVmAddr;
|
||
#endif
|
||
|
||
vmSpace = totalVmSpace;
|
||
hasZeroFill = writeExpansion || hasHole || hasOutOfOrderSegments;
|
||
}
|
||
|
||
uint32_t MachOFile::segmentCount() const
|
||
{
|
||
__block uint32_t count = 0;
|
||
forEachSegment(^(const SegmentInfo& info, bool& stop) {
|
||
++count;
|
||
});
|
||
return count;
|
||
}
|
||
|
||
|
||
void MachOFile::forEachDOFSection(Diagnostics& diag, void (^callback)(uint32_t offset)) const
|
||
{
|
||
forEachSection(^(const SectionInfo& info, bool malformedSectionRange, bool &stop) {
|
||
if ( ( (info.sectFlags & SECTION_TYPE) == S_DTRACE_DOF ) && !malformedSectionRange ) {
|
||
callback((uint32_t)(info.sectAddr - info.segInfo.vmAddr));
|
||
}
|
||
});
|
||
}
|
||
|
||
bool MachOFile::hasExportTrie(uint32_t& runtimeOffset, uint32_t& size) const
|
||
{
|
||
__block uint64_t textUnslidVMAddr = 0;
|
||
__block uint64_t linkeditUnslidVMAddr = 0;
|
||
__block uint64_t linkeditFileOffset = 0;
|
||
forEachSegment(^(const SegmentInfo& info, bool& stop) {
|
||
if ( strcmp(info.segName, "__TEXT") == 0 ) {
|
||
textUnslidVMAddr = info.vmAddr;
|
||
} else if ( strcmp(info.segName, "__LINKEDIT") == 0 ) {
|
||
linkeditUnslidVMAddr = info.vmAddr;
|
||
linkeditFileOffset = info.fileOffset;
|
||
stop = true;
|
||
}
|
||
});
|
||
|
||
Diagnostics diag;
|
||
__block uint32_t fileOffset = ~0U;
|
||
this->forEachLoadCommand(diag, ^(const load_command* cmd, bool& stop) {
|
||
switch ( cmd->cmd ) {
|
||
case LC_DYLD_INFO:
|
||
case LC_DYLD_INFO_ONLY: {
|
||
const auto* dyldInfo = (const dyld_info_command*)cmd;
|
||
fileOffset = dyldInfo->export_off;
|
||
size = dyldInfo->export_size;
|
||
break;
|
||
}
|
||
case LC_DYLD_EXPORTS_TRIE: {
|
||
const auto* linkeditCmd = (const linkedit_data_command*)cmd;
|
||
fileOffset = linkeditCmd->dataoff;
|
||
size = linkeditCmd->datasize;
|
||
break;
|
||
}
|
||
}
|
||
});
|
||
if ( diag.hasError() )
|
||
return false;
|
||
|
||
if ( fileOffset == ~0U )
|
||
return false;
|
||
|
||
runtimeOffset = (uint32_t)((fileOffset - linkeditFileOffset) + (linkeditUnslidVMAddr - textUnslidVMAddr));
|
||
return true;
|
||
}
|
||
|
||
#if !TARGET_OS_EXCLAVEKIT
|
||
// Note, this has to match the kernel
|
||
static const uint32_t hashPriorities[] = {
|
||
CS_HASHTYPE_SHA1,
|
||
CS_HASHTYPE_SHA256_TRUNCATED,
|
||
CS_HASHTYPE_SHA256,
|
||
CS_HASHTYPE_SHA384,
|
||
};
|
||
|
||
static unsigned int hash_rank(const CS_CodeDirectory *cd)
|
||
{
|
||
uint32_t type = cd->hashType;
|
||
for (uint32_t n = 0; n < sizeof(hashPriorities) / sizeof(hashPriorities[0]); ++n) {
|
||
if (hashPriorities[n] == type)
|
||
return n + 1;
|
||
}
|
||
|
||
/* not supported */
|
||
return 0;
|
||
}
|
||
|
||
// Note, this does NOT match the kernel.
|
||
// On watchOS, in main executables, we will record all cd hashes then make sure
|
||
// one of the ones we record matches the kernel.
|
||
// This list is only for dylibs where we embed the cd hash in the closure instead of the
|
||
// mod time and inode
|
||
// This is sorted so that we choose sha1 first when checking dylibs
|
||
static const uint32_t hashPriorities_watchOS_dylibs[] = {
|
||
CS_HASHTYPE_SHA256_TRUNCATED,
|
||
CS_HASHTYPE_SHA256,
|
||
CS_HASHTYPE_SHA384,
|
||
CS_HASHTYPE_SHA1
|
||
};
|
||
|
||
static unsigned int hash_rank_watchOS_dylibs(const CS_CodeDirectory *cd)
|
||
{
|
||
uint32_t type = cd->hashType;
|
||
for (uint32_t n = 0; n < sizeof(hashPriorities_watchOS_dylibs) / sizeof(hashPriorities_watchOS_dylibs[0]); ++n) {
|
||
if (hashPriorities_watchOS_dylibs[n] == type)
|
||
return n + 1;
|
||
}
|
||
|
||
/* not supported */
|
||
return 0;
|
||
}
|
||
|
||
// This calls the callback for all code directories required for a given platform/binary combination.
|
||
// On watchOS main executables this is all cd hashes.
|
||
// On watchOS dylibs this is only the single cd hash we need (by rank defined by dyld, not the kernel).
|
||
// On all other platforms this always returns a single best cd hash (ranked to match the kernel).
|
||
// Note the callback parameter is really a CS_CodeDirectory.
|
||
void MachOFile::forEachCodeDirectoryBlob(const void* codeSigStart, size_t codeSignLen,
|
||
void (^callback)(const void* cd)) const
|
||
{
|
||
// verify min length of overall code signature
|
||
if ( codeSignLen < sizeof(CS_SuperBlob) )
|
||
return;
|
||
|
||
// verify magic at start
|
||
const CS_SuperBlob* codeSuperBlob = (CS_SuperBlob*)codeSigStart;
|
||
if ( codeSuperBlob->magic != htonl(CSMAGIC_EMBEDDED_SIGNATURE) )
|
||
return;
|
||
|
||
// verify count of sub-blobs not too large
|
||
uint32_t subBlobCount = htonl(codeSuperBlob->count);
|
||
if ( (codeSignLen-sizeof(CS_SuperBlob))/sizeof(CS_BlobIndex) < subBlobCount )
|
||
return;
|
||
|
||
// Note: The kernel sometimes chooses sha1 on watchOS, and sometimes sha256.
|
||
// Embed all of them so that we just need to match any of them
|
||
const bool isWatchOS = this->builtForPlatform(Platform::watchOS);
|
||
const bool isMainExecutable = this->isMainExecutable();
|
||
auto hashRankFn = isWatchOS ? &hash_rank_watchOS_dylibs : &hash_rank;
|
||
|
||
// walk each sub blob, looking at ones with type CSSLOT_CODEDIRECTORY
|
||
const CS_CodeDirectory* bestCd = nullptr;
|
||
for (uint32_t i=0; i < subBlobCount; ++i) {
|
||
if ( codeSuperBlob->index[i].type == htonl(CSSLOT_CODEDIRECTORY) ) {
|
||
// Ok, this is the regular code directory
|
||
} else if ( codeSuperBlob->index[i].type >= htonl(CSSLOT_ALTERNATE_CODEDIRECTORIES) && codeSuperBlob->index[i].type <= htonl(CSSLOT_ALTERNATE_CODEDIRECTORY_LIMIT)) {
|
||
// Ok, this is the alternative code directory
|
||
} else {
|
||
continue;
|
||
}
|
||
uint32_t cdOffset = htonl(codeSuperBlob->index[i].offset);
|
||
// verify offset is not out of range
|
||
if ( cdOffset > (codeSignLen - sizeof(CS_CodeDirectory)) )
|
||
continue;
|
||
const CS_CodeDirectory* cd = (CS_CodeDirectory*)((uint8_t*)codeSuperBlob + cdOffset);
|
||
uint32_t cdLength = htonl(cd->length);
|
||
// verify code directory length not out of range
|
||
if ( cdLength > (codeSignLen - cdOffset) )
|
||
continue;
|
||
|
||
// The watch main executable wants to know about all cd hashes
|
||
if ( isWatchOS && isMainExecutable ) {
|
||
callback(cd);
|
||
continue;
|
||
}
|
||
|
||
if ( cd->magic == htonl(CSMAGIC_CODEDIRECTORY) ) {
|
||
if ( !bestCd || (hashRankFn(cd) > hashRankFn(bestCd)) )
|
||
bestCd = cd;
|
||
}
|
||
}
|
||
|
||
// Note this callback won't happen on watchOS as that one was done in the loop
|
||
if ( bestCd != nullptr )
|
||
callback(bestCd);
|
||
}
|
||
|
||
void MachOFile::forEachCDHashOfCodeSignature(const void* codeSigStart, size_t codeSignLen,
|
||
void (^callback)(const uint8_t cdHash[20])) const
|
||
{
|
||
forEachCodeDirectoryBlob(codeSigStart, codeSignLen, ^(const void *cdBuffer) {
|
||
const CS_CodeDirectory* cd = (const CS_CodeDirectory*)cdBuffer;
|
||
uint32_t cdLength = htonl(cd->length);
|
||
uint8_t cdHash[20];
|
||
if ( cd->hashType == CS_HASHTYPE_SHA384 ) {
|
||
uint8_t digest[CCSHA384_OUTPUT_SIZE];
|
||
const struct ccdigest_info* di = ccsha384_di();
|
||
ccdigest_di_decl(di, tempBuf); // declares tempBuf array in stack
|
||
ccdigest_init(di, tempBuf);
|
||
ccdigest_update(di, tempBuf, cdLength, cd);
|
||
ccdigest_final(di, tempBuf, digest);
|
||
ccdigest_di_clear(di, tempBuf);
|
||
// cd-hash of sigs that use SHA384 is the first 20 bytes of the SHA384 of the code digest
|
||
memcpy(cdHash, digest, 20);
|
||
callback(cdHash);
|
||
return;
|
||
}
|
||
else if ( (cd->hashType == CS_HASHTYPE_SHA256) || (cd->hashType == CS_HASHTYPE_SHA256_TRUNCATED) ) {
|
||
uint8_t digest[CCSHA256_OUTPUT_SIZE];
|
||
const struct ccdigest_info* di = ccsha256_di();
|
||
ccdigest_di_decl(di, tempBuf); // declares tempBuf array in stack
|
||
ccdigest_init(di, tempBuf);
|
||
ccdigest_update(di, tempBuf, cdLength, cd);
|
||
ccdigest_final(di, tempBuf, digest);
|
||
ccdigest_di_clear(di, tempBuf);
|
||
// cd-hash of sigs that use SHA256 is the first 20 bytes of the SHA256 of the code digest
|
||
memcpy(cdHash, digest, 20);
|
||
callback(cdHash);
|
||
return;
|
||
}
|
||
else if ( cd->hashType == CS_HASHTYPE_SHA1 ) {
|
||
// compute hash directly into return buffer
|
||
const struct ccdigest_info* di = ccsha1_di();
|
||
ccdigest_di_decl(di, tempBuf); // declares tempBuf array in stack
|
||
ccdigest_init(di, tempBuf);
|
||
ccdigest_update(di, tempBuf, cdLength, cd);
|
||
ccdigest_final(di, tempBuf, cdHash);
|
||
ccdigest_di_clear(di, tempBuf);
|
||
callback(cdHash);
|
||
return;
|
||
}
|
||
});
|
||
}
|
||
#endif // !TARGET_OS_EXCLAVEKIT
|
||
|
||
// These are mangled symbols for all the variants of operator new and delete
|
||
// which a main executable can define (non-weak) and override the
|
||
// weak-def implementation in the OS.
|
||
static const char* const sTreatAsWeak[] = {
|
||
"__Znwm", "__ZnwmRKSt9nothrow_t",
|
||
"__Znam", "__ZnamRKSt9nothrow_t",
|
||
"__ZdlPv", "__ZdlPvRKSt9nothrow_t", "__ZdlPvm",
|
||
"__ZdaPv", "__ZdaPvRKSt9nothrow_t", "__ZdaPvm",
|
||
"__ZnwmSt11align_val_t", "__ZnwmSt11align_val_tRKSt9nothrow_t",
|
||
"__ZnamSt11align_val_t", "__ZnamSt11align_val_tRKSt9nothrow_t",
|
||
"__ZdlPvSt11align_val_t", "__ZdlPvSt11align_val_tRKSt9nothrow_t", "__ZdlPvmSt11align_val_t",
|
||
"__ZdaPvSt11align_val_t", "__ZdaPvSt11align_val_tRKSt9nothrow_t", "__ZdaPvmSt11align_val_t",
|
||
"__ZnwmSt19__type_descriptor_t", "__ZnamSt19__type_descriptor_t"
|
||
};
|
||
|
||
void MachOFile::forEachTreatAsWeakDef(void (^handler)(const char* symbolName))
|
||
{
|
||
for (const char* sym : sTreatAsWeak)
|
||
handler(sym);
|
||
}
|
||
|
||
MachOFile::PointerMetaData::PointerMetaData()
|
||
{
|
||
this->diversity = 0;
|
||
this->high8 = 0;
|
||
this->authenticated = 0;
|
||
this->key = 0;
|
||
this->usesAddrDiversity = 0;
|
||
}
|
||
|
||
MachOFile::PointerMetaData::PointerMetaData(const ChainedFixupPointerOnDisk* fixupLoc, uint16_t pointer_format)
|
||
{
|
||
this->diversity = 0;
|
||
this->high8 = 0;
|
||
this->authenticated = 0;
|
||
this->key = 0;
|
||
this->usesAddrDiversity = 0;
|
||
switch ( pointer_format ) {
|
||
case DYLD_CHAINED_PTR_ARM64E:
|
||
case DYLD_CHAINED_PTR_ARM64E_KERNEL:
|
||
case DYLD_CHAINED_PTR_ARM64E_USERLAND:
|
||
case DYLD_CHAINED_PTR_ARM64E_FIRMWARE:
|
||
case DYLD_CHAINED_PTR_ARM64E_USERLAND24:
|
||
this->authenticated = fixupLoc->arm64e.authRebase.auth;
|
||
if ( this->authenticated ) {
|
||
this->key = fixupLoc->arm64e.authRebase.key;
|
||
this->usesAddrDiversity = fixupLoc->arm64e.authRebase.addrDiv;
|
||
this->diversity = fixupLoc->arm64e.authRebase.diversity;
|
||
}
|
||
else if ( fixupLoc->arm64e.bind.bind == 0 ) {
|
||
this->high8 = fixupLoc->arm64e.rebase.high8;
|
||
}
|
||
break;
|
||
case DYLD_CHAINED_PTR_64:
|
||
case DYLD_CHAINED_PTR_64_OFFSET:
|
||
if ( fixupLoc->generic64.bind.bind == 0 )
|
||
this->high8 = fixupLoc->generic64.rebase.high8;
|
||
break;
|
||
}
|
||
}
|
||
|
||
bool MachOFile::PointerMetaData::operator==(const PointerMetaData& other) const
|
||
{
|
||
return (this->diversity == other.diversity)
|
||
&& (this->high8 == other.high8)
|
||
&& (this->authenticated == other.authenticated)
|
||
&& (this->key == other.key)
|
||
&& (this->usesAddrDiversity == other.usesAddrDiversity);
|
||
}
|
||
|
||
#if !SUPPORT_VM_LAYOUT
|
||
bool MachOFile::getLinkeditLayout(Diagnostics& diag, mach_o::LinkeditLayout& layout) const
|
||
{
|
||
// Note, in file layout all linkedit offsets are just file offsets.
|
||
// It is essential no-one calls this on a MachOLoaded or MachOAnalyzer
|
||
|
||
// FIXME: Other load commands
|
||
this->forEachLoadCommand(diag, ^(const load_command *cmd, bool &stop) {
|
||
switch ( cmd->cmd ) {
|
||
case LC_SYMTAB: {
|
||
const symtab_command* symTabCmd = (const symtab_command*)cmd;
|
||
|
||
// Record that we found a LC_SYMTAB
|
||
layout.hasSymTab = true;
|
||
|
||
// NList
|
||
uint64_t nlistEntrySize = this->is64() ? sizeof(struct nlist_64) : sizeof(struct nlist);
|
||
layout.symbolTable.fileOffset = symTabCmd->symoff;
|
||
layout.symbolTable.buffer = (uint8_t*)this + symTabCmd->symoff;
|
||
layout.symbolTable.bufferSize = (uint32_t)(symTabCmd->nsyms * nlistEntrySize);
|
||
layout.symbolTable.entryCount = symTabCmd->nsyms;
|
||
layout.symbolTable.hasLinkedit = true;
|
||
|
||
// Symbol strings
|
||
layout.symbolStrings.fileOffset = symTabCmd->stroff;
|
||
layout.symbolStrings.buffer = (uint8_t*)this + symTabCmd->stroff;
|
||
layout.symbolStrings.bufferSize = symTabCmd->strsize;
|
||
layout.symbolStrings.hasLinkedit = true;
|
||
break;
|
||
}
|
||
case LC_DYSYMTAB: {
|
||
const dysymtab_command* dynSymTabCmd = (const dysymtab_command*)cmd;
|
||
|
||
// Record that we found a LC_DYSYMTAB
|
||
layout.hasDynSymTab = true;
|
||
|
||
// Local relocs
|
||
layout.localRelocs.fileOffset = dynSymTabCmd->locreloff;
|
||
layout.localRelocs.buffer = (uint8_t*)this + dynSymTabCmd->locreloff;
|
||
layout.localRelocs.bufferSize = 0; // Use entryCount instead
|
||
layout.localRelocs.entryIndex = 0; // Use buffer instead
|
||
layout.localRelocs.entryCount = dynSymTabCmd->nlocrel;
|
||
layout.localRelocs.hasLinkedit = true;
|
||
|
||
// Extern relocs
|
||
layout.externRelocs.fileOffset = dynSymTabCmd->extreloff;
|
||
layout.externRelocs.buffer = (uint8_t*)this + dynSymTabCmd->extreloff;
|
||
layout.externRelocs.bufferSize = 0; // Use entryCount instead
|
||
layout.externRelocs.entryIndex = 0; // Use buffer instead
|
||
layout.externRelocs.entryCount = dynSymTabCmd->nextrel;
|
||
layout.externRelocs.hasLinkedit = true;
|
||
|
||
// Indirect symbol table
|
||
layout.indirectSymbolTable.fileOffset = dynSymTabCmd->indirectsymoff;
|
||
layout.indirectSymbolTable.buffer = (uint8_t*)this + dynSymTabCmd->indirectsymoff;
|
||
layout.indirectSymbolTable.bufferSize = 0; // Use entryCount instead
|
||
layout.indirectSymbolTable.entryIndex = 0; // Use buffer instead
|
||
layout.indirectSymbolTable.entryCount = dynSymTabCmd->nindirectsyms;
|
||
layout.indirectSymbolTable.hasLinkedit = true;
|
||
|
||
// Locals
|
||
layout.localSymbolTable.fileOffset = 0; // unused
|
||
layout.localSymbolTable.buffer = nullptr; // Use entryIndex instead
|
||
layout.localSymbolTable.bufferSize = 0; // Use entryCount instead
|
||
layout.localSymbolTable.entryIndex = dynSymTabCmd->ilocalsym;
|
||
layout.localSymbolTable.entryCount = dynSymTabCmd->nlocalsym;
|
||
layout.localSymbolTable.hasLinkedit = true;
|
||
|
||
// Globals
|
||
layout.globalSymbolTable.fileOffset = 0; // unused
|
||
layout.globalSymbolTable.buffer = nullptr; // Use entryIndex instead
|
||
layout.globalSymbolTable.bufferSize = 0; // Use entryCount instead
|
||
layout.globalSymbolTable.entryIndex = dynSymTabCmd->iextdefsym;
|
||
layout.globalSymbolTable.entryCount = dynSymTabCmd->nextdefsym;
|
||
layout.globalSymbolTable.hasLinkedit = true;
|
||
|
||
// Imports
|
||
layout.undefSymbolTable.fileOffset = 0; // unused
|
||
layout.undefSymbolTable.buffer = nullptr; // Use entryIndex instead
|
||
layout.undefSymbolTable.bufferSize = 0; // Use entryCount instead
|
||
layout.undefSymbolTable.entryIndex = dynSymTabCmd->iundefsym;
|
||
layout.undefSymbolTable.entryCount = dynSymTabCmd->nundefsym;
|
||
layout.undefSymbolTable.hasLinkedit = true;
|
||
break;
|
||
}
|
||
case LC_DYLD_INFO:
|
||
case LC_DYLD_INFO_ONLY: {
|
||
const dyld_info_command* linkeditCmd = (const dyld_info_command*)cmd;
|
||
|
||
// Record what kind of DYLD_INFO we found
|
||
layout.dyldInfoCmd = cmd->cmd;
|
||
|
||
// Rebase
|
||
layout.rebaseOpcodes.fileOffset = linkeditCmd->rebase_off;
|
||
layout.rebaseOpcodes.buffer = (uint8_t*)this + linkeditCmd->rebase_off;
|
||
layout.rebaseOpcodes.bufferSize = linkeditCmd->rebase_size;
|
||
layout.rebaseOpcodes.hasLinkedit = true;
|
||
|
||
// Bind
|
||
layout.regularBindOpcodes.fileOffset = linkeditCmd->bind_off;
|
||
layout.regularBindOpcodes.buffer = (uint8_t*)this + linkeditCmd->bind_off;
|
||
layout.regularBindOpcodes.bufferSize = linkeditCmd->bind_size;
|
||
layout.regularBindOpcodes.hasLinkedit = true;
|
||
|
||
// Lazy bind
|
||
layout.lazyBindOpcodes.fileOffset = linkeditCmd->lazy_bind_off;
|
||
layout.lazyBindOpcodes.buffer = (uint8_t*)this + linkeditCmd->lazy_bind_off;
|
||
layout.lazyBindOpcodes.bufferSize = linkeditCmd->lazy_bind_size;
|
||
layout.lazyBindOpcodes.hasLinkedit = true;
|
||
|
||
// Weak bind
|
||
layout.weakBindOpcodes.fileOffset = linkeditCmd->weak_bind_off;
|
||
layout.weakBindOpcodes.buffer = (uint8_t*)this + linkeditCmd->weak_bind_off;
|
||
layout.weakBindOpcodes.bufferSize = linkeditCmd->weak_bind_size;
|
||
layout.weakBindOpcodes.hasLinkedit = true;
|
||
|
||
// Export trie
|
||
layout.exportsTrie.fileOffset = linkeditCmd->export_off;
|
||
layout.exportsTrie.buffer = (uint8_t*)this + linkeditCmd->export_off;
|
||
layout.exportsTrie.bufferSize = linkeditCmd->export_size;
|
||
layout.exportsTrie.hasLinkedit = true;
|
||
break;
|
||
}
|
||
case LC_DYLD_CHAINED_FIXUPS: {
|
||
const linkedit_data_command* linkeditCmd = (const linkedit_data_command*)cmd;
|
||
|
||
layout.chainedFixups.fileOffset = linkeditCmd->dataoff;
|
||
layout.chainedFixups.buffer = (uint8_t*)this + linkeditCmd->dataoff;
|
||
layout.chainedFixups.bufferSize = linkeditCmd->datasize;
|
||
layout.chainedFixups.entryCount = 0; // Not needed here
|
||
layout.chainedFixups.hasLinkedit = true;
|
||
layout.chainedFixups.cmd = linkeditCmd;
|
||
break;
|
||
}
|
||
case LC_DYLD_EXPORTS_TRIE: {
|
||
const linkedit_data_command* linkeditCmd = (const linkedit_data_command*)cmd;
|
||
|
||
layout.exportsTrie.fileOffset = linkeditCmd->dataoff;
|
||
layout.exportsTrie.buffer = (uint8_t*)this + linkeditCmd->dataoff;
|
||
layout.exportsTrie.bufferSize = linkeditCmd->datasize;
|
||
layout.exportsTrie.entryCount = 0; // Not needed here
|
||
layout.exportsTrie.hasLinkedit = true;
|
||
break;
|
||
}
|
||
case LC_SEGMENT_SPLIT_INFO: {
|
||
const linkedit_data_command* linkeditCmd = (const linkedit_data_command*)cmd;
|
||
|
||
layout.splitSegInfo.fileOffset = linkeditCmd->dataoff;
|
||
layout.splitSegInfo.buffer = (uint8_t*)this + linkeditCmd->dataoff;
|
||
layout.splitSegInfo.bufferSize = linkeditCmd->datasize;
|
||
layout.splitSegInfo.entryCount = 0; // Not needed here
|
||
layout.splitSegInfo.hasLinkedit = true;
|
||
break;
|
||
}
|
||
case LC_FUNCTION_STARTS: {
|
||
const linkedit_data_command* linkeditCmd = (const linkedit_data_command*)cmd;
|
||
|
||
layout.functionStarts.fileOffset = linkeditCmd->dataoff;
|
||
layout.functionStarts.buffer = (uint8_t*)this + linkeditCmd->dataoff;
|
||
layout.functionStarts.bufferSize = linkeditCmd->datasize;
|
||
layout.functionStarts.entryCount = 0; // Not needed here
|
||
layout.functionStarts.hasLinkedit = true;
|
||
break;
|
||
}
|
||
case LC_DATA_IN_CODE: {
|
||
const linkedit_data_command* linkeditCmd = (const linkedit_data_command*)cmd;
|
||
|
||
layout.dataInCode.fileOffset = linkeditCmd->dataoff;
|
||
layout.dataInCode.buffer = (uint8_t*)this + linkeditCmd->dataoff;
|
||
layout.dataInCode.bufferSize = linkeditCmd->datasize;
|
||
layout.dataInCode.entryCount = 0; // Not needed here
|
||
layout.dataInCode.hasLinkedit = true;
|
||
break;
|
||
}
|
||
case LC_CODE_SIGNATURE: {
|
||
const linkedit_data_command* linkeditCmd = (const linkedit_data_command*)cmd;
|
||
|
||
layout.codeSignature.fileOffset = linkeditCmd->dataoff;
|
||
layout.codeSignature.buffer = (uint8_t*)this + linkeditCmd->dataoff;
|
||
layout.codeSignature.bufferSize = linkeditCmd->datasize;
|
||
layout.codeSignature.entryCount = 0; // Not needed here
|
||
layout.codeSignature.hasLinkedit = true;
|
||
break;
|
||
}
|
||
}
|
||
});
|
||
|
||
return true;
|
||
}
|
||
|
||
void MachOFile::withFileLayout(Diagnostics &diag, void (^callback)(const mach_o::Layout &layout)) const
|
||
{
|
||
// Use the fixups from the source dylib
|
||
mach_o::LinkeditLayout linkedit;
|
||
if ( !this->getLinkeditLayout(diag, linkedit) ) {
|
||
diag.error("Couldn't get dylib layout");
|
||
return;
|
||
}
|
||
|
||
uint32_t numSegments = this->segmentCount();
|
||
BLOCK_ACCCESSIBLE_ARRAY(mach_o::SegmentLayout, segmentLayout, numSegments);
|
||
this->forEachSegment(^(const SegmentInfo &info, bool &stop) {
|
||
mach_o::SegmentLayout segment;
|
||
segment.vmAddr = info.vmAddr;
|
||
segment.vmSize = info.vmSize;
|
||
segment.fileOffset = info.fileOffset;
|
||
segment.fileSize = info.fileSize;
|
||
segment.buffer = (uint8_t*)this + info.fileOffset;
|
||
segment.protections = info.protections;
|
||
|
||
segment.kind = mach_o::SegmentLayout::Kind::unknown;
|
||
if ( !strcmp(info.segName, "__TEXT") ) {
|
||
segment.kind = mach_o::SegmentLayout::Kind::text;
|
||
} else if ( !strcmp(info.segName, "__LINKEDIT") ) {
|
||
segment.kind = mach_o::SegmentLayout::Kind::linkedit;
|
||
}
|
||
|
||
segmentLayout[info.segIndex] = segment;
|
||
});
|
||
|
||
mach_o::Layout layout(this, { &segmentLayout[0], &segmentLayout[numSegments] }, linkedit);
|
||
callback(layout);
|
||
}
|
||
#endif // !SUPPORT_VM_LAYOUT
|
||
|
||
bool MachOFile::hasObjCMessageReferences() const {
|
||
|
||
__block bool foundSection = false;
|
||
forEachSection(^(const SectionInfo& sectInfo, bool malformedSectionRange, bool& stop) {
|
||
if ( strncmp(sectInfo.segInfo.segName, "__DATA", 6) != 0 )
|
||
return;
|
||
if ( strcmp(sectInfo.sectName, "__objc_msgrefs") != 0 )
|
||
return;
|
||
foundSection = true;
|
||
stop = true;
|
||
});
|
||
return foundSection;
|
||
}
|
||
|
||
uint32_t MachOFile::loadCommandsFreeSpace() const
|
||
{
|
||
__block uint32_t firstSectionFileOffset = 0;
|
||
__block uint32_t firstSegmentFileOffset = 0;
|
||
forEachSection(^(const SectionInfo& sectInfo, bool malformedSectionRange, bool& stop) {
|
||
firstSectionFileOffset = sectInfo.sectFileOffset;
|
||
firstSegmentFileOffset = (uint32_t)sectInfo.segInfo.fileOffset;
|
||
stop = true;
|
||
});
|
||
|
||
uint32_t headerSize = (this->magic == MH_MAGIC_64) ? sizeof(mach_header_64) : sizeof(mach_header);
|
||
uint32_t existSpaceUsed = this->sizeofcmds + headerSize;
|
||
return firstSectionFileOffset - firstSegmentFileOffset - existSpaceUsed;
|
||
}
|
||
|
||
bool MachOFile::findObjCDataSection(const char *sectionName, uint64_t& sectionRuntimeOffset, uint64_t& sectionSize) const
|
||
{
|
||
uint64_t baseAddress = preferredLoadAddress();
|
||
|
||
__block bool foundSection = false;
|
||
forEachSection(^(const SectionInfo& sectInfo, bool malformedSectionRange, bool& stop) {
|
||
if ( (strcmp(sectInfo.segInfo.segName, "__DATA") != 0) &&
|
||
(strcmp(sectInfo.segInfo.segName, "__DATA_CONST") != 0) &&
|
||
(strcmp(sectInfo.segInfo.segName, "__DATA_DIRTY") != 0) )
|
||
return;
|
||
if ( strcmp(sectInfo.sectName, sectionName) != 0 )
|
||
return;
|
||
foundSection = true;
|
||
sectionRuntimeOffset = sectInfo.sectAddr - baseAddress;
|
||
sectionSize = sectInfo.sectSize;
|
||
stop = true;
|
||
});
|
||
return foundSection;
|
||
}
|
||
|
||
bool MachOFile::enforceFormat(Malformed kind) const
|
||
{
|
||
// TODO: Add a mapping from generic releases to platform versions
|
||
#if BUILDING_DYLDINFO || BUILDING_APP_CACHE_UTIL || BUILDING_RUN_STATIC
|
||
// HACK: If we are the kernel, we have a different format to enforce
|
||
if ( isFileSet() ) {
|
||
bool result = false;
|
||
switch (kind) {
|
||
case Malformed::linkeditOrder:
|
||
case Malformed::linkeditAlignment:
|
||
case Malformed::dyldInfoAndlocalRelocs:
|
||
result = true;
|
||
break;
|
||
case Malformed::segmentOrder:
|
||
// The aux KC has __DATA first
|
||
result = false;
|
||
break;
|
||
case Malformed::linkeditPermissions:
|
||
case Malformed::executableData:
|
||
case Malformed::writableData:
|
||
case Malformed::codeSigAlignment:
|
||
case Malformed::sectionsAddrRangeWithinSegment:
|
||
case Malformed::loaderPathsAreReal:
|
||
case Malformed::mainExecInDyldCache:
|
||
result = true;
|
||
break;
|
||
case Malformed::noLinkedDylibs:
|
||
case Malformed::textPermissions:
|
||
// The kernel has its own __TEXT_EXEC for executable memory
|
||
result = false;
|
||
break;
|
||
case Malformed::noUUID:
|
||
case Malformed::zerofillSwiftMetadata:
|
||
case Malformed::sdkOnOrAfter2021:
|
||
case Malformed::sdkOnOrAfter2022:
|
||
result = true;
|
||
break;
|
||
}
|
||
return result;
|
||
}
|
||
|
||
if ( isStaticExecutable() ) {
|
||
bool result = false;
|
||
switch (kind) {
|
||
case Malformed::linkeditOrder:
|
||
case Malformed::linkeditAlignment:
|
||
case Malformed::dyldInfoAndlocalRelocs:
|
||
result = true;
|
||
break;
|
||
case Malformed::segmentOrder:
|
||
case Malformed::textPermissions:
|
||
result = false;
|
||
break;
|
||
case Malformed::linkeditPermissions:
|
||
case Malformed::executableData:
|
||
case Malformed::codeSigAlignment:
|
||
case Malformed::sectionsAddrRangeWithinSegment:
|
||
case Malformed::loaderPathsAreReal:
|
||
case Malformed::mainExecInDyldCache:
|
||
result = true;
|
||
break;
|
||
case Malformed::noLinkedDylibs:
|
||
case Malformed::writableData:
|
||
case Malformed::noUUID:
|
||
case Malformed::zerofillSwiftMetadata:
|
||
case Malformed::sdkOnOrAfter2021:
|
||
case Malformed::sdkOnOrAfter2022:
|
||
// The kernel has __DATA_CONST marked as r/o
|
||
result = false;
|
||
break;
|
||
}
|
||
return result;
|
||
}
|
||
|
||
#endif
|
||
|
||
__block bool result = false;
|
||
forEachSupportedPlatform(^(Platform platform, uint32_t minOS, uint32_t sdk) {
|
||
switch (platform) {
|
||
case Platform::macOS:
|
||
switch (kind) {
|
||
case Malformed::linkeditOrder:
|
||
case Malformed::linkeditAlignment:
|
||
case Malformed::dyldInfoAndlocalRelocs:
|
||
// enforce these checks on new binaries only
|
||
if (sdk >= 0x000A0E00) // macOS 10.14
|
||
result = true;
|
||
break;
|
||
case Malformed::segmentOrder:
|
||
case Malformed::linkeditPermissions:
|
||
case Malformed::textPermissions:
|
||
case Malformed::executableData:
|
||
case Malformed::writableData:
|
||
case Malformed::codeSigAlignment:
|
||
// enforce these checks on new binaries only
|
||
if (sdk >= 0x000A0F00) // macOS 10.15
|
||
result = true;
|
||
break;
|
||
case Malformed::sectionsAddrRangeWithinSegment:
|
||
// enforce these checks on new binaries only
|
||
if (sdk >= 0x000A1000) // macOS 10.16
|
||
result = true;
|
||
break;
|
||
case Malformed::noLinkedDylibs:
|
||
case Malformed::loaderPathsAreReal:
|
||
case Malformed::mainExecInDyldCache:
|
||
case Malformed::zerofillSwiftMetadata:
|
||
case Malformed::sdkOnOrAfter2021:
|
||
// enforce these checks on new binaries only
|
||
if (sdk >= 0x000D0000) // macOS 13.0
|
||
result = true;
|
||
break;
|
||
case Malformed::noUUID:
|
||
case Malformed::sdkOnOrAfter2022:
|
||
if (sdk >= 0x000E0000) // macOS 14.0 FIXME
|
||
result = true;
|
||
break;
|
||
}
|
||
break;
|
||
case Platform::iOS:
|
||
case Platform::tvOS:
|
||
case Platform::iOSMac:
|
||
switch (kind) {
|
||
case Malformed::linkeditOrder:
|
||
case Malformed::dyldInfoAndlocalRelocs:
|
||
case Malformed::textPermissions:
|
||
case Malformed::executableData:
|
||
case Malformed::writableData:
|
||
result = true;
|
||
break;
|
||
case Malformed::linkeditAlignment:
|
||
case Malformed::segmentOrder:
|
||
case Malformed::linkeditPermissions:
|
||
case Malformed::codeSigAlignment:
|
||
// enforce these checks on new binaries only
|
||
if (sdk >= 0x000D0000) // iOS 13
|
||
result = true;
|
||
break;
|
||
case Malformed::sectionsAddrRangeWithinSegment:
|
||
// enforce these checks on new binaries only
|
||
if (sdk >= 0x000E0000) // iOS 14
|
||
result = true;
|
||
break;
|
||
case Malformed::noLinkedDylibs:
|
||
case Malformed::loaderPathsAreReal:
|
||
case Malformed::mainExecInDyldCache:
|
||
case Malformed::zerofillSwiftMetadata:
|
||
case Malformed::sdkOnOrAfter2021:
|
||
// enforce these checks on new binaries only
|
||
if (sdk >= 0x00100000) // iOS 16
|
||
result = true;
|
||
break;
|
||
case Malformed::noUUID:
|
||
case Malformed::sdkOnOrAfter2022:
|
||
if (sdk >= 0x00110000) // iOS 17.0 FIXME
|
||
result = true;
|
||
break;
|
||
}
|
||
break;
|
||
case Platform::watchOS:
|
||
switch (kind) {
|
||
case Malformed::linkeditOrder:
|
||
case Malformed::dyldInfoAndlocalRelocs:
|
||
case Malformed::textPermissions:
|
||
case Malformed::executableData:
|
||
case Malformed::writableData:
|
||
result = true;
|
||
break;
|
||
case Malformed::linkeditAlignment:
|
||
case Malformed::segmentOrder:
|
||
case Malformed::linkeditPermissions:
|
||
case Malformed::codeSigAlignment:
|
||
case Malformed::sectionsAddrRangeWithinSegment:
|
||
case Malformed::noLinkedDylibs:
|
||
case Malformed::loaderPathsAreReal:
|
||
case Malformed::mainExecInDyldCache:
|
||
case Malformed::zerofillSwiftMetadata:
|
||
case Malformed::sdkOnOrAfter2021:
|
||
// enforce these checks on new binaries only
|
||
if (sdk >= 0x00090000) // watchOS 9
|
||
result = true;
|
||
break;
|
||
case Malformed::noUUID:
|
||
case Malformed::sdkOnOrAfter2022:
|
||
if (sdk >= 0x000A0000) // watchOS 10 FIXME
|
||
result = true;
|
||
break;
|
||
}
|
||
break;
|
||
case Platform::driverKit:
|
||
result = true;
|
||
break;
|
||
default:
|
||
result = true;
|
||
break;
|
||
}
|
||
});
|
||
// if binary is so old, there is no platform info, don't enforce malformed errors
|
||
return result;
|
||
}
|
||
|
||
bool MachOFile::validSegments(Diagnostics& diag, const char* path, size_t fileLen) const
|
||
{
|
||
// check segment load command size
|
||
__block bool badSegmentLoadCommand = false;
|
||
forEachLoadCommand(diag, ^(const load_command* cmd, bool& stop) {
|
||
if ( cmd->cmd == LC_SEGMENT_64 ) {
|
||
const segment_command_64* seg = (segment_command_64*)cmd;
|
||
int32_t sectionsSpace = cmd->cmdsize - sizeof(segment_command_64);
|
||
if ( sectionsSpace < 0 ) {
|
||
diag.error("in '%s' load command size too small for LC_SEGMENT_64", path);
|
||
badSegmentLoadCommand = true;
|
||
stop = true;
|
||
}
|
||
else if ( (sectionsSpace % sizeof(section_64)) != 0 ) {
|
||
diag.error("in '%s' segment load command size 0x%X will not fit whole number of sections", path, cmd->cmdsize);
|
||
badSegmentLoadCommand = true;
|
||
stop = true;
|
||
}
|
||
else if ( sectionsSpace != (int32_t)(seg->nsects * sizeof(section_64)) ) {
|
||
diag.error("in '%s' load command size 0x%X does not match nsects %d", path, cmd->cmdsize, seg->nsects);
|
||
badSegmentLoadCommand = true;
|
||
stop = true;
|
||
}
|
||
else if ( greaterThanAddOrOverflow(seg->fileoff, seg->filesize, fileLen) ) {
|
||
diag.error("in '%s' segment load command content extends beyond end of file", path);
|
||
badSegmentLoadCommand = true;
|
||
stop = true;
|
||
}
|
||
else if ( (seg->filesize > seg->vmsize) && ((seg->vmsize != 0) || ((seg->flags & SG_NORELOC) == 0)) ) {
|
||
// <rdar://problem/19986776> dyld should support non-allocatable __LLVM segment
|
||
diag.error("in '%s' segment '%s' filesize exceeds vmsize", path, seg->segname);
|
||
badSegmentLoadCommand = true;
|
||
stop = true;
|
||
}
|
||
}
|
||
else if ( cmd->cmd == LC_SEGMENT ) {
|
||
const segment_command* seg = (segment_command*)cmd;
|
||
int32_t sectionsSpace = cmd->cmdsize - sizeof(segment_command);
|
||
if ( sectionsSpace < 0 ) {
|
||
diag.error("in '%s' load command size too small for LC_SEGMENT", path);
|
||
badSegmentLoadCommand = true;
|
||
stop = true;
|
||
}
|
||
else if ( (sectionsSpace % sizeof(section)) != 0 ) {
|
||
diag.error("in '%s' segment load command size 0x%X will not fit whole number of sections", path, cmd->cmdsize);
|
||
badSegmentLoadCommand = true;
|
||
stop = true;
|
||
}
|
||
else if ( sectionsSpace != (int32_t)(seg->nsects * sizeof(section)) ) {
|
||
diag.error("in '%s' load command size 0x%X does not match nsects %d", path, cmd->cmdsize, seg->nsects);
|
||
badSegmentLoadCommand = true;
|
||
stop = true;
|
||
}
|
||
else if ( (seg->filesize > seg->vmsize) && ((seg->vmsize != 0) || ((seg->flags & SG_NORELOC) == 0)) ) {
|
||
// <rdar://problem/19986776> dyld should support non-allocatable __LLVM segment
|
||
diag.error("in '%s' segment '%s' filesize exceeds vmsize", path, seg->segname);
|
||
badSegmentLoadCommand = true;
|
||
stop = true;
|
||
}
|
||
}
|
||
});
|
||
if ( badSegmentLoadCommand )
|
||
return false;
|
||
|
||
// check mapping permissions of segments
|
||
__block bool badPermissions = false;
|
||
__block bool badSize = false;
|
||
__block bool hasTEXT = false;
|
||
__block bool hasLINKEDIT = false;
|
||
forEachSegment(^(const SegmentInfo& info, bool& stop) {
|
||
if ( strcmp(info.segName, "__TEXT") == 0 ) {
|
||
if ( (info.protections != (VM_PROT_READ|VM_PROT_EXECUTE)) && enforceFormat(Malformed::textPermissions) ) {
|
||
diag.error("in '%s' __TEXT segment permissions is not 'r-x'", path);
|
||
badPermissions = true;
|
||
stop = true;
|
||
}
|
||
hasTEXT = true;
|
||
}
|
||
else if ( strcmp(info.segName, "__LINKEDIT") == 0 ) {
|
||
if ( (info.protections != VM_PROT_READ) && enforceFormat(Malformed::linkeditPermissions) ) {
|
||
diag.error("in '%s' __LINKEDIT segment permissions is not 'r--'", path);
|
||
badPermissions = true;
|
||
stop = true;
|
||
}
|
||
hasLINKEDIT = true;
|
||
}
|
||
else if ( (info.protections & 0xFFFFFFF8) != 0 ) {
|
||
diag.error("in '%s' %s segment permissions has invalid bits set", path, info.segName);
|
||
badPermissions = true;
|
||
stop = true;
|
||
}
|
||
if ( greaterThanAddOrOverflow(info.fileOffset, info.fileSize, fileLen) ) {
|
||
diag.error("in '%s' %s segment content extends beyond end of file", path, info.segName);
|
||
badSize = true;
|
||
stop = true;
|
||
}
|
||
if ( is64() ) {
|
||
if ( info.vmAddr+info.vmSize < info.vmAddr ) {
|
||
diag.error("in '%s' %s segment vm range wraps", path, info.segName);
|
||
badSize = true;
|
||
stop = true;
|
||
}
|
||
}
|
||
else {
|
||
if ( (uint32_t)(info.vmAddr+info.vmSize) < (uint32_t)(info.vmAddr) ) {
|
||
diag.error("in '%s' %s segment vm range wraps", path, info.segName);
|
||
badSize = true;
|
||
stop = true;
|
||
}
|
||
}
|
||
});
|
||
if ( badPermissions || badSize )
|
||
return false;
|
||
if ( !hasTEXT ) {
|
||
diag.error("in '%s' missing __TEXT segment", path);
|
||
return false;
|
||
}
|
||
if ( !hasLINKEDIT && !this->isPreload() ) {
|
||
diag.error("in '%s' missing __LINKEDIT segment", path);
|
||
return false;
|
||
}
|
||
|
||
// check for overlapping segments
|
||
__block bool badSegments = false;
|
||
forEachSegment(^(const SegmentInfo& info1, bool& stop1) {
|
||
uint64_t seg1vmEnd = info1.vmAddr + info1.vmSize;
|
||
uint64_t seg1FileEnd = info1.fileOffset + info1.fileSize;
|
||
forEachSegment(^(const SegmentInfo& info2, bool& stop2) {
|
||
if ( info1.segIndex == info2.segIndex )
|
||
return;
|
||
uint64_t seg2vmEnd = info2.vmAddr + info2.vmSize;
|
||
uint64_t seg2FileEnd = info2.fileOffset + info2.fileSize;
|
||
if ( ((info2.vmAddr <= info1.vmAddr) && (seg2vmEnd > info1.vmAddr) && (seg1vmEnd > info1.vmAddr )) || ((info2.vmAddr >= info1.vmAddr ) && (info2.vmAddr < seg1vmEnd) && (seg2vmEnd > info2.vmAddr)) ) {
|
||
diag.error("in '%s' segment %s vm range overlaps segment %s", path, info1.segName, info2.segName);
|
||
badSegments = true;
|
||
stop1 = true;
|
||
stop2 = true;
|
||
}
|
||
if ( ((info2.fileOffset <= info1.fileOffset) && (seg2FileEnd > info1.fileOffset) && (seg1FileEnd > info1.fileOffset)) || ((info2.fileOffset >= info1.fileOffset) && (info2.fileOffset < seg1FileEnd) && (seg2FileEnd > info2.fileOffset )) ) {
|
||
if ( !inDyldCache() ) {
|
||
// HACK: Split shared caches might put the __TEXT in a SubCache, then the __DATA in a later SubCache.
|
||
// The file offsets are in to each SubCache file, which means that they might overlap
|
||
// For now we have no choice but to disable this error
|
||
diag.error("in '%s' segment %s file content overlaps segment %s", path, info1.segName, info2.segName);
|
||
badSegments = true;
|
||
stop1 = true;
|
||
stop2 = true;
|
||
}
|
||
}
|
||
if ( (info1.segIndex < info2.segIndex) && !stop1 ) {
|
||
if ( (info1.vmAddr > info2.vmAddr) || ((info1.fileOffset > info2.fileOffset ) && (info1.fileOffset != 0) && (info2.fileOffset != 0)) ){
|
||
if ( !inDyldCache() && enforceFormat(Malformed::segmentOrder) && !isStaticExecutable() ) {
|
||
// <rdar://80084852> whitelist go libraries __DWARF segments
|
||
if ( (strcmp(info1.segName, "__DWARF") != 0 && strcmp(info2.segName, "__DWARF") != 0) ) {
|
||
// dyld cache __DATA_* segments are moved around
|
||
// The static kernel also has segments with vmAddr's before __TEXT
|
||
diag.error("in '%s' segment load commands out of order with respect to layout for %s and %s", path, info1.segName, info2.segName);
|
||
badSegments = true;
|
||
stop1 = true;
|
||
stop2 = true;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
});
|
||
});
|
||
if ( badSegments )
|
||
return false;
|
||
|
||
// check sections are within segment
|
||
__block bool badSections = false;
|
||
forEachLoadCommand(diag, ^(const load_command* cmd, bool& stop) {
|
||
if ( cmd->cmd == LC_SEGMENT_64 ) {
|
||
const segment_command_64* seg = (segment_command_64*)cmd;
|
||
const section_64* const sectionsStart = (section_64*)((char*)seg + sizeof(struct segment_command_64));
|
||
const section_64* const sectionsEnd = §ionsStart[seg->nsects];
|
||
for (const section_64* sect=sectionsStart; (sect < sectionsEnd); ++sect) {
|
||
if ( (int64_t)(sect->size) < 0 ) {
|
||
diag.error("in '%s' section '%s' size too large 0x%llX", path, sect->sectname, sect->size);
|
||
badSections = true;
|
||
}
|
||
else if ( sect->addr < seg->vmaddr ) {
|
||
diag.error("in '%s' section '%s' start address 0x%llX is before containing segment's address 0x%0llX", path, sect->sectname, sect->addr, seg->vmaddr);
|
||
badSections = true;
|
||
}
|
||
else if ( sect->addr+sect->size > seg->vmaddr+seg->vmsize ) {
|
||
bool ignoreError = !enforceFormat(Malformed::sectionsAddrRangeWithinSegment);
|
||
#if BUILDING_APP_CACHE_UTIL
|
||
if ( (seg->vmsize == 0) && !strcmp(seg->segname, "__CTF") )
|
||
ignoreError = true;
|
||
#endif
|
||
if ( !ignoreError ) {
|
||
diag.error("in '%s' section '%s' end address 0x%llX is beyond containing segment's end address 0x%0llX", path, sect->sectname, sect->addr+sect->size, seg->vmaddr+seg->vmsize);
|
||
badSections = true;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
else if ( cmd->cmd == LC_SEGMENT ) {
|
||
const segment_command* seg = (segment_command*)cmd;
|
||
const section* const sectionsStart = (section*)((char*)seg + sizeof(struct segment_command));
|
||
const section* const sectionsEnd = §ionsStart[seg->nsects];
|
||
for (const section* sect=sectionsStart; !stop && (sect < sectionsEnd); ++sect) {
|
||
if ( (int64_t)(sect->size) < 0 ) {
|
||
diag.error("in '%s' section %s size too large 0x%X", path, sect->sectname, sect->size);
|
||
badSections = true;
|
||
}
|
||
else if ( sect->addr < seg->vmaddr ) {
|
||
diag.error("in '%s' section %s start address 0x%X is before containing segment's address 0x%0X", path, sect->sectname, sect->addr, seg->vmaddr);
|
||
badSections = true;
|
||
}
|
||
else if ( sect->addr+sect->size > seg->vmaddr+seg->vmsize ) {
|
||
diag.error("in '%s' section %s end address 0x%X is beyond containing segment's end address 0x%0X", path, sect->sectname, sect->addr+sect->size, seg->vmaddr+seg->vmsize);
|
||
badSections = true;
|
||
}
|
||
}
|
||
}
|
||
});
|
||
|
||
return !badSections;
|
||
}
|
||
|
||
void MachOFile::forEachSingletonPatch(Diagnostics& diag, void (^handler)(SingletonPatchKind kind,
|
||
uint64_t runtimeOffset)) const
|
||
{
|
||
uint32_t ptrSize = this->pointerSize();
|
||
uint32_t elementSize = (2 * ptrSize);
|
||
uint64_t loadAddress = this->preferredLoadAddress();
|
||
this->forEachSection(^(const SectionInfo §Info, bool malformedSectionRange, bool &stop) {
|
||
if ( strcmp(sectInfo.sectName, "__const_cfobj2") != 0 )
|
||
return;
|
||
stop = true;
|
||
|
||
if ( (sectInfo.sectSize % elementSize) != 0 ) {
|
||
diag.error("Incorrect patching size (%lld). Should be a multiple of (2 * ptrSize)", sectInfo.sectSize);
|
||
return;
|
||
}
|
||
|
||
if ( sectInfo.reserved2 != elementSize ) {
|
||
// ld64 must have rejected one or more of the elements in the section, so
|
||
// didn't set the reserved2 to let us patch
|
||
diag.error("reserved2 is unsupported value %d. Expected %d",
|
||
sectInfo.reserved2, elementSize);
|
||
return;
|
||
}
|
||
|
||
for ( uint64_t offset = 0; offset != sectInfo.sectSize; offset += elementSize ) {
|
||
uint64_t targetRuntimeOffset = (sectInfo.sectAddr + offset) - loadAddress;
|
||
handler(SingletonPatchKind::cfObj2, targetRuntimeOffset);
|
||
}
|
||
});
|
||
}
|
||
|
||
|
||
} // namespace dyld3
|