Something went wrong. Try again.
Cider Isn't Darwin Emulation, Really
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284/*This file is part of Darling.
Copyright (C) 2015 Lubos Dolezel
Darling is free software: you can redistribute it and/or modifyit under the terms of the GNU General Public License as published bythe Free Software Foundation, either version 3 of the License, or(at your option) any later version.
Darling is distributed in the hope that it will be useful,but WITHOUT ANY WARRANTY; without even the implied warranty ofMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See theGNU General Public License for more details.
You should have received a copy of the GNU General Public Licensealong with Darling. If not, see <http://www.gnu.org/licenses/>.*/
#include "dl_public.h"#include "MachOObject.h"#include <limits>#include <cstring>#include <cassert>#include <cstdlib>#include <stdexcept>#include <sstream>#include <fstream>#include <sys/mman.h>#include <unistd.h>#include <util/debug.h>#include <libgen.h>#include <iostream>#include "MachOMgr.h"#include "eh/EHSection.h"#include "DylibSearch.h"#include "arch.h"#include "TLS.h"
extern "C" int g_argc asm("NXArgc") = 0;extern "C" char** g_argv asm("NXArgv") = nullptr;extern "C" char** __darwin_environ;
namespace Darling {
struct ProgramVars{ const void* mh; int* NXArgcPtr; const char*** NXArgvPtr; const char*** environPtr; const char** __prognamePtr;};
struct DyldSectData{ void* lazyBinderPtr; bool (*funcLookupPtr)(const char*, void**); ProgramVars vars;};
MachOObject::MachOObject(const std::string& path){ m_file = MachO::readFile(path, ARCH_NAME, true, MachOMgr::instance()->loadAnyArchitecture()); if (!m_file) { std::stringstream ss; ss << "Cannot load " << path; throw std::runtime_error(ss.str()); } postConstruct();}
MachOObject::MachOObject(MachO* file): m_file(file){ postConstruct();}
void MachOObject::postConstruct(){ detectAbsolutePath(); m_header = m_file->header();
for (std::string rpath : m_file->rpaths()) { if (rpath.compare(0, 16, "@executable_path") == 0) { if (isMainModule()) rpath.replace(0, 16, directory()); else { MachOObject* mainModule = MachOMgr::instance()->mainModule(); if (!mainModule) throw std::runtime_error("Cannot resolve @executable_path without a main module"); rpath.replace(0, 16, mainModule->directory()); } } else if (rpath.compare(0, 12, "@loader_path") == 0) { rpath = expandLoaderPath(rpath, this); }
m_rpaths.push_back(rpath); }
m_bindAllAtLoad = MachOMgr::instance()->bindAtLaunch();}
MachOObject::~MachOObject(){ if (isLoaded()) unload(); delete m_file;}
std::string MachOObject::expandLoaderPath(std::string path, MachOObject* loader){ char* p; path.replace(0, 12, loader->directory());
p = realpath(path.c_str(), nullptr); if (p != nullptr) { path = p; free(p); }
return path;}
void MachOObject::setRequesterRunpaths(MachOObject* requester){ const auto& rpaths = requester->m_rpaths; m_rpaths.insert(m_rpaths.begin(), rpaths.begin(), rpaths.end());}
void* MachOObject::maxAddress() const{ if (m_mappings.empty()) return nullptr; auto& last = m_mappings.back(); return static_cast<char*>(last.start) + last.size;}
bool MachOObject::isMainModule() const{ return m_file->header().filetype == MH_EXECUTE;}
void MachOObject::load(){ if (isLoaded()) throw std::logic_error("Module already loaded"); if (strcmp(m_file->platform(), ARCH_NAME) != 0) { std::stringstream ss; ss << "This version of Darling dyld cannot load binaries for " << m_file->platform() << "."; throw std::runtime_error(ss.str()); } loadSegments(); transitionState(dyld_image_state_mapped); if (m_slide > 0) rebase(); transitionState(dyld_image_state_rebased); readSymbols(); readExports();
MachOMgr::instance()->add(this, isMainModule());
loadDependencies(); performRelocations(); performBinds(); transitionState(dyld_image_state_bound); transitionState(dyld_image_state_dependents_initialized); // registerEHSection(); if (isMainModule()) fillInProgramVars(); fillInDyldData(); setInitialSegmentProtection(); setupTLS(); MachOMgr::instance()->notifyAdd(this); runInitializers();
transitionState(dyld_image_state_initialized); m_file->closeFd(); if (MachOMgr::instance()->printLibraries()) std::cerr << "dyld: Loaded " << this->path() << std::endl;}
void MachOObject::unload(){ if (!isLoaded()) throw std::logic_error("Module not loaded"); MachOMgr::instance()->remove(this); teardownTLS(); runFinalizers(); if (!MachOMgr::instance()->isDestroying()) { unloadSegments(); // unregisterEHSection(); } for (LoadableObject* dep : m_dependencies) dep->delRef(); m_dependencies.clear(); transitionState(dyld_image_state_terminated);}
bool MachOObject::isLoaded() const{ return !m_mappings.empty();}
#if (__WORDSIZE == 64)
typedef segment_command_64 Segment;static const std::vector<Segment*>& getSegments(const MachO& mach) { return mach.segments64(); }
#elif (__WORDSIZE == 32)
typedef segment_command Segment;static const std::vector<Segment*>& getSegments(const MachO& mach) { return mach.segments(); }
#else# error Unknown __WORDSIZE value!#endif
static std::string pathToSelf(){ char buf[4096]; int rd; rd = readlink("/proc/self/exe", buf, sizeof(buf)-1); if (rd <= 0) return std::string(); buf[rd] = 0; return std::string(buf);}
void MachOObject::loadSegments(){ for (Segment* seg : getSegments(*m_file)) { uintptr_t mappingSize; int initprot, maxprot; void* mappingAddr; void* rv; int flags = MAP_PRIVATE; if (strcmp(seg->segname, SEG_PAGEZERO) == 0 || seg->vmsize == 0) continue; assert(seg->vmsize >= seg->filesize); if (!m_base) { mappingAddr = (void*) seg->vmaddr; if (mappingAddr < MachOMgr::instance()->maxAddress()) mappingAddr = MachOMgr::instance()->maxAddress(); } else mappingAddr = (void*) (seg->vmaddr + m_slide); mappingSize = pageAlign(seg->filesize); maxprot = machoProtectionFlagsToMmap(seg->maxprot); initprot = machoProtectionFlagsToMmap(seg->initprot); if (MachOMgr::instance()->printSegments()) std::cerr << "dyld: Mapping segment " << seg->segname << " from " << m_file->filename() << " to " << mappingAddr << ", slide is 0x" << std::hex << m_slide << std::dec << std::endl; #ifndef __arm__ // All executables on iOS are PIE // The first segment can be moved by mmap, but not the following ones auto filetype = m_file->header().filetype; if (m_base || (!(m_file->header().flags & MH_PIE) && filetype != MH_DYLIB && filetype != MH_BUNDLE)) flags |= MAP_FIXED; else#endif { // When letting the system decide where to place the mapping, we need to make sure that the spot chosen // is big enough for all segments uintptr_t size = getTotalMappingSize(); rv = ::mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, 0, 0); if (rv == MAP_FAILED) { std::stringstream ss; ss << "Failed to mmap temporary anonymous range: " << strerror(errno); throw std::runtime_error(ss.str()); } flags |= MAP_FIXED; mappingAddr = rv; }
rv = ::mmap(mappingAddr, mappingSize, maxprot, flags, m_file->fd(), m_file->offset() + seg->fileoff); if (rv == MAP_FAILED) { std::stringstream ss; if (errno == EPERM && uintptr_t(mappingAddr) < getMinMappingAddr()) { ss << "This executable is not position independent and your vm.mmap_min_addr is not low enough to load it. "; ss << "As low as " << uintptr_t(mappingAddr) << " is needed.\n"; ss << "This problem can be fixed by running 'setcap cap_sys_rawio=ep " << pathToSelf() << "' as root.\n"; } else ss << "Failed to mmap '" << m_file->filename() << "': " << strerror(errno); throw std::runtime_error(ss.str()); } if (!m_base) { m_slide = (intptr_t(rv) - intptr_t(seg->vmaddr)); m_base = rv; mappingAddr = rv; } m_mappings.push_back(Mapping { mappingAddr, pageAlign(seg->vmsize), initprot, maxprot });
if (seg->vmsize > mappingSize) { // Map empty pages to cover the vmsize range mappingAddr = (void*) (seg->vmaddr + m_slide + mappingSize); mappingSize = seg->vmsize - mappingSize; rv = ::mmap(mappingAddr, mappingSize, maxprot, MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS, 0, 0); //int err = ::mprotect(mappingAddr, mappingSize, maxprot); if (rv == MAP_FAILED) { std::stringstream ss; ss << "Failed to mmap anonymous pages for '" << m_file->filename() << "': " << strerror(errno); throw std::runtime_error(ss.str()); } } }}
uintptr_t MachOObject::getTotalMappingSize(){ uintptr_t minAddr = std::numeric_limits<uintptr_t>::max(); uintptr_t maxAddr = 0;
for (Segment* seg : getSegments(*m_file)) { if (strcmp(seg->segname, SEG_PAGEZERO) == 0) continue; if (seg->vmaddr < minAddr) minAddr = seg->vmaddr; if (seg->vmaddr+seg->vmsize > maxAddr) maxAddr = seg->vmaddr+seg->vmsize; }
if (!maxAddr) return 0; else return maxAddr-minAddr;}
void MachOObject::setInitialSegmentProtection(){ for (const Mapping& map : m_mappings) { if (::mprotect(map.start, map.size, map.initprot) == -1) { std::stringstream ss; ss << "Failed to mprotect() memory: " << strerror(errno); throw std::runtime_error(ss.str()); } }}
void MachOObject::unloadSegments(){ ::munmap(baseAddress(), uintptr_t(maxAddress()) - uintptr_t(baseAddress())); m_mappings.clear();}
uintptr_t MachOObject::pageAlign(uintptr_t what){ static const uintptr_t howMuch = getpagesize() - 1; return (what + howMuch) & ~howMuch;}
int MachOObject::machoProtectionFlagsToMmap(int machoFlags){ int prot = 0; if (machoFlags & VM_PROT_READ) prot |= PROT_READ;
if (machoFlags & VM_PROT_WRITE) prot |= PROT_WRITE; if (machoFlags & VM_PROT_EXECUTE) prot |= PROT_EXEC; return prot;}
uintptr_t MachOObject::getMinMappingAddr(){ static uintptr_t minimum = -1; if (minimum == -1) { std::ifstream f("/proc/sys/vm/mmap_min_addr");
if (!f.is_open()) minimum = 0x1000; else f >> minimum; }
return minimum;}
void MachOObject::rebase(){ for (const MachO::Rebase* rebase : m_file->rebases()) { void* addr = reinterpret_cast<void*>(rebase->vmaddr + m_slide); switch (rebase->type) { case REBASE_TYPE_POINTER: { uintptr_t* ptr = reinterpret_cast<uintptr_t*>(addr);
*ptr += m_slide; break; } case REBASE_TYPE_TEXT_ABSOLUTE32: { uint32_t* ptr = reinterpret_cast<uint32_t*>(addr);
*ptr = m_file->fixEndian(*ptr); *ptr += static_cast<uint32_t>(m_slide); break; } case REBASE_TYPE_TEXT_PCREL32: // TODO: test it { uint32_t* ptr = reinterpret_cast<uint32_t*>(addr); *ptr = m_file->fixEndian(*ptr); *ptr = uintptr_t(addr) - 4 - (*ptr); break; }
default: { std::stringstream ss; ss << "Unknown rebase type: " << int(rebase->type); throw std::runtime_error(ss.str()); } } }}
void MachOObject::writeBind(int type, void** ptr, void* newAddr, const std::string& name){ //assert(newAddr != nullptr); if (MachOMgr::instance()->printBindings()) std::cerr << "dyld: Binding " << name << " at " << ptr << ": " << (void*)(*ptr) << " -> " << (void*)newAddr << std::endl;
if (type == BIND_TYPE_POINTER) *ptr = newAddr;
#ifdef __i386__ else if (type == BIND_TYPE_STUB) { struct jmp_instr { uint8_t relJmp; uint32_t addr; } __attribute__((packed)); static_assert(sizeof(jmp_instr) == 5, "Incorrect jmp instruction size");
jmp_instr* instr = reinterpret_cast<jmp_instr*>(ptr);
instr->relJmp = 0xE9; // x86 jmp rel32 instr->addr = uint32_t(newAddr) - uint32_t(ptr) - sizeof(jmp_instr); } else if (type == BIND_TYPE_PCREL) { *ptr = (void*) (uint32_t(newAddr) - uintptr_t(ptr) - 4); }#endif}
void MachOObject::loadDependencies(){ std::vector<MachO::Dylib> dylibs = m_file->dylibs(); std::string path; const char* libsystem_dylib = "/usr/lib/libSystem.B.dylib";
// libSystem MUST always be loaded first // This is essentially a hack, but it should be safe. LoadableObject* libSystem;
path = DylibSearch::instance()->resolve(libsystem_dylib, this); libSystem = LoadableObject::instantiateForPath(path, this); libSystem->load();
for (const MachO::Dylib& d : dylibs) { LoadableObject* dep; std::string dylib = d.name; if (dylib.empty()) { ERROR() << "Empty dylib dependency in " << this->path(); continue; } path = DylibSearch::instance()->resolve(dylib, this); if (path.empty()) { std::stringstream ss; ss << "dyld: Library not loaded: " << dylib << " as a dependency of " << this->path(); throw std::runtime_error(ss.str()); } if (path == libSystem->path()) dep = libSystem; else dep = MachOMgr::instance()->lookup(path);
if (dep != nullptr) { dep->addRef(); m_dependencies.push_back(dep); } else { dep = LoadableObject::instantiateForPath(path, this); dep->load(); m_dependencies.push_back(dep); } }
if (libSystem->refCount() > 1) libSystem->delRef(); else if (isMainModule()) { std::cerr << "Beware! This executable (" << this->name() << ") does not link against libSystem. " "It is very likely a Go binary or an otherwise non-standard executable that makes direct system calls.\n" "Darling currently doesn't support that (#193).\n"; }}
void MachOObject::readSymbols(){ for (const MachO::Symbol& sym : m_file->symbols()) { void* addr = (void*)(sym.addr + m_slide); m_symbols[addr] = sym.name.c_str(); }}
void MachOObject::readExports(){ for (MachO::Export* exp : m_file->exports()) { MachO::Export e = *exp; e.addr += m_slide; if (e.resolver && (e.flag & EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER)) // EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER support e.resolver += m_slide; m_exports[e.name] = e; }}
void* MachOObject::getExportedSymbol(const std::string& symbolName, bool nonWeakOnly) const{ auto it = m_exports.find("_"+symbolName); if (it != m_exports.end()) { if (nonWeakOnly && (it->second.flag & EXPORT_SYMBOL_FLAGS_WEAK_DEFINITION)) return nullptr; if (it->second.resolver && (it->second.flag & EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER)) { typedef void* (*Resolver)(); Resolver res = Resolver(it->second.resolver); it->second.addr = (uint64_t) res(); it->second.resolver = 0; } return (void*)it->second.addr; } else return nullptr;}
bool MachOObject::findSymbolInfo(const void* addr, Dl_info* p) const{ p->dli_fname = m_file->filename().c_str(); p->dli_fbase = m_base; auto it = m_symbols.upper_bound(const_cast<void*>(addr)); if (it != m_symbols.begin()) { it--; p->dli_sname = it->second; p->dli_saddr = it->first; return true; } else { p->dli_saddr = nullptr; p->dli_sname = nullptr; return false; }}
void MachOObject::fillInProgramVars(){ ProgramVars* vars = getProgramVars(); if (vars != nullptr) { if (!vars->mh) { vars->mh = &m_header; vars->NXArgcPtr = &g_argc; vars->NXArgvPtr = (const char***) &g_argv; vars->environPtr = (const char***) &environ; vars->__prognamePtr = (const char**) &g_argv[0]; } else { *((void**) vars->mh) = getMachHeader(); *vars->NXArgcPtr = g_argc; *vars->NXArgvPtr = (const char**) g_argv; *vars->environPtr = (const char**) __darwin_environ; *vars->__prognamePtr = g_argv[0]; } } else { auto it = m_exports.find("_NXArgc"); if (it != m_exports.end()) *((int*)it->second.addr) = g_argc; it = m_exports.find("_NXArgv"); if (it != m_exports.end()) *((char***)it->second.addr) = g_argv; it = m_exports.find("_environ"); if (it != m_exports.end()) *((char***)it->second.addr) = __darwin_environ; it = m_exports.find("___progname"); if (it != m_exports.end()) *((const char**)it->second.addr) = g_argv[0]; }}
void MachOObject::fillInDyldData(){ DyldSectData* dyld = (DyldSectData*) getSection("__DATA", "__dyld"); if (dyld) dyld->funcLookupPtr = lookupDyldFunction;}
bool MachOObject::lookupDyldFunction(const char* name, void** addr){ LOG << "lookupDyldFunction: " << name << std::endl; *addr = dlsym(RTLD_DEFAULT, name);
if (!*addr) *addr = (void*) (void (*)()) []() { LOG << "Fake dyld function called\n"; };
return (*addr) != 0;}
ProgramVars* MachOObject::getProgramVars(){ ProgramVars* vars = (ProgramVars*) getSection("__DATA", "__program_vars"); if (!vars) { uintptr_t size; DyldSectData* dyld = (DyldSectData*) getSection("__DATA", "__dyld", &size); if (dyld && size > sizeof(void*)*2) vars = &dyld->vars; } return vars;}
// These are libgcc functionsextern "C" void __register_frame(void*) __attribute__((weak));extern "C" void __deregister_frame(void*) __attribute__((weak));
void MachOObject::registerEHSection(){ if (!__register_frame) return;
auto eh_frame = m_file->get_eh_frame(); if (!eh_frame.first) return; try { EHSection ehSection; void* original_eh_data; // On Darwin/i386, esp and ebp register numbers are swapped#ifdef __i386__ static const std::map<int, int> regSwap = { std::make_pair<int, int>(4, 5), std::make_pair<int, int>(5, 4) };#endif original_eh_data = (void*) (eh_frame.first + m_slide); LOG << "Reworking __eh_frame at " << original_eh_data << std::endl; ehSection.load(original_eh_data, eh_frame.second); #ifdef __i386__ ehSection.swapRegisterNumbers(regSwap);#endif ehSection.store(&m_reworkedEHData, nullptr); LOG << "Registering reworked __eh_frame at " << m_reworkedEHData << std::endl;#ifndef __arm__ __register_frame(m_reworkedEHData);#endif } catch (const std::exception& e) { ERROR() << "Failed to rework the __eh_frame: " << e.what() << ", exception handling WILL NOT WORK!"; }}
void MachOObject::unregisterEHSection(){ if (m_reworkedEHData) {#ifndef __arm__ __deregister_frame(m_reworkedEHData);#endif EHSection::free(m_reworkedEHData); m_reworkedEHData = nullptr; }}
void MachOObject::performRelocations(){ for (const MachO::Relocation* rel : m_file->relocations()) { uintptr_t* ptr = (uintptr_t*) (uintptr_t(rel->addr) + m_slide); uintptr_t symbol; uintptr_t value = *ptr; int libraryOrdinal;
libraryOrdinal = usesTwoLevelNamespace() ? rel->ordinal : MachO::Bind::OrdinalSpecialFlatLookup;
symbol = (uintptr_t) resolveSymbol(rel->name, libraryOrdinal);
value += symbol;
#ifdef __i386__ if (rel->pcrel) { //LOG << "reloc(pcrel): @" << ptr << " " << std::hex << *ptr << " -> " << (value - uintptr_t(ptr) - 4) << std::dec << std::endl; writeBind(BIND_TYPE_PCREL, (void**) ptr, (void*) value, rel->name); //*ptr = value - uintptr_t(ptr) - 4; } else#endif { //LOG << "reloc: @" << ptr << " " << std::hex << *ptr << " -> " << value << std::dec << std::endl; *ptr = value; writeBind(BIND_TYPE_POINTER, (void**) ptr, (void*) value, rel->name); } }}
void MachOObject::performBinds(){ for (MachO::Bind* bind : m_file->binds()) { if (bind->name.empty()) continue; // WTF... if (!bind->is_lazy || m_bindAllAtLoad) performBind(bind); }}
void* MachOObject::performBind(MachO::Bind* bind){ if (bind->type == BIND_TYPE_POINTER || bind->type == BIND_TYPE_STUB) { void* addr = nullptr;
if (bind->is_classic && bind->is_local) addr = (void*) bind->value; else { int ordinal = usesTwoLevelNamespace() ? bind->ordinal : MachO::Bind::OrdinalSpecialFlatLookup; addr = resolveSymbol(bind->name, ordinal); } if (!addr && !bind->is_weak) {#ifdef HAS_DEBUG_HELPERS if (MachOMgr::instance()->ignoreMissingSymbols() && bind->name[0] == '_') { ERROR().write("Creating a fake implementation for " + bind->name.substr(1)); addr = MachOMgr::instance()->undefMgr()->generateNew(bind->name.c_str()+1); bind->addend = 0; } else#endif { std::stringstream ss; ss << "Symbol not found: " << bind->name; if (usesTwoLevelNamespace()) { if (bind->ordinal > 0 && bind->ordinal <= m_dependencies.size()) { LoadableObject* obj = m_dependencies[bind->ordinal-1]; ss << " (expected in " << obj->name() << ")"; } } ss << " when loading " << name(); throw std::runtime_error(ss.str()); } } if (!bind->is_classic) addr = (void*) (uintptr_t(addr) + bind->addend);
#ifdef HAS_DEBUG_HELPERS if (addr && MachOMgr::instance()->useTrampolines() && bind->name[0] == '_') { TrampolineMgr* mgr = MachOMgr::instance()->trampolineMgr();
addr = mgr->generate(addr, bind->name.c_str()+1); }#endif if (addr != nullptr) writeBind(bind->type, (void**)(bind->vmaddr + m_slide), addr, bind->name); return addr; } else { std::stringstream ss; ss << "Unknown bind type: 0x" << std::hex << bind->type; throw std::runtime_error(ss.str()); }}
void* MachOObject::resolveSymbol(const std::string& name, int libraryOrdinal){ void* addr; void* module; if (name.empty()) return nullptr; if (name == "__mh_dylib_header") return m_base; if (name[0] != '_') { addr = nullptr; lookupDyldFunction(name.c_str(), &addr); return addr; }
if (MachOMgr::instance()->forceFlatNamespace()) libraryOrdinal = MachO::Bind::OrdinalSpecialFlatLookup; switch (libraryOrdinal) { case MachO::Bind::OrdinalSpecialSelf: module = this; break; case MachO::Bind::OrdinalSpecialExecutable: module = DARWIN_RTLD_MAIN_ONLY; break; case MachO::Bind::OrdinalSpecialFlatLookup: module = DARWIN_RTLD_DEFAULT; break; default: { if (libraryOrdinal > 0 && libraryOrdinal <= m_dependencies.size()) module = m_dependencies[libraryOrdinal-1]; else return nullptr; } }
addr = __darwin_dlsym(module, name.c_str() + 1); // ELF doesn't really support symbol re-exporting, // and Apple likes to move symbols around. If we're using a two-level // namespace, the symbol is not found, but a re-export is indicated, // we try to find the symbol using RTLD_DEFAULT. if (!addr && libraryOrdinal > 0) { std::string reexport; reexport = name.substr(1); reexport += "$$reexport"; if (__darwin_dlsym(module, reexport.c_str()) != nullptr) { std::cout << "Reexport confirmed\n"; addr = __darwin_dlsym(DARWIN_RTLD_DEFAULT, name.c_str() + 1); } } return addr;}
void MachOObject::runInitializers(){ char* apple[2] = { const_cast<char*>(m_absolutePath.c_str()), nullptr }; ProgramVars* pvars = nullptr; if (MachOMgr::instance()->mainModule() != nullptr) pvars = MachOMgr::instance()->mainModule()->getProgramVars(); else ; // TODO: provide an alternative ProgramVars instance typedef void (*Initializer)(int argc, char** argv, char** envp, char** apple, ProgramVars* progvars); for (uint64_t offset : m_file->init_funcs()) { Initializer* init = (Initializer*)(offset + m_slide); if (MachOMgr::instance()->printInitializers()) std::cerr << "dyld: Running initializer at " << (void*)*init << std::endl; (*init)(g_argc, g_argv, environ, apple, pvars); }}
void MachOObject::runFinalizers(){ typedef void (*Finalizer)(); for (uint64_t offset : m_file->exit_funcs()) { Finalizer* fini = (Finalizer*)(offset + m_slide); (*fini)(); }}
void MachOObject::detectAbsolutePath(){ char path[PATH_MAX]; size_t pos; realpath(m_file->filename().c_str(), path); m_absolutePath = path; m_absolutePathDir = dirname(path);
pos = m_absolutePath.rfind('/');
if (pos == std::string::npos) m_name = m_absolutePath; else m_name = m_absolutePath.substr(pos+1);}
void MachOObject::setCommandLine(int argc, char** argv, char** envp){ if (argv[argc] != nullptr) throw std::invalid_argument("argv is not nullptr terminated"); if (!isMainModule()) throw std::logic_error("setCommandLine() used on a non-main module"); m_argc = argc; m_argv = argv; m_envp = envp; // FIXME: this needs to go away g_argc = argc; g_argv = argv;}
void MachOObject::commandLine(int* argc, char*** argv, char*** envp){ if (argc) *argc = m_argc; if (argv) *argv = m_argv; if (envp) *envp = m_envp;}
void MachOObject::commandLine(int** argc, char**** argv, char**** envp){ if (argc) *argc = &m_argc; if (argv) *argv = &m_argv; if (envp) *envp = &m_envp;}
void MachOObject::run(){ if (!isMainModule()) throw std::runtime_error("Tried to run a file that is not the main module"); char* apple[2] = { const_cast<char*>(m_absolutePath.c_str()), nullptr }; void (*pAtExit)(void(*)());
pAtExit = reinterpret_cast<void(*)(void(*)())>(MachOMgr::instance()->getExportedSymbol("atexit"));
if (pAtExit != nullptr) { pAtExit([]() { MachOMgr::instance()->atexit(); }); }
#ifdef HAS_DEBUG_HELPERS if (MachOMgr::instance()->useTrampolines()) MachOMgr::instance()->trampolineMgr()->printPath();#endif if (m_file->entry()) { LOG << "Jumping to " << (void*)m_file->entry() << "..." << std::endl; jumpToStart(); } else if (m_file->main()) { LOG << "Running main at " << (void*) m_file->main() << "...\n"; int (*pMain)(int, char**, char**, char**) = reinterpret_cast<int (*)(int, char**, char**, char**)>(m_file->main() + m_slide); void (*pExit)(int);
pExit = reinterpret_cast<void(*)(int)>(MachOMgr::instance()->getExportedSymbol("exit")); int rv = pMain(m_argc, m_argv, m_envp, apple);
if (pExit != nullptr) pExit(rv); else abort();
__builtin_unreachable(); } else throw std::runtime_error("No entry point found");}
void MachOObject::jumpToStart(){ char* apple[2] = { const_cast<char*>(m_absolutePath.c_str()), nullptr }; uintptr_t entry = m_file->entry() + m_slide; void** sp; int pushCount = 0; #ifdef __x86_64__# define GETSP(ptr) __asm__ volatile("movq %%rsp, %0" : "=r"(ptr) ::)# define JUMPX(pushCount, addr) __asm__ volatile("sub %1, %%rsp; jmpq *%0" :: "m"(addr), "r"(pushCount * sizeof(void*)) :)#elif defined(__i386__)# define GETSP(ptr) __asm__ volatile("movl %%esp, %0" : "=m"(ptr) ::)# define JUMPX(pushCount, addr) __asm__ volatile("sub %1, %%esp; jmp *%0" :: "m"(addr), "r"(pushCount * sizeof(void*)) :)#elif defined(__arm__)# define GETSP(ptr) __asm__ volatile("mov %0, sp" : "=r"(ptr) ::)# define JUMPX(pushCount, addr) __asm__ volatile("sub sp, %1; bx %0" :: "r"(addr), "r"(pushCount * sizeof(void*)) :)#else # error Unsupported platform!#endif GETSP(sp); sp--;
for (int i = std::char_traits<char*>::length(apple)-1; i >= 0; i--) *(sp-(pushCount++)) = apple[i];
for (int i = std::char_traits<char*>::length(m_envp)-1; i >= 0; i--) *(sp-(pushCount++)) = m_envp[i];
for (int i = m_argc; i >= 0; i--) *(sp-(pushCount++)) = m_argv[i];
*(sp-(pushCount++)) = (void*) uintptr_t(m_argc); JUMPX(pushCount, entry);
__builtin_unreachable();}
void* MachOObject::getSection(const std::string& segmentName, const std::string& sectionName, uintptr_t* sectionSize){ LOG << "Looking for section " << sectionName << " in segment " << segmentName << std::endl;
for (const MachO::Section& sect : m_file->sections()) { if ((segmentName.empty() || sect.segment == segmentName) && sect.section == sectionName) { void* addr;
if (sectionSize) *sectionSize = sect.size;
addr = (void*) (sect.addr + m_slide);
LOG << "...found at " << addr << std::endl;
return addr; } }
LOG << "...not found\n"; return nullptr;}
void MachOObject::setupTLS(){#ifdef HAS_TLS_SUPPORT std::vector<tlv_descriptor*> descriptors; const std::vector<MachO::TLVSection>& tlvSections = m_file->tlv_sections(); std::vector<void*> initializers; if (tlvSections.empty()) return; for (MachO::TLVSection sect : tlvSections) { tlv_descriptor* des = reinterpret_cast<tlv_descriptor*>(sect.firstDescriptor+m_slide); for (size_t i = 0; i < sect.count; i++) descriptors.push_back(&des[i]); } for (uint64_t offset : m_file->tlv_init_funcs()) initializers.push_back((void*) (offset+m_slide)); Darling::TLSSetup(this, descriptors, initializers, (void*) (m_file->tlv_initial_values().first+m_slide), m_file->tlv_initial_values().second);#endif}
void MachOObject::teardownTLS(){#ifdef HAS_TLS_SUPPORT Darling::TLSTeardown(this);#endif}
void* MachOObject::doLazyBind(uintptr_t lazyOffset){ for (MachO::Bind* bind : m_file->binds()) { if (bind->is_lazy && bind->offset == lazyOffset) { try { return performBind(bind); } catch (const std::exception& e) { std::cerr << "dyld_stub_binder_fixup(): Failed to resolve the symbol: " << e.what() << std::endl; abort(); } } }
std::cerr << "dyld_stub_binder_fixup(): Lazy bind not found for offset " << std::hex << lazyOffset << std::dec << std::endl; abort();}
const char* MachOObject::name() const{ return m_name.c_str();}
void* MachOObject::getExportedSymbolRecursive(const std::string& symbolName) const{ std::set<const LoadableObject*> seen; void* addr;
seen.insert(this);
if ((addr = getExportedSymbol(symbolName, false))) return addr;
return getExportedSymbolRecursive(symbolName, seen);}
void* MachOObject::getExportedSymbolRecursive(const std::string& symbolName, std::set<const LoadableObject*>& visited) const{ std::set<const MachOObject*> recurse;
for (const LoadableObject* dep : m_dependencies) { void* addr;
if (visited.find(dep) != visited.end()) continue;
visited.insert(dep);
if ((addr = dep->getExportedSymbol(symbolName, false))) return addr;
if (const MachOObject* obj = dynamic_cast<const MachOObject*>(dep)) recurse.insert(obj); }
for (const MachOObject* obj : recurse) { void* addr;
if ((addr = obj->getExportedSymbolRecursive(symbolName, visited))) return addr; }
return nullptr;}
void* MachOObject::dyld_stub_binder_fixup(MachOObject** obj, uintptr_t lazyOffset){ if (!*obj) { LOG << "Finding MachOObject for address " << obj << std::endl; *obj = MachOMgr::instance()->objectForAddress((void*) obj);
if (!*obj) { std::cerr << "dyld_stub_binder_fixup(): Mach-O object not found for " << obj << std::endl; abort(); } }
return (*obj)->doLazyBind(lazyOffset);}
void MachOObject::atExit(){ runFinalizers();}
} // namespace Darling