Loading...
--- dyld/dyld-940/testing/unit-tests/MockO.cpp
+++ /dev/null
@@ -1,1157 +0,0 @@
-/* -*- mode: C++; c-basic-offset: 4; tab-width: 4 -*-
- *
- * Copyright (c) 2020 Apple Inc. All rights reserved.
- *
- * @APPLE_LICENSE_HEADER_START@
- *
- * This file contains Original Code and/or Modifications of Original Code
- * as defined in and that are subject to the Apple Public Source License
- * Version 2.0 (the 'License'). You may not use this file except in
- * compliance with the License. Please obtain a copy of the License at
- * http://www.opensource.apple.com/apsl/ and read it before using this
- * file.
- *
- * The Original Code and all software distributed under the License are
- * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
- * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
- * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
- * Please see the License for the specific language governing rights and
- * limitations under the License.
- *
- * @APPLE_LICENSE_HEADER_END@
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <mach-o/loader.h>
-#include <mach-o/dyld.h>
-#include <mach-o/nlist.h>
-
-#include "Trie.hpp"
-#include "MockO.h"
-
-using dyld3::MachOFile;
-using dyld3::Platform;
-using dyld3::GradedArchs;
-
-
-//
-// MARK: --- methods for configuring a mach-o image ---
-//
-
-MockO::MockO(uint32_t filetype, const char* archName, Platform platform, const char* minOsStr, const char* sdkStr)
-{
- _header.cputype = MachOFile::cpuTypeFromArchName(archName);
- _header.cpusubtype = MachOFile::cpuSubtypeFromArchName(archName);
- _header.magic = (_header.cputype & CPU_ARCH_ABI64) ? MH_MAGIC_64 : MH_MAGIC;
- _header.filetype = filetype;
- _header.ncmds = 0;
- _header.sizeofcmds = 0;
- _header.flags = 0;
-
- if ( filetype == MH_EXECUTE )
- addSegment("__PAGEZERO", 0);
-
- addSegment("__TEXT", VM_PROT_READ|VM_PROT_EXECUTE);
- addSegment("__DATA", VM_PROT_READ|VM_PROT_WRITE);
- addSegment("__LINKEDIT", VM_PROT_READ);
- customizeAddSection("__TEXT", "__text", S_REGULAR);
- customizeAddSection("__DATA", "__data", S_REGULAR);
-
- _platform = platform;
- _minOSVersion = 0;
- _sdkVersion = 0;
- if ( minOsStr != nullptr )
- _minOSVersion = parseVersionNumber32(minOsStr);
- if ( sdkStr != nullptr )
- _sdkVersion = parseVersionNumber32(sdkStr);
- if (_sdkVersion == 0 ) {
- if ( _minOSVersion == 0 ) {
- // if minOS not specified, use defaults for macOS and iOS
- if ( _platform == Platform::macOS )
- _minOSVersion = 0x000C0000; // default is macOS 12.0
- else if ( _platform == Platform::iOS )
- _minOSVersion = 0x000F0000; // default is iOS 15.0
- else
- assert(0 && "no default SDK/minOS for platform");
- }
- // if sdk version not specified, use same as minOS version
- _sdkVersion = _minOSVersion;
- }
- if ( _minOSVersion == 0 ) {
- _minOSVersion = _sdkVersion;
- }
-
- // give dylibs a default install name
- if ( filetype == MH_DYLIB )
- _installName.push_back({"/usr/lib/libfoo.dylib", 1, 1, LC_ID_DYLIB});
-
- // add platform/minOS info
- if ( targetIsAtLeast(epochFall2018) )
- addBuildVersion(_platform, _minOSVersion, _sdkVersion);
- else
- addVersionMin(_platform, _minOSVersion, _sdkVersion);
-
- // add a uuid to the binary
- addUniqueUUID();
-
- if ( filetype == MH_EXECUTE )
- _baseAddress = (_header.cputype & CPU_ARCH_ABI64) ? 0x100000000ULL : 0x1000;
-
- if ( (filetype == MH_EXECUTE) && (_platform != Platform::driverKit) )
- _mainOffset = 0x1000;
-
- // make main executable dynamic by default
- if ( filetype == MH_EXECUTE )
- _dynamicLinker.push_back("/usr/lib/dyld");
-
- // all binaries link with libSystem by default
- if ( _dependents.empty() )
- _dependents.push_back({"/usr/lib/libSystem.B.dylib", 1, 1, LC_LOAD_DYLIB});
-}
-
-//
-// Parses number of form X[.Y[.Z]] into a uint32_t where the nibbles are xxxx.yy.zz
-//
-uint32_t MockO::parseVersionNumber32(const char* versionString)
-{
- unsigned long x = 0;
- unsigned long y = 0;
- unsigned long z = 0;
- char* end;
- x = strtoul(versionString, &end, 10);
- if ( *end == '.' ) {
- y = strtoul(&end[1], &end, 10);
- if ( *end == '.' ) {
- z = strtoul(&end[1], &end, 10);
- }
- }
- assert((*end == '\0') && (x <= 0xffff) && (y <= 0xff) && (z <= 0xff) && "malformed 32-bit x.y.z version number");
- return (uint32_t)((x << 16) | ( y << 8 ) | z);
-}
-
-
-void MockO::customizeMakeZippered()
-{
- addBuildVersion(Platform::iOSMac, 0x000E0000, 0x000E0000);
-}
-
-void MockO::customizeInstallName(const char* path, uint32_t compatVers, uint32_t curVers)
-{
- assert(_header.filetype == MH_DYLIB);
- assert(_installName.size() == 1);
- _installName[0] = { path, compatVers, curVers, LC_ID_DYLIB };
-}
-
-void MockO::customizeAddDependentDylib(const char* path, bool isWeak, bool isUpward, bool isReexport, uint32_t compatVers, uint32_t curVers)
-{
- PathWithVersions pv;
- pv.path = path;
- pv.compatVers = compatVers;
- pv.curVers = curVers;
- if ( isWeak )
- pv.cmd = LC_LOAD_WEAK_DYLIB;
- else if ( isReexport )
- pv.cmd = LC_REEXPORT_DYLIB;
- else if ( isUpward )
- pv.cmd = LC_LOAD_UPWARD_DYLIB;
- else
- pv.cmd = LC_LOAD_DYLIB;
-
- _dependents.push_back(pv);
-}
-
-void MockO::customizeAddDyldEnvVar(const char* envVar)
-{
- _dyldEnvVars.push_back(envVar);
-}
-
-void MockO::customizeAddRPath(const char* path)
-{
- _rpaths.push_back(path);
-}
-
-MockO::SectInfo* MockO::findSection(const char* segName, const char* sectionName)
-{
- for (SegInfo& segInfo : _segments) {
- if ( strcmp(segInfo.name, segName) == 0 ) {
- for (SectInfo& sectInfo : segInfo.sections) {
- if ( strcmp(sectInfo.name, sectionName) == 0 ) {
- return §Info;
- }
- }
- }
- }
- return nullptr;
-}
-
-void MockO::customizeAddSegment(const char* segName)
-{
- addSegment(segName, VM_PROT_READ);
-}
-
-void* MockO::customizeAddSection(const char* segName, const char* sectionName, uint32_t sectFlags)
-{
- for (SegInfo& segInfo : _segments) {
- if ( strcmp(segInfo.name, segName) == 0 ) {
- segInfo.sections.emplace_back(sectionName, sectFlags);
- }
- }
- return nullptr;
-}
-
-void MockO::customizeAddZeroFillSection(const char* segName, const char* sectionName)
-{
- for (SegInfo& segInfo : _segments) {
- if ( strcmp(segInfo.name, segName) == 0 ) {
- segInfo.sections.emplace_back(sectionName, S_ZEROFILL);
- }
- }
-}
-
-void MockO::customizeAddFunction(const char* functionName, bool global)
-{
- SectInfo* text = this->findSection("__TEXT", "__text");
- assert(text != nullptr);
- const std::vector<uint8_t> bytes = {0x90, 0x90, 0x90, 0x90};
- text->content.emplace_back(functionName, global, bytes);
-}
-
-void MockO::customizeAddData(const char* dataName, bool global)
-{
- SectInfo* data = this->findSection("__DATA", "__data");
- assert(data != nullptr);
- const std::vector<uint8_t> bytes = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
- data->content.emplace_back(dataName, global, bytes);
-}
-
-void MockO::customizeAddZeroFillData(const char* dataName, uint64_t size, bool global)
-{
- if ( global ) {
- SectInfo* data = this->findSection("__DATA", "__common");
- if ( data == nullptr ) {
- customizeAddZeroFillSection("__DATA", "__common");
- data = this->findSection("__DATA", "__common");
- }
- data->content.emplace_back(dataName, true, size);
- }
- else {
- SectInfo* bss = this->findSection("__DATA", "__bss");
- if ( bss == nullptr ) {
- customizeAddZeroFillSection("__DATA", "__bss");
- bss = this->findSection("__DATA", "__bss");
- }
- bss->content.emplace_back(dataName, false, size);
- }
-}
-
-void MockO::customizeAddInitializer()
-{
- const std::vector<uint8_t> bytes4 = { 0, 0, 0, 0 };
- const std::vector<uint8_t> bytes8 = { 0, 0, 0, 0, 0, 0, 0, 0 };
- customizeAddFunction("myinit", false);
- if ( targetIsAtLeast(epochFall2021) ) {
- customizeAddSection("__TEXT", "__init_offsets", S_INIT_FUNC_OFFSETS);
- SectInfo* sect = this->findSection("__TEXT", "__init_offsets");
- sect->content.emplace_back("", false, bytes4);
- }
- else {
- customizeAddSection("__DATA", "__mod_init_func", S_MOD_INIT_FUNC_POINTERS);
- SectInfo* sect = this->findSection("__DATA", "__mod_init_func");
- if ( is64() )
- sect->content.emplace_back("", false, bytes8);
- else
- sect->content.emplace_back("", false, bytes4);
- }
- // FIXME
-}
-
-
-
-void MockO::addSegment(const char* segName, uint32_t perms)
-{
- _segments.emplace_back(segName, perms);
-}
-
-void MockO::addSection(const char* segName, const char* sectionName, uint32_t sectFlags)
-{
- for (SegInfo& info : _segments) {
- if ( strcmp(info.name, segName) == 0 ) {
- info.sections.emplace_back(sectionName, sectFlags);
- return;
- }
- }
- assert(0 && "segment not found");
-}
-
-
-
-#if 0
-uint64_t MockO::getSegmentSize(const char* segName)
-{
- for (SegInfo& info : _segments) {
- if ( strcmp(info.name, segName) == 0 ) {
- return info.size;
- }
- }
- assert(0 && "segment not found");
-}
-
-void MockO::setSegmentPerm(const char* segName, uint32_t protections)
-{
- for (SegInfo& info : _segments) {
- if ( strcmp(info.name, segName) == 0 ) {
- info.perms = protections;
- return;
- }
- }
- assert(0 && "segment not found");
-}
-
-uint64_t MockO::getSegmentOffset(const char* segName)
-{
- for (SegInfo& info : _segments) {
- if ( strcmp(info.name, segName) == 0 ) {
- return info.fileOffset;
- }
- }
- assert(0 && "segment not found");
-}
-
-void MockO::setSegmentOffset(const char* segName, uint64_t offset)
-{
- for (SegInfo& info : _segments) {
- if ( strcmp(info.name, segName) == 0 ) {
- info.fileOffset = offset;
- return;
- }
- }
- assert(0 && "segment not found");
-}
-
-
-
-void MockO::setSectionOffset(const char* segName, const char* sectionName, uint64_t offset)
-{
- for (SegInfo& segInfo : _segments) {
- if ( strcmp(segInfo.name, segName) == 0 ) {
- for (SectInfo& sectInfo : segInfo.sections) {
- if ( strcmp(sectInfo.name, sectionName) == 0 )
- sectInfo.fileOffset = offset;
- }
- return;
- }
- }
- assert(0 && "section not found");
-}
-#endif
-
-bool MockO::is64() const
-{
- return (_header.magic == MH_MAGIC_64);
-}
-
-
-void MockO::addUniqueUUID()
-{
- uuid_t aUUID;
- uuid_generate_random(aUUID);
- uuid_command uc;
- uc.cmd = LC_UUID;
- uc.cmdsize = sizeof(uuid_command);
- memcpy(uc.uuid, aUUID, sizeof(uuid_t));
- _uuid.push_back(uc);
-}
-
-void MockO::addBuildVersion(Platform platform, uint32_t minOS, uint32_t sdk)
-{
- build_version_command bv;
- bv.cmd = LC_BUILD_VERSION;
- bv.cmdsize = sizeof(build_version_command);
- bv.platform = (uint32_t)platform;
- bv.minos = minOS;
- bv.sdk = sdk;
- bv.ntools = 0;
- _buildVersions.push_back(bv);
-}
-
-void MockO::addVersionMin(Platform platform, uint32_t minOS, uint32_t sdk)
-{
- version_min_command vc;
- vc.cmdsize = sizeof(version_min_command);
- vc.version = minOS;
- vc.sdk = sdk;
- switch (platform) {
- case Platform::macOS:
- vc.cmd = LC_VERSION_MIN_MACOSX;
- break;
- case Platform::iOS:
- vc.cmd = LC_VERSION_MIN_IPHONEOS;
- break;
- case Platform::watchOS:
- vc.cmd = LC_VERSION_MIN_WATCHOS;
- break;
- case Platform::tvOS:
- vc.cmd = LC_VERSION_MIN_TVOS;
- break;
- default:
- assert(0 && "invalid platform for min version load command");
- }
- _versionMin.push_back(vc);
-}
-
-#define VERS(major, minor) ((((major)&0xffff) << 16) | (((minor)&0xff) << 8))
-
-// Fall 2018 introduced LC_BUILD_VERSION
-const MockO::PlatformEpoch MockO::epochFall2018[] = {
- { Platform::macOS, VERS(10,14) },
- { Platform::iOS, VERS(12,0) },
- { Platform::watchOS, VERS( 5,0) },
- { Platform::tvOS, VERS(12,0) },
- { Platform::unknown, VERS( 0,0) } // zero terminate
-};
-
-// Fall 2019 introduced __DATA_CONST and zippering
-const MockO::PlatformEpoch MockO::epochFall2019[] = {
- { Platform::macOS, VERS(10,15) },
- { Platform::iOS, VERS(13,0) },
- { Platform::watchOS, VERS( 6,0) },
- { Platform::tvOS, VERS(13,0) },
- { Platform::unknown, VERS( 0,0) } // zero terminate
-};
-
-// Fall 2020 introduced relative method lists
-const MockO::PlatformEpoch MockO::epochFall2020[] = {
- { Platform::macOS, VERS(11,0) },
- { Platform::iOS, VERS(14,0) },
- { Platform::watchOS, VERS( 7,0) },
- { Platform::tvOS, VERS(14,0) },
- { Platform::driverKit, VERS(20,0) },
- { Platform::unknown, VERS( 0,0) } // zero terminate
-};
-
-// Fall 2021 introduced chained fixups and initializer offsets
-const MockO::PlatformEpoch MockO::epochFall2021[] = {
- { Platform::macOS, VERS(12,0) },
- { Platform::iOS, VERS(15,0) },
- { Platform::watchOS, VERS( 8,0) },
- { Platform::tvOS, VERS(15,0) },
- { Platform::driverKit, VERS(21,0) },
- { Platform::unknown, VERS( 0,0) } // zero terminate
-};
-
-
-bool MockO::targetIsAtLeast(const PlatformEpoch epoch[])
-{
- for (const PlatformEpoch* e=epoch; e->osVersion != 0; ++e) {
- if ( _platform == e->platform ) {
- return ( _minOSVersion >= e->osVersion );
- }
- }
- return true;
-}
-
-
-
-
-//
-// MARK: --- methods for malforming mach-o ---
-//
-
-void MockO::wrenchRemoveDyld()
-{
- _dynamicLinker.clear();
-}
-
-void MockO::wrenchRemoveInstallName()
-{
- _installName.clear();
-}
-
-void MockO::wrenchAddExtraInstallName(const char* path)
-{
- _installName.push_back({ path, 1, 1, LC_ID_DYLIB });
-}
-
-void MockO::wrenchSetNoDependentDylibs()
-{
- _dependents.clear();
-}
-
-void MockO::wrenchRemoveUUID()
-{
- _uuid.clear();
-}
-
-void MockO::wrenchAddUUID()
-{
- addUniqueUUID();
-}
-
-void MockO::wrenchRemoveVersionInfo()
-{
- _versionMin.clear();
- _buildVersions.clear();
-}
-
-void MockO::wrenchAddMain()
-{
- _mainOffset = 0x1000;
-}
-
-load_command* MockO::wrenchFindLoadCommand(uint32_t cmd)
-{
- const MachOAnalyzer* ma = header();
- __block load_command* result = nullptr;
- Diagnostics diag;
- ma->forEachLoadCommand(diag, ^(const load_command* lc, bool& stop) {
- if ( lc->cmd == cmd ) {
- result = (load_command*)lc;
- stop = true;
- }
- });
- return result;
-}
-
-
-//
-// MARK: --- methods for building actual mach-o image ---
-//
-
-uint32_t MockO::alignLC(uint32_t value)
-{
- // mach-o requires all load command sizes to be a multiple the pointer size
- if ( is64() )
- return ((value + 7) & (-8));
- else
- return ((value + 3) & (-4));
-}
-
-
-MockO::~MockO()
-{
-}
-
-const MachOAnalyzer* MockO::header()
-{
- if ( _mf == nullptr )
- buildMachO();
-
- return _mf;
-}
-
-const size_t MockO::size() const
-{
- return _size;
-}
-
-void MockO::save(char savedPath[PATH_MAX])
-{
- const MachOAnalyzer* ma = this->header();
-
- ::strcpy(savedPath, "/tmp/mocko-XXXXXX");
- int fd = ::mkstemp(savedPath);
- ::pwrite(fd, ma, _size, 0);
- ::close(fd);
-}
-
-void MockO::buildMachO()
-{
- // assign addresses/offsets to segments, sections, symbols
- layout();
-
- // allocate space for mach-o image
- assert(::vm_allocate(mach_task_self(), (vm_address_t*)&_mf, (vm_size_t)_size, VM_FLAGS_ANYWHERE) == KERN_SUCCESS);
-
-
- writeHeaderAndLoadCommands();
-
- writeLinkEdit();
-}
-
-
-void MockO::writeHeaderAndLoadCommands()
-{
- // copy header
- *((mach_header*)_mf) = _header;
-
- // add segment load commands
- for (const SegInfo& info : _segments)
- appendSegmentLoadCommand(info);
-
- // add fixup info
- appendFixupLoadCommand();
-
- // add nlist symbol table
- appendSymbolTableLoadCommand();
-
- // if set, add install name
- for ( const PathWithVersions& install : _installName )
- appendPathVersionLoadCommand(install);
-
- // add dyld load command
- for ( const std::string& pth : _dynamicLinker )
- appendStringLoadCommand(LC_LOAD_DYLINKER,pth);
-
- // add uuid
- for ( const uuid_command& id : _uuid )
- appendLoadCommand((load_command*)&id);
-
- // if set, add version-min load command
- for ( const version_min_command& vc : _versionMin )
- appendLoadCommand((load_command*)&vc);
-
- // add build-version load command(s)
- for (const build_version_command& bv : _buildVersions)
- appendLoadCommand((load_command*)&bv);
-
- // add entry
- if ( (_header.filetype == MH_EXECUTE) && (_mainOffset != 0) )
- appendEntryLoadCommand();
-
- // add dyld info
- if ( _dyldInfo.has_value() )
- appendLoadCommand((load_command*)&_dyldInfo.value());
-
- if ( _routinesInitOffset )
- appendRoutinesLoadCommand(_routinesInitOffset.value());
-
-
- // add dependent dylibs
- for (const PathWithVersions& dep : _dependents)
- appendPathVersionLoadCommand(dep);
-
- // add any dyld env var load commands
- for (const std::string& str : _dyldEnvVars)
- appendStringLoadCommand(LC_DYLD_ENVIRONMENT, str);
-
- // add any rpath load commands
- for (const std::string& str : _rpaths)
- appendStringLoadCommand(LC_RPATH, str);
-
-}
-
-static int segmentOrder(const char* name)
-{
- if ( strcmp(name, "__PAGEZERO") == 0 )
- return 1;
- if ( strcmp(name, "__TEXT") == 0 )
- return 2;
- if ( strcmp(name, "__DATA_CONST") == 0 )
- return 3;
- if ( strcmp(name, "__DATA") == 0 )
- return 4;
- if ( strcmp(name, "__LINKEDIT") == 0 )
- return 999;
- return 10;
-}
-
-void MockO::pageAlign(uint64_t& value)
-{
- // FIXME: is there any case where we want 4KB pages?
- value = ((value + 0x3FFF) & (-0x4000));
-}
-
-void MockO::pageAlign(uint32_t& value)
-{
- // FIXME: is there any case where we want 4KB pages?
- value = ((value + 0x3FFF) & (-0x4000));
-}
-
-void MockO::layout()
-{
- // sort segments
- std::sort(_segments.begin(), _segments.end(), [](const SegInfo& s1, const SegInfo& s2) {
- return segmentOrder(s1.name) < segmentOrder(s2.name);
- });
-
- // assign address to segments
- SegInfo* leSegInfo = nullptr;
- uint32_t curFileOffset = 0;
- uint64_t curVMAddr = 0;
- for (SegInfo& segInfo : _segments) {
- pageAlign(curFileOffset);
- pageAlign(curVMAddr);
- segInfo.fileOffset = curFileOffset;
- segInfo.vmAddr = curVMAddr;
- if ( strcmp(segInfo.name, "__TEXT") == 0 ) {
- curVMAddr = _baseAddress;
- segInfo.fileOffset = 0;
- segInfo.vmAddr = curVMAddr;
- // reverse layout TEXT so padding is after load commands and before __text
- uint64_t totalSectionsSize = 0;
- for (SectInfo& sectInfo : segInfo.sections) {
- sectInfo.size = 0;
- for (const Content& cont : sectInfo.content) {
- sectInfo.size += cont.bytes.size();
- }
- totalSectionsSize += sectInfo.size;
- }
- uint32_t textPageSize = (uint32_t)totalSectionsSize+2048; // FIXME: guestimate of load commands size
- pageAlign(textPageSize);
- segInfo.fileSize = textPageSize;
- segInfo.vmSize = textPageSize;
- uint64_t addr = segInfo.vmAddr + segInfo.vmSize;
- uint64_t off = textPageSize;
- for (int i=(int)segInfo.sections.size(); i > 0; --i) {
- SectInfo& sectInfo = segInfo.sections[i-1];
- addr -= sectInfo.size;
- off -= sectInfo.size;
- uint64_t symAddr = addr;
- for (const Content& cont : sectInfo.content) {
- if ( cont.symbolName[0] != '\0' ) {
- if ( cont.global )
- _exportedSymbols.push_back({cont.symbolName, symAddr});
- else
- _localSymbols.push_back({cont.symbolName, symAddr});
- }
- symAddr += cont.bytes.size();
- }
- sectInfo.vmAddr = addr;
- sectInfo.fileOffset = off;
- }
- curFileOffset = textPageSize;
- curVMAddr = segInfo.vmAddr + segInfo.vmSize;
- }
- else if ( strcmp(segInfo.name, "__PAGEZERO") == 0 ) {
- segInfo.fileOffset = 0;
- segInfo.fileSize = 0;
- segInfo.vmAddr = 0;
- segInfo.vmSize = _baseAddress;
- }
- else if ( strcmp(segInfo.name, "__LINKEDIT") == 0 ) {
- // LINKEDIT size set later
- leSegInfo = &segInfo;
- }
- else {
- // sort sections so zero-fill at end
- std::sort(segInfo.sections.begin(), segInfo.sections.end(), [](const SectInfo& l, const SectInfo& r) {
- bool lzf = (l.flags == S_ZEROFILL);
- bool rzf = (r.flags == S_ZEROFILL);
- if ( lzf != rzf )
- return rzf;
- return (strcmp(l.name, r.name) == 0);
- });
- for (SectInfo& sectInfo : segInfo.sections) {
- sectInfo.fileOffset = curFileOffset;
- sectInfo.vmAddr = curVMAddr;
- sectInfo.size = 0;
- uint64_t symVmOffset = curVMAddr - _baseAddress;
- if ( sectInfo.flags == S_ZEROFILL ) {
- sectInfo.fileOffset = 0; // all zero-fill sections have no file offset
- for (const Content& cont : sectInfo.content) {
- assert(cont.zeroFillSize != 0);
- assert(cont.bytes.empty());
- if ( cont.symbolName[0] != '\0' ) {
- if ( cont.global )
- _exportedSymbols.push_back({cont.symbolName, symVmOffset});
- else
- _localSymbols.push_back({cont.symbolName, symVmOffset});
- }
- sectInfo.size += cont.zeroFillSize;
- segInfo.vmSize += cont.zeroFillSize;
- symVmOffset += cont.zeroFillSize;
- curVMAddr += cont.zeroFillSize;
- }
- }
- else {
- for (const Content& cont : sectInfo.content) {
- assert(cont.zeroFillSize == 0);
- assert(!cont.bytes.empty());
- // FIXME: support alignment
- if ( cont.symbolName[0] != '\0' ) {
- if ( cont.global )
- _exportedSymbols.push_back({cont.symbolName, symVmOffset});
- else
- _localSymbols.push_back({cont.symbolName, symVmOffset});
- }
- sectInfo.size += cont.bytes.size();
- segInfo.fileSize += sectInfo.size;
- segInfo.vmSize += sectInfo.size;
- curVMAddr += sectInfo.size;
- curFileOffset += sectInfo.size;
- }
- }
- }
- pageAlign(segInfo.fileSize);
- pageAlign(segInfo.vmSize);
- }
- }
-
- // layout linkedit
-
- // build exports trie
- std::vector<ExportInfoTrie::Entry> trieEntrys;
- for (const Symbol& exp : _exportedSymbols) {
- ExportInfoTrie::Entry entry;
- entry.name = exp.name;
- entry.info.address = exp.vmOffset;
- entry.info.flags = 0; // FIXME
- entry.info.other = 0;
- entry.info.importName = "";
- trieEntrys.push_back(entry);
- }
- ExportInfoTrie programTrie(trieEntrys);
- programTrie.emit(_leContent.exportsTrieBytes);
- while ( (_leContent.exportsTrieBytes.size() % 8) != 0 )
- _leContent.exportsTrieBytes.push_back(0);
- _leLayout.exportsTrieOffset = curFileOffset;
- _leLayout.exportsTrieSize = (uint32_t)_leContent.exportsTrieBytes.size();
- curFileOffset += _leLayout.exportsTrieSize;
-
- // nlist symbol table
- _leLayout.symbolTableOffset = curFileOffset;
- _leLayout.symbolTableCount = (uint32_t)(_exportedSymbols.size() + _localSymbols.size());
- _leContent.symbolTableStringPool.push_back('\0');
- for (const Symbol& exp : _localSymbols) {
- if ( is64() ) {
- nlist_64 sym;
- sym.n_un.n_strx = (uint32_t)_leContent.symbolTableStringPool.size();
- sym.n_type = N_SECT;
- sym.n_sect = 1; // FIXME
- sym.n_desc = 0;
- sym.n_value = exp.vmOffset;
- _leContent.symbolTable64.push_back(sym);
- }
- else {
- struct nlist sym;
- sym.n_un.n_strx = (uint32_t)_leContent.symbolTableStringPool.size();
- sym.n_type = N_SECT;
- sym.n_sect = 1; // FIXME
- sym.n_desc = 0;
- sym.n_value = (uint32_t)exp.vmOffset;
- _leContent.symbolTable32.push_back(sym);
- }
- const char* str = exp.name.c_str();
- _leContent.symbolTableStringPool.insert(_leContent.symbolTableStringPool.end(), str, str+strlen(str)+1);
- }
- for (const Symbol& exp : _exportedSymbols) {
- if ( is64() ) {
- nlist_64 sym;
- sym.n_un.n_strx = (uint32_t)_leContent.symbolTableStringPool.size();
- sym.n_type = N_SECT | N_EXT;;
- sym.n_sect = 1; // FIXME
- sym.n_desc = 0;
- sym.n_value = exp.vmOffset;
- _leContent.symbolTable64.push_back(sym);
- }
- else {
- struct nlist sym;
- sym.n_un.n_strx = (uint32_t)_leContent.symbolTableStringPool.size();
- sym.n_type = N_SECT | N_EXT;;
- sym.n_sect = 1; // FIXME
- sym.n_desc = 0;
- sym.n_value = (uint32_t)exp.vmOffset;
- _leContent.symbolTable32.push_back(sym);
- }
- const char* str = exp.name.c_str();
- _leContent.symbolTableStringPool.insert(_leContent.symbolTableStringPool.end(), str, str+strlen(str)+1);
- }
- while ( (_leContent.symbolTableStringPool.size() % 8) != 0 )
- _leContent.symbolTableStringPool.push_back('\0');
-
- curFileOffset += _leLayout.symbolTableCount * (is64() ? sizeof(nlist_64) : sizeof(struct nlist));
- _leLayout.symbolStringsOffset = curFileOffset;
- _leLayout.symbolStringsSize = (uint32_t)_leContent.symbolTableStringPool.size();
- curFileOffset += _leLayout.symbolStringsSize;
-
- _size = curFileOffset;
-
- leSegInfo->fileSize = curFileOffset - leSegInfo->fileOffset;
- leSegInfo->vmSize = leSegInfo->fileSize;
- pageAlign(leSegInfo->vmSize);
-}
-
-
-void MockO::writeLinkEdit()
-{
- // write exports trie
- ::memcpy((char*)_mf + _leLayout.exportsTrieOffset, &_leContent.exportsTrieBytes[0], _leLayout.exportsTrieSize);
-
-
- // write symbol table
- if ( is64() )
- ::memcpy((char*)_mf + _leLayout.symbolTableOffset, &_leContent.symbolTable64[0], _leLayout.symbolTableCount*sizeof(nlist_64));
- else
- ::memcpy((char*)_mf + _leLayout.symbolTableOffset, &_leContent.symbolTable32[0], _leLayout.symbolTableCount*sizeof(struct nlist));
-
- // write symbol table string pool
- ::memcpy((char*)_mf + _leLayout.symbolStringsOffset, &_leContent.symbolTableStringPool[0], _leLayout.symbolStringsSize);
-
-}
-
-load_command* MockO::firstLoadCommand() const
-{
- if ( _mf->magic == MH_MAGIC_64 )
- return (load_command*)((uint8_t*)_mf + sizeof(mach_header_64));
- else if ( _mf->magic == MH_MAGIC )
- return (load_command*)((uint8_t*)_mf + sizeof(mach_header));
- assert(0 && "no mach-o magic");
-}
-
-// creates space for a new load command, but does not fill in its payload
-load_command* MockO::appendLoadCommand(uint32_t cmd, uint32_t cmdSize)
-{
- assert(cmdSize == alignLC(cmdSize)); // size must be multiple of 4
- load_command* thisCmd = (load_command*)((uint8_t*)firstLoadCommand() + _mf->sizeofcmds);
- thisCmd->cmd = cmd;
- thisCmd->cmdsize = cmdSize;
- _mf->ncmds += 1;
- _mf->sizeofcmds += cmdSize;
-
- return thisCmd;
-}
-
-// copies a new load command from another
-void MockO::appendLoadCommand(const load_command* lc)
-{
- assert(lc->cmdsize == alignLC(lc->cmdsize)); // size must be multiple of 4
- load_command* thisCmd = (load_command*)((uint8_t*)firstLoadCommand() + _mf->sizeofcmds);
- ::memcpy(thisCmd, lc, lc->cmdsize);
- _mf->ncmds += 1;
- _mf->sizeofcmds += lc->cmdsize;
-}
-
-void MockO::appendEntryLoadCommand()
-{
- // FIXME: old macOS binaries use different load command
- entry_point_command* mlc = (entry_point_command*)appendLoadCommand(LC_MAIN, sizeof(entry_point_command));
- mlc->entryoff = _mainOffset;
- mlc->stacksize = 0;
-}
-
-void MockO::appendFixupLoadCommand()
-{
- if ( targetIsAtLeast(epochFall2021) ) {
- linkedit_data_command* etlc = (linkedit_data_command*)appendLoadCommand(LC_DYLD_EXPORTS_TRIE, sizeof(linkedit_data_command));
- etlc->dataoff = _leLayout.exportsTrieOffset;
- etlc->datasize = _leLayout.exportsTrieSize;
- }
- else {
- dyld_info_command* dilc = (dyld_info_command*)appendLoadCommand(LC_DYLD_INFO_ONLY, sizeof(dyld_info_command));
- dilc->rebase_off = 0;
- dilc->rebase_size = 0;
- dilc->bind_off = 0;
- dilc->bind_size = 0;
- dilc->weak_bind_off = 0;
- dilc->weak_bind_size = 0;
- dilc->lazy_bind_off = 0;
- dilc->lazy_bind_size = 0;
- dilc->export_off = _leLayout.exportsTrieOffset;
- dilc->export_size = _leLayout.exportsTrieSize;;
- }
-
-}
-
-
-void MockO::appendSymbolTableLoadCommand()
-{
- symtab_command* stlc = (symtab_command*)appendLoadCommand(LC_SYMTAB, sizeof(symtab_command));
- stlc->symoff = _leLayout.symbolTableOffset;
- stlc->nsyms = _leLayout.symbolTableCount;
- stlc->stroff = _leLayout.symbolStringsOffset;
- stlc->strsize = _leLayout.symbolStringsSize;
-
- dysymtab_command* dlc = (dysymtab_command*)appendLoadCommand(LC_DYSYMTAB, sizeof(dysymtab_command));
- dlc->ilocalsym = 0;
- dlc->nlocalsym = (uint32_t)_localSymbols.size();
- dlc->iextdefsym = dlc->nlocalsym;
- dlc->nextdefsym = (uint32_t)_exportedSymbols.size();
- dlc->iundefsym = 0;
- dlc->nundefsym = 0;
- dlc->tocoff = 0;
- dlc->ntoc = 0;
- dlc->modtaboff = 0;
- dlc->nmodtab = 0;
- dlc->extrefsymoff = 0;
- dlc->nextrefsyms = 0;
- dlc->indirectsymoff = 0;
- dlc->nindirectsyms = 0;
- dlc->extreloff = 0;
- dlc->nextrel = 0;
- dlc->locreloff = 0;
- dlc->nlocrel = 0;
-}
-
-void MockO::appendPathVersionLoadCommand(const PathWithVersions& pv)
-{
- uint32_t cmdSize = alignLC((uint32_t)(sizeof(dylib_command)+pv.path.size()+1));
- dylib_command* lc = (dylib_command*)appendLoadCommand(pv.cmd, cmdSize);
- lc->dylib.name.offset = sizeof(dylib_command);
- lc->dylib.timestamp = 1;
- lc->dylib.current_version = pv.curVers;
- lc->dylib.compatibility_version = pv.compatVers;
- strcpy((char*)lc + sizeof(dylib_command), pv.path.c_str());
-}
-
-
-void MockO::appendSegmentLoadCommand(const SegInfo& info)
-{
- if ( is64() ) {
- uint32_t cmdSize = (uint32_t)(sizeof(segment_command_64)+info.sections.size()*sizeof(section_64));
- segment_command_64* newCmd = (segment_command_64*)appendLoadCommand(LC_SEGMENT_64, cmdSize);
- strlcpy(newCmd->segname, info.name, 16);
- newCmd->vmaddr = info.vmAddr;
- newCmd->vmsize = info.vmSize;
- newCmd->fileoff = info.fileOffset;
- newCmd->filesize = info.fileSize;
- newCmd->maxprot = info.perms;
- newCmd->initprot = info.perms;
- newCmd->nsects = (uint32_t)info.sections.size();
- newCmd->flags = 0;
- section_64* sect = (section_64*)((uint8_t*)newCmd + sizeof(segment_command_64));
- for (const SectInfo& sectInfo : info.sections) {
- size_t sectSize = 0;
- for (const Content& cont : sectInfo.content) {
- sectSize += cont.bytes.size();
- }
- strlcpy(sect->sectname, sectInfo.name, 17); // section name can be 16 chars, may overflow into segname, but about to set that
- strlcpy(sect->segname, info.name, 16);
- sect->addr = sectInfo.vmAddr;
- sect->size = sectInfo.size;
- sect->offset = (uint32_t)sectInfo.fileOffset;
- sect->flags = sectInfo.flags;
- ++sect;
- }
- }
- else {
- uint32_t cmdSize = (uint32_t)(sizeof(segment_command)+info.sections.size()*sizeof(section));
- segment_command* newCmd = (segment_command*)appendLoadCommand(LC_SEGMENT, cmdSize);
- strlcpy(newCmd->segname, info.name, 16);
- newCmd->vmaddr = (uint32_t)info.vmAddr;
- newCmd->vmsize = (uint32_t)info.vmSize;
- newCmd->fileoff = (uint32_t)info.fileOffset;
- newCmd->filesize = (uint32_t)info.fileSize;
- newCmd->maxprot = info.perms;
- newCmd->initprot = info.perms;
- newCmd->nsects = (uint32_t)info.sections.size();
- newCmd->flags = 0;
- section* sect = (section*)((uint8_t*)newCmd + sizeof(segment_command));
- for (const SectInfo& sectInfo : info.sections) {
- size_t sectSize = 0;
- for (const Content& cont : sectInfo.content)
- sectSize += cont.bytes.size();
- strlcpy(sect->sectname, sectInfo.name, 17); // section name can be 16 chars, may overflow into segname, but about to set that
- strlcpy(sect->segname, info.name, 16);
- sect->addr = newCmd->vmaddr;
- sect->size = (uint32_t)sectSize;
- sect->offset = (uint32_t)sectInfo.fileOffset;
- sect->flags = sectInfo.flags;
- ++sect;
- }
- }
-}
-
-void MockO::appendStringLoadCommand(uint32_t cmd, const std::string& str)
-{
- uint32_t size = alignLC((uint32_t)(sizeof(dylinker_command)+str.size()+1));
- dylinker_command* newCmd = (dylinker_command*)appendLoadCommand(cmd, size);
- newCmd->name.offset = sizeof(dylinker_command);
- strcpy((char*)newCmd + newCmd->name.offset, str.c_str());
-}
-
-void MockO::appendRoutinesLoadCommand(uint64_t offset)
-{
- if ( is64() ) {
- uint32_t cmdSize = sizeof(routines_command_64);
- routines_command_64* newCmd = (routines_command_64*)appendLoadCommand(LC_ROUTINES_64, cmdSize);
- newCmd->init_address = offset;
- newCmd->init_module = 0;
- newCmd->reserved1 = 0;
- newCmd->reserved2 = 0;
- newCmd->reserved3 = 0;
- newCmd->reserved4 = 0;
- newCmd->reserved5 = 0;
- newCmd->reserved6 = 0;
- }
- else {
- uint32_t cmdSize = sizeof(routines_command);
- routines_command* newCmd = (routines_command*)appendLoadCommand(LC_ROUTINES, cmdSize);
- newCmd->init_address = (uint32_t)offset;
- newCmd->init_module = 0;
- newCmd->reserved1 = 0;
- newCmd->reserved2 = 0;
- newCmd->reserved3 = 0;
- newCmd->reserved4 = 0;
- newCmd->reserved5 = 0;
- newCmd->reserved6 = 0;
- }
-}
-
-
-//
-// MARK: --- methods for configuring a FAT image ---
-//
-
-Muckle::Muckle() { }
-
-Muckle::~Muckle() { }
-
-void Muckle::addMockO(MockO *mock)
-{
- _mockos.push_back(mock);
-}
-
-const FatFile* Muckle::header()
-{
- if ( _fat == nullptr )
- buildFATFile();
-
- return _fat;
-}
-
-static uint32_t alignPage(uint32_t value)
-{
- return ((value + 16383) & (-16384));
-}
-
-void Muckle::buildFATFile()
-{
- // Add a page for the FAT header
- _size = 16384;
-
- for ( MockO* mock : _mockos ) {
- // Force the MockO to build
- mock->header();
- _size += alignPage((uint32_t)mock->size());
- }
-
- vm_address_t loadAddress = 0;
- assert(::vm_allocate(mach_task_self(), &loadAddress, (vm_size_t)_size, VM_FLAGS_ANYWHERE) == KERN_SUCCESS);
- _fat = (FatFile*)loadAddress;
-
- // Add the FAT header to the start of the buffer
- fat_header* header = (fat_header*)_fat;
- header->magic = OSSwapHostToBigInt32(FAT_MAGIC);
- header->nfat_arch = OSSwapHostToBigInt32((uint32_t)_mockos.size());
-
- size_t offsetInBuffer = 16384;
- for (uint32_t i = 0; i != _mockos.size(); ++i) {
- MockO* mock = _mockos[i];
- const MachOAnalyzer* ma = mock->header();
-
- fat_arch* archBuffer = (fat_arch*)((uint8_t*)header + sizeof(fat_header));
- archBuffer[i].cputype = OSSwapHostToBigInt32(ma->cputype);
- archBuffer[i].cpusubtype = OSSwapHostToBigInt32(ma->cpusubtype);
- archBuffer[i].offset = OSSwapHostToBigInt32(offsetInBuffer);
- archBuffer[i].size = OSSwapHostToBigInt32(mock->size());
- archBuffer[i].align = OSSwapHostToBigInt32(14);
-
- uint32_t alignedSize = alignPage((uint32_t)mock->size());
- memcpy((uint8_t*)_fat + offsetInBuffer, ma, mock->size());
-
- offsetInBuffer += alignedSize;
- assert(offsetInBuffer <= _size);
- }
-}
-
-void Muckle::save(char savedPath[PATH_MAX])
-{
- const FatFile* ff = this->header();
-
- ::strcpy(savedPath, "/tmp/muckle-XXXXXX");
- int fd = ::mkstemp(savedPath);
- ::pwrite(fd, ff, _size, 0);
- ::close(fd);
-}