/*
This file is part of Darling.
Copyright (C) 2015 Lubos Dolezel
Darling is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the 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 of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Darling. If not, see .
*/
#include "dl_public.h"
#include "MachOObject.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#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(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& getSegments(const MachO& mach) { return mach.segments64(); }
#elif (__WORDSIZE == 32)
typedef segment_command Segment;
static const std::vector& 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::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(rebase->vmaddr + m_slide);
switch (rebase->type)
{
case REBASE_TYPE_POINTER:
{
uintptr_t* ptr = reinterpret_cast(addr);
*ptr += m_slide;
break;
}
case REBASE_TYPE_TEXT_ABSOLUTE32:
{
uint32_t* ptr = reinterpret_cast(addr);
*ptr = m_file->fixEndian(*ptr);
*ptr += static_cast(m_slide);
break;
}
case REBASE_TYPE_TEXT_PCREL32: // TODO: test it
{
uint32_t* ptr = reinterpret_cast(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(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 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(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 functions
extern "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 regSwap = {
std::make_pair(4, 5),
std::make_pair(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(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(m_absolutePath.c_str()), nullptr };
void (*pAtExit)(void(*)());
pAtExit = reinterpret_cast(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(m_file->main() + m_slide);
void (*pExit)(int);
pExit = reinterpret_cast(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(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::length(apple)-1; i >= 0; i--)
*(sp-(pushCount++)) = apple[i];
for (int i = std::char_traits::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 descriptors;
const std::vector& tlvSections = m_file->tlv_sections();
std::vector initializers;
if (tlvSections.empty())
return;
for (MachO::TLVSection sect : tlvSections)
{
tlv_descriptor* des = reinterpret_cast(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 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& visited) const
{
std::set 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(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