Skip to content
Permalink
1128192c01
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Go to file
 
 
Cannot retrieve contributors at this time
3017 lines (2752 sloc) 115 KB
/* -*- mode: C++; c-basic-offset: 4; tab-width: 4 -*-
*
* Copyright (c) 2004-2010 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@
*/
// work around until conformance work is complete rdar://problem/4508801
#define __srr0 srr0
#define __eip eip
#define __rip rip
#define __STDC_LIMIT_MACROS
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/fcntl.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <mach/mach.h>
#include <mach/thread_status.h>
#include <mach-o/loader.h>
#include <mach-o/nlist.h>
#include <mach-o/dyld_images.h>
#include <sys/sysctl.h>
#include <sys/syscall.h>
#include <libkern/OSAtomic.h>
#include <libkern/OSCacheControl.h>
#include <stdint.h>
#include <System/sys/codesign.h>
#if __has_feature(ptrauth_calls)
#include <ptrauth.h>
#endif
#include "ImageLoaderMachO.h"
#include "ImageLoaderMachOCompressed.h"
#if SUPPORT_CLASSIC_MACHO
#include "ImageLoaderMachOClassic.h"
#endif
#include "Tracing.h"
#include "dyld2.h"
// <rdar://problem/8718137> use stack guard random value to add padding between dylibs
extern "C" long __stack_chk_guard;
#define LIBSYSTEM_DYLIB_PATH "/usr/lib/libSystem.B.dylib"
#define LIBDYLD_DYLIB_PATH "/usr/lib/system/libdyld.dylib"
#if TARGET_OS_OSX
#define DRIVERKIT_LIBSYSTEM_DYLIB_PATH "/System/DriverKit/usr/lib/libSystem.dylib"
#define DRIVERKIT_LIBDYLD_DYLIB_PATH "/System/DriverKit/usr/lib/system/libdyld.dylib"
#endif
// relocation_info.r_length field has value 3 for 64-bit executables and value 2 for 32-bit executables
#if __LP64__
#define LC_SEGMENT_COMMAND LC_SEGMENT_64
#define LC_ROUTINES_COMMAND LC_ROUTINES_64
#define LC_SEGMENT_COMMAND_WRONG LC_SEGMENT
struct macho_segment_command : public segment_command_64 {};
struct macho_section : public section_64 {};
struct macho_routines_command : public routines_command_64 {};
#else
#define LC_SEGMENT_COMMAND LC_SEGMENT
#define LC_ROUTINES_COMMAND LC_ROUTINES
#define LC_SEGMENT_COMMAND_WRONG LC_SEGMENT_64
struct macho_segment_command : public segment_command {};
struct macho_section : public section {};
struct macho_routines_command : public routines_command {};
#endif
uint32_t ImageLoaderMachO::fgSymbolTableBinarySearchs = 0;
ImageLoaderMachO::ImageLoaderMachO(const macho_header* mh, const char* path, unsigned int segCount,
uint32_t segOffsets[], unsigned int libCount)
: ImageLoader(path, libCount), fCoveredCodeLength(0), fMachOData((uint8_t*)mh), fLinkEditBase(NULL), fSlide(0),
fEHFrameSectionOffset(0), fUnwindInfoSectionOffset(0), fDylibIDOffset(0),
fSegmentsCount(segCount), fIsSplitSeg(false), fInSharedCache(false),
#if TEXT_RELOC_SUPPORT
fTextSegmentRebases(false),
fTextSegmentBinds(false),
#else
fReadOnlyDataSegment(false),
#endif
#if __i386__
fReadOnlyImportSegment(false),
#endif
fHasSubLibraries(false), fHasSubUmbrella(false), fInUmbrella(false), fHasDOFSections(false), fHasDashInit(false),
fHasInitializers(false), fHasTerminators(false), fNotifyObjC(false), fRetainForObjC(false), fRegisteredAsRequiresCoalescing(false), fOverrideOfCacheImageNum(0)
{
fIsSplitSeg = ((mh->flags & MH_SPLIT_SEGS) != 0);
// construct SegmentMachO object for each LC_SEGMENT cmd using "placement new" to put
// each SegmentMachO object in array at end of ImageLoaderMachO object
const uint32_t cmd_count = mh->ncmds;
const struct load_command* const cmds = (struct load_command*)&fMachOData[sizeof(macho_header)];
const struct load_command* cmd = cmds;
for (uint32_t i = 0, segIndex=0; i < cmd_count; ++i) {
if ( cmd->cmd == LC_SEGMENT_COMMAND ) {
const struct macho_segment_command* segCmd = (struct macho_segment_command*)cmd;
// ignore zero-sized segments
if ( segCmd->vmsize != 0 ) {
// record offset of load command
segOffsets[segIndex++] = (uint32_t)((uint8_t*)segCmd - fMachOData);
}
}
cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize);
}
}
#if TARGET_OS_OSX
static uintptr_t pageAlign(uintptr_t value)
{
return (value + 4095) & (-4096);
}
#endif
// determine if this mach-o file has classic or compressed LINKEDIT and number of segments it has
void ImageLoaderMachO::sniffLoadCommands(const macho_header* mh, const char* path, bool inCache, bool* compressed,
unsigned int* segCount, unsigned int* libCount, const LinkContext& context,
const linkedit_data_command** codeSigCmd,
const encryption_info_command** encryptCmd)
{
*compressed = false;
*segCount = 0;
*libCount = 0;
*codeSigCmd = NULL;
*encryptCmd = NULL;
const uint32_t cmd_count = mh->ncmds;
const uint32_t sizeofcmds = mh->sizeofcmds;
if ( cmd_count > (sizeofcmds/sizeof(load_command)) )
dyld::throwf("malformed mach-o: ncmds (%u) too large to fit in sizeofcmds (%u)", cmd_count, sizeofcmds);
const struct load_command* const startCmds = (struct load_command*)(((uint8_t*)mh) + sizeof(macho_header));
const struct load_command* const endCmds = (struct load_command*)(((uint8_t*)mh) + sizeof(macho_header) + sizeofcmds);
const struct load_command* cmd = startCmds;
bool foundLoadCommandSegment = false;
const macho_segment_command* linkeditSegCmd = NULL;
const macho_segment_command* startOfFileSegCmd = NULL;
const dyld_info_command* dyldInfoCmd = NULL;
const linkedit_data_command* chainedFixupsCmd = NULL;
const linkedit_data_command* exportsTrieCmd = NULL;
const symtab_command* symTabCmd = NULL;
const dysymtab_command* dynSymbTabCmd = NULL;
for (uint32_t i = 0; i < cmd_count; ++i) {
uint32_t cmdLength = cmd->cmdsize;
const macho_segment_command* segCmd;
const dylib_command* dylibCmd;
if ( cmdLength < 8 ) {
dyld::throwf("malformed mach-o image: load command #%d length (%u) too small in %s",
i, cmdLength, path);
}
const struct load_command* const nextCmd = (const struct load_command*)(((char*)cmd)+cmdLength);
if ( (nextCmd > endCmds) || (nextCmd < cmd) ) {
dyld::throwf("malformed mach-o image: load command #%d length (%u) would exceed sizeofcmds (%u) in %s",
i, cmdLength, mh->sizeofcmds, path);
}
switch (cmd->cmd) {
case LC_DYLD_INFO:
case LC_DYLD_INFO_ONLY:
if ( cmd->cmdsize != sizeof(dyld_info_command) )
throw "malformed mach-o image: LC_DYLD_INFO size wrong";
dyldInfoCmd = (struct dyld_info_command*)cmd;
*compressed = true;
break;
case LC_DYLD_CHAINED_FIXUPS:
if ( cmd->cmdsize != sizeof(linkedit_data_command) )
throw "malformed mach-o image: LC_DYLD_CHAINED_FIXUPS size wrong";
chainedFixupsCmd = (struct linkedit_data_command*)cmd;
*compressed = true;
break;
case LC_DYLD_EXPORTS_TRIE:
if ( cmd->cmdsize != sizeof(linkedit_data_command) )
throw "malformed mach-o image: LC_DYLD_EXPORTS_TRIE size wrong";
exportsTrieCmd = (struct linkedit_data_command*)cmd;
break;
case LC_SEGMENT_COMMAND:
segCmd = (struct macho_segment_command*)cmd;
#if TARGET_OS_OSX
// rdar://problem/19617624 allow unmapped segments on OSX (but not iOS)
if ( ((segCmd->filesize) > pageAlign(segCmd->vmsize)) && (segCmd->vmsize != 0) )
#else
// <rdar://problem/19986776> dyld should support non-allocatable __LLVM segment
if ( (segCmd->filesize > segCmd->vmsize) && ((segCmd->vmsize != 0) || ((segCmd->flags & SG_NORELOC) == 0)) )
#endif
dyld::throwf("malformed mach-o image: segment load command %s filesize (0x%0lX) is larger than vmsize (0x%0lX)", segCmd->segname, (long)segCmd->filesize , (long)segCmd->vmsize );
if ( cmd->cmdsize < sizeof(macho_segment_command) )
throw "malformed mach-o image: LC_SEGMENT size too small";
if ( cmd->cmdsize != (sizeof(macho_segment_command) + segCmd->nsects * sizeof(macho_section)) )
throw "malformed mach-o image: LC_SEGMENT size wrong for number of sections";
// ignore zero-sized segments
if ( segCmd->vmsize != 0 )
*segCount += 1;
if ( strcmp(segCmd->segname, "__LINKEDIT") == 0 ) {
#if TARGET_OS_SIMULATOR
// Note: should check on all platforms that __LINKEDIT is read-only, but <rdar://problem/22637626&22525618>
if ( segCmd->initprot != VM_PROT_READ )
throw "malformed mach-o image: __LINKEDIT segment does not have read-only permissions";
#endif
if ( segCmd->fileoff == 0 )
throw "malformed mach-o image: __LINKEDIT has fileoff==0 which overlaps mach_header";
if ( linkeditSegCmd != NULL )
throw "malformed mach-o image: multiple __LINKEDIT segments";
linkeditSegCmd = segCmd;
}
else {
if ( segCmd->initprot & 0xFFFFFFF8 )
dyld::throwf("malformed mach-o image: %s segment has invalid permission bits (0x%X) in initprot", segCmd->segname, segCmd->initprot);
if ( segCmd->maxprot & 0xFFFFFFF8 )
dyld::throwf("malformed mach-o image: %s segment has invalid permission bits (0x%X) in maxprot", segCmd->segname, segCmd->maxprot);
if ( (segCmd->initprot != 0) && ((segCmd->initprot & VM_PROT_READ) == 0) )
dyld::throwf("malformed mach-o image: %s segment is not mapped readable", segCmd->segname);
}
if ( (segCmd->fileoff == 0) && (segCmd->filesize != 0) ) {
if ( (segCmd->initprot & VM_PROT_READ) == 0 )
dyld::throwf("malformed mach-o image: %s segment maps start of file but is not readable", segCmd->segname);
if ( (segCmd->initprot & VM_PROT_WRITE) == VM_PROT_WRITE ) {
if ( context.strictMachORequired )
dyld::throwf("malformed mach-o image: %s segment maps start of file but is writable", segCmd->segname);
}
if ( segCmd->filesize < (sizeof(macho_header) + mh->sizeofcmds) )
dyld::throwf("malformed mach-o image: %s segment does not map all of load commands", segCmd->segname);
if ( startOfFileSegCmd != NULL )
dyld::throwf("malformed mach-o image: multiple segments map start of file: %s %s", startOfFileSegCmd->segname, segCmd->segname);
startOfFileSegCmd = segCmd;
}
if ( context.strictMachORequired ) {
uintptr_t vmStart = segCmd->vmaddr;
uintptr_t vmSize = segCmd->vmsize;
uintptr_t vmEnd = vmStart + vmSize;
uintptr_t fileStart = segCmd->fileoff;
uintptr_t fileSize = segCmd->filesize;
if ( (intptr_t)(vmSize) < 0 )
dyld::throwf("malformed mach-o image: segment load command %s vmsize too large in %s", segCmd->segname, path);
if ( vmStart > vmEnd )
dyld::throwf("malformed mach-o image: segment load command %s wraps around address space", segCmd->segname);
if ( vmSize != fileSize ) {
if ( segCmd->initprot == 0 ) {
// allow: fileSize == 0 && initprot == 0 e.g. __PAGEZERO
// allow: vmSize == 0 && initprot == 0 e.g. __LLVM
if ( (fileSize != 0) && (vmSize != 0) )
dyld::throwf("malformed mach-o image: unaccessable segment %s has non-zero filesize and vmsize", segCmd->segname);
}
else {
// allow: vmSize > fileSize && initprot != X e.g. __DATA
if ( vmSize < fileSize ) {
dyld::throwf("malformed mach-o image: segment %s has vmsize < filesize", segCmd->segname);
}
if ( segCmd->initprot & VM_PROT_EXECUTE ) {
dyld::throwf("malformed mach-o image: segment %s has vmsize != filesize and is executable", segCmd->segname);
}
}
}
if ( inCache ) {
if ( (fileSize != 0) && (segCmd->initprot == (VM_PROT_READ | VM_PROT_EXECUTE)) ) {
if ( foundLoadCommandSegment )
throw "load commands in multiple segments";
foundLoadCommandSegment = true;
}
}
else if ( (fileStart < mh->sizeofcmds) && (fileSize != 0) ) {
// <rdar://problem/7942521> all load commands must be in an executable segment
if ( (fileStart != 0) || (fileSize < (mh->sizeofcmds+sizeof(macho_header))) )
dyld::throwf("malformed mach-o image: segment %s does not span all load commands", segCmd->segname);
if ( segCmd->initprot != (VM_PROT_READ | VM_PROT_EXECUTE) )
dyld::throwf("malformed mach-o image: load commands found in segment %s with wrong permissions", segCmd->segname);
if ( foundLoadCommandSegment )
throw "load commands in multiple segments";
foundLoadCommandSegment = true;
}
const struct macho_section* const sectionsStart = (struct macho_section*)((char*)segCmd + sizeof(struct macho_segment_command));
const struct macho_section* const sectionsEnd = &sectionsStart[segCmd->nsects];
for (const struct macho_section* sect=sectionsStart; sect < sectionsEnd; ++sect) {
if (!inCache && sect->offset != 0 && ((sect->offset + sect->size) > (segCmd->fileoff + segCmd->filesize)))
dyld::throwf("malformed mach-o image: section %s,%s of '%s' exceeds segment %s booundary", sect->segname, sect->sectname, path, segCmd->segname);
}
}
break;
case LC_SEGMENT_COMMAND_WRONG:
dyld::throwf("malformed mach-o image: wrong LC_SEGMENT[_64] for architecture");
break;
case LC_LOAD_DYLIB:
case LC_LOAD_WEAK_DYLIB:
case LC_REEXPORT_DYLIB:
case LC_LOAD_UPWARD_DYLIB:
*libCount += 1;
// fall thru
[[clang::fallthrough]];
case LC_ID_DYLIB:
dylibCmd = (dylib_command*)cmd;
if ( dylibCmd->dylib.name.offset > cmdLength )
dyld::throwf("malformed mach-o image: dylib load command #%d has offset (%u) outside its size (%u)", i, dylibCmd->dylib.name.offset, cmdLength);
if ( (dylibCmd->dylib.name.offset + strlen((char*)dylibCmd + dylibCmd->dylib.name.offset) + 1) > cmdLength )
dyld::throwf("malformed mach-o image: dylib load command #%d string extends beyond end of load command", i);
break;
case LC_CODE_SIGNATURE:
if ( cmd->cmdsize != sizeof(linkedit_data_command) )
throw "malformed mach-o image: LC_CODE_SIGNATURE size wrong";
// <rdar://problem/22799652> only support one LC_CODE_SIGNATURE per image
if ( *codeSigCmd != NULL )
throw "malformed mach-o image: multiple LC_CODE_SIGNATURE load commands";
*codeSigCmd = (struct linkedit_data_command*)cmd;
break;
case LC_ENCRYPTION_INFO:
if ( cmd->cmdsize != sizeof(encryption_info_command) )
throw "malformed mach-o image: LC_ENCRYPTION_INFO size wrong";
// <rdar://problem/22799652> only support one LC_ENCRYPTION_INFO per image
if ( *encryptCmd != NULL )
throw "malformed mach-o image: multiple LC_ENCRYPTION_INFO load commands";
*encryptCmd = (encryption_info_command*)cmd;
break;
case LC_ENCRYPTION_INFO_64:
if ( cmd->cmdsize != sizeof(encryption_info_command_64) )
throw "malformed mach-o image: LC_ENCRYPTION_INFO_64 size wrong";
// <rdar://problem/22799652> only support one LC_ENCRYPTION_INFO_64 per image
if ( *encryptCmd != NULL )
throw "malformed mach-o image: multiple LC_ENCRYPTION_INFO_64 load commands";
*encryptCmd = (encryption_info_command*)cmd;
break;
case LC_SYMTAB:
if ( cmd->cmdsize != sizeof(symtab_command) )
throw "malformed mach-o image: LC_SYMTAB size wrong";
symTabCmd = (symtab_command*)cmd;
break;
case LC_DYSYMTAB:
if ( cmd->cmdsize != sizeof(dysymtab_command) )
throw "malformed mach-o image: LC_DYSYMTAB size wrong";
dynSymbTabCmd = (dysymtab_command*)cmd;
break;
#if TARGET_OS_OSX
// <rdar://problem/26797345> error when loading iOS Simulator mach-o binary into macOS process
case LC_VERSION_MIN_WATCHOS:
case LC_VERSION_MIN_TVOS:
case LC_VERSION_MIN_IPHONEOS:
if ( !context.iOSonMac )
throw "mach-o, but built for simulator (not macOS)";
break;
#endif
}
cmd = nextCmd;
}
if ( context.strictMachORequired && !foundLoadCommandSegment )
throw "load commands not in a segment";
if ( linkeditSegCmd == NULL )
throw "malformed mach-o image: missing __LINKEDIT segment";
if ( !inCache && (startOfFileSegCmd == NULL) )
throw "malformed mach-o image: missing __TEXT segment that maps start of file";
// <rdar://problem/13145644> verify every segment does not overlap another segment
if ( context.strictMachORequired && !inCache ) {
uintptr_t lastFileStart = 0;
uintptr_t linkeditFileStart = 0;
const struct load_command* cmd1 = startCmds;
for (uint32_t i = 0; i < cmd_count; ++i) {
if ( cmd1->cmd == LC_SEGMENT_COMMAND ) {
struct macho_segment_command* segCmd1 = (struct macho_segment_command*)cmd1;
uintptr_t vmStart1 = segCmd1->vmaddr;
uintptr_t vmEnd1 = segCmd1->vmaddr + segCmd1->vmsize;
uintptr_t fileStart1 = segCmd1->fileoff;
uintptr_t fileEnd1 = segCmd1->fileoff + segCmd1->filesize;
if (fileStart1 > lastFileStart)
lastFileStart = fileStart1;
if ( strcmp(&segCmd1->segname[0], "__LINKEDIT") == 0 ) {
linkeditFileStart = fileStart1;
}
const struct load_command* cmd2 = startCmds;
for (uint32_t j = 0; j < cmd_count; ++j) {
if ( cmd2 == cmd1 )
continue;
if ( cmd2->cmd == LC_SEGMENT_COMMAND ) {
struct macho_segment_command* segCmd2 = (struct macho_segment_command*)cmd2;
uintptr_t vmStart2 = segCmd2->vmaddr;
uintptr_t vmEnd2 = segCmd2->vmaddr + segCmd2->vmsize;
uintptr_t fileStart2 = segCmd2->fileoff;
uintptr_t fileEnd2 = segCmd2->fileoff + segCmd2->filesize;
if ( ((vmStart2 <= vmStart1) && (vmEnd2 > vmStart1) && (vmEnd1 > vmStart1))
|| ((vmStart2 >= vmStart1) && (vmStart2 < vmEnd1) && (vmEnd2 > vmStart2)) )
dyld::throwf("malformed mach-o image: segment %s vm overlaps segment %s", segCmd1->segname, segCmd2->segname);
if ( ((fileStart2 <= fileStart1) && (fileEnd2 > fileStart1) && (fileEnd1 > fileStart1))
|| ((fileStart2 >= fileStart1) && (fileStart2 < fileEnd1) && (fileEnd2 > fileStart2)) )
dyld::throwf("malformed mach-o image: segment %s file content overlaps segment %s", segCmd1->segname, segCmd2->segname);
}
cmd2 = (const struct load_command*)(((char*)cmd2)+cmd2->cmdsize);
}
}
cmd1 = (const struct load_command*)(((char*)cmd1)+cmd1->cmdsize);
}
if (lastFileStart != linkeditFileStart)
dyld::throwf("malformed mach-o image: __LINKEDIT must be last segment");
}
// validate linkedit content
if ( (dyldInfoCmd == NULL) && (chainedFixupsCmd == NULL) && (symTabCmd == NULL) )
throw "malformed mach-o image: missing LC_SYMTAB, LC_DYLD_INFO, or LC_DYLD_CHAINED_FIXUPS";
if ( dynSymbTabCmd == NULL )
throw "malformed mach-o image: missing LC_DYSYMTAB";
uint32_t linkeditFileOffsetStart = (uint32_t)linkeditSegCmd->fileoff;
uint32_t linkeditFileOffsetEnd = (uint32_t)linkeditSegCmd->fileoff + (uint32_t)linkeditSegCmd->filesize;
if ( !inCache && (dyldInfoCmd != NULL) && context.strictMachORequired ) {
// validate all LC_DYLD_INFO chunks fit in LINKEDIT and don't overlap
uint32_t offset = linkeditFileOffsetStart;
if ( dyldInfoCmd->rebase_size != 0 ) {
if ( dyldInfoCmd->rebase_size & 0x80000000 )
throw "malformed mach-o image: dyld rebase info size overflow";
if ( dyldInfoCmd->rebase_off < offset )
throw "malformed mach-o image: dyld rebase info underruns __LINKEDIT";
offset = dyldInfoCmd->rebase_off + dyldInfoCmd->rebase_size;
if ( offset > linkeditFileOffsetEnd )
throw "malformed mach-o image: dyld rebase info overruns __LINKEDIT";
}
if ( dyldInfoCmd->bind_size != 0 ) {
if ( dyldInfoCmd->bind_size & 0x80000000 )
throw "malformed mach-o image: dyld bind info size overflow";
if ( dyldInfoCmd->bind_off < offset )
throw "malformed mach-o image: dyld bind info overlaps rebase info";
offset = dyldInfoCmd->bind_off + dyldInfoCmd->bind_size;
if ( offset > linkeditFileOffsetEnd )
throw "malformed mach-o image: dyld bind info overruns __LINKEDIT";
}
if ( dyldInfoCmd->weak_bind_size != 0 ) {
if ( dyldInfoCmd->weak_bind_size & 0x80000000 )
throw "malformed mach-o image: dyld weak bind info size overflow";
if ( dyldInfoCmd->weak_bind_off < offset )
throw "malformed mach-o image: dyld weak bind info overlaps bind info";
offset = dyldInfoCmd->weak_bind_off + dyldInfoCmd->weak_bind_size;
if ( offset > linkeditFileOffsetEnd )
throw "malformed mach-o image: dyld weak bind info overruns __LINKEDIT";
}
if ( dyldInfoCmd->lazy_bind_size != 0 ) {
if ( dyldInfoCmd->lazy_bind_size & 0x80000000 )
throw "malformed mach-o image: dyld lazy bind info size overflow";
if ( dyldInfoCmd->lazy_bind_off < offset )
throw "malformed mach-o image: dyld lazy bind info overlaps weak bind info";
offset = dyldInfoCmd->lazy_bind_off + dyldInfoCmd->lazy_bind_size;
if ( offset > linkeditFileOffsetEnd )
throw "malformed mach-o image: dyld lazy bind info overruns __LINKEDIT";
}
if ( dyldInfoCmd->export_size != 0 ) {
if ( dyldInfoCmd->export_size & 0x80000000 )
throw "malformed mach-o image: dyld export info size overflow";
if ( dyldInfoCmd->export_off < offset )
throw "malformed mach-o image: dyld export info overlaps lazy bind info";
offset = dyldInfoCmd->export_off + dyldInfoCmd->export_size;
if ( offset > linkeditFileOffsetEnd )
throw "malformed mach-o image: dyld export info overruns __LINKEDIT";
}
}
if ( !inCache && (chainedFixupsCmd != NULL) && context.strictMachORequired ) {
// validate all LC_DYLD_CHAINED_FIXUPS chunks fit in LINKEDIT and don't overlap
if ( chainedFixupsCmd->dataoff < linkeditFileOffsetStart )
throw "malformed mach-o image: dyld chained fixups info underruns __LINKEDIT";
if ( (chainedFixupsCmd->dataoff + chainedFixupsCmd->datasize) > linkeditFileOffsetEnd )
throw "malformed mach-o image: dyld chained fixups info overruns __LINKEDIT";
}
if ( !inCache && (exportsTrieCmd != NULL) && context.strictMachORequired ) {
// validate all LC_DYLD_EXPORTS_TRIE chunks fit in LINKEDIT and don't overlap
if ( exportsTrieCmd->dataoff < linkeditFileOffsetStart )
throw "malformed mach-o image: dyld chained fixups info underruns __LINKEDIT";
if ( (exportsTrieCmd->dataoff + exportsTrieCmd->datasize) > linkeditFileOffsetEnd )
throw "malformed mach-o image: dyld chained fixups info overruns __LINKEDIT";
}
if ( symTabCmd != NULL ) {
// validate symbol table fits in LINKEDIT
if ( (symTabCmd->nsyms > 0) && (symTabCmd->symoff < linkeditFileOffsetStart) )
throw "malformed mach-o image: symbol table underruns __LINKEDIT";
if ( symTabCmd->nsyms > 0x10000000 )
throw "malformed mach-o image: symbol table too large";
uint32_t symbolsSize = symTabCmd->nsyms * sizeof(macho_nlist);
if ( symbolsSize > linkeditSegCmd->filesize )
throw "malformed mach-o image: symbol table overruns __LINKEDIT";
if ( symTabCmd->symoff + symbolsSize < symTabCmd->symoff )
throw "malformed mach-o image: symbol table size wraps";
if ( symTabCmd->symoff + symbolsSize > symTabCmd->stroff )
throw "malformed mach-o image: symbol table overlaps symbol strings";
if ( symTabCmd->stroff + symTabCmd->strsize < symTabCmd->stroff )
throw "malformed mach-o image: symbol string size wraps";
if ( symTabCmd->stroff + symTabCmd->strsize > linkeditFileOffsetEnd ) {
// <rdar://problem/24220313> let old apps overflow as long as it stays within mapped page
if ( context.strictMachORequired || (symTabCmd->stroff + symTabCmd->strsize > ((linkeditFileOffsetEnd + 4095) & (-4096))) )
throw "malformed mach-o image: symbol strings overrun __LINKEDIT";
}
#if TARGET_OS_OSX
if ( (symTabCmd->symoff % sizeof(void*)) != 0 ) {
// <rdar://53723577> allow old malformed plugins in new app
if ( sdkVersion((mach_header*)mh) >= DYLD_PACKED_VERSION(10,15,0) )
throw "malformed mach-o image: mis-aligned symbol table __LINKEDIT";
}
#endif
// validate indirect symbol table
if ( dynSymbTabCmd->nindirectsyms != 0 ) {
if ( dynSymbTabCmd->indirectsymoff < linkeditFileOffsetStart )
throw "malformed mach-o image: indirect symbol table underruns __LINKEDIT";
if ( dynSymbTabCmd->nindirectsyms > 0x10000000 )
throw "malformed mach-o image: indirect symbol table too large";
uint32_t indirectTableSize = dynSymbTabCmd->nindirectsyms * sizeof(uint32_t);
if ( indirectTableSize > linkeditSegCmd->filesize )
throw "malformed mach-o image: indirect symbol table overruns __LINKEDIT";
if ( dynSymbTabCmd->indirectsymoff + indirectTableSize < dynSymbTabCmd->indirectsymoff )
throw "malformed mach-o image: indirect symbol table size wraps";
if ( context.strictMachORequired && (dynSymbTabCmd->indirectsymoff + indirectTableSize > symTabCmd->stroff) )
throw "malformed mach-o image: indirect symbol table overruns string pool";
}
if ( (dynSymbTabCmd->nlocalsym > symTabCmd->nsyms) || (dynSymbTabCmd->ilocalsym > symTabCmd->nsyms) )
throw "malformed mach-o image: indirect symbol table local symbol count exceeds total symbols";
if ( dynSymbTabCmd->ilocalsym + dynSymbTabCmd->nlocalsym < dynSymbTabCmd->ilocalsym )
throw "malformed mach-o image: indirect symbol table local symbol count wraps";
if ( (dynSymbTabCmd->nextdefsym > symTabCmd->nsyms) || (dynSymbTabCmd->iextdefsym > symTabCmd->nsyms) )
throw "malformed mach-o image: indirect symbol table extern symbol count exceeds total symbols";
if ( dynSymbTabCmd->iextdefsym + dynSymbTabCmd->nextdefsym < dynSymbTabCmd->iextdefsym )
throw "malformed mach-o image: indirect symbol table extern symbol count wraps";
if ( (dynSymbTabCmd->nundefsym > symTabCmd->nsyms) || (dynSymbTabCmd->iundefsym > symTabCmd->nsyms) )
throw "malformed mach-o image: indirect symbol table undefined symbol count exceeds total symbols";
if ( dynSymbTabCmd->iundefsym + dynSymbTabCmd->nundefsym < dynSymbTabCmd->iundefsym )
throw "malformed mach-o image: indirect symbol table undefined symbol count wraps";
}
// fSegmentsArrayCount is only 8-bits
if ( *segCount > 255 )
dyld::throwf("malformed mach-o image: more than 255 segments in %s", path);
// fSegmentsArrayCount is only 8-bits
if ( *libCount > 4095 )
dyld::throwf("malformed mach-o image: more than 4095 dependent libraries in %s", path);
if ( needsAddedLibSystemDepency(*libCount, mh) )
*libCount = 1;
// dylibs that use LC_DYLD_CHAINED_FIXUPS have that load command removed when put in the dyld cache
if ( !*compressed && (mh->flags & MH_DYLIB_IN_CACHE) )
*compressed = true;
}
// create image for main executable
ImageLoader* ImageLoaderMachO::instantiateMainExecutable(const macho_header* mh, uintptr_t slide, const char* path, const LinkContext& context)
{
//dyld::log("ImageLoader=%ld, ImageLoaderMachO=%ld, ImageLoaderMachOClassic=%ld, ImageLoaderMachOCompressed=%ld\n",
// sizeof(ImageLoader), sizeof(ImageLoaderMachO), sizeof(ImageLoaderMachOClassic), sizeof(ImageLoaderMachOCompressed));
bool compressed;
unsigned int segCount;
unsigned int libCount;
const linkedit_data_command* codeSigCmd;
const encryption_info_command* encryptCmd;
sniffLoadCommands(mh, path, false, &compressed, &segCount, &libCount, context, &codeSigCmd, &encryptCmd);
// instantiate concrete class based on content of load commands
if ( compressed )
return ImageLoaderMachOCompressed::instantiateMainExecutable(mh, slide, path, segCount, libCount, context);
else
#if SUPPORT_CLASSIC_MACHO
return ImageLoaderMachOClassic::instantiateMainExecutable(mh, slide, path, segCount, libCount, context);
#else
throw "missing LC_DYLD_INFO load command";
#endif
}
// create image by mapping in a mach-o file
ImageLoader* ImageLoaderMachO::instantiateFromFile(const char* path, int fd, const uint8_t firstPages[], size_t firstPagesSize, uint64_t offsetInFat,
uint64_t lenInFat, const struct stat& info, const LinkContext& context)
{
bool compressed;
unsigned int segCount;
unsigned int libCount;
const linkedit_data_command* codeSigCmd;
const encryption_info_command* encryptCmd;
sniffLoadCommands((const macho_header*)firstPages, path, false, &compressed, &segCount, &libCount, context, &codeSigCmd, &encryptCmd);
// instantiate concrete class based on content of load commands
if ( compressed )
return ImageLoaderMachOCompressed::instantiateFromFile(path, fd, firstPages, firstPagesSize, offsetInFat, lenInFat, info, segCount, libCount, codeSigCmd, encryptCmd, context);
else
#if SUPPORT_CLASSIC_MACHO
return ImageLoaderMachOClassic::instantiateFromFile(path, fd, firstPages, firstPagesSize, offsetInFat, lenInFat, info, segCount, libCount, codeSigCmd, context);
#else
throw "missing LC_DYLD_INFO load command";
#endif
}
// create image by using cached mach-o file
ImageLoader* ImageLoaderMachO::instantiateFromCache(const macho_header* mh, const char* path, long slide, const struct stat& info, const LinkContext& context)
{
// instantiate right concrete class
bool compressed;
unsigned int segCount;
unsigned int libCount;
const linkedit_data_command* codeSigCmd;
const encryption_info_command* encryptCmd;
sniffLoadCommands(mh, path, true, &compressed, &segCount, &libCount, context, &codeSigCmd, &encryptCmd);
// instantiate concrete class based on content of load commands
if ( compressed )
return ImageLoaderMachOCompressed::instantiateFromCache(mh, path, slide, info, segCount, libCount, context);
else
#if SUPPORT_CLASSIC_MACHO
return ImageLoaderMachOClassic::instantiateFromCache(mh, path, slide, info, segCount, libCount, context);
#else
throw "missing LC_DYLD_INFO load command";
#endif
}
// create image by copying an in-memory mach-o file
ImageLoader* ImageLoaderMachO::instantiateFromMemory(const char* moduleName, const macho_header* mh, uint64_t len, const LinkContext& context)
{
bool compressed;
unsigned int segCount;
unsigned int libCount;
const linkedit_data_command* sigcmd;
const encryption_info_command* encryptCmd;
sniffLoadCommands(mh, moduleName, false, &compressed, &segCount, &libCount, context, &sigcmd, &encryptCmd);
// instantiate concrete class based on content of load commands
if ( compressed )
return ImageLoaderMachOCompressed::instantiateFromMemory(moduleName, mh, len, segCount, libCount, context);
else
#if SUPPORT_CLASSIC_MACHO
return ImageLoaderMachOClassic::instantiateFromMemory(moduleName, mh, len, segCount, libCount, context);
#else
throw "missing LC_DYLD_INFO load command";
#endif
}
int ImageLoaderMachO::crashIfInvalidCodeSignature()
{
// Now that segments are mapped in, try reading from first executable segment.
// If code signing is enabled the kernel will validate the code signature
// when paging in, and kill the process if invalid.
for(unsigned int i=0; i < fSegmentsCount; ++i) {
if ( (segFileOffset(i) == 0) && (segFileSize(i) != 0) ) {
// return read value to ensure compiler does not optimize away load
int* p = (int*)segActualLoadAddress(i);
return *p;
}
}
return 0;
}
void ImageLoaderMachO::parseLoadCmds(const LinkContext& context)
{
// now that segments are mapped in, get real fMachOData, fLinkEditBase, and fSlide
for(unsigned int i=0; i < fSegmentsCount; ++i) {
// set up pointer to __LINKEDIT segment
if ( strcmp(segName(i),"__LINKEDIT") == 0 ) {
#if !TARGET_OS_OSX
// <rdar://problem/42419336> historically, macOS never did this check
if ( segFileOffset(i) > fCoveredCodeLength )
dyld::throwf("cannot load '%s' (segment outside of code signature)", this->getShortName());
#endif
fLinkEditBase = (uint8_t*)(segActualLoadAddress(i) - segFileOffset(i));
}
#if TEXT_RELOC_SUPPORT
// __TEXT segment always starts at beginning of file and contains mach_header and load commands
if ( segExecutable(i) ) {
if ( segHasRebaseFixUps(i) && (fSlide != 0) )
fTextSegmentRebases = true;
if ( segHasBindFixUps(i) )
fTextSegmentBinds = true;
}
#else
if ( segIsReadOnlyData(i) )
fReadOnlyDataSegment = true;
#endif
#if __i386__
if ( segIsReadOnlyImport(i) )
fReadOnlyImportSegment = true;
#endif
// some segment always starts at beginning of file and contains mach_header and load commands
if ( (segFileOffset(i) == 0) && (segFileSize(i) != 0) ) {
fMachOData = (uint8_t*)(segActualLoadAddress(i));
}
}
// keep count of prebound images with weak exports
if ( this->participatesInCoalescing() ) {
++fgImagesRequiringCoalescing;
fRegisteredAsRequiresCoalescing = true;
if ( this->hasCoalescedExports() )
++fgImagesHasWeakDefinitions;
}
// keep count of images used in shared cache
if ( fInSharedCache )
++fgImagesUsedFromSharedCache;
// walk load commands (mapped in at start of __TEXT segment)
const dyld_info_command* dyldInfo = NULL;
const linkedit_data_command* chainedFixupsCmd = NULL;
const linkedit_data_command* exportsTrieCmd = NULL;
const macho_nlist* symbolTable = NULL;
const char* symbolTableStrings = NULL;
const struct load_command* firstUnknownCmd = NULL;
const struct version_min_command* minOSVersionCmd = NULL;
const dysymtab_command* dynSymbolTable = NULL;
const uint32_t cmd_count = ((macho_header*)fMachOData)->ncmds;
const struct load_command* const cmds = (struct load_command*)&fMachOData[sizeof(macho_header)];
const struct load_command* cmd = cmds;
for (uint32_t i = 0; i < cmd_count; ++i) {
switch (cmd->cmd) {
case LC_SYMTAB:
{
const struct symtab_command* symtab = (struct symtab_command*)cmd;
symbolTableStrings = (const char*)&fLinkEditBase[symtab->stroff];
symbolTable = (macho_nlist*)(&fLinkEditBase[symtab->symoff]);
}
break;
case LC_DYSYMTAB:
dynSymbolTable = (struct dysymtab_command*)cmd;
break;
case LC_SUB_UMBRELLA:
fHasSubUmbrella = true;
break;
case LC_SUB_FRAMEWORK:
fInUmbrella = true;
break;
case LC_SUB_LIBRARY:
fHasSubLibraries = true;
break;
case LC_ROUTINES_COMMAND:
fHasDashInit = true;
break;
case LC_DYLD_INFO:
case LC_DYLD_INFO_ONLY:
dyldInfo = (struct dyld_info_command*)cmd;
break;
case LC_DYLD_CHAINED_FIXUPS:
chainedFixupsCmd = (struct linkedit_data_command*)cmd;
break;
case LC_DYLD_EXPORTS_TRIE:
exportsTrieCmd = (struct linkedit_data_command*)cmd;
break;
case LC_SEGMENT_COMMAND:
{
const struct macho_segment_command* seg = (struct macho_segment_command*)cmd;
const bool isTextSeg = (strcmp(seg->segname, "__TEXT") == 0);
#if __i386__ && TARGET_OS_OSX
const bool isObjCSeg = (strcmp(seg->segname, "__OBJC") == 0);
if ( isObjCSeg )
fNotifyObjC = true;
#else
const bool isDataSeg = (strncmp(seg->segname, "__DATA", 6) == 0);
#endif
const struct macho_section* const sectionsStart = (struct macho_section*)((char*)seg + sizeof(struct macho_segment_command));
const struct macho_section* const sectionsEnd = &sectionsStart[seg->nsects];
for (const struct macho_section* sect=sectionsStart; sect < sectionsEnd; ++sect) {
const uint8_t type = sect->flags & SECTION_TYPE;
if ( type == S_MOD_INIT_FUNC_POINTERS )
fHasInitializers = true;
else if ( type == S_INIT_FUNC_OFFSETS )
fHasInitializers = true;
else if ( type == S_MOD_TERM_FUNC_POINTERS )
fHasTerminators = true;
else if ( type == S_DTRACE_DOF )
fHasDOFSections = true;
else if ( isTextSeg && (strcmp(sect->sectname, "__eh_frame") == 0) )
fEHFrameSectionOffset = (uint32_t)((uint8_t*)sect - fMachOData);
else if ( isTextSeg && (strcmp(sect->sectname, "__unwind_info") == 0) )
fUnwindInfoSectionOffset = (uint32_t)((uint8_t*)sect - fMachOData);
#if __i386__ && TARGET_OS_OSX
else if ( isObjCSeg ) {
if ( strcmp(sect->sectname, "__image_info") == 0 ) {
const uint32_t* imageInfo = (uint32_t*)(sect->addr + fSlide);
uint32_t flags = imageInfo[1];
if ( (flags & 4) && (((macho_header*)fMachOData)->filetype != MH_EXECUTE) )
dyld::throwf("cannot load '%s' because Objective-C garbage collection is not supported", getPath());
}
else if ( ((macho_header*)fMachOData)->filetype == MH_DYLIB ) {
fRetainForObjC = true;
}
}
#else
else if ( isDataSeg && (strncmp(sect->sectname, "__objc_imageinfo", 16) == 0) ) {
#if TARGET_OS_OSX
const uint32_t* imageInfo = (uint32_t*)(sect->addr + fSlide);
uint32_t flags = imageInfo[1];
if ( (flags & 4) && (((macho_header*)fMachOData)->filetype != MH_EXECUTE) )
dyld::throwf("cannot load '%s' because Objective-C garbage collection is not supported", getPath());
#endif
fNotifyObjC = true;
}
else if ( isDataSeg && (strncmp(sect->sectname, "__objc_", 7) == 0) && (((macho_header*)fMachOData)->filetype == MH_DYLIB) )
fRetainForObjC = true;
#endif
}
}
break;
case LC_TWOLEVEL_HINTS:
// no longer supported
break;
case LC_ID_DYLIB:
{
fDylibIDOffset = (uint32_t)((uint8_t*)cmd - fMachOData);
}
break;
case LC_RPATH:
case LC_LOAD_WEAK_DYLIB:
case LC_REEXPORT_DYLIB:
case LC_LOAD_UPWARD_DYLIB:
case LC_MAIN:
break;
case LC_VERSION_MIN_MACOSX:
case LC_VERSION_MIN_IPHONEOS:
case LC_VERSION_MIN_TVOS:
case LC_VERSION_MIN_WATCHOS:
minOSVersionCmd = (version_min_command*)cmd;
break;
default:
if ( (cmd->cmd & LC_REQ_DYLD) != 0 ) {
if ( firstUnknownCmd == NULL )
firstUnknownCmd = cmd;
}
break;
}
cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize);
}
if ( firstUnknownCmd != NULL ) {
if ( minOSVersionCmd != NULL ) {
dyld::throwf("cannot load '%s' because it was built for OS version %u.%u (load command 0x%08X is unknown)",
this->getShortName(),
minOSVersionCmd->version >> 16, ((minOSVersionCmd->version >> 8) & 0xff),
firstUnknownCmd->cmd);
}
else {
dyld::throwf("cannot load '%s' (load command 0x%08X is unknown)", this->getShortName(), firstUnknownCmd->cmd);
}
}
if ( dyldInfo != NULL )
this->setDyldInfo(dyldInfo);
if ( chainedFixupsCmd != NULL )
this->setChainedFixups(chainedFixupsCmd);
if ( exportsTrieCmd != NULL )
this->setExportsTrie(exportsTrieCmd);
if ( symbolTable != NULL)
this->setSymbolTableInfo(symbolTable, symbolTableStrings, dynSymbolTable);
}
// don't do this work in destructor because we need object to be full subclass
// for UnmapSegments() to work
void ImageLoaderMachO::destroy()
{
// update count of images with weak exports
if ( fRegisteredAsRequiresCoalescing ) {
--fgImagesRequiringCoalescing;
if ( this->hasCoalescedExports() )
--fgImagesHasWeakDefinitions;
}
// keep count of images used in shared cache
if ( fInSharedCache )
--fgImagesUsedFromSharedCache;
// unmap image when done
UnmapSegments();
}
unsigned int ImageLoaderMachO::segmentCount() const
{
return fSegmentsCount;
}
const macho_segment_command* ImageLoaderMachO::segLoadCommand(unsigned int segIndex) const
{
uint32_t* lcOffsets = this->segmentCommandOffsets();
uint32_t lcOffset = lcOffsets[segIndex];
return (macho_segment_command*)(&fMachOData[lcOffset]);
}
const char* ImageLoaderMachO::segName(unsigned int segIndex) const
{
return segLoadCommand(segIndex)->segname;
}
uintptr_t ImageLoaderMachO::segSize(unsigned int segIndex) const
{
return segLoadCommand(segIndex)->vmsize;
}
uintptr_t ImageLoaderMachO::segFileSize(unsigned int segIndex) const
{
return segLoadCommand(segIndex)->filesize;
}
bool ImageLoaderMachO::segHasTrailingZeroFill(unsigned int segIndex)
{
return ( segWriteable(segIndex) && (segSize(segIndex) > segFileSize(segIndex)) );
}
uintptr_t ImageLoaderMachO::segFileOffset(unsigned int segIndex) const
{
return segLoadCommand(segIndex)->fileoff;
}
bool ImageLoaderMachO::segReadable(unsigned int segIndex) const
{
return ( (segLoadCommand(segIndex)->initprot & VM_PROT_READ) != 0);
}
bool ImageLoaderMachO::segWriteable(unsigned int segIndex) const
{
return ( (segLoadCommand(segIndex)->initprot & VM_PROT_WRITE) != 0);
}
bool ImageLoaderMachO::segExecutable(unsigned int segIndex) const
{
return ( (segLoadCommand(segIndex)->initprot & VM_PROT_EXECUTE) != 0);
}
bool ImageLoaderMachO::segUnaccessible(unsigned int segIndex) const
{
return (segLoadCommand(segIndex)->initprot == 0);
}
bool ImageLoaderMachO::segHasPreferredLoadAddress(unsigned int segIndex) const
{
return (segLoadCommand(segIndex)->vmaddr != 0);
}
uintptr_t ImageLoaderMachO::segPreferredLoadAddress(unsigned int segIndex) const
{
return segLoadCommand(segIndex)->vmaddr;
}
uintptr_t ImageLoaderMachO::segActualLoadAddress(unsigned int segIndex) const
{
return segLoadCommand(segIndex)->vmaddr + fSlide;
}
uintptr_t ImageLoaderMachO::segActualEndAddress(unsigned int segIndex) const
{
return segActualLoadAddress(segIndex) + segSize(segIndex);
}
bool ImageLoaderMachO::segHasRebaseFixUps(unsigned int segIndex) const
{
#if TEXT_RELOC_SUPPORT
// scan sections for fix-up bit
const macho_segment_command* segCmd = segLoadCommand(segIndex);
const struct macho_section* const sectionsStart = (struct macho_section*)((char*)segCmd + sizeof(struct macho_segment_command));
const struct macho_section* const sectionsEnd = &sectionsStart[segCmd->nsects];
for (const struct macho_section* sect=sectionsStart; sect < sectionsEnd; ++sect) {
if ( (sect->flags & S_ATTR_LOC_RELOC) != 0 )
return true;
}
#endif
return false;
}
bool ImageLoaderMachO::segHasBindFixUps(unsigned int segIndex) const
{
#if TEXT_RELOC_SUPPORT
// scan sections for fix-up bit
const macho_segment_command* segCmd = segLoadCommand(segIndex);
const struct macho_section* const sectionsStart = (struct macho_section*)((char*)segCmd + sizeof(struct macho_segment_command));
const struct macho_section* const sectionsEnd = &sectionsStart[segCmd->nsects];
for (const struct macho_section* sect=sectionsStart; sect < sectionsEnd; ++sect) {
if ( (sect->flags & S_ATTR_EXT_RELOC) != 0 )
return true;
}
#endif
return false;
}
#if __i386__
bool ImageLoaderMachO::segIsReadOnlyImport(unsigned int segIndex) const
{
const macho_segment_command* segCmd = segLoadCommand(segIndex);
return ( (segCmd->initprot & VM_PROT_EXECUTE)
&& ((segCmd->initprot & VM_PROT_WRITE) == 0)
&& (strcmp(segCmd->segname, "__IMPORT") == 0) );
}
#endif
bool ImageLoaderMachO::segIsReadOnlyData(unsigned int segIndex) const
{
const macho_segment_command* segCmd = segLoadCommand(segIndex);
return ( (segCmd->initprot & VM_PROT_WRITE)
&& ((segCmd->initprot & VM_PROT_EXECUTE) == 0)
&& (segCmd->flags & SG_READ_ONLY) );
}
void ImageLoaderMachO::UnmapSegments()
{
// usually unmap image when done
if ( ! this->leaveMapped() && (this->getState() >= dyld_image_state_mapped) ) {
// unmap TEXT segment last because it contains load command being inspected
unsigned int textSegmentIndex = 0;
for(unsigned int i=0; i < fSegmentsCount; ++i) {
//dyld::log("unmap %s at 0x%08lX\n", seg->getName(), seg->getActualLoadAddress(this));
if ( (segFileOffset(i) == 0) && (segFileSize(i) != 0) ) {
textSegmentIndex = i;
}
else {
// update stats
--ImageLoader::fgTotalSegmentsMapped;
ImageLoader::fgTotalBytesMapped -= segSize(i);
munmap((void*)segActualLoadAddress(i), segSize(i));
}
}
// now unmap TEXT
--ImageLoader::fgTotalSegmentsMapped;
ImageLoader::fgTotalBytesMapped -= segSize(textSegmentIndex);
munmap((void*)segActualLoadAddress(textSegmentIndex), segSize(textSegmentIndex));
}
}
bool ImageLoaderMachO::segmentsMustSlideTogether() const
{
return true;
}
bool ImageLoaderMachO::segmentsCanSlide() const
{
return (this->isDylib() || this->isBundle() || this->isPositionIndependentExecutable());
}
bool ImageLoaderMachO::isBundle() const
{
const macho_header* mh = (macho_header*)fMachOData;
return ( mh->filetype == MH_BUNDLE );
}
bool ImageLoaderMachO::isDylib() const
{
const macho_header* mh = (macho_header*)fMachOData;
return ( mh->filetype == MH_DYLIB );
}
bool ImageLoaderMachO::isExecutable() const
{
const macho_header* mh = (macho_header*)fMachOData;
return ( mh->filetype == MH_EXECUTE );
}
bool ImageLoaderMachO::isPositionIndependentExecutable() const
{
const macho_header* mh = (macho_header*)fMachOData;
return ( (mh->filetype == MH_EXECUTE) && ((mh->flags & MH_PIE) != 0) );
}
bool ImageLoaderMachO::forceFlat() const
{
const macho_header* mh = (macho_header*)fMachOData;
return ( (mh->flags & MH_FORCE_FLAT) != 0 );
}
bool ImageLoaderMachO::usesTwoLevelNameSpace() const
{
const macho_header* mh = (macho_header*)fMachOData;
return ( (mh->flags & MH_TWOLEVEL) != 0 );
}
bool ImageLoaderMachO::isPrebindable() const
{
const macho_header* mh = (macho_header*)fMachOData;
return ( (mh->flags & MH_PREBOUND) != 0 );
}
bool ImageLoaderMachO::hasCoalescedExports() const
{
const macho_header* mh = (macho_header*)fMachOData;
return ( (mh->flags & MH_WEAK_DEFINES) != 0 );
}
bool ImageLoaderMachO::hasReferencesToWeakSymbols() const
{
const macho_header* mh = (macho_header*)fMachOData;
return ( (mh->flags & MH_BINDS_TO_WEAK) != 0 );
}
bool ImageLoaderMachO::participatesInCoalescing() const
{
const macho_header* mh = (macho_header*)fMachOData;
// if image is loaded with RTLD_LOCAL, then its symbols' visibility
// is reduced and it can't coalesce with other images
if ( this->hasHiddenExports() )
return false;
return ( (mh->flags & (MH_WEAK_DEFINES|MH_BINDS_TO_WEAK)) != 0 );
}
void ImageLoaderMachO::setSlide(intptr_t slide)
{
fSlide = slide;
}
void ImageLoaderMachO::loadCodeSignature(const struct linkedit_data_command* codeSigCmd, int fd, uint64_t offsetInFatFile, const LinkContext& context)
{
dyld3::ScopedTimer(DBG_DYLD_TIMING_ATTACH_CODESIGNATURE, 0, 0, 0);
// if dylib being loaded has no code signature load command
if ( codeSigCmd == NULL) {
disableCoverageCheck();
}
else {
#if TARGET_OS_OSX
// <rdar://problem/13622786> ignore code signatures in binaries built with pre-10.9 tools
if ( this->sdkVersion() < DYLD_PACKED_VERSION(10,9,0) ) {
disableCoverageCheck();
return;
}
#endif
fsignatures_t siginfo;
siginfo.fs_file_start=offsetInFatFile; // start of mach-o slice in fat file
siginfo.fs_blob_start=(void*)(long)(codeSigCmd->dataoff); // start of CD in mach-o file
siginfo.fs_blob_size=codeSigCmd->datasize; // size of CD
int result = fcntl(fd, F_ADDFILESIGS_RETURN, &siginfo);
#if TARGET_OS_SIMULATOR
// rdar://problem/18759224> check range covered by the code directory after loading
// Attempt to fallback only if we are in the simulator
if ( result == -1 ) {
result = fcntl(fd, F_ADDFILESIGS, &siginfo);
siginfo.fs_file_start = codeSigCmd->dataoff;
}
#endif
if ( result == -1 ) {
if ( (errno == EPERM) || (errno == EBADEXEC) )
dyld::throwf("code signature invalid for '%s'\n", this->getPath());
if ( context.verboseCodeSignatures )
dyld::log("dyld: Failed registering code signature for %s, errno=%d\n", this->getPath(), errno);
siginfo.fs_file_start = UINT64_MAX;
} else if ( context.verboseCodeSignatures ) {
dyld::log("dyld: Registered code signature for %s\n", this->getPath());
}
fCoveredCodeLength = siginfo.fs_file_start;
}
{
fchecklv checkInfo;
char messageBuffer[512];
messageBuffer[0] = '\0';
checkInfo.lv_file_start = offsetInFatFile;
checkInfo.lv_error_message_size = sizeof(messageBuffer);
checkInfo.lv_error_message = messageBuffer;
int res = fcntl(fd, F_CHECK_LV, &checkInfo);
if ( res == -1 ) {
dyld::throwf("code signature in (%s) not valid for use in process using Library Validation: %s", this->getPath(), messageBuffer);
}
}
}
void ImageLoaderMachO::validateFirstPages(const struct linkedit_data_command* codeSigCmd, int fd, const uint8_t *fileData, size_t lenFileData, off_t offsetInFat, const LinkContext& context)
{
#if TARGET_OS_OSX
// rdar://problem/21839703> 15A226d: dyld crashes in mageLoaderMachO::validateFirstPages during dlopen() after encountering an mmap failure
// We need to ignore older code signatures because they will be bad.
if ( this->sdkVersion() < DYLD_PACKED_VERSION(10,9,0) ) {
return;
}
#endif
if (codeSigCmd != NULL) {
void *fdata = xmmap(NULL, lenFileData, PROT_READ, MAP_SHARED, fd, offsetInFat);
if ( fdata == MAP_FAILED ) {
int errnoCopy = errno;
if ( errnoCopy == EPERM ) {
if ( dyld::sandboxBlockedMmap(getPath()) )
dyld::throwf("file system sandbox blocked mmap() of '%s'", getPath());
else
dyld::throwf("code signing blocked mmap() of '%s'", getPath());
}
else
dyld::throwf("mmap() errno=%d validating first page of '%s'", errnoCopy, getPath());
}
if ( memcmp(fdata, fileData, lenFileData) != 0 )
dyld::throwf("mmap() page compare failed for '%s'", getPath());
munmap(fdata, lenFileData);
}
}
const char* ImageLoaderMachO::getInstallPath() const
{
if ( fDylibIDOffset != 0 ) {
const dylib_command* dylibID = (dylib_command*)(&fMachOData[fDylibIDOffset]);
return (char*)dylibID + dylibID->dylib.name.offset;
}
return NULL;
}
void ImageLoaderMachO::registerInterposing(const LinkContext& context)
{
// mach-o files advertise interposing by having a __DATA __interpose section
struct InterposeData { uintptr_t replacement; uintptr_t replacee; };
const uint32_t cmd_count = ((macho_header*)fMachOData)->ncmds;
const struct load_command* const cmds = (struct load_command*)&fMachOData[sizeof(macho_header)];
const struct load_command* cmd = cmds;
for (uint32_t i = 0; i < cmd_count; ++i) {
switch (cmd->cmd) {
case LC_SEGMENT_COMMAND:
{
const struct macho_segment_command* seg = (struct macho_segment_command*)cmd;
const struct macho_section* const sectionsStart = (struct macho_section*)((char*)seg + sizeof(struct macho_segment_command));
const struct macho_section* const sectionsEnd = &sectionsStart[seg->nsects];
for (const struct macho_section* sect=sectionsStart; sect < sectionsEnd; ++sect) {
if ( ((sect->flags & SECTION_TYPE) == S_INTERPOSING) || ((strcmp(sect->sectname, "__interpose") == 0) && (strcmp(seg->segname, "__DATA") == 0)) ) {
// <rdar://problem/23929217> Ensure section is within segment
if ( (sect->addr < seg->vmaddr) || (sect->addr+sect->size > seg->vmaddr+seg->vmsize) || (sect->addr+sect->size < sect->addr) )
dyld::throwf("interpose section has malformed address range for %s\n", this->getPath());
const InterposeData* interposeArray = (InterposeData*)(sect->addr + fSlide);
const size_t count = sect->size / sizeof(InterposeData);
for (size_t j=0; j < count; ++j) {
ImageLoader::InterposeTuple tuple;
tuple.replacement = interposeArray[j].replacement;
tuple.neverImage = this;
tuple.onlyImage = NULL;
tuple.replacee = interposeArray[j].replacee;
// <rdar://problem/25686570> ignore interposing on a weak function that does not exist
if ( tuple.replacee == 0 )
continue;
// <rdar://problem/7937695> verify that replacement is in this image
if ( this->containsAddress((void*)tuple.replacement) ) {
// chain to any existing interpositions
for (std::vector<InterposeTuple>::iterator it=fgInterposingTuples.begin(); it != fgInterposingTuples.end(); it++) {
if ( it->replacee == tuple.replacee ) {
tuple.replacee = it->replacement;
}
}
ImageLoader::fgInterposingTuples.push_back(tuple);
}
}
}
}
}
break;
}
cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize);
}
}
uint32_t ImageLoaderMachO::sdkVersion(const mach_header* mh)
{
const uint32_t cmd_count = mh->ncmds;
const struct load_command* const cmds = (struct load_command*)(((char*)mh) + sizeof(macho_header));
const struct load_command* cmd = cmds;
const struct version_min_command* versCmd;
const struct build_version_command* buildVersCmd;
for (uint32_t i = 0; i < cmd_count; ++i) {
switch ( cmd->cmd ) {
case LC_VERSION_MIN_MACOSX:
case LC_VERSION_MIN_IPHONEOS:
case LC_VERSION_MIN_TVOS:
case LC_VERSION_MIN_WATCHOS:
versCmd = (version_min_command*)cmd;
return versCmd->sdk;
case LC_BUILD_VERSION:
buildVersCmd = (build_version_command*)cmd;
return buildVersCmd->sdk;
}
cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize);
}
return 0;
}
uint32_t ImageLoaderMachO::sdkVersion() const
{
return ImageLoaderMachO::sdkVersion(machHeader());
}
uint32_t ImageLoaderMachO::minOSVersion(const mach_header* mh)
{
const uint32_t cmd_count = mh->ncmds;
const struct load_command* const cmds = (struct load_command*)(((char*)mh) + sizeof(macho_header));
const struct load_command* cmd = cmds;
const struct version_min_command* versCmd;
const struct build_version_command* buildVersCmd;
for (uint32_t i = 0; i < cmd_count; ++i) {
switch ( cmd->cmd ) {
case LC_VERSION_MIN_MACOSX:
case LC_VERSION_MIN_IPHONEOS:
case LC_VERSION_MIN_TVOS:
case LC_VERSION_MIN_WATCHOS:
versCmd = (version_min_command*)cmd;
return versCmd->version;
case LC_BUILD_VERSION:
buildVersCmd = (build_version_command*)cmd;
return buildVersCmd->minos;
}
cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize);
}
return 0;
}
uint32_t ImageLoaderMachO::minOSVersion() const
{
return ImageLoaderMachO::minOSVersion(machHeader());
}
void* ImageLoaderMachO::getEntryFromLC_MAIN() const
{
const uint32_t cmd_count = ((macho_header*)fMachOData)->ncmds;
const struct load_command* const cmds = (struct load_command*)&fMachOData[sizeof(macho_header)];
const struct load_command* cmd = cmds;
for (uint32_t i = 0; i < cmd_count; ++i) {
if ( cmd->cmd == LC_MAIN ) {
entry_point_command* mainCmd = (entry_point_command*)cmd;
void* entry = (void*)(mainCmd->entryoff + (char*)fMachOData);
// <rdar://problem/8543820&9228031> verify entry point is in image
if ( this->containsAddress(entry) ) {
#if __has_feature(ptrauth_calls)
// start() calls the result pointer as a function pointer so we need to sign it.
return __builtin_ptrauth_sign_unauthenticated(entry, 0, 0);
#endif
return entry;
}
else
throw "LC_MAIN entryoff is out of range";
}
cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize);
}
return NULL;
}
void* ImageLoaderMachO::getEntryFromLC_UNIXTHREAD() const
{
const uint32_t cmd_count = ((macho_header*)fMachOData)->ncmds;
const struct load_command* const cmds = (struct load_command*)&fMachOData[sizeof(macho_header)];
const struct load_command* cmd = cmds;
for (uint32_t i = 0; i < cmd_count; ++i) {
if ( cmd->cmd == LC_UNIXTHREAD ) {
#if __i386__
const i386_thread_state_t* registers = (i386_thread_state_t*)(((char*)cmd) + 16);
void* entry = (void*)(registers->eip + fSlide);
// <rdar://problem/8543820&9228031> verify entry point is in image
if ( this->containsAddress(entry) )
return entry;
#elif __x86_64__
const x86_thread_state64_t* registers = (x86_thread_state64_t*)(((char*)cmd) + 16);
void* entry = (void*)(registers->rip + fSlide);
// <rdar://problem/8543820&9228031> verify entry point is in image
if ( this->containsAddress(entry) )
return entry;
#endif
}
cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize);
}
throw "no valid entry point";
}
bool ImageLoaderMachO::needsAddedLibSystemDepency(unsigned int libCount, const macho_header* mh)
{
// <rdar://problem/6357561> ensure that every image depends on something which depends on libSystem
if ( libCount > 1 )
return false;
// <rdar://problem/6409800> dyld implicit-libSystem breaks valgrind
if ( mh->filetype == MH_EXECUTE )
return false;
bool isNonOSdylib = false;
const uint32_t cmd_count = mh->ncmds;
const struct load_command* const cmds = (struct load_command*)((uint8_t*)mh+sizeof(macho_header));
const struct load_command* cmd = cmds;
for (uint32_t i = 0; i < cmd_count; ++i) {
switch (cmd->cmd) {
case LC_LOAD_DYLIB:
case LC_LOAD_WEAK_DYLIB:
case LC_REEXPORT_DYLIB:
case LC_LOAD_UPWARD_DYLIB:
return false;
case LC_ID_DYLIB:
{
const dylib_command* dylibID = (dylib_command*)cmd;
const char* installPath = (char*)cmd + dylibID->dylib.name.offset;
// It is OK for OS dylibs (libSystem or libmath) to have no dependents
// but all other dylibs must depend on libSystem for initialization to initialize libSystem first
isNonOSdylib = ( (strncmp(installPath, "/usr/lib/", 9) != 0) && (strncmp(installPath, "/System/DriverKit/usr/lib/", 26) != 0) );
// if (isNonOSdylib) dyld::log("ImageLoaderMachO::needsAddedLibSystemDepency(%s)\n", installPath);
}
break;
}
cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize);
}
return isNonOSdylib;
}
void ImageLoaderMachO::doGetDependentLibraries(DependentLibraryInfo libs[])
{
if ( needsAddedLibSystemDepency(libraryCount(), (macho_header*)fMachOData) ) {
DependentLibraryInfo* lib = &libs[0];
lib->name = LIBSYSTEM_DYLIB_PATH;
lib->info.checksum = 0;
lib->info.minVersion = 0;
lib->info.maxVersion = 0;
lib->required = false;
lib->reExported = false;
lib->upward = false;
}
else {
uint32_t index = 0;
const uint32_t cmd_count = ((macho_header*)fMachOData)->ncmds;
const struct load_command* const cmds = (struct load_command*)&fMachOData[sizeof(macho_header)];
const struct load_command* cmd = cmds;
for (uint32_t i = 0; i < cmd_count; ++i) {
switch (cmd->cmd) {
case LC_LOAD_DYLIB:
case LC_LOAD_WEAK_DYLIB:
case LC_REEXPORT_DYLIB:
case LC_LOAD_UPWARD_DYLIB:
{
const struct dylib_command* dylib = (struct dylib_command*)cmd;
DependentLibraryInfo* lib = &libs[index++];
lib->name = (char*)cmd + dylib->dylib.name.offset;
//lib->name = strdup((char*)cmd + dylib->dylib.name.offset);
lib->info.checksum = dylib->dylib.timestamp;
lib->info.minVersion = dylib->dylib.compatibility_version;
lib->info.maxVersion = dylib->dylib.current_version;
lib->required = (cmd->cmd != LC_LOAD_WEAK_DYLIB);
lib->reExported = (cmd->cmd == LC_REEXPORT_DYLIB);
lib->upward = (cmd->cmd == LC_LOAD_UPWARD_DYLIB);
}
break;
}
cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize);
}
}
}
ImageLoader::LibraryInfo ImageLoaderMachO::doGetLibraryInfo(const LibraryInfo&)
{
LibraryInfo info;
if ( fDylibIDOffset != 0 ) {
const dylib_command* dylibID = (dylib_command*)(&fMachOData[fDylibIDOffset]);
info.minVersion = dylibID->dylib.compatibility_version;
info.maxVersion = dylibID->dylib.current_version;
info.checksum = dylibID->dylib.timestamp;
}
else {
info.minVersion = 0;
info.maxVersion = 0;
info.checksum = 0;
}
return info;
}
void ImageLoaderMachO::getRPaths(const LinkContext& context, std::vector<const char*>& paths) const
{
const uint32_t cmd_count = ((macho_header*)fMachOData)->ncmds;
const struct load_command* const cmds = (struct load_command*)&fMachOData[sizeof(macho_header)];
const struct load_command* cmd = cmds;
for (uint32_t i = 0; i < cmd_count; ++i) {
switch (cmd->cmd) {
case LC_RPATH:
const char* pathToAdd = NULL;
const char* path = (char*)cmd + ((struct rpath_command*)cmd)->path.offset;
if ( (strncmp(path, "@loader_path", 12) == 0) && ((path[12] == '/') || (path[12] == '\0')) ) {
if ( !context.allowAtPaths && (context.mainExecutable == this) ) {
dyld::warn("LC_RPATH %s in %s being ignored in restricted program because of @loader_path (Codesign main executable with Library Validation to allow @ paths)\n", path, this->getPath());
break;
}
char resolvedPath[PATH_MAX];
if ( realpath(this->getPath(), resolvedPath) != NULL ) {
char newRealPath[strlen(resolvedPath) + strlen(path)];
strcpy(newRealPath, resolvedPath);
char* addPoint = strrchr(newRealPath,'/');
if ( addPoint != NULL ) {
strcpy(addPoint, &path[12]);
pathToAdd = strdup(newRealPath);
}
}
}
else if ( (strncmp(path, "@executable_path", 16) == 0) && ((path[16] == '/') || (path[16] == '\0')) ) {
if ( !context.allowAtPaths) {
dyld::warn("LC_RPATH %s in %s being ignored in restricted program because of @executable_path (Codesign main executable with Library Validation to allow @ paths)\n", path, this->getPath());
break;
}
char resolvedPath[PATH_MAX];
if ( realpath(context.mainExecutable->getPath(), resolvedPath) != NULL ) {
char newRealPath[strlen(resolvedPath) + strlen(path)];
strcpy(newRealPath, resolvedPath);
char* addPoint = strrchr(newRealPath,'/');
if ( addPoint != NULL ) {
strcpy(addPoint, &path[16]);
pathToAdd = strdup(newRealPath);
}
}
}
else if ( (path[0] != '/') && !context.allowAtPaths) {
dyld::warn("LC_RPATH %s in %s being ignored in restricted program because it is a relative path\n", path, this->getPath());
break;
}
#if SUPPORT_ROOT_PATH
else if ( (path[0] == '/') && (context.rootPaths != NULL) ) {
// <rdar://problem/5869973> DYLD_ROOT_PATH should apply to LC_RPATH rpaths
// <rdar://problem/49576123> Even if DYLD_ROOT_PATH exists, LC_RPATH should add raw path to rpaths
// DYLD_ROOT_PATH can be a list of paths, but at this point we can only support one, so use first combination that exists
for (const char** rp = context.rootPaths; *rp != NULL; ++rp) {
char newPath[PATH_MAX];
strlcpy(newPath, *rp, PATH_MAX);
strlcat(newPath, path, PATH_MAX);
struct stat stat_buf;
if ( dyld3::stat(newPath, &stat_buf) != -1 ) {
// dyld::log("combined DYLD_ROOT_PATH and LC_RPATH: %s\n", newPath);
paths.push_back(strdup(newPath));
}
}
// add in raw absolute path without root prefix
pathToAdd = strdup(path);
}
#endif
else {
// realpath() is slow, and /usr/lib/swift is a real path, so don't realpath it
if ( strcmp(path, "/usr/lib/swift") != 0 ) {
char resolvedPath[PATH_MAX];
if ( (realpath(path, resolvedPath) != NULL) && (strcmp(path, resolvedPath) != 0) ) {
// <rdar://problem/45470293> support LC_RPATH symlinks to directories of things in the dyld cache
path = resolvedPath;
}
}
// make copy so that all elements of 'paths' can be freed
pathToAdd = strdup(path);
}
if ( pathToAdd != NULL )
paths.push_back(pathToAdd);
break;
}
cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize);
}
}
bool ImageLoaderMachO::getUUID(uuid_t uuid) const
{
const uint32_t cmd_count = ((macho_header*)fMachOData)->ncmds;
const struct load_command* const cmds = (struct load_command*)&fMachOData[sizeof(macho_header)];
const struct load_command* cmd = cmds;
for (uint32_t i = 0; i < cmd_count; ++i) {
switch (cmd->cmd) {
case LC_UUID:
uuid_command* uc = (uuid_command*)cmd;
memcpy(uuid, uc->uuid, 16);
return true;
}
cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize);
}
bzero(uuid, 16);
return false;
}
void ImageLoaderMachO::doRebase(const LinkContext& context)
{
// <rdar://problem/25329861> Delay calling setNeverUnload() until we know this is not for dlopen_preflight()
if ( fRetainForObjC )
this->setNeverUnload();
// dylibs with thread local variables cannot be unloaded because there is no way to clean up all threads
if ( !this->inSharedCache() && (this->machHeader()->flags & MH_HAS_TLV_DESCRIPTORS) )
this->setNeverUnload();
// if prebound and loaded at prebound address, then no need to rebase
if ( this->usablePrebinding(context) ) {
// skip rebasing because prebinding is valid
++fgImagesWithUsedPrebinding; // bump totals for statistics
return;
}
// print why prebinding was not used
if ( context.verbosePrebinding ) {
if ( !this->isPrebindable() ) {
dyld::log("dyld: image not prebound, so could not use prebinding in %s\n", this->getPath());
}
else if ( fSlide != 0 ) {
dyld::log("dyld: image slid, so could not use prebinding in %s\n", this->getPath());
}
else if ( !this->allDependentLibrariesAsWhenPreBound() ) {
dyld::log("dyld: dependent libraries changed, so could not use prebinding in %s\n", this->getPath());
}
else if ( !this->usesTwoLevelNameSpace() ){
dyld::log("dyld: image uses flat-namespace so, parts of prebinding ignored %s\n", this->getPath());
}
else {
dyld::log("dyld: environment variable disabled use of prebinding in %s\n", this->getPath());
}
}
//dyld::log("slide=0x%08lX for %s\n", slide, this->getPath());
#if PREBOUND_IMAGE_SUPPORT
// if prebound and we got here, then prebinding is not valid, so reset all lazy pointers
// if this image is in the shared cache, do not reset, they will be bound in doBind()
if ( this->isPrebindable() && !fInSharedCache )
this->resetPreboundLazyPointers(context);
#endif
// if loaded at preferred address, no rebasing necessary
if ( this->fSlide == 0 )
return;
#if TEXT_RELOC_SUPPORT
// if there are __TEXT fixups, temporarily make __TEXT writable
if ( fTextSegmentRebases )
this->makeTextSegmentWritable(context, true);
#endif
// do actual rebasing
this->rebase(context, fSlide);
#if TEXT_RELOC_SUPPORT
// if there were __TEXT fixups, restore write protection
if ( fTextSegmentRebases )
this->makeTextSegmentWritable(context, false);
#endif
}
#if TEXT_RELOC_SUPPORT
void ImageLoaderMachO::makeTextSegmentWritable(const LinkContext& context, bool writeable)
{
for(unsigned int i=0; i < fSegmentsCount; ++i) {
if ( segExecutable(i) ) {
if ( writeable ) {
segMakeWritable(i, context);
}
else {
#if !__i386__ && !__x86_64__
// some processors require range to be invalidated before it is made executable
sys_icache_invalidate((void*)segActualLoadAddress(i), segSize(textSegmentIndex));
#endif
segProtect(i, context);
}
}
}
}
#endif
const ImageLoader::Symbol* ImageLoaderMachO::findExportedSymbol(const char* name, bool searchReExports, const char* thisPath, const ImageLoader** foundIn) const
{
// look in this image first
const ImageLoader::Symbol* result = this->findShallowExportedSymbol(name, foundIn);
if ( result != NULL )
return result;
if ( searchReExports ) {
for(unsigned int i=0; i < libraryCount(); ++i){
if ( libReExported(i) ) {
ImageLoader* image = libImage(i);
if ( image != NULL ) {
const char* reExPath = libPath(i);
result = image->findExportedSymbol(name, searchReExports, reExPath, foundIn);
if ( result != NULL )
return result;
}
}
}
}
return NULL;
}
uintptr_t ImageLoaderMachO::getExportedSymbolAddress(const Symbol* sym, const LinkContext& context,
const ImageLoader* requestor, bool runResolver, const char* symbolName) const
{
return this->getSymbolAddress(sym, requestor, context, runResolver);
}
uintptr_t ImageLoaderMachO::getSymbolAddress(const Symbol* sym, const ImageLoader* requestor,
const LinkContext& context, bool runResolver) const
{
uintptr_t result = exportedSymbolAddress(context, sym, requestor, runResolver);
// check for interposing overrides
result = interposedAddress(context, result, requestor);
return result;
}
ImageLoader::DefinitionFlags ImageLoaderMachO::getExportedSymbolInfo(const Symbol* sym) const
{
if ( exportedSymbolIsWeakDefintion(sym) )
return kWeakDefinition;
else
return kNoDefinitionOptions;
}
const char* ImageLoaderMachO::getExportedSymbolName(const Symbol* sym) const
{
return exportedSymbolName(sym);
}
uint32_t ImageLoaderMachO::getExportedSymbolCount() const
{
return exportedSymbolCount();
}
const ImageLoader::Symbol* ImageLoaderMachO::getIndexedExportedSymbol(uint32_t index) const
{
return exportedSymbolIndexed(index);
}
uint32_t ImageLoaderMachO::getImportedSymbolCount() const
{
return importedSymbolCount();
}
const ImageLoader::Symbol* ImageLoaderMachO::getIndexedImportedSymbol(uint32_t index) const
{
return importedSymbolIndexed(index);
}
ImageLoader::ReferenceFlags ImageLoaderMachO::getImportedSymbolInfo(const ImageLoader::Symbol* sym) const
{
ImageLoader::ReferenceFlags flags = kNoReferenceOptions;
return flags;
}
const char* ImageLoaderMachO::getImportedSymbolName(const ImageLoader::Symbol* sym) const
{
return importedSymbolName(sym);
}
bool ImageLoaderMachO::getSectionContent(const char* segmentName, const char* sectionName, void** start, size_t* length)
{
const uint32_t cmd_count = ((macho_header*)fMachOData)->ncmds;
const struct load_command* const cmds = (struct load_command*)&fMachOData[sizeof(macho_header)];
const struct load_command* cmd = cmds;
for (uint32_t i = 0; i < cmd_count; ++i) {
switch (cmd->cmd) {
case LC_SEGMENT_COMMAND:
{
const struct macho_segment_command* seg = (struct macho_segment_command*)cmd;
const struct macho_section* const sectionsStart = (struct macho_section*)((char*)seg + sizeof(struct macho_segment_command));
const struct macho_section* const sectionsEnd = &sectionsStart[seg->nsects];
for (const struct macho_section* sect=sectionsStart; sect < sectionsEnd; ++sect) {
if ( (strcmp(sect->segname, segmentName) == 0) && (strcmp(sect->sectname, sectionName) == 0) ) {
*start = (uintptr_t*)(sect->addr + fSlide);
*length = sect->size;
return true;
}
}
}
break;
}
cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize);
}
*start = NULL;
*length = 0;
return false;
}
void ImageLoaderMachO::getUnwindInfo(dyld_unwind_sections* info)
{
info->mh = this->machHeader();
info->dwarf_section = 0;
info->dwarf_section_length = 0;
info->compact_unwind_section = 0;
info->compact_unwind_section_length = 0;
if ( fEHFrameSectionOffset != 0 ) {
const macho_section* sect = (macho_section*)&fMachOData[fEHFrameSectionOffset];
info->dwarf_section = (void*)(sect->addr + fSlide);
info->dwarf_section_length = sect->size;
}
if ( fUnwindInfoSectionOffset != 0 ) {
const macho_section* sect = (macho_section*)&fMachOData[fUnwindInfoSectionOffset];
info->compact_unwind_section = (void*)(sect->addr + fSlide);
info->compact_unwind_section_length = sect->size;
}
}
intptr_t ImageLoaderMachO::computeSlide(const mach_header* mh)
{
const uint32_t cmd_count = mh->ncmds;
const load_command* const cmds = (load_command*)((char*)mh + sizeof(macho_header));
const load_command* cmd = cmds;
for (uint32_t i = 0; i < cmd_count; ++i) {
if ( cmd->cmd == LC_SEGMENT_COMMAND ) {
const macho_segment_command* seg = (macho_segment_command*)cmd;
if ( strcmp(seg->segname, "__TEXT") == 0 )
return (char*)mh - (char*)(seg->vmaddr);
}
cmd = (const load_command*)(((char*)cmd)+cmd->cmdsize);
}
return 0;
}
bool ImageLoaderMachO::findSection(const mach_header* mh, const char* segmentName, const char* sectionName, void** sectAddress, size_t* sectSize)
{
const uint32_t cmd_count = mh->ncmds;
const load_command* const cmds = (load_command*)((char*)mh + sizeof(macho_header));
const load_command* cmd = cmds;
for (uint32_t i = 0; i < cmd_count; ++i) {
switch (cmd->cmd) {
case LC_SEGMENT_COMMAND:
{
const macho_segment_command* seg = (macho_segment_command*)cmd;
const macho_section* const sectionsStart = (macho_section*)((char*)seg + sizeof(macho_segment_command));
const macho_section* const sectionsEnd = &sectionsStart[seg->nsects];
for (const macho_section* sect=sectionsStart; sect < sectionsEnd; ++sect) {
if ( (strcmp(sect->segname, segmentName) == 0) && (strcmp(sect->sectname, sectionName) == 0) ) {
*sectAddress = (void*)(sect->addr + computeSlide(mh));
*sectSize = sect->size;
return true;
}
}
}
break;
}
cmd = (const load_command*)(((char*)cmd)+cmd->cmdsize);
}
return false;
}
const macho_section* ImageLoaderMachO::findSection(const void* imageInterior) const
{
const uint32_t cmd_count = ((macho_header*)fMachOData)->ncmds;
const struct load_command* const cmds = (struct load_command*)&fMachOData[sizeof(macho_header)];
const struct load_command* cmd = cmds;
const uintptr_t unslidInteriorAddress = (uintptr_t)imageInterior - this->getSlide();
for (uint32_t i = 0; i < cmd_count; ++i) {
switch (cmd->cmd) {
case LC_SEGMENT_COMMAND:
{
const struct macho_segment_command* seg = (struct macho_segment_command*)cmd;
if ( (unslidInteriorAddress >= seg->vmaddr) && (unslidInteriorAddress < (seg->vmaddr+seg->vmsize)) ) {
const struct macho_section* const sectionsStart = (struct macho_section*)((char*)seg + sizeof(struct macho_segment_command));
const struct macho_section* const sectionsEnd = &sectionsStart[seg->nsects];
for (const struct macho_section* sect=sectionsStart; sect < sectionsEnd; ++sect) {
if ((sect->addr <= unslidInteriorAddress) && (unslidInteriorAddress < (sect->addr+sect->size))) {
return sect;
}
}
}
}
break;
}
cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize);
}
return nullptr;
}
bool ImageLoaderMachO::findSection(const void* imageInterior, const char** segmentName, const char** sectionName, size_t* sectionOffset)
{
if (const struct macho_section* sect = findSection(imageInterior)) {
const uintptr_t unslidInteriorAddress = (uintptr_t)imageInterior - this->getSlide();
if ( segmentName != NULL )
*segmentName = sect->segname;
if ( sectionName != NULL )
*sectionName = sect->sectname;
if ( sectionOffset != NULL )
*sectionOffset = unslidInteriorAddress - sect->addr;
return true;
}
return false;
}
const char* ImageLoaderMachO::libPath(unsigned int index) const
{
const uint32_t cmd_count = ((macho_header*)fMachOData)->ncmds;
const struct load_command* const cmds = (struct load_command*)&fMachOData[sizeof(macho_header)];
const struct load_command* cmd = cmds;
unsigned count = 0;
for (uint32_t i = 0; i < cmd_count; ++i) {
switch ( cmd->cmd ) {
case LC_LOAD_DYLIB:
case LC_LOAD_WEAK_DYLIB:
case LC_REEXPORT_DYLIB:
case LC_LOAD_UPWARD_DYLIB:
if ( index == count ) {
const struct dylib_command* dylibCmd = (struct dylib_command*)cmd;
return (char*)cmd + dylibCmd->dylib.name.offset;
}
++count;
break;
}
cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize);
}
// <rdar://problem/24256354> if image linked with nothing and we implicitly added libSystem.dylib, return that
if ( needsAddedLibSystemDepency(libraryCount(), (macho_header*)fMachOData) ) {
return LIBSYSTEM_DYLIB_PATH;
}
return NULL;
}
void __attribute__((noreturn)) ImageLoaderMachO::throwSymbolNotFound(const LinkContext& context, const char* symbol,
const char* referencedFrom, const char* fromVersMismatch,
const char* expectedIn)
{
// record values for possible use by CrashReporter or Finder
(*context.setErrorStrings)(DYLD_EXIT_REASON_SYMBOL_MISSING, referencedFrom, expectedIn, symbol);
dyld::throwf("Symbol not found: %s\n Referenced from: %s%s\n Expected in: %s\n",
symbol, referencedFrom, fromVersMismatch, expectedIn);
}
const mach_header* ImageLoaderMachO::machHeader() const
{
return (mach_header*)fMachOData;
}
uintptr_t ImageLoaderMachO::getSlide() const
{
return fSlide;
}
// hmm. maybe this should be up in ImageLoader??
const void* ImageLoaderMachO::getEnd() const
{
uintptr_t lastAddress = 0;
for(unsigned int i=0; i < fSegmentsCount; ++i) {
uintptr_t segEnd = segActualEndAddress(i);
if ( strcmp(segName(i), "__UNIXSTACK") != 0 ) {
if ( segEnd > lastAddress )
lastAddress = segEnd;
}
}
return (const void*)lastAddress;
}
uintptr_t ImageLoaderMachO::bindLocation(const LinkContext& context, uintptr_t baseVMAddress,
uintptr_t location, uintptr_t value,
uint8_t type, const char* symbolName,
intptr_t addend, const char* inPath, const char* toPath, const char* msg,
ExtraBindData *extraBindData, uintptr_t slide)
{
auto logBind = [&]() {
if ( !context.verboseBind )
return;
if ( addend != 0 ) {
dyld::log("dyld: %sbind: %s:0x%08lX = %s:%s, *0x%08lX = 0x%08lX + %ld\n",
msg, shortName(inPath), (uintptr_t)location,
((toPath != NULL) ? shortName(toPath) : "<missing weak_import>"),
symbolName, (uintptr_t)location, value, addend);
} else {
dyld::log("dyld: %sbind: %s:0x%08lX = %s:%s, *0x%08lX = 0x%08lX\n",
msg, shortName(inPath), (uintptr_t)location,
((toPath != NULL) ? shortName(toPath) : "<missing weak_import>"),
symbolName, (uintptr_t)location, value);
}
};
#if LOG_BINDINGS
// dyld::logBindings("%s: %s\n", targetImage->getShortName(), symbolName);
#endif
// do actual update
uintptr_t* locationToFix = (uintptr_t*)location;
uint32_t* loc32;
uintptr_t newValue = value+addend;
uint32_t value32;
switch (type) {
case BIND_TYPE_POINTER:
logBind();
// test first so we don't needless dirty pages
if ( *locationToFix != newValue )
*locationToFix = newValue;
break;
case BIND_TYPE_TEXT_ABSOLUTE32:
logBind();
loc32 = (uint32_t*)locationToFix;
value32 = (uint32_t)newValue;
if ( *loc32 != value32 )
*loc32 = value32;
break;
case BIND_TYPE_TEXT_PCREL32:
logBind();
loc32 = (uint32_t*)locationToFix;
value32 = (uint32_t)(newValue - (((uintptr_t)locationToFix) + 4));
if ( *loc32 != value32 )
*loc32 = value32;
break;
case BIND_TYPE_THREADED_BIND:
logBind();
// test first so we don't needless dirty pages
if ( *locationToFix != newValue )
*locationToFix = newValue;
break;
case BIND_TYPE_THREADED_REBASE: {
// Regular pointer which needs to fit in 51-bits of value.
// C++ RTTI uses the top bit, so we'll allow the whole top-byte
// and the signed-extended bottom 43-bits to be fit in to 51-bits.
uint64_t top8Bits = *locationToFix & 0x0007F80000000000ULL;
uint64_t bottom43Bits = *locationToFix & 0x000007FFFFFFFFFFULL;
uint64_t targetValue = ( top8Bits << 13 ) | (((intptr_t)(bottom43Bits << 21) >> 21) & 0x00FFFFFFFFFFFFFF);
newValue = (uintptr_t)(targetValue + slide);
if ( context.verboseRebase ) {
dyld::log("dyld: rebase: %s:*0x%08lX += 0x%08lX = 0x%08lX\n", shortName(inPath), (uintptr_t)locationToFix, slide, newValue);
}
*locationToFix = newValue;
break;
}
default:
dyld::throwf("bad bind type %d", type);
}
// update statistics
++fgTotalBindFixups;
return newValue;
}
#if SUPPORT_OLD_CRT_INITIALIZATION
// first 16 bytes of "start" in crt1.o
#if __i386__
static uint8_t sStandardEntryPointInstructions[16] = { 0x6a, 0x00, 0x89, 0xe5, 0x83, 0xe4, 0xf0, 0x83, 0xec, 0x10, 0x8b, 0x5d, 0x04, 0x89, 0x5c, 0x24 };
#endif
#endif
struct DATAdyld {
void* dyldLazyBinder; // filled in at launch by dyld to point into dyld to &stub_binding_helper
void* dyldFuncLookup; // filled in at launch by dyld to point into dyld to &_dyld_func_lookup
// the following only exist in main executables built for 10.5 or later
ProgramVars vars;
};
// These are defined in dyldStartup.s
extern "C" void stub_binding_helper();
extern "C" int _dyld_func_lookup(const char* name, void** address);
static const char* libDyldPath(const ImageLoader::LinkContext& context)
{
#if TARGET_OS_OSX
if ( context.driverKit )
return DRIVERKIT_LIBDYLD_DYLIB_PATH;
else
#endif
return LIBDYLD_DYLIB_PATH;
}
void ImageLoaderMachO::setupLazyPointerHandler(const LinkContext& context)
{
const macho_header* mh = (macho_header*)fMachOData;
const uint32_t cmd_count = mh->ncmds;
const struct load_command* const cmds = (struct load_command*)&fMachOData[sizeof(macho_header)];
const struct load_command* cmd;
// There used to be some optimizations to skip this section scan, but we need to handle the
// __dyld section in libdyld.dylib, so everything needs to be scanned for now.
// <rdar://problem/10910062> CrashTracer: 1,295 crashes in bash at bash: getenv
if ( true ) {
cmd = cmds;
for (uint32_t i = 0; i < cmd_count; ++i) {
if ( cmd->cmd == LC_SEGMENT_COMMAND ) {
const struct macho_segment_command* seg = (struct macho_segment_command*)cmd;
if ( strncmp(seg->segname, "__DATA", 6) == 0 ) {
const struct macho_section* const sectionsStart = (struct macho_section*)((char*)seg + sizeof(struct macho_segment_command));
const struct macho_section* const sectionsEnd = &sectionsStart[seg->nsects];
for (const struct macho_section* sect=sectionsStart; sect < sectionsEnd; ++sect) {
if ( strcmp(sect->sectname, "__dyld" ) == 0 ) {
struct DATAdyld* dd = (struct DATAdyld*)(sect->addr + fSlide);
#if !__arm64__ && !__ARM_ARCH_7K__
if ( sect->size > offsetof(DATAdyld, dyldLazyBinder) ) {
if ( dd->dyldLazyBinder != (void*)&stub_binding_helper )
dd->dyldLazyBinder = (void*)&stub_binding_helper;
}
#endif // !__arm64__
// <rdar://problem/40352925> Add work around for existing apps that have deprecated __dyld section
const char* installNm = this->getInstallPath();
if ( (mh->filetype != MH_DYLIB) || (installNm == NULL) || (strcmp(installNm, libDyldPath(context)) != 0) ) {
#if TARGET_OS_OSX
// don't allow macOS apps build with 10.14 or later SDK and targeting 10.8 or later to have a __dyld section
if ( (minOSVersion() >= 0x000a0800) && (sdkVersion() >= 0x000a0e00) )
dyld::throwf("__dyld section not supported in %s", this->getPath());
#endif
#if TARGET_OS_IOS || TARGET_OS_TV
// don't allow iOS apps build with 12.0 or later SDK to have a __dyld section
if ( sdkVersion() >= 0x000c0000 )
dyld::throwf("__dyld section not supported in %s", this->getPath());
#endif
#if TARGET_OS_WATCH
if ( sdkVersion() >= 0x00050000 )
dyld::throwf("__dyld section not supported in %s", this->getPath());
#endif
}
if ( sect->size > offsetof(DATAdyld, dyldFuncLookup) ) {
if ( dd->dyldFuncLookup != (void*)&_dyld_func_lookup )
dd->dyldFuncLookup = (void*)&_dyld_func_lookup;
}
if ( mh->filetype == MH_EXECUTE ) {
// there are two ways to get the program variables
if ( (sect->size > offsetof(DATAdyld, vars)) && (dd->vars.mh == mh) ) {
// some really old binaries have space for vars, but it is zero filled
// main executable has 10.5 style __dyld section that has program variable pointers
context.setNewProgramVars(dd->vars);
}
else {
// main executable is pre-10.5 and requires the symbols names to be looked up
this->lookupProgramVars(context);
#if SUPPORT_OLD_CRT_INITIALIZATION
// If the first 16 bytes of the entry point's instructions do not
// match what crt1.o supplies, then the program has a custom entry point.
// This means it might be doing something that needs to be executed before
// initializers are run.
if ( memcmp(this->getEntryFromLC_UNIXTHREAD(), sStandardEntryPointInstructions, 16) != 0 ) {
if ( context.verboseInit )
dyld::log("dyld: program uses non-standard entry point so delaying running of initializers\n");
context.setRunInitialzersOldWay();
}
#endif
}
}
else if ( mh->filetype == MH_DYLIB ) {
const char* installPath = this->getInstallPath();
if ( (installPath != NULL) && ((strncmp(installPath, "/usr/lib/", 9) == 0) || (strncmp(installPath, "/System/DriverKit/usr/lib/", 26) == 0)) ) {
if ( sect->size > offsetof(DATAdyld, vars) ) {
// use ProgramVars from libdyld.dylib but tweak mh field to correct value
dd->vars.mh = context.mainExecutable->machHeader();
context.setNewProgramVars(dd->vars);
}
}
}
}
else if ( (strcmp(sect->sectname, "__program_vars" ) == 0) && (mh->filetype == MH_EXECUTE) ) {
// this is a Mac OS X 10.6 or later main executable
struct ProgramVars* pv = (struct ProgramVars*)(sect->addr + fSlide);
context.setNewProgramVars(*pv);
}
}
}
}
cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize);
}
}
}
void ImageLoaderMachO::lookupProgramVars(const LinkContext& context) const
{
ProgramVars vars = context.programVars;
const ImageLoader::Symbol* sym;
// get mach header directly
vars.mh = (macho_header*)fMachOData;
// lookup _NXArgc
sym = this->findShallowExportedSymbol("_NXArgc", NULL);
if ( sym != NULL )
vars.NXArgcPtr = (int*)this->getExportedSymbolAddress(sym, context, this, false, NULL);
// lookup _NXArgv
sym = this->findShallowExportedSymbol("_NXArgv", NULL);
if ( sym != NULL )
vars.NXArgvPtr = (const char***)this->getExportedSymbolAddress(sym, context, this, false, NULL);
// lookup _environ
sym = this->findShallowExportedSymbol("_environ", NULL);
if ( sym != NULL )
vars.environPtr = (const char***)this->getExportedSymbolAddress(sym, context, this, false, NULL);
// lookup __progname
sym = this->findShallowExportedSymbol("___progname", NULL);
if ( sym != NULL )
vars.__prognamePtr = (const char**)this->getExportedSymbolAddress(sym, context, this, false, NULL);
context.setNewProgramVars(vars);
}
bool ImageLoaderMachO::usablePrebinding(const LinkContext& context) const
{
// dylibs in dyld cache do not need to be rebased or bound
// for chained fixups always pretend dylib is up to date, patch tables will be used later
if ( fInSharedCache && (this->allDependentLibrariesAsWhenPreBound() || context.dyldCache->header.builtFromChainedFixups) ) {
// allow environment variables to disable prebinding
if ( context.bindFlat )
return false;
switch ( context.prebindUsage ) {
case kUseAllPrebinding:
return true;
case kUseSplitSegPrebinding:
return this->fIsSplitSeg;
case kUseAllButAppPredbinding:
return (this != context.mainExecutable);
case kUseNoPrebinding:
return false;
}
}
return false;
}
static void *stripPointer(void *ptr) {
#if __has_feature(ptrauth_calls)
return __builtin_ptrauth_strip(ptr, ptrauth_key_asia);
#else
return ptr;
#endif
}
void ImageLoaderMachO::doImageInit(const LinkContext& context)
{
if ( fHasDashInit ) {
const uint32_t cmd_count = ((macho_header*)fMachOData)->ncmds;
const struct load_command* const cmds = (struct load_command*)&fMachOData[sizeof(macho_header)];
const struct load_command* cmd = cmds;
for (uint32_t i = 0; i < cmd_count; ++i) {
switch (cmd->cmd) {
case LC_ROUTINES_COMMAND:
Initializer func = (Initializer)(((struct macho_routines_command*)cmd)->init_address + fSlide);
#if __has_feature(ptrauth_calls)
func = (Initializer)__builtin_ptrauth_sign_unauthenticated((void*)func, ptrauth_key_asia, 0);
#endif
// <rdar://problem/8543820&9228031> verify initializers are in image
if ( ! this->containsAddress(stripPointer((void*)func)) ) {
dyld::throwf("initializer function %p not in mapped image for %s\n", func, this->getPath());
}
if ( ! dyld::gProcessInfo->libSystemInitialized ) {
// <rdar://problem/17973316> libSystem initializer must run first
dyld::throwf("-init function in image (%s) that does not link with libSystem.dylib\n", this->getPath());
}
if ( context.verboseInit )
dyld::log("dyld: calling -init function %p in %s\n", func, this->getPath());
{
dyld3::ScopedTimer(DBG_DYLD_TIMING_STATIC_INITIALIZER, (uint64_t)fMachOData, (uint64_t)func, 0);
func(context.argc, context.argv, context.envp, context.apple, &context.programVars);
}
break;
}
cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize);
}
}
}
static const char* libSystemPath(const ImageLoader::LinkContext& context)
{
#if TARGET_OS_OSX
if ( context.driverKit )
return DRIVERKIT_LIBSYSTEM_DYLIB_PATH;
else
#endif
return LIBSYSTEM_DYLIB_PATH;
}
void ImageLoaderMachO::doModInitFunctions(const LinkContext& context)
{
if ( fHasInitializers ) {
const uint32_t cmd_count = ((macho_header*)fMachOData)->ncmds;
const struct load_command* const cmds = (struct load_command*)&fMachOData[sizeof(macho_header)];
const struct load_command* cmd = cmds;
for (uint32_t i = 0; i < cmd_count; ++i) {
if ( cmd->cmd == LC_SEGMENT_COMMAND ) {
const struct macho_segment_command* seg = (struct macho_segment_command*)cmd;
const struct macho_section* const sectionsStart = (struct macho_section*)((char*)seg + sizeof(struct macho_segment_command));
const struct macho_section* const sectionsEnd = &sectionsStart[seg->nsects];
for (const struct macho_section* sect=sectionsStart; sect < sectionsEnd; ++sect) {
const uint8_t type = sect->flags & SECTION_TYPE;
if ( type == S_MOD_INIT_FUNC_POINTERS ) {
Initializer* inits = (Initializer*)(sect->addr + fSlide);
const size_t count = sect->size / sizeof(uintptr_t);
// <rdar://problem/23929217> Ensure __mod_init_func section is within segment
if ( (sect->addr < seg->vmaddr) || (sect->addr+sect->size > seg->vmaddr+seg->vmsize) || (sect->addr+sect->size < sect->addr) )
dyld::throwf("__mod_init_funcs section has malformed address range for %s\n", this->getPath());
for (size_t j=0; j < count; ++j) {
Initializer func = inits[j];
// <rdar://problem/8543820&9228031> verify initializers are in image
if ( ! this->containsAddress(stripPointer((void*)func)) ) {
dyld::throwf("initializer function %p not in mapped image for %s\n", func, this->getPath());
}
if ( ! dyld::gProcessInfo->libSystemInitialized ) {
// <rdar://problem/17973316> libSystem initializer must run first
const char* installPath = getInstallPath();
if ( (installPath == NULL) || (strcmp(installPath, libSystemPath(context)) != 0) )
dyld::throwf("initializer in image (%s) that does not link with libSystem.dylib\n", this->getPath());
}
if ( context.verboseInit )
dyld::log("dyld: calling initializer function %p in %s\n", func, this->getPath());
bool haveLibSystemHelpersBefore = (dyld::gLibSystemHelpers != NULL);
{
dyld3::ScopedTimer(DBG_DYLD_TIMING_STATIC_INITIALIZER, (uint64_t)fMachOData, (uint64_t)func, 0);
func(context.argc, context.argv, context.envp, context.apple, &context.programVars);
}
bool haveLibSystemHelpersAfter = (dyld::gLibSystemHelpers != NULL);
if ( !haveLibSystemHelpersBefore && haveLibSystemHelpersAfter ) {
// now safe to use malloc() and other calls in libSystem.dylib
dyld::gProcessInfo->libSystemInitialized = true;
}
}
}
else if ( type == S_INIT_FUNC_OFFSETS ) {
const uint32_t* inits = (uint32_t*)(sect->addr + fSlide);
const size_t count = sect->size / sizeof(uint32_t);
// Ensure section is within segment
if ( (sect->addr < seg->vmaddr) || (sect->addr+sect->size > seg->vmaddr+seg->vmsize) || (sect->addr+sect->size < sect->addr) )
dyld::throwf("__init_offsets section has malformed address range for %s\n", this->getPath());
if ( seg->initprot & VM_PROT_WRITE )
dyld::throwf("__init_offsets section is not in read-only segment %s\n", this->getPath());
for (size_t j=0; j < count; ++j) {
uint32_t funcOffset = inits[j];
// verify initializers are in image
if ( ! this->containsAddress((uint8_t*)this->machHeader() + funcOffset) ) {
dyld::throwf("initializer function offset 0x%08X not in mapped image for %s\n", funcOffset, this->getPath());
}
if ( ! dyld::gProcessInfo->libSystemInitialized ) {
// <rdar://problem/17973316> libSystem initializer must run first
const char* installPath = getInstallPath();
if ( (installPath == NULL) || (strcmp(installPath, libSystemPath(context)) != 0) )
dyld::throwf("initializer in image (%s) that does not link with libSystem.dylib\n", this->getPath());
}
Initializer func = (Initializer)((uint8_t*)this->machHeader() + funcOffset);
if ( context.verboseInit )
dyld::log("dyld: calling initializer function %p in %s\n", func, this->getPath());
#if __has_feature(ptrauth_calls)
func = (Initializer)__builtin_ptrauth_sign_unauthenticated((void*)func, ptrauth_key_asia, 0);
#endif
bool haveLibSystemHelpersBefore = (dyld::gLibSystemHelpers != NULL);
{
dyld3::ScopedTimer(DBG_DYLD_TIMING_STATIC_INITIALIZER, (uint64_t)fMachOData, (uint64_t)func, 0);
func(context.argc, context.argv, context.envp, context.apple, &context.programVars);
}
bool haveLibSystemHelpersAfter = (dyld::gLibSystemHelpers != NULL);
if ( !haveLibSystemHelpersBefore && haveLibSystemHelpersAfter ) {
// now safe to use malloc() and other calls in libSystem.dylib
dyld::gProcessInfo->libSystemInitialized = true;
}
}
}
}
}
cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize);
}
}
}
void ImageLoaderMachO::doGetDOFSections(const LinkContext& context, std::vector<ImageLoader::DOFInfo>& dofs)
{
if ( fHasDOFSections ) {
// walk load commands (mapped in at start of __TEXT segment)
const uint32_t cmd_count = ((macho_header*)fMachOData)->ncmds;
const struct load_command* const cmds = (struct load_command*)&fMachOData[sizeof(macho_header)];
const struct load_command* cmd = cmds;
for (uint32_t i = 0; i < cmd_count; ++i) {
switch (cmd->cmd) {
case LC_SEGMENT_COMMAND:
{
const struct macho_segment_command* seg = (struct macho_segment_command*)cmd;
const struct macho_section* const sectionsStart = (struct macho_section*)((char*)seg + sizeof(struct macho_segment_command));
const struct macho_section* const sectionsEnd = &sectionsStart[seg->nsects];
for (const struct macho_section* sect=sectionsStart; sect < sectionsEnd; ++sect) {
if ( (sect->flags & SECTION_TYPE) == S_DTRACE_DOF ) {
// <rdar://problem/23929217> Ensure section is within segment
if ( (sect->addr < seg->vmaddr) || (sect->addr+sect->size > seg->vmaddr+seg->vmsize) || (sect->addr+sect->size < sect->addr) )
dyld::throwf("DOF section has malformed address range for %s\n", this->getPath());
ImageLoader::DOFInfo info;
info.dof = (void*)(sect->addr + fSlide);
info.imageHeader = this->machHeader();
info.imageShortName = this->getShortName();
dofs.push_back(info);
}
}
}
break;
}
cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize);
}
}
}
bool ImageLoaderMachO::doInitialization(const LinkContext& context)
{
CRSetCrashLogMessage2(this->getPath());
// mach-o has -init and static initializers
doImageInit(context);
doModInitFunctions(context);
CRSetCrashLogMessage2(NULL);
return (fHasDashInit || fHasInitializers);
}
bool ImageLoaderMachO::needsInitialization()
{
return ( fHasDashInit || fHasInitializers );
}
bool ImageLoaderMachO::needsTermination()
{
return fHasTerminators;
}
void ImageLoaderMachO::doTermination(const LinkContext& context)
{
if ( fHasTerminators ) {
const uint32_t cmd_count = ((macho_header*)fMachOData)->ncmds;
const struct load_command* const cmds = (struct load_command*)&fMachOData[sizeof(macho_header)];
const struct load_command* cmd = cmds;
for (uint32_t i = 0; i < cmd_count; ++i) {
if ( cmd->cmd == LC_SEGMENT_COMMAND ) {
const struct macho_segment_command* seg = (struct macho_segment_command*)cmd;
const struct macho_section* const sectionsStart = (struct macho_section*)((char*)seg + sizeof(struct macho_segment_command));
const struct macho_section* const sectionsEnd = &sectionsStart[seg->nsects];
for (const struct macho_section* sect=sectionsStart; sect < sectionsEnd; ++sect) {
const uint8_t type = sect->flags & SECTION_TYPE;
if ( type == S_MOD_TERM_FUNC_POINTERS ) {
// <rdar://problem/23929217> Ensure section is within segment
if ( (sect->addr < seg->vmaddr) || (sect->addr+sect->size > seg->vmaddr+seg->vmsize) || (sect->addr+sect->size < sect->addr) )
dyld::throwf("DOF section has malformed address range for %s\n", this->getPath());
Terminator* terms = (Terminator*)(sect->addr + fSlide);
const size_t count = sect->size / sizeof(uintptr_t);
for (size_t j=count; j > 0; --j) {
Terminator func = terms[j-1];
#if __has_feature(ptrauth_calls)
func = (Terminator)__builtin_ptrauth_sign_unauthenticated((void*)func, ptrauth_key_asia, 0);
#endif
// <rdar://problem/8543820&9228031> verify terminators are in image
if ( ! this->containsAddress(stripPointer((void*)func)) ) {
dyld::throwf("termination function %p not in mapped image for %s\n", func, this->getPath());
}
if ( context.verboseInit )
dyld::log("dyld: calling termination function %p in %s\n", func, this->getPath());
func();
}
}
}
}
cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize);
}
}
}
void ImageLoaderMachO::printStatisticsDetails(unsigned int imageCount, const InitializerTimingList& timingInfo)
{
ImageLoader::printStatisticsDetails(imageCount, timingInfo);
dyld::log("total symbol trie searches: %d\n", fgSymbolTrieSearchs);
dyld::log("total symbol table binary searches: %d\n", fgSymbolTableBinarySearchs);
dyld::log("total images defining weak symbols: %u\n", fgImagesHasWeakDefinitions);
dyld::log("total images using weak symbols: %u\n", fgImagesRequiringCoalescing);
}
intptr_t ImageLoaderMachO::assignSegmentAddresses(const LinkContext& context, size_t extraAllocationSize)
{
// preflight and calculate slide if needed
const bool inPIE = (fgNextPIEDylibAddress != 0);
intptr_t slide = 0;
if ( this->segmentsCanSlide() && this->segmentsMustSlideTogether() ) {
intptr_t segmentReAlignSlide = 0;
bool needsToSlide = false;
bool imageHasPreferredLoadAddress = segHasPreferredLoadAddress(0);
uintptr_t lowAddr = (unsigned long)(-1);
uintptr_t highAddr = 0;
for(unsigned int i=0, e=segmentCount(); i < e; ++i) {
const uintptr_t segLow = segPreferredLoadAddress(i);
const uintptr_t segHigh = dyld_page_round(segLow + segSize(i));
if ( segLow < highAddr ) {
if ( dyld_page_size > 4096 )
dyld::throwf("can't map segments into 16KB pages");
else
dyld::throwf("overlapping segments");
}
if ( segLow < lowAddr )
lowAddr = segLow;
if ( segHigh > highAddr )
highAddr = segHigh;
#if defined(__x86_64__) && !TARGET_OS_SIMULATOR
// For Cambria on Aruba systems (16k page size), realign the image so the first segment ends on a 16k boundry.
// FIXME: this can be removed when Aruba dev systems are no longer supported.
if ( dyld::isTranslated() && vm_page_size == 0x4000 && i == 0 && segLow == 0 ) {
const uintptr_t segHighPageOffset = segHigh & vm_page_mask;
if ( segHighPageOffset > 0 ) {
// Adjust the slide to make the first segment end on a page boundry.
needsToSlide = true;
segmentReAlignSlide = vm_page_size - segHighPageOffset;
if (context.verboseMapping) {
dyld::log("dyld: Image %s first segment(%s) does not end on a page boundry [0x%lx, 0x%lx) adding 0x%lx to slide to realign\n", getPath(), segName(i), segLow, segHigh, segmentReAlignSlide);
}
}
}
#endif
if ( needsToSlide || !imageHasPreferredLoadAddress || inPIE || !reserveAddressRange(segPreferredLoadAddress(i), segSize(i)) )
needsToSlide = true;
}
if ( needsToSlide ) {
// find a chunk of address space to hold all segments
size_t size = highAddr-lowAddr+segmentReAlignSlide;
uintptr_t addr = reserveAnAddressRange(size+extraAllocationSize, context);
slide = addr - lowAddr + segmentReAlignSlide;
} else if ( extraAllocationSize ) {
if (!reserveAddressRange(highAddr, extraAllocationSize)) {
throw "failed to reserve space for aot";
}
}
}
else if ( ! this->segmentsCanSlide() ) {
uintptr_t highAddr = 0;
for(unsigned int i=0, e=segmentCount(); i < e; ++i) {
const uintptr_t segLow = segPreferredLoadAddress(i);
const uintptr_t segHigh = dyld_page_round(segLow + segSize(i));
if ( segHigh > highAddr )
highAddr = segHigh;
if ( (strcmp(segName(i), "__PAGEZERO") == 0) && (segFileSize(i) == 0) && (segPreferredLoadAddress(i) == 0) )
continue;
if ( !reserveAddressRange(segPreferredLoadAddress(i), segSize(i)) )
dyld::throwf("can't map unslidable segment %s to 0x%lX with size 0x%lX", segName(i), segPreferredLoadAddress(i), segSize(i));
}
if (extraAllocationSize) {
dyld::throwf("binaries with non-slidable segments don't support aot: %s", this->getPath());
}
}
else {
throw "mach-o does not support independently sliding segments";
}
return slide;
}
uintptr_t ImageLoaderMachO::reserveAnAddressRange(size_t length, const ImageLoader::LinkContext& context)
{
vm_address_t addr = 0;
vm_size_t size = length;
// in PIE programs, load initial dylibs after main executable so they don't have fixed addresses either
if ( fgNextPIEDylibAddress != 0 ) {
// add small (0-3 pages) random padding between dylibs
addr = fgNextPIEDylibAddress + (__stack_chk_guard/fgNextPIEDylibAddress & (sizeof(long)-1))*dyld_page_size;
//dyld::log("padding 0x%08llX, guard=0x%08llX\n", (long long)(addr - fgNextPIEDylibAddress), (long long)(__stack_chk_guard));
kern_return_t r = vm_alloc(&addr, size, VM_FLAGS_FIXED | VM_MAKE_TAG(VM_MEMORY_DYLIB));
if ( r == KERN_SUCCESS ) {
fgNextPIEDylibAddress = addr + size;
return addr;
}
fgNextPIEDylibAddress = 0;
}
kern_return_t r = vm_alloc(&addr, size, VM_FLAGS_ANYWHERE | VM_MAKE_TAG(VM_MEMORY_DYLIB));
if ( r != KERN_SUCCESS )
throw "out of address space";
return addr;
}
bool ImageLoaderMachO::reserveAddressRange(uintptr_t start, size_t length)
{
vm_address_t addr = start;
vm_size_t size = length;
kern_return_t r = vm_alloc(&addr, size, VM_FLAGS_FIXED | VM_MAKE_TAG(VM_MEMORY_DYLIB));
if ( r != KERN_SUCCESS )
return false;
return true;
}
void ImageLoaderMachO::mapSegments(int fd, uint64_t offsetInFat, uint64_t lenInFat, uint64_t fileLen, const LinkContext& context)
{
uint64_t extra_allocation_size = 0;
#if defined(__x86_64__) && !TARGET_OS_SIMULATOR
if (dyld::isTranslated()) {
fAotPath = new char[PATH_MAX];
int ret = syscall(0x7000001, fd, this->getPath(), &extra_allocation_size, fAotPath, PATH_MAX);
if (ret != 0) {
delete fAotPath;
fAotPath = nullptr;
}
}
#endif
// find address range for image
intptr_t slide = this->assignSegmentAddresses(context, extra_allocation_size);
if ( context.verboseMapping ) {
if ( offsetInFat != 0 )
dyld::log("dyld: Mapping %s (slice offset=%llu)\n", this->getPath(), (unsigned long long)offsetInFat);
else
dyld::log("dyld: Mapping %s\n", this->getPath());
}
// <rdar://problem/47163421> speculatively read whole slice
fspecread_t specread = {} ;
specread.fsr_offset = offsetInFat;
specread.fsr_length = lenInFat;
specread.fsr_flags = 0;
fcntl(fd, F_SPECULATIVE_READ, &specread);
if ( context.verboseMapping )
dyld::log("dyld: Speculatively read offset=0x%08llX, len=0x%08llX, path=%s\n", offsetInFat, lenInFat, this->getPath());
// map in all segments
uintptr_t baseAddress = (unsigned long)(-1);
uintptr_t endAddress = 0;
uintptr_t mappedMachHeaderAddress = 0;
for(unsigned int i=0, e=segmentCount(); i < e; ++i) {
vm_offset_t fileOffset = (vm_offset_t)(segFileOffset(i) + offsetInFat);
vm_size_t size = segFileSize(i);
uintptr_t requestedLoadAddress = segPreferredLoadAddress(i) + slide;
const uintptr_t segmentEnd = dyld_page_round(requestedLoadAddress + segSize(i));
if ( requestedLoadAddress < baseAddress )
baseAddress = requestedLoadAddress;
if ( segmentEnd > endAddress )
endAddress = segmentEnd;
if (segFileOffset(i) == 0 && segFileSize(i) != 0) {
mappedMachHeaderAddress = requestedLoadAddress;
}
int protection = 0;
if ( !segUnaccessible(i) ) {
if ( segExecutable(i) )
protection |= PROT_EXEC;
if ( segReadable(i) )
protection |= PROT_READ;
if ( segWriteable(i) ) {
protection |= PROT_WRITE;
// rdar://problem/22525618 force __LINKEDIT to always be mapped read-only
if ( strcmp(segName(i), "__LINKEDIT") == 0 )
protection = PROT_READ;
}
}
#if __i386__
// initially map __IMPORT segments R/W so dyld can update them
if ( segIsReadOnlyImport(i) )
protection |= PROT_WRITE;
#endif
// wholly zero-fill segments have nothing to mmap() in
if ( size > 0 ) {
if ( (fileOffset+size) > fileLen ) {
dyld::throwf("truncated mach-o error: segment %s extends to %llu which is past end of file %llu",
segName(i), (uint64_t)(fileOffset+size), fileLen);
}
void* loadAddress = xmmap((void*)requestedLoadAddress, size, protection, MAP_FIXED | MAP_PRIVATE, fd, fileOffset);
if ( loadAddress == ((void*)(-1)) ) {
int mmapErr = errno;
if ( mmapErr == EPERM ) {
if ( dyld::sandboxBlockedMmap(getPath()) )
dyld::throwf("file system sandbox blocked mmap() of '%s'", this->getPath());
else
dyld::throwf("code signing blocked mmap() of '%s'", this->getPath());
}
else
dyld::throwf("mmap() errno=%d at address=0x%08lX, size=0x%08lX segment=%s in Segment::map() mapping %s",
mmapErr, requestedLoadAddress, (uintptr_t)size, segName(i), getPath());
}
}
// update stats
++ImageLoader::fgTotalSegmentsMapped;
ImageLoader::fgTotalBytesMapped += size;
if ( context.verboseMapping )
dyld::log("%18s at 0x%08lX->0x%08lX with permissions %c%c%c\n", segName(i), requestedLoadAddress, requestedLoadAddress+size-1,
(protection & PROT_READ) ? 'r' : '.', (protection & PROT_WRITE) ? 'w' : '.', (protection & PROT_EXEC) ? 'x' : '.' );
}
#if defined(__x86_64__) && !TARGET_OS_SIMULATOR
if (dyld::isTranslated() && extra_allocation_size != 0) {
const struct mach_header* aot_load_address;
dyld_aot_image_info aot_image_info = {};
int ret = syscall(0x7000002, this->getPath(), mappedMachHeaderAddress, endAddress, &aot_load_address, &aot_image_info.aotImageSize, aot_image_info.aotImageKey);
if (ret == 0) {
extern void addAotImagesToAllAotImages(uint32_t aotInfoCount, const dyld_aot_image_info aotInfo[]);
// fill in the aot load address, at this point the cambria trap has filled in
// the image size and image key fields
aot_image_info.aotLoadAddress = aot_load_address;
aot_image_info.x86LoadAddress = (struct mach_header*)baseAddress;
addAotImagesToAllAotImages(1, &aot_image_info);
}
}
#endif
// update slide to reflect load location
this->setSlide(slide);
}
void ImageLoaderMachO::mapSegments(const void* memoryImage, uint64_t imageLen, const LinkContext& context)
{
// find address range for image
intptr_t slide = this->assignSegmentAddresses(context, 0);
if ( context.verboseMapping )
dyld::log("dyld: Mapping memory %p\n", memoryImage);
// map in all segments
for(unsigned int i=0, e=segmentCount(); i < e; ++i) {
vm_address_t loadAddress = segPreferredLoadAddress(i) + slide;
vm_address_t srcAddr = (uintptr_t)memoryImage + segFileOffset(i);
vm_size_t size = segFileSize(i);
kern_return_t r = vm_copy(mach_task_self(), srcAddr, size, loadAddress);
if ( r != KERN_SUCCESS )
throw "can't map segment";
if ( context.verboseMapping )
dyld::log("%18s at 0x%08lX->0x%08lX\n", segName(i), (uintptr_t)loadAddress, (uintptr_t)loadAddress+size-1);
}
// update slide to reflect load location
this->setSlide(slide);
// set R/W permissions on all segments at slide location
for(unsigned int i=0, e=segmentCount(); i < e; ++i) {
segProtect(i, context);
}
}
static vm_prot_t protectionForSegIndex(const ImageLoaderMachO* image, unsigned int segIndex)
{
if ( image->segUnaccessible(segIndex) )
return 0;
vm_prot_t protection = 0;
if ( image->segExecutable(segIndex) )
protection |= PROT_EXEC;
if ( image->segReadable(segIndex) )
protection |= PROT_READ;
if ( image->segWriteable(segIndex) )
protection |= PROT_WRITE;
return protection;
}
void ImageLoaderMachO::segProtect(unsigned int segIndex, const ImageLoader::LinkContext& context)
{
vm_prot_t protection = protectionForSegIndex(this, segIndex);
vm_address_t addr = segActualLoadAddress(segIndex);
vm_size_t size = segSize(segIndex);
#if defined(__x86_64__) && !TARGET_OS_SIMULATOR
if ( dyld::isTranslated() ) {
// <rdar://problem/60543794> can't vm_protect non-16KB segments
if ( (segIndex > 0) && ((addr & 0x3FFF) != 0) ) {
// overlaps previous segment
vm_prot_t prevProt = protectionForSegIndex(this, segIndex-1);
if ( (protection & prevProt) != prevProt ) {
// previous had more bits, so we need to not apply new permissions to the overlap
vm_size_t overlap = 0x4000 - (addr & 0x3FFF);
addr += overlap;
if ( size >= overlap )
size -= overlap;
else if ( size < overlap )
size = 0;
}
if ( size == 0 )
return;
}
}
#endif
const bool setCurrentPermissions = false;
kern_return_t r = vm_protect(mach_task_self(), addr, size, setCurrentPermissions, protection);
if ( r != KERN_SUCCESS ) {
dyld::throwf("vm_protect(0x%08llX, 0x%08llX, false, 0x%02X) failed, result=%d for segment %s in %s",
(long long)addr, (long long)size, protection, r, segName(segIndex), this->getPath());
}
if ( context.verboseMapping ) {
dyld::log("%18s at 0x%08lX->0x%08lX altered permissions to %c%c%c\n", segName(segIndex), (uintptr_t)addr, (uintptr_t)addr+size-1,
(protection & PROT_READ) ? 'r' : '.', (protection & PROT_WRITE) ? 'w' : '.', (protection & PROT_EXEC) ? 'x' : '.' );
}
}
#if TEXT_RELOC_SUPPORT
void ImageLoaderMachO::segMakeWritable(unsigned int segIndex, const ImageLoader::LinkContext& context)
{
vm_address_t addr = segActualLoadAddress(segIndex);
vm_size_t size = segSize(segIndex);
const bool setCurrentPermissions = false;
vm_prot_t protection = VM_PROT_WRITE | VM_PROT_READ | VM_PROT_COPY;
if ( segExecutable(segIndex) && !segHasRebaseFixUps(segIndex) )
protection |= VM_PROT_EXECUTE;
kern_return_t r = vm_protect(mach_task_self(), addr, size, setCurrentPermissions, protection);
if ( r != KERN_SUCCESS ) {
dyld::throwf("vm_protect(0x%08llX, 0x%08llX, false, 0x%02X) failed, result=%d for segment %s in %s",
(long long)addr, (long long)size, protection, r, segName(segIndex), this->getPath());
}
if ( context.verboseMapping ) {
dyld::log("%18s at 0x%08lX->0x%08lX altered permissions to %c%c%c\n", segName(segIndex), (uintptr_t)addr, (uintptr_t)addr+size-1,
(protection & PROT_READ) ? 'r' : '.', (protection & PROT_WRITE) ? 'w' : '.', (protection & PROT_EXEC) ? 'x' : '.' );
}
}
#endif
const char* ImageLoaderMachO::findClosestSymbol(const mach_header* mh, const void* addr, const void** closestAddr)
{
// called by dladdr()
// only works with compressed LINKEDIT if classic symbol table is also present
const dysymtab_command* dynSymbolTable = NULL;
const symtab_command* symtab = NULL;
const macho_segment_command* seg;
const uint8_t* unslidLinkEditBase = NULL;
bool linkEditBaseFound = false;
intptr_t slide = 0;
const uint32_t cmd_count = mh->ncmds;
const load_command* const cmds = (load_command*)((char*)mh + sizeof(macho_header));
const load_command* cmd = cmds;
for (uint32_t i = 0; i < cmd_count; ++i) {
switch (cmd->cmd) {
case LC_SEGMENT_COMMAND:
seg = (macho_segment_command*)cmd;
if ( strcmp(seg->segname, "__LINKEDIT") == 0 ) {
unslidLinkEditBase = (uint8_t*)(seg->vmaddr - seg->fileoff);
linkEditBaseFound = true;
}
else if ( strcmp(seg->segname, "__TEXT") == 0 ) {
slide = (uintptr_t)mh - seg->vmaddr;
}
break;
case LC_SYMTAB:
symtab = (symtab_command*)cmd;
break;
case LC_DYSYMTAB:
dynSymbolTable = (dysymtab_command*)cmd;
break;
}
cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize);
}
// no symbol table => no lookup by address
if ( (symtab == NULL) || (dynSymbolTable == NULL) || !linkEditBaseFound )
return NULL;
const uint8_t* linkEditBase = unslidLinkEditBase + slide;
const char* symbolTableStrings = (const char*)&linkEditBase[symtab->stroff];
const macho_nlist* symbolTable = (macho_nlist*)(&linkEditBase[symtab->symoff]);
uintptr_t targetAddress = (uintptr_t)addr - slide;
const struct macho_nlist* bestSymbol = NULL;
// first walk all global symbols
const struct macho_nlist* const globalsStart = &symbolTable[dynSymbolTable->iextdefsym];
const struct macho_nlist* const globalsEnd= &globalsStart[dynSymbolTable->nextdefsym];
for (const struct macho_nlist* s = globalsStart; s < globalsEnd; ++s) {
if ( (s->n_type & N_TYPE) == N_SECT ) {
if ( bestSymbol == NULL ) {
if ( s->n_value <= targetAddress )
bestSymbol = s;
}
else if ( (s->n_value <= targetAddress) && (bestSymbol->n_value < s->n_value) ) {
bestSymbol = s;
}
}
}
// next walk all local symbols
const struct macho_nlist* const localsStart = &symbolTable[dynSymbolTable->ilocalsym];
const struct macho_nlist* const localsEnd= &localsStart[dynSymbolTable->nlocalsym];
for (const struct macho_nlist* s = localsStart; s < localsEnd; ++s) {
if ( ((s->n_type & N_TYPE) == N_SECT) && ((s->n_type & N_STAB) == 0) ) {
if ( bestSymbol == NULL ) {
if ( s->n_value <= targetAddress )
bestSymbol = s;
}
else if ( (s->n_value <= targetAddress) && (bestSymbol->n_value < s->n_value) ) {
bestSymbol = s;
}
}
}
if ( bestSymbol != NULL ) {
#if __arm__
if (bestSymbol->n_desc & N_ARM_THUMB_DEF)
*closestAddr = (void*)((bestSymbol->n_value | 1) + slide);
else
*closestAddr = (void*)(bestSymbol->n_value + slide);
#else
*closestAddr = (void*)(bestSymbol->n_value + slide);
#endif
return &symbolTableStrings[bestSymbol->n_un.n_strx];
}
return NULL;
}
bool ImageLoaderMachO::getLazyBindingInfo(uint32_t& lazyBindingInfoOffset, const uint8_t* lazyInfoStart, const uint8_t* lazyInfoEnd,
uint8_t* segIndex, uintptr_t* segOffset, int* ordinal, const char** symbolName, bool* doneAfterBind)
{
if ( lazyBindingInfoOffset > (lazyInfoEnd-lazyInfoStart) )
return false;
bool done = false;
const uint8_t* p = &lazyInfoStart[lazyBindingInfoOffset];
while ( !done && (p < lazyInfoEnd) ) {
uint8_t immediate = *p & BIND_IMMEDIATE_MASK;
uint8_t opcode = *p & BIND_OPCODE_MASK;
++p;
switch (opcode) {
case BIND_OPCODE_DONE:
*doneAfterBind = false;
return true;
break;
case BIND_OPCODE_SET_DYLIB_ORDINAL_IMM:
*ordinal = immediate;
break;
case BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB:
*ordinal = (int)read_uleb128(p, lazyInfoEnd);
break;
case BIND_OPCODE_SET_DYLIB_SPECIAL_IMM:
// the special ordinals are negative numbers
if ( immediate == 0 )
*ordinal = 0;
else {
int8_t signExtended = BIND_OPCODE_MASK | immediate;
*ordinal = signExtended;
}
break;
case BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM:
*symbolName = (char*)p;
while (*p != '\0')
++p;
++p;
break;
case BIND_OPCODE_SET_TYPE_IMM:
break;
case BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB:
*segIndex = immediate;
*segOffset = read_uleb128(p, lazyInfoEnd);
break;
case BIND_OPCODE_DO_BIND:
*doneAfterBind = ((*p & BIND_OPCODE_MASK) == BIND_OPCODE_DONE);
lazyBindingInfoOffset += p - &lazyInfoStart[lazyBindingInfoOffset];
return true;
break;
case BIND_OPCODE_SET_ADDEND_SLEB:
case BIND_OPCODE_ADD_ADDR_ULEB:
case BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB:
case BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED:
case BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB:
default:
return false;
}
}
return false;
}
const dyld_info_command* ImageLoaderMachO::findDyldInfoLoadCommand(const mach_header* mh)
{
const uint32_t cmd_count = mh->ncmds;
const load_command* const cmds = (load_command*)((char*)mh + sizeof(macho_header));
const load_command* cmd = cmds;
for (uint32_t i = 0; i < cmd_count; ++i) {
switch (cmd->cmd) {
case LC_DYLD_INFO:
case LC_DYLD_INFO_ONLY:
return (dyld_info_command*)cmd;
}
cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize);
}
return NULL;
}
uintptr_t ImageLoaderMachO::segPreferredAddress(const mach_header* mh, unsigned segIndex)
{
const uint32_t cmd_count = mh->ncmds;
const load_command* const cmds = (load_command*)((char*)mh + sizeof(macho_header));
const load_command* cmd = cmds;
unsigned curSegIndex = 0;
for (uint32_t i = 0; i < cmd_count; ++i) {
if ( cmd->cmd == LC_SEGMENT_COMMAND ) {
if ( segIndex == curSegIndex ) {
const macho_segment_command* segCmd = (macho_segment_command*)cmd;
return segCmd->vmaddr;
}
++curSegIndex;
}
cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize);
}
return 0;
}
uintptr_t ImageLoaderMachO::imageBaseAddress() const {
//printf("imageBaseAddress: %s %d->%d\n", getPath(), 0, segmentCount());
for (unsigned int i = 0, e = segmentCount(); i != e; ++i) {
if ( (segFileOffset(i) == 0) && (segFileSize(i) != 0) )
return segPreferredLoadAddress(i);
}
return 0;
}