#include <string.h>
#include <stdint.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <limits.h>
#include <sys/errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/param.h>
#include <sys/mman.h>
#include <mach/mach_vm.h>
#include <mach-o/dyld.h>
#include <mach-o/dyld_priv.h>
#include <uuid/uuid.h>
#include <os/log.h>
#include <string>
#include <vector>
#include <array>
#include "ImageProxy.h"
#include "FileUtils.h"
#include "StringUtils.h"
#include "MachOParser.h"
#include "LaunchCacheFormat.h"
#include "LaunchCacheWriter.h"
#include "PathOverrides.h"
#include "libdyldEntryVector.h"
namespace dyld3 {
typedef launch_cache::TargetSymbolValue TargetSymbolValue;
ImageProxy::ImageProxy(const mach_header* mh, const BinaryImageData* imageData, uint32_t indexInGroup, bool dyldCacheIsRaw)
: _mh(mh), _sliceFileOffset(0), _modTime(0), _inode(0), _imageBinaryData(imageData), _runtimePath(launch_cache::Image(imageData).path()),
_groupNum(0), _indexInGroup(indexInGroup), _isSetUID(false), _dyldCacheIsRaw(dyldCacheIsRaw), _platformBinary(false), _overrideOf(ImageRef::weakImportMissing()),
_directDependentsSet(false), _deepDependentsSet(false), _initBeforesArraySet(false), _initBeforesComputed(false),
_invalid(launch_cache::Image(imageData).isInvalid()), _staticallyReferenced(false), _cwdMustBeThisDir(false)
{
}
ImageProxy::ImageProxy(const DyldSharedCache::MappedMachO& mapping, uint32_t groupNum, uint32_t indexInGroup, bool dyldCacheIsRaw)
: _mh(mapping.mh), _sliceFileOffset(mapping.sliceFileOffset), _modTime(mapping.modTime), _inode(mapping.inode), _imageBinaryData(nullptr), _runtimePath(mapping.runtimePath),
_groupNum(groupNum), _indexInGroup(indexInGroup), _isSetUID(mapping.isSetUID), _dyldCacheIsRaw(dyldCacheIsRaw), _platformBinary(mapping.protectedBySIP),
_overrideOf(ImageRef::weakImportMissing()), _directDependentsSet(false), _deepDependentsSet(false), _initBeforesArraySet(false), _initBeforesComputed(false),
_invalid(false), _staticallyReferenced(false), _cwdMustBeThisDir(false)
{
}
void ImageProxy::processRPaths(ImageProxyGroup& owningGroup)
{
__block std::unordered_set<std::string> rawRpaths;
MachOParser parser(_mh, _dyldCacheIsRaw);
parser.forEachRPath(^(const char* rpath, bool& stop) {
if ( rawRpaths.count(rpath) ) {
_diag.warning("duplicate LC_RPATH (%s) in %s", rpath, _runtimePath.c_str());
return;
}
rawRpaths.insert(rpath);
std::string thisRPath = rpath;
if ( startsWith(thisRPath, "@executable_path/") ) {
std::string mainPath = owningGroup.mainProgRuntimePath();
if ( mainPath.empty() && parser.isDynamicExecutable() ) {
mainPath = _runtimePath;
}
if ( !mainPath.empty() ) {
std::string newPath = mainPath.substr(0, mainPath.rfind('/')+1) + thisRPath.substr(17);
std::string normalizedPath = owningGroup.normalizedPath(newPath);
if ( fileExists(normalizedPath) )
_rpaths.push_back(normalizedPath);
else
_diag.warning("LC_RPATH to nowhere (%s) in %s", rpath, _runtimePath.c_str());
char resolvedMainPath[PATH_MAX];
if ( (realpath(mainPath.c_str(), resolvedMainPath) != nullptr) && (mainPath.c_str() != resolvedMainPath) ) {
std::string realMainPath = resolvedMainPath;
size_t lastSlashPos = realMainPath.rfind('/');
std::string newRealPath = realMainPath.substr(0, lastSlashPos+1) + thisRPath.substr(17);
if ( realMainPath != mainPath ) {
for (const std::string& pre : owningGroup._buildTimePrefixes) {
std::string aPath = owningGroup.normalizedPath(pre + newRealPath);
if ( fileExists(aPath) ) {
_rpaths.push_back(owningGroup.normalizedPath(newRealPath));
}
}
}
}
}
else {
_diag.warning("LC_RPATH uses @executable_path in %s", _runtimePath.c_str());
}
}
else if ( thisRPath == "@executable_path" ) {
std::string mainPath = owningGroup.mainProgRuntimePath();
if ( mainPath.empty() && parser.isDynamicExecutable() ) {
mainPath = _runtimePath;
}
if ( !mainPath.empty() ) {
std::string newPath = mainPath.substr(0, mainPath.rfind('/')+1);
std::string normalizedPath = owningGroup.normalizedPath(newPath);
_rpaths.push_back(normalizedPath);
}
else {
_diag.warning("LC_RPATH uses @executable_path in %s", _runtimePath.c_str());
}
}
else if ( startsWith(thisRPath, "@loader_path/") ) {
size_t lastSlashPos = _runtimePath.rfind('/');
std::string newPath = _runtimePath.substr(0, lastSlashPos+1) + thisRPath.substr(13);
bool found = false;
for (const std::string& pre : owningGroup._buildTimePrefixes) {
std::string aPath = owningGroup.normalizedPath(pre + newPath);
if ( fileExists(aPath) ) {
_rpaths.push_back(owningGroup.normalizedPath(newPath));
found = true;
break;
}
}
char resolvedPath[PATH_MAX];
if ( (realpath(_runtimePath.c_str(), resolvedPath) != nullptr) && (_runtimePath.c_str() != resolvedPath) ) {
std::string realRunPath = resolvedPath;
lastSlashPos = realRunPath.rfind('/');
std::string newRealPath = realRunPath.substr(0, lastSlashPos+1) + thisRPath.substr(13);
if ( newRealPath != newPath ) {
for (const std::string& pre : owningGroup._buildTimePrefixes) {
std::string aPath = owningGroup.normalizedPath(pre + newRealPath);
if ( fileExists(aPath) ) {
_rpaths.push_back(owningGroup.normalizedPath(newRealPath));
found = true;
break;
}
}
}
}
if ( !found ) {
_rpaths.push_back(owningGroup.normalizedPath(newPath));
_diag.warning("LC_RPATH to nowhere (%s) in %s", rpath, _runtimePath.c_str());
}
}
else if ( thisRPath == "@loader_path" ) {
size_t lastSlashPos = _runtimePath.rfind('/');
std::string newPath = _runtimePath.substr(0, lastSlashPos+1);
std::string normalizedPath = owningGroup.normalizedPath(newPath);
_rpaths.push_back(normalizedPath);
}
else if ( rpath[0] == '@' ) {
_diag.warning("LC_RPATH with unknown @ variable (%s) in %s", rpath, _runtimePath.c_str());
}
else {
if ( rpath[0] == '/' )
_diag.warning("LC_RPATH is absolute path (%s) in %s", rpath, _runtimePath.c_str());
_rpaths.push_back(rpath);
}
});
}
void ImageProxy::addDependentsDeep(ImageProxyGroup& owningGroup, RPathChain* prev, bool staticallyReferenced)
{
if ( staticallyReferenced )
_staticallyReferenced = true;
if ( _deepDependentsSet )
return;
addDependentsShallow(owningGroup, prev);
if ( _diag.hasError() ) {
_invalid = true;
return;
}
RPathChain rchain = { this, prev, _rpaths };
for (ImageProxy* proxy : _dependents) {
if ( proxy == nullptr )
continue; if ( !proxy->_directDependentsSet )
proxy->addDependentsDeep(owningGroup, &rchain, staticallyReferenced);
if ( proxy->invalid() )
_invalid = true;
}
_deepDependentsSet = true;
}
void ImageProxy::addDependentsShallow(ImageProxyGroup& owningGroup, RPathChain* prev)
{
if ( _directDependentsSet )
return;
MachOParser thisParser(mh(), _dyldCacheIsRaw);
dyld3::Platform thisPlatform = thisParser.platform();
processRPaths(owningGroup);
__block RPathChain rchain = { this, prev, _rpaths };
thisParser.forEachDependentDylib(^(const char* loadPath, bool isWeak, bool isReExport, bool isUpward, uint32_t compatVersion, uint32_t curVersion, bool &stop) {
if ( (loadPath[0] != '/') && (loadPath[0] != '@') ) {
_diag.warning("load path is file system relative (%s) in %s", loadPath, runtimePath().c_str());
}
Diagnostics depDiag;
ImageProxy* dep = owningGroup.findImage(depDiag, loadPath, isWeak, &rchain);
if ( (dep == nullptr) || dep->invalid() ) {
if (isWeak) {
dep = nullptr;
} else {
if ( depDiag.warnings().empty() ) {
if ( thisParser.header()->filetype == MH_EXECUTE )
_diag.error("required dylib '%s' not found", loadPath);
else
_diag.error("required dylib '%s' not found, needed by '%s'", loadPath, runtimePath().c_str());
}
else {
std::string allTries;
for (const std::string& warn : depDiag.warnings()) {
if ( allTries.empty() )
allTries = warn;
else
allTries = allTries + ", " + warn;
}
_diag.error("required dylib '%s' not found, needed by '%s'. Did try: %s", loadPath, runtimePath().c_str(), allTries.c_str());
}
}
}
else {
MachOParser depParser(dep->mh(), _dyldCacheIsRaw);
if ( _diag.noError() ) {
dyld3::Platform depPlatform = depParser.platform();
if ( depPlatform != thisPlatform ) {
if ( !inLibSystem() || !dep->inLibSystem() ) {
_diag.error("found '%s' but it was built for different platform '%s' than required '%s'. Needed by '%s'", dep->runtimePath().c_str(),
MachOParser::platformName(depPlatform).c_str(), MachOParser::platformName(thisPlatform).c_str(), runtimePath().c_str());
}
}
}
if ( _diag.noError() ) {
const char* installName;
uint32_t foundCompatVers;
uint32_t foundCurrentVers;
if ( depParser.header()->filetype != MH_DYLIB ) {
_diag.error("found '%s' which is not a dylib. Needed by '%s'", dep->runtimePath().c_str(), runtimePath().c_str());
}
else {
depParser.getDylibInstallName(&installName, &foundCompatVers, &foundCurrentVers);
if ( foundCompatVers < compatVersion ) {
_diag.error("found '%s' which has compat version (%s) which is less than required (%s). Needed by '%s'", dep->runtimePath().c_str(),
MachOParser::versionString(foundCompatVers).c_str(), MachOParser::versionString(compatVersion).c_str(), runtimePath().c_str());
}
}
}
}
if ( _diag.hasError() ) {
stop = true;
_invalid = true;
}
_dependents.push_back(dep);
if ( isWeak )
_dependentsKind.push_back(launch_cache::Image::LinkKind::weak);
else if ( isReExport )
_dependentsKind.push_back(launch_cache::Image::LinkKind::reExport);
else if ( isUpward )
_dependentsKind.push_back(launch_cache::Image::LinkKind::upward);
else
_dependentsKind.push_back(launch_cache::Image::LinkKind::regular);
});
_directDependentsSet = true;
}
bool ImageProxy::inLibSystem() const
{
return startsWith(runtimePath(), "/usr/lib/system/") || startsWith(runtimePath(), "/usr/lib/libSystem.");
}
void ImageProxy::forEachDependent(void (^handler)(ImageProxy* dep, LinkKind)) const
{
for (int i=0; i < _dependents.size(); ++i) {
handler(_dependents[i], _dependentsKind[i]);
}
}
bool ImageProxy::findExportedSymbol(Diagnostics& diag, const char* symbolName, MachOParser::FoundSymbol& foundInfo) const
{
MachOParser parser(_mh, _dyldCacheIsRaw);
return parser.findExportedSymbol(diag, symbolName, (void*)this, foundInfo, ^(uint32_t depIndex, const char* depLoadPath, void* extra, const mach_header** foundMH, void** foundExtra) {
ImageProxy* proxy = (ImageProxy*)extra;
if ( depIndex < proxy->_dependents.size() ) {
ImageProxy* depProxy = proxy->_dependents[depIndex];
*foundMH = depProxy->_mh;
*foundExtra = (void*)depProxy;
return true;
}
return false;
});
}
bool ImageProxy::InitOrderInfo::beforeHas(ImageRef ref)
{
ImageRef clearRef = ref;
clearRef.clearKind();
return ( std::find(initBefore.begin(), initBefore.end(), clearRef) != initBefore.end() );
}
bool ImageProxy::InitOrderInfo::upwardHas(ImageProxy* proxy)
{
return ( std::find(danglingUpward.begin(), danglingUpward.end(), proxy) != danglingUpward.end() );
}
void ImageProxy::InitOrderInfo::removeRedundantUpwards()
{
danglingUpward.erase(std::remove_if(danglingUpward.begin(), danglingUpward.end(),
[&](ImageProxy* proxy) {
ImageRef ref(0, proxy->_groupNum, proxy->_indexInGroup);
return beforeHas(ref);
}), danglingUpward.end());
}
void ImageProxy::recursiveBuildInitBeforeInfo(ImageProxyGroup& owningGroup)
{
if ( _initBeforesComputed )
return;
_initBeforesComputed = true;
if ( _imageBinaryData != nullptr ) {
assert(_groupNum == 0);
launch_cache::Image image(_imageBinaryData);
image.forEachInitBefore(^(launch_cache::binary_format::ImageRef ref) {
if ( (LinkKind)ref.kind() == LinkKind::upward ) {
ImageProxyGroup* groupP = &owningGroup;
while (groupP->_groupNum != 0)
groupP = groupP->_nextSearchGroup;
launch_cache::ImageGroup dyldCacheGroup(groupP->_basedOn);
launch_cache::Image dyldCacheImage = dyldCacheGroup.image(ref.indexInGroup());
Diagnostics diag;
ImageProxy* p = groupP->findAbsoluteImage(diag, dyldCacheImage.path(), false, false);
if ( diag.noError() )
_initBeforesInfo.danglingUpward.push_back(p);
}
else {
_initBeforesInfo.initBefore.push_back(ref);
}
});
}
else {
unsigned depIndex = 0;
for (ImageProxy* depProxy : _dependents) {
if ( depProxy == nullptr ) {
assert(_dependentsKind[depIndex] == LinkKind::weak);
}
else {
if ( _dependentsKind[depIndex] == LinkKind::upward ) {
if ( _initBeforesInfo.upwardHas(depProxy) ) {
}
else {
ImageRef ref(0, depProxy->_groupNum, depProxy->_indexInGroup);
if ( _initBeforesInfo.beforeHas(ref) ) {
}
else {
_initBeforesInfo.danglingUpward.push_back(depProxy);
}
}
}
else {
depProxy->recursiveBuildInitBeforeInfo(owningGroup);
for (ImageRef depInit : depProxy->_initBeforesInfo.initBefore) {
if ( !_initBeforesInfo.beforeHas(depInit) )
_initBeforesInfo.initBefore.push_back(depInit);
}
for (ImageProxy* upProxy : depProxy->_initBeforesInfo.danglingUpward) {
ImageRef ref(0, upProxy->_groupNum, upProxy->_indexInGroup);
if ( _initBeforesInfo.beforeHas(ref) ) {
}
else if ( _initBeforesInfo.upwardHas(upProxy) ) {
}
else {
_initBeforesInfo.danglingUpward.push_back(upProxy);
}
}
}
}
++depIndex;
}
_initBeforesInfo.removeRedundantUpwards();
MachOParser parser(_mh, _dyldCacheIsRaw);
Diagnostics diag;
if ( parser.hasInitializer(diag) || parser.hasPlusLoadMethod(diag) ) {
launch_cache::binary_format::ImageRef ref(0, _groupNum, _indexInGroup);
_initBeforesInfo.initBefore.push_back(ref);
}
}
}
void ImageProxy::convertInitBeforeInfoToArray(ImageProxyGroup& owningGroup)
{
if ( _initBeforesInfo.danglingUpward.empty() ) {
_initBeforesArray = _initBeforesInfo.initBefore;
}
else {
for (ImageRef ref : _initBeforesInfo.initBefore)
_initBeforesArray.push_back(ref);
bool inLibSys = inLibSystem();
for (ImageProxy* proxy : _initBeforesInfo.danglingUpward) {
if ( inLibSys && proxy->inLibSystem() )
continue;
proxy->getInitBeforeList(owningGroup);
for (ImageRef depInit : proxy->_initBeforesInfo.initBefore) {
if ( std::find(_initBeforesArray.begin(), _initBeforesArray.end(), depInit) == _initBeforesArray.end() )
_initBeforesArray.push_back(depInit);
}
ImageRef ref(0, proxy->_groupNum, proxy->_indexInGroup);
if ( std::find(_initBeforesArray.begin(), _initBeforesArray.end(), ref) == _initBeforesArray.end() )
_initBeforesArray.push_back(ref);
}
}
}
const std::vector<ImageRef>& ImageProxy::getInitBeforeList(ImageProxyGroup& owningGroup)
{
if ( !_initBeforesArraySet ) {
_initBeforesArraySet = true; recursiveBuildInitBeforeInfo(owningGroup);
convertInitBeforeInfoToArray(owningGroup);
}
return _initBeforesArray;
}
ImageProxy::FixupInfo ImageProxy::buildFixups(Diagnostics& diag, uint64_t cacheUnslideBaseAddress, launch_cache::ImageGroupWriter& groupWriter) const
{
__block ImageProxy::FixupInfo info;
MachOParser image(_mh, _dyldCacheIsRaw);
__block bool rebaseError = false;
image.forEachRebase(diag, ^(uint32_t segIndex, uint64_t segOffset, uint8_t type, bool& stop) {
dyld3::launch_cache::ImageGroupWriter::FixupType fixupType = launch_cache::ImageGroupWriter::FixupType::rebase;
switch ( type ) {
case REBASE_TYPE_POINTER:
fixupType = launch_cache::ImageGroupWriter::FixupType::rebase;
break;
case REBASE_TYPE_TEXT_ABSOLUTE32:
fixupType = launch_cache::ImageGroupWriter::FixupType::rebaseText;
info.hasTextRelocs = true;
break;
case REBASE_TYPE_TEXT_PCREL32:
diag.error("pcrel text rebasing not supported");
stop = true;
rebaseError = true;
break;
default:
diag.error("unknown rebase type");
stop = true;
rebaseError = true;
break;
}
info.fixups.push_back({segIndex, segOffset, fixupType, TargetSymbolValue::makeInvalid()});
});
if ( diag.hasError() )
return FixupInfo();
image.forEachBind(diag, ^(uint32_t segIndex, uint64_t segOffset, uint8_t type, int libOrdinal,
uint64_t addend, const char* symbolName, bool weakImport, bool lazy, bool& stop) {
launch_cache::ImageGroupWriter::FixupType fixupType;
switch ( type ) {
case BIND_TYPE_POINTER:
if ( lazy )
fixupType = launch_cache::ImageGroupWriter::FixupType::pointerLazyBind;
else
fixupType = launch_cache::ImageGroupWriter::FixupType::pointerBind;
break;
case BIND_TYPE_TEXT_ABSOLUTE32:
fixupType = launch_cache::ImageGroupWriter::FixupType::bindText;
info.hasTextRelocs = true;
break;
case BIND_TYPE_TEXT_PCREL32:
fixupType = launch_cache::ImageGroupWriter::FixupType::bindTextRel;
info.hasTextRelocs = true;
break;
case BIND_TYPE_IMPORT_JMP_REL32:
fixupType = launch_cache::ImageGroupWriter::FixupType::bindImportJmpRel;
break;
default:
diag.error("malformed executable, unknown bind type (%d)", type);
stop = true;
return;
}
const ImageProxy* depProxy = nullptr;
bool isWeakDylib = false;
if ( libOrdinal == BIND_SPECIAL_DYLIB_MAIN_EXECUTABLE ) {
uint32_t imagePathPoolOffset = groupWriter.addString("@main");
uint32_t imageSymbolPoolOffset = groupWriter.addString(symbolName);
info.fixups.push_back({segIndex, segOffset, fixupType, TargetSymbolValue::makeDynamicGroupValue(imagePathPoolOffset, imageSymbolPoolOffset, weakImport)});
return;
}
else if ( libOrdinal == BIND_SPECIAL_DYLIB_FLAT_LOOKUP ) {
uint32_t imagePathPoolOffset = groupWriter.addString("@flat");
uint32_t imageSymbolPoolOffset = groupWriter.addString(symbolName);
info.fixups.push_back({segIndex, segOffset, fixupType, TargetSymbolValue::makeDynamicGroupValue(imagePathPoolOffset, imageSymbolPoolOffset, weakImport)});
return;
}
else if ( libOrdinal == BIND_SPECIAL_DYLIB_SELF ) {
depProxy = this;
}
else if ( (libOrdinal >= 1) && (libOrdinal <= _dependents.size()) ) {
isWeakDylib = (_dependentsKind[libOrdinal-1] == LinkKind::weak);
depProxy = _dependents[libOrdinal-1];
}
else {
diag.error("ordinal %d not supported", libOrdinal);
stop = true;
return;
}
if ( depProxy != nullptr ) {
MachOParser::FoundSymbol foundInfo;
if ( depProxy->findExportedSymbol(diag, symbolName, foundInfo) ) {
MachOParser implDylib(foundInfo.foundInDylib, _dyldCacheIsRaw);
switch ( foundInfo.kind ) {
case MachOParser::FoundSymbol::Kind::headerOffset:
case MachOParser::FoundSymbol::Kind::resolverOffset:
if ( implDylib.inDyldCache() ) {
uint32_t cacheOffset = (uint32_t)(implDylib.preferredLoadAddress() + foundInfo.value - cacheUnslideBaseAddress + addend);
info.fixups.push_back({segIndex, segOffset, fixupType, TargetSymbolValue::makeSharedCacheOffset(cacheOffset)});
}
else {
ImageProxy* foundProxy = (ImageProxy*)(foundInfo.foundExtra);
bool isIndirectGroupNum = foundProxy->_groupNum >= 128;
uint32_t groupNum = isIndirectGroupNum ? groupWriter.addIndirectGroupNum(foundProxy->_groupNum) : foundProxy->_groupNum;
info.fixups.push_back({segIndex, segOffset, fixupType, TargetSymbolValue::makeGroupValue(groupNum, foundProxy->_indexInGroup, foundInfo.value+addend, isIndirectGroupNum)});
}
break;
case MachOParser::FoundSymbol::Kind::absolute:
if (((((intptr_t)(foundInfo.value+addend)) << 2) >> 2) != (foundInfo.value+addend)) {
diag.error("absolute value %lld not supported", foundInfo.value+addend);
stop = true;
return;
}
info.fixups.push_back({segIndex, segOffset, fixupType, TargetSymbolValue::makeAbsolute(foundInfo.value+addend)});
break;
}
}
else {
if ( !weakImport ) {
diag.error("symbol '%s' not found, expected in '%s'", symbolName, depProxy->runtimePath().c_str());
stop = true;
}
info.fixups.push_back({segIndex, segOffset, fixupType, TargetSymbolValue::makeAbsolute(0)});
}
}
else {
if ( isWeakDylib ) {
info.fixups.push_back({segIndex, segOffset, fixupType, TargetSymbolValue::makeAbsolute(0)});
}
else {
diag.error("dylib ordinal %d not found and not weak", libOrdinal);
stop = true;
}
}
});
if ( diag.hasError() )
return FixupInfo();
uint32_t weakDefPathPoolOffset = groupWriter.addString("@weak_def");
image.forEachWeakDef(diag, ^(bool strongDef, uint32_t segIndex, uint64_t segOffset, uint64_t addend, const char* symbolName, bool& stop) {
if ( strongDef )
return;
bool altered = false;
for (FixUp& fixup : info.fixups) {
if ( (fixup.segOffset == segOffset) && (fixup.segIndex == segIndex) ) {
uint32_t symbolPoolOffset = groupWriter.addString(symbolName);
fixup.type = launch_cache::ImageGroupWriter::FixupType::pointerBind;
fixup.target = TargetSymbolValue::makeDynamicGroupValue(weakDefPathPoolOffset, symbolPoolOffset, false);
altered = true;
}
}
if ( !altered ) {
if ( image.isSlideable() ) {
fprintf(stderr, "weak def for %s can't find underlying rebase/bind in %s\n", symbolName, runtimePath().c_str());
}
else {
uint32_t symbolPoolOffset = groupWriter.addString(symbolName);
info.fixups.push_back({segIndex, segOffset, launch_cache::ImageGroupWriter::FixupType::pointerBind, TargetSymbolValue::makeDynamicGroupValue(weakDefPathPoolOffset, symbolPoolOffset, false)} );
}
}
});
return info;
}
void ImageProxy::setOverrideOf(uint32_t groupNum, uint32_t indexInGroup)
{
_overrideOf = ImageRef(0, groupNum, indexInGroup);
}
static bool alreadyInList(const std::vector<ImageProxy*>& imageList, ImageProxy* image)
{
for (ImageProxy* proxy : imageList) {
if ( proxy == image )
return true;
}
return false;
}
void ImageProxy::addToFlatLookup(std::vector<ImageProxy*>& imageList)
{
bool addedSomething = false;
for (ImageProxy* dep : _dependents) {
if ( dep == nullptr )
continue;
if ( !alreadyInList(imageList, dep) ) {
imageList.push_back(dep);
addedSomething = true;
}
}
if ( addedSomething ) {
for (ImageProxy* dep : _dependents) {
if ( dep == nullptr )
continue;
dep->addToFlatLookup(imageList);
}
}
}
class StringPool
{
public:
uint32_t add(const std::string& str);
size_t size() const { return _buffer.size(); }
const char* buffer() const { return &_buffer[0]; }
void align();
private:
std::vector<char> _buffer;
std::unordered_map<std::string, uint32_t> _existingEntries;
};
uint32_t StringPool::add(const std::string& str)
{
auto pos = _existingEntries.find(str);
if ( pos != _existingEntries.end() )
return pos->second;
size_t len = str.size() + 1;
size_t offset = _buffer.size();
_buffer.insert(_buffer.end(), &str[0], &str[len]);
_existingEntries[str] = (uint32_t)offset;
assert(offset < 0xFFFF);
return (uint32_t)offset;
}
void StringPool::align()
{
while ( (_buffer.size() % 4) != 0 )
_buffer.push_back('\0');
}
ImageProxyGroup::ImageProxyGroup(uint32_t groupNum, const DyldCacheParser& dyldCache, const launch_cache::binary_format::ImageGroup* basedOn,
ImageProxyGroup* next, const std::string& mainProgRuntimePath,
const std::vector<const BinaryImageGroupData*>& knownGroups,
const std::vector<std::string>& buildTimePrefixes,
const std::vector<std::string>& envVars,
bool stubsEliminated, bool dylibsExpectedOnDisk, bool inodesAreSameAsRuntime)
: _pathOverrides(envVars), _patchTable(nullptr), _basedOn(basedOn), _dyldCache(dyldCache), _nextSearchGroup(next), _groupNum(groupNum),
_stubEliminated(stubsEliminated), _dylibsExpectedOnDisk(dylibsExpectedOnDisk), _inodesAreSameAsRuntime(inodesAreSameAsRuntime),
_knownGroups(knownGroups), _buildTimePrefixes(buildTimePrefixes), _mainProgRuntimePath(mainProgRuntimePath), _platform(Platform::unknown)
{
_archName = dyldCache.cacheHeader()->archName();
_platform = (Platform)(dyldCache.cacheHeader()->platform());
}
ImageProxyGroup::~ImageProxyGroup()
{
for (DyldSharedCache::MappedMachO& mapping : _ownedMappings ) {
vm_deallocate(mach_task_self(), (vm_address_t)mapping.mh, mapping.length);
}
for (ImageProxy* proxy : _images) {
delete proxy;
}
}
std::string ImageProxyGroup::normalizedPath(const std::string& path)
{
for (const std::string& prefix : _buildTimePrefixes) {
std::string fullPath = prefix + path;
if ( fileExists(fullPath) ) {
if ( (fullPath.find("/../") != std::string::npos) || (fullPath.find("//") != std::string::npos) || (fullPath.find("/./") != std::string::npos) ) {
char resolvedPath[PATH_MAX];
if ( realpath(fullPath.c_str(), resolvedPath) != nullptr ) {
std::string resolvedUnPrefixed = &resolvedPath[prefix.size()];
return resolvedUnPrefixed;
}
}
break;
}
}
return path;
}
ImageProxy* ImageProxyGroup::findImage(Diagnostics& diag, const std::string& runtimeLoadPath, bool canBeMissing, ImageProxy::RPathChain* rChain)
{
__block ImageProxy* result = nullptr;
_pathOverrides.forEachPathVariant(runtimeLoadPath.c_str(), _platform, ^(const char* possiblePath, bool& stop) {
if ( startsWith(possiblePath, "@rpath/") ) {
std::string trailing = &possiblePath[6];
for (const ImageProxy::RPathChain* cur=rChain; cur != nullptr; cur = cur->prev) {
for (const std::string& rpath : cur->rpaths) {
std::string aPath = rpath + trailing;
result = findAbsoluteImage(diag, aPath, true, false);
if ( result != nullptr ) {
_pathToProxy[runtimeLoadPath] = result;
stop = true;
return;
}
}
}
auto pos = _pathToProxy.find(possiblePath);
if ( pos != _pathToProxy.end() ) {
result = pos->second;
stop = true;
return;
}
}
else if ( startsWith(possiblePath, "@loader_path/") ) {
std::string loaderFile = rChain->inProxy->runtimePath();
size_t lastSlash = loaderFile.rfind('/');
if ( lastSlash != std::string::npos ) {
std::string loaderDir = loaderFile.substr(0, lastSlash);
std::string newPath = loaderDir + &possiblePath[12];
result = findAbsoluteImage(diag, newPath, canBeMissing, false);
if ( result != nullptr ) {
_pathToProxy[runtimeLoadPath] = result;
stop = true;
return;
}
}
}
else if ( startsWith(possiblePath, "@executable_path/") ) {
for (const ImageProxy::RPathChain* cur=rChain; cur != nullptr; cur = cur->prev) {
if ( cur->inProxy->mh()->filetype == MH_EXECUTE ) {
std::string mainProg = cur->inProxy->runtimePath();
size_t lastSlash = mainProg.rfind('/');
if ( lastSlash != std::string::npos ) {
std::string mainDir = mainProg.substr(0, lastSlash);
std::string newPath = mainDir + &possiblePath[16];
result = findAbsoluteImage(diag, newPath, canBeMissing, false);
if ( result != nullptr ) {
_pathToProxy[runtimeLoadPath] = result;
stop = true;
return;
}
}
}
}
}
else {
result = findAbsoluteImage(diag, possiblePath, canBeMissing, false);
if ( result != nullptr ) {
stop = true;
return;
}
}
});
if ( (result != nullptr) && (_groupNum > 1) && !result->isProxyForCachedDylib() ) {
for (ImageProxyGroup* grp = this; grp != nullptr; grp = grp->_nextSearchGroup) {
if ( grp->_basedOn == nullptr )
continue;
uint32_t indexInGroup;
launch_cache::ImageGroup imageGroup(grp->_basedOn);
if ( imageGroup.findImageByPath(runtimeLoadPath.c_str(), indexInGroup) ) {
result->setOverrideOf(imageGroup.groupNum(), indexInGroup);
break;
}
}
}
return result;
}
bool ImageProxyGroup::builtImageStillValid(const launch_cache::Image& image)
{
if ( _buildTimePrefixes.size() != 1 )
return true;
if ( _buildTimePrefixes.front().size() != 0 )
return true;
if ( _platform != MachOParser::currentPlatform() )
return true;
struct stat statBuf;
bool expectedOnDisk = image.group().dylibsExpectedOnDisk();
bool overridableDylib = image.overridableDylib();
bool cachedDylib = !image.isDiskImage();
bool fileFound = ( ::stat(image.path(), &statBuf) == 0 );
if ( cachedDylib ) {
if ( expectedOnDisk ) {
if ( fileFound ) {
return ( (image.fileModTime() == statBuf.st_mtime) && (image.fileINode() == statBuf.st_ino) );
}
else {
return false;
}
}
else {
if ( fileFound ) {
if ( overridableDylib ) {
return false;
}
else {
return true;
}
}
else {
return true;
}
}
}
else {
if ( fileFound ) {
if ( image.validateUsingModTimeAndInode() ) {
return ( (image.fileModTime() == statBuf.st_mtime) && (image.fileINode() == statBuf.st_ino) );
}
else {
return true;
}
}
else {
return false;
}
}
}
ImageProxy* ImageProxyGroup::findAbsoluteImage(Diagnostics& diag, const std::string& runtimeLoadPath, bool canBeMissing, bool makeErrorMessage, bool pathIsAlreadyReal)
{
auto pos = _pathToProxy.find(runtimeLoadPath);
if ( pos != _pathToProxy.end() )
return pos->second;
if ( _basedOn != nullptr ) {
uint32_t foundIndex;
launch_cache::ImageGroup imageGroup(_basedOn);
if ( imageGroup.findImageByPath(runtimeLoadPath.c_str(), foundIndex) ) {
launch_cache::Image image = imageGroup.image(foundIndex);
if ( builtImageStillValid(image) ) {
ImageProxy* proxy = nullptr;
if ( _groupNum == 0 ) {
const mach_header* mh = (mach_header*)((uint8_t*)(_dyldCache.cacheHeader()) + image.cacheOffset());
proxy = new ImageProxy(mh, image.binaryData(), foundIndex, _dyldCache.cacheIsMappedRaw());
}
else {
DyldSharedCache::MappedMachO* mapping = addMappingIfValidMachO(diag, runtimeLoadPath);
if ( mapping != nullptr ) {
proxy = new ImageProxy(*mapping, _groupNum, foundIndex, false);
}
}
if ( proxy != nullptr ) {
_pathToProxy[runtimeLoadPath] = proxy;
_images.push_back(proxy);
if ( runtimeLoadPath != image.path() ) {
_pathToProxy[image.path()] = proxy;
}
return proxy;
}
}
}
}
if ( _nextSearchGroup != nullptr ) {
ImageProxy* result = _nextSearchGroup->findAbsoluteImage(diag, runtimeLoadPath, true, false);
if ( result != nullptr )
return result;
}
if ( !pathIsAlreadyReal ) {
for (const std::string& prefix : _buildTimePrefixes) {
std::string fullPath = prefix + runtimeLoadPath;
if ( endsWith(prefix, "/") )
fullPath = prefix.substr(0, prefix.size()-1) + runtimeLoadPath;
if ( fileExists(fullPath) ) {
std::string resolvedPath = realFilePath(fullPath);
if ( !resolvedPath.empty() && (resolvedPath!= fullPath) ) {
std::string resolvedRuntimePath = resolvedPath.substr(prefix.size());
ImageProxy* proxy = findAbsoluteImage(diag, resolvedRuntimePath, true, false, true);
if ( proxy != nullptr )
return proxy;
}
}
}
}
if ( (_groupNum >= 2) && (_basedOn == nullptr) ) {
if ( (runtimeLoadPath[0] != '/') && (runtimeLoadPath[0] != '@') ) {
for (ImageProxy* aProxy : _images) {
if ( endsWith(aProxy->runtimePath(), runtimeLoadPath) ) {
aProxy->setCwdMustBeThisDir();
return aProxy;
}
}
}
DyldSharedCache::MappedMachO* mapping = addMappingIfValidMachO(diag, runtimeLoadPath);
if ( mapping != nullptr ) {
ImageProxy* proxy = new ImageProxy(*mapping, _groupNum, (uint32_t)_images.size(), false);
_pathToProxy[runtimeLoadPath] = proxy;
_images.push_back(proxy);
return proxy;
}
}
if ( !canBeMissing && makeErrorMessage ) {
if ( diag.warnings().empty() ) {
if ( diag.hasError() ) {
std::string orgMsg = diag.errorMessage();
diag.error("'%s' %s", runtimeLoadPath.c_str(), orgMsg.c_str());
}
else {
diag.error("could not find '%s'", runtimeLoadPath.c_str());
}
}
else {
std::string allTries;
for (const std::string& warn : diag.warnings()) {
if ( allTries.empty() )
allTries = warn;
else
allTries = allTries + ", " + warn;
}
diag.clearWarnings();
diag.error("could not use '%s'. Did try: %s", runtimeLoadPath.c_str(), allTries.c_str());
}
}
_mustBeMissingFiles.insert(runtimeLoadPath);
return nullptr;
}
DyldSharedCache::MappedMachO* ImageProxyGroup::addMappingIfValidMachO(Diagnostics& diag, const std::string& runtimePath, bool ignoreMainExecutables)
{
bool fileFound = false;
for (const std::string& prefix : _buildTimePrefixes) {
std::string fullPath = prefix + runtimePath;
struct stat statBuf;
if ( stat(fullPath.c_str(), &statBuf) != 0 )
continue;
fileFound = true;
int fd = ::open(fullPath.c_str(), O_RDONLY);
if ( fd < 0 ) {
diag.warning("file not open()able '%s' errno=%d", fullPath.c_str(), errno);
continue;
}
size_t len = (size_t)statBuf.st_size;
size_t offset = 0;
const void* p = ::mmap(NULL, len, PROT_READ, MAP_PRIVATE, fd, 0);
if ( p != MAP_FAILED ) {
size_t sliceLen;
size_t sliceOffset;
bool missingSlice;
Diagnostics fatDiag;
if ( FatUtil::isFatFileWithSlice(fatDiag, p, len, _archName, sliceOffset, sliceLen, missingSlice) ) {
::munmap((void*)p, len);
p = ::mmap(NULL, sliceLen, PROT_READ, MAP_PRIVATE, fd, sliceOffset);
if ( p != MAP_FAILED ) {
offset = sliceOffset;
len = sliceLen;
}
}
else if ( fatDiag.hasError() ) {
diag.warning("%s", fatDiag.errorMessage().c_str());
}
if ( (p != MAP_FAILED) && !missingSlice && MachOParser::isValidMachO(diag, _archName, _platform, p, len, fullPath, ignoreMainExecutables) ) {
bool issetuid = (statBuf.st_mode & (S_ISUID|S_ISGID));
bool sip = false; _ownedMappings.emplace_back(runtimePath, (mach_header*)p, len, issetuid, sip, offset, statBuf.st_mtime, statBuf.st_ino);
::close(fd);
return &_ownedMappings.back();
}
else if (p != MAP_FAILED) {
::munmap((void*)p, len);
}
}
::close(fd);
}
if ( !fileFound )
diag.warning("file not found '%s'", runtimePath.c_str());
return nullptr;
}
static bool dontExamineDir(const std::string& dirPath)
{
return endsWith(dirPath, ".app") || endsWith(dirPath, ".xctoolchain") || endsWith(dirPath, ".sdk") || endsWith(dirPath, ".platform");
}
void ImageProxyGroup::addExtraMachOsInBundle(const std::string& appDir)
{
iterateDirectoryTree("", appDir, ^(const std::string& dirPath) { return dontExamineDir(dirPath); }, ^(const std::string& path, const struct stat& statBuf) {
const bool hasXBit = ((statBuf.st_mode & S_IXOTH) == S_IXOTH);
if ( !hasXBit )
return;
if ( statBuf.st_size < 0x1000 )
return;
if ( _pathToProxy.find(path) == _pathToProxy.end() ) {
Diagnostics machoDiag;
DyldSharedCache::MappedMachO* mapping = addMappingIfValidMachO(machoDiag, path, true);
if ( mapping != nullptr ) {
ImageProxy* proxy = new ImageProxy(*mapping, _groupNum, (uint32_t)_images.size(), false);
if ( proxy != nullptr ) {
_pathToProxy[path] = proxy;
_images.push_back(proxy);
}
}
}
});
}
ImageProxyGroup* ImageProxyGroup::makeDyldCacheDylibsGroup(Diagnostics& diag, const DyldCacheParser& dyldCache,
const std::vector<DyldSharedCache::MappedMachO>& cachedDylibs,
const std::vector<std::string>& buildTimePrefixes,
const PatchTable& patchTable, bool stubEliminated, bool dylibsExpectedOnDisk)
{
std::vector<std::string> emptyEnvVars; std::vector<const BinaryImageGroupData*> noExistingGroups;
ImageProxyGroup* groupProxy = new ImageProxyGroup(0, dyldCache, nullptr, nullptr, "", noExistingGroups, buildTimePrefixes, emptyEnvVars, stubEliminated, dylibsExpectedOnDisk);
groupProxy->_patchTable = &patchTable;
uint32_t indexInGroup = 0;
for (const DyldSharedCache::MappedMachO& mapping : cachedDylibs) {
ImageProxy* proxy = new ImageProxy(mapping, 0, indexInGroup++, true);
groupProxy->_images.push_back(proxy);
groupProxy->_pathToProxy[mapping.runtimePath] = proxy;
}
ImageRef libdyldEntryImageRef = ImageRef::makeEmptyImageRef();
uint32_t libdyldEntryOffset;
groupProxy->findLibdyldEntry(diag, libdyldEntryImageRef, libdyldEntryOffset);
if ( diag.hasError() ) {
delete groupProxy;
return nullptr;
}
bool hadError = false;
for (size_t i=0; i < groupProxy->_images.size(); ++i) {
ImageProxy* proxy = groupProxy->_images[i];
proxy->addDependentsShallow(*groupProxy);
if ( proxy->diagnostics().hasError() ) {
hadError = true;
diag.copy(proxy->diagnostics());
break;
}
}
if ( hadError ) {
delete groupProxy;
return nullptr;
}
return groupProxy;
}
ImageProxyGroup* ImageProxyGroup::makeOtherOsGroup(Diagnostics& diag, const DyldCacheParser& dyldCache, ImageProxyGroup* cachedDylibsGroup,
const std::vector<DyldSharedCache::MappedMachO>& otherDylibsAndBundles,
bool inodesAreSameAsRuntime, const std::vector<std::string>& buildTimePrefixes)
{
std::vector<std::string> emptyEnvVars; const BinaryImageGroupData* cachedDylibsGroupData = dyldCache.cachedDylibsGroup();
std::vector<const BinaryImageGroupData*> existingGroups = { cachedDylibsGroupData };
ImageProxyGroup dyldCacheDylibProxyGroup(0, dyldCache, cachedDylibsGroupData, nullptr, "", existingGroups, buildTimePrefixes, emptyEnvVars);
ImageProxyGroup* groupProxy = new ImageProxyGroup(1, dyldCache, nullptr, cachedDylibsGroup, "", existingGroups, buildTimePrefixes, emptyEnvVars,
false, true, inodesAreSameAsRuntime);
uint32_t indexInGroup = 0;
for (const DyldSharedCache::MappedMachO& mapping : otherDylibsAndBundles) {
ImageProxy* proxy = new ImageProxy(mapping, 1, indexInGroup++, true);
groupProxy->_images.push_back(proxy);
groupProxy->_pathToProxy[mapping.runtimePath] = proxy;
}
for (size_t i=0; i < groupProxy->_images.size(); ++i) {
ImageProxy* proxy = groupProxy->_images[i];
proxy->addDependentsShallow(*groupProxy);
if ( proxy->diagnostics().hasError() ) {
diag.warning("adding dependents to %s: %s", proxy->runtimePath().c_str(), proxy->diagnostics().errorMessage().c_str());
proxy->markInvalid();
}
}
__block bool somethingInvalid;
do {
somethingInvalid = false;
for (ImageProxy* proxy : groupProxy->_images) {
proxy->forEachDependent(^(ImageProxy* dep, LinkKind) {
if ( (dep != nullptr) && dep->invalid() && !proxy->invalid()) {
proxy->markInvalid();
somethingInvalid = true;
}
});
}
} while (somethingInvalid);
return groupProxy;
}
const BinaryImageGroupData* ImageProxyGroup::makeDlopenGroup(Diagnostics& diag, const DyldCacheParser& dyldCache, uint32_t groupNum,
const std::vector<const BinaryImageGroupData*>& existingGroups,
const std::string& imagePath, const std::vector<std::string>& envVars)
{
const std::vector<std::string>& noBuildTimePrefixes = {""};
ImageProxyGroup dyldCacheDylibProxyGroup(0, dyldCache, existingGroups[0], nullptr, "", existingGroups, noBuildTimePrefixes, envVars);
ImageProxyGroup dyldCacheOtherProxyGroup(1, dyldCache, nullptr, &dyldCacheDylibProxyGroup, "", existingGroups, noBuildTimePrefixes, envVars);
ImageProxyGroup dlopenGroupProxy(groupNum, dyldCache, nullptr, &dyldCacheOtherProxyGroup, imagePath, existingGroups, noBuildTimePrefixes, envVars, false, true, true);
DyldSharedCache::MappedMachO* topMapping = dlopenGroupProxy.addMappingIfValidMachO(diag, imagePath, true);
if ( topMapping == nullptr ) {
if ( diag.noError() ) {
const std::set<std::string>& warnings = diag.warnings();
if ( warnings.empty() )
diag.error("no loadable mach-o in %s", imagePath.c_str());
else
diag.error("%s", (*warnings.begin()).c_str());
}
return nullptr;
}
ImageProxy* topImageProxy = new ImageProxy(*topMapping, groupNum, 0, false);
if ( topImageProxy == nullptr ) {
diag.error("can't find slice matching dyld cache in %s", imagePath.c_str());
return nullptr;
}
dlopenGroupProxy._images.push_back(topImageProxy);
dlopenGroupProxy._pathToProxy[imagePath] = topImageProxy;
topImageProxy->addDependentsDeep(dlopenGroupProxy, nullptr, false);
if ( topImageProxy->diagnostics().hasError() ) {
diag.copy(topImageProxy->diagnostics());
return nullptr;
}
const BinaryImageGroupData* result = dlopenGroupProxy.makeImageGroupBinary(diag);
return result;
}
BinaryClosureData* ImageProxyGroup::makeClosure(Diagnostics& diag, const DyldCacheParser& dyldCache, ImageProxyGroup* cachedDylibsGroup,
ImageProxyGroup* otherOsDylibs, const DyldSharedCache::MappedMachO& mainProgMapping,
bool inodesAreSameAsRuntime, const std::vector<std::string>& buildTimePrefixes)
{
if ( cachedDylibsGroup->_basedOn == nullptr ) {
cachedDylibsGroup->_basedOn = dyldCache.cachedDylibsGroup();
}
const BinaryImageGroupData* cachedDylibsGroupData = dyldCache.cachedDylibsGroup();
const BinaryImageGroupData* otherDylibsGroupData = dyldCache.otherDylibsGroup();
std::vector<const BinaryImageGroupData*> existingGroups = { cachedDylibsGroupData, otherDylibsGroupData };
std::vector<std::string> emptyEnvVars; ImageProxyGroup mainClosureGroupProxy(2, dyldCache, nullptr, otherOsDylibs, mainProgMapping.runtimePath, existingGroups, buildTimePrefixes,
emptyEnvVars, false, true, inodesAreSameAsRuntime);
ImageProxy* mainProxy = new ImageProxy(mainProgMapping, 2, 0, true);
if ( mainProxy == nullptr ) {
diag.error("can't find slice matching dyld cache in %s", mainProgMapping.runtimePath.c_str());
return nullptr;
}
mainClosureGroupProxy._images.push_back(mainProxy);
mainClosureGroupProxy._pathToProxy[mainProgMapping.runtimePath] = mainProxy;
return mainClosureGroupProxy.makeClosureBinary(diag, mainProxy, false);
}
bool ImageProxyGroup::addInsertedDylibs(Diagnostics& diag)
{
__block bool success = true;
_pathOverrides.forEachInsertedDylib(^(const char* dylibPath) {
ImageProxy* insertProxy = findAbsoluteImage(diag, dylibPath, false, true);
if ( insertProxy == nullptr )
success = false;
});
return success;
}
static DyldCacheParser findDyldCache(Diagnostics& diag, const ClosureBuffer::CacheIdent& cacheIdent, task_t requestor, bool* dealloc)
{
*dealloc = false;
#if !defined(__MAC_OS_X_VERSION_MIN_REQUIRED) || (__MAC_OS_X_VERSION_MIN_REQUIRED >= 101300)
size_t currentCacheSize;
const DyldSharedCache* currentCache = (const DyldSharedCache*)_dyld_get_shared_cache_range(¤tCacheSize);
if ( currentCache != nullptr ) {
uuid_t currentCacheUUID;
currentCache->getUUID(currentCacheUUID);
if ( memcmp(currentCacheUUID, cacheIdent.cacheUUID, 16) == 0 )
return DyldCacheParser((const DyldSharedCache*)currentCache, false);
}
#endif
if ( requestor == mach_task_self() ) {
const DyldSharedCache* altCache = (DyldSharedCache*)cacheIdent.cacheAddress;
uuid_t altCacheUUID;
altCache->getUUID(altCacheUUID);
if ( memcmp(altCacheUUID, cacheIdent.cacheUUID, 16) == 0 )
return DyldCacheParser(altCache, true); else
diag.error("dyld cache uuid has changed");
}
#if BUILDING_CLOSURED
else {
uint8_t cacheBuffer[4096];
mach_vm_size_t actualReadSize = sizeof(cacheBuffer);
kern_return_t r;
r = mach_vm_read_overwrite(requestor, cacheIdent.cacheAddress, sizeof(cacheBuffer), (vm_address_t)&cacheBuffer, &actualReadSize);
if ( r != KERN_SUCCESS ) {
diag.error("unable to read cache header from requesting process (addr=0x%llX), kern err=%d", cacheIdent.cacheAddress, r);
return DyldCacheParser(nullptr, false);
}
const dyld_cache_header* header = (dyld_cache_header*)cacheBuffer;
const dyld_cache_mapping_info* mappings = (dyld_cache_mapping_info*)(cacheBuffer + header->mappingOffset);
vm_address_t bufferAddress = 0;
r = vm_allocate(mach_task_self(), &bufferAddress, (long)cacheIdent.cacheMappedSize, VM_FLAGS_ANYWHERE);
if ( r != KERN_SUCCESS ) {
diag.error("unable to allocate space to copy custom dyld cache (size=0x%llX), kern err=%d", cacheIdent.cacheMappedSize, r);
return DyldCacheParser(nullptr, false);
}
uint64_t slide = cacheIdent.cacheAddress - mappings[0].address;
for (int i=0; i < 3; ++i) {
mach_vm_address_t mappedAddress = bufferAddress + (mappings[i].address - mappings[0].address);
mach_vm_size_t mappedSize = mappings[i].size;
vm_prot_t curProt = VM_PROT_READ;
vm_prot_t maxProt = VM_PROT_READ;
r = mach_vm_remap(mach_task_self(), &mappedAddress, mappedSize, 0, VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE,
requestor, mappings[i].address+slide, true, &curProt, &maxProt, VM_INHERIT_NONE);
if ( r != KERN_SUCCESS ) {
diag.error("unable to mach_vm_remap region %d custom dyld cache (request addr=0x%llX, size=0x%llX), kern err=%d, localBuffer=0x%lX, localMapTarget=0x%llX",
i, mappings[i].address+slide, mappedSize, r, (long)bufferAddress, mappedAddress);
return DyldCacheParser(nullptr, false);
}
if ( curProt != VM_PROT_READ )
vm_protect(mach_task_self(), (long)mappedAddress, (long)mappedSize, false, VM_PROT_READ);
}
*dealloc = true;
return DyldCacheParser((DyldSharedCache*)bufferAddress, false); }
#endif
return DyldCacheParser(nullptr, false);
}
BinaryClosureData* ImageProxyGroup::makeClosure(Diagnostics& diag, const ClosureBuffer& buffer, task_t requestor, const std::vector<std::string>& buildTimePrefixes)
{
bool deallocCacheCopy;
DyldCacheParser dyldCache = findDyldCache(diag, buffer.cacheIndent(), requestor, &deallocCacheCopy);
if ( diag.hasError() )
return nullptr;
const char* mainProg = buffer.targetPath();
std::vector<std::string> envVars;
int envCount = buffer.envVarCount();
const char* envVarCStrings[envCount];
buffer.copyImageGroups(envVarCStrings);
for (int i=0; i < envCount; ++i) {
envVars.push_back(envVarCStrings[i]);
}
const BinaryImageGroupData* cachedDylibsGroupData = dyldCache.cachedDylibsGroup();
const BinaryImageGroupData* otherDylibsGroupData = dyldCache.otherDylibsGroup();
std::vector<std::string> realBuildTimePrefixes;
for (const std::string& prefix : buildTimePrefixes) {
char resolvedPath[PATH_MAX];
if ( realpath(prefix.c_str(), resolvedPath) != nullptr )
realBuildTimePrefixes.push_back(resolvedPath);
else
realBuildTimePrefixes.push_back(prefix);
}
std::vector<const BinaryImageGroupData*> existingGroups = { cachedDylibsGroupData, otherDylibsGroupData };
ImageProxyGroup dyldCacheDylibProxyGroup(0, dyldCache, cachedDylibsGroupData, nullptr, "", existingGroups, realBuildTimePrefixes, envVars);
ImageProxyGroup dyldCacheOtherProxyGroup(1, dyldCache, otherDylibsGroupData, &dyldCacheDylibProxyGroup, "", existingGroups, realBuildTimePrefixes, envVars);
ImageProxyGroup mainClosureGroupProxy( 2, dyldCache, nullptr, &dyldCacheOtherProxyGroup, mainProg, existingGroups, realBuildTimePrefixes, envVars, false, true, true);
BinaryClosureData* result = nullptr;
if ( mainClosureGroupProxy.addInsertedDylibs(diag) ) {
ImageProxy* proxy = mainClosureGroupProxy.findAbsoluteImage(diag, mainProg, false, true);
if ( proxy != nullptr ) {
result = mainClosureGroupProxy.makeClosureBinary(diag, proxy, false);
}
}
if ( deallocCacheCopy )
vm_deallocate(mach_task_self(), (vm_address_t)dyldCache.cacheHeader(), (long)buffer.cacheIndent().cacheMappedSize);
return result;
}
ClosureBuffer closured_CreateImageGroup(const ClosureBuffer& input)
{
Diagnostics diag;
const BinaryImageGroupData* newGroup = ImageProxyGroup::makeDlopenGroup(diag, input, mach_task_self(), {""});
if ( diag.noError() ) {
dyld3::ClosureBuffer result(newGroup);
free((void*)newGroup);
return result;
}
else {
dyld3::ClosureBuffer err(diag.errorMessage().c_str());
return err;
}
}
const BinaryImageGroupData* ImageProxyGroup::makeDlopenGroup(Diagnostics& diag, const ClosureBuffer& buffer, task_t requestor, const std::vector<std::string>& buildTimePrefixes)
{
bool deallocCacheCopy;
DyldCacheParser dyldCache = findDyldCache(diag, buffer.cacheIndent(), requestor, &deallocCacheCopy);
if ( diag.hasError() )
return nullptr;
const char* targetDylib = buffer.targetPath();
std::vector<std::string> envVars;
int envCount = buffer.envVarCount();
const char* envVarCStrings[envCount];
buffer.copyImageGroups(envVarCStrings);
for (int i=0; i < envCount; ++i) {
envVars.push_back(envVarCStrings[i]);
}
uint32_t groupCount = buffer.imageGroupCount() + 2;
const launch_cache::BinaryImageGroupData* groupDataPtrs[groupCount];
groupDataPtrs[0] = dyldCache.cachedDylibsGroup();
groupDataPtrs[1] = dyldCache.otherDylibsGroup();
buffer.copyImageGroups(&groupDataPtrs[2]);
std::vector<const launch_cache::BinaryImageGroupData*> existingGroups;
std::vector<std::unique_ptr<ImageProxyGroup>> proxies;
ImageProxyGroup* prevProxy = nullptr;
for (uint32_t i=0; i < groupCount; ++i) {
const launch_cache::BinaryImageGroupData* groupData = groupDataPtrs[i];
existingGroups.push_back(groupData);
launch_cache::ImageGroup group(groupData);
uint32_t groupNum = group.groupNum();
assert(groupNum == proxies.size());
proxies.emplace_back(new ImageProxyGroup(groupNum, dyldCache, groupData, prevProxy, "", existingGroups, buildTimePrefixes, envVars));
prevProxy = proxies.back().get();
}
ImageProxyGroup dlopenGroupProxy(groupCount, dyldCache, nullptr, prevProxy, targetDylib, existingGroups, buildTimePrefixes, envVars);
DyldSharedCache::MappedMachO* topMapping = dlopenGroupProxy.addMappingIfValidMachO(diag, targetDylib, true);
if ( topMapping == nullptr ) {
std::string allWarnings;
for (const std::string& warn : diag.warnings()) {
if ( allWarnings.empty() )
allWarnings = warn;
else
allWarnings = allWarnings + ", " + warn;
}
diag.clearWarnings();
diag.error("%s", allWarnings.c_str());
if ( deallocCacheCopy )
vm_deallocate(mach_task_self(), (vm_address_t)dyldCache.cacheHeader(), (long)buffer.cacheIndent().cacheMappedSize);
return nullptr;
}
ImageProxy* topImageProxy = new ImageProxy(*topMapping, groupCount, 0, false);
if ( topImageProxy == nullptr ) {
diag.error("can't find slice matching dyld cache in %s", targetDylib);
if ( deallocCacheCopy )
vm_deallocate(mach_task_self(), (vm_address_t)dyldCache.cacheHeader(), (long)buffer.cacheIndent().cacheMappedSize);
return nullptr;
}
dlopenGroupProxy._images.push_back(topImageProxy);
dlopenGroupProxy._pathToProxy[targetDylib] = topImageProxy;
topImageProxy->addDependentsDeep(dlopenGroupProxy, nullptr, false);
if ( topImageProxy->diagnostics().hasError() ) {
diag.copy(topImageProxy->diagnostics());
if ( deallocCacheCopy )
vm_deallocate(mach_task_self(), (vm_address_t)dyldCache.cacheHeader(), (long)buffer.cacheIndent().cacheMappedSize);
return nullptr;
}
const BinaryImageGroupData* result = dlopenGroupProxy.makeImageGroupBinary(diag);
if ( deallocCacheCopy )
vm_deallocate(mach_task_self(), (vm_address_t)dyldCache.cacheHeader(), (long)buffer.cacheIndent().cacheMappedSize);
return result;
}
BinaryClosureData* ImageProxyGroup::makeClosure(Diagnostics& diag, const DyldCacheParser& dyldCache,
const std::string& mainProg, bool includeDylibsInDir,
const std::vector<std::string>& buildTimePrefixes,
const std::vector<std::string>& envVars)
{
const BinaryImageGroupData* cachedDylibsGroupData = dyldCache.cachedDylibsGroup();
const BinaryImageGroupData* otherDylibsGroupData = dyldCache.otherDylibsGroup();
std::vector<std::string> realBuildTimePrefixes;
for (const std::string& prefix : buildTimePrefixes) {
char resolvedPath[PATH_MAX];
if ( realpath(prefix.c_str(), resolvedPath) != nullptr )
realBuildTimePrefixes.push_back(resolvedPath);
else
realBuildTimePrefixes.push_back(prefix);
}
std::vector<const BinaryImageGroupData*> existingGroups = { cachedDylibsGroupData, otherDylibsGroupData };
ImageProxyGroup dyldCacheDylibProxyGroup(0, dyldCache, cachedDylibsGroupData, nullptr, "", existingGroups, realBuildTimePrefixes, envVars);
ImageProxyGroup dyldCacheOtherProxyGroup(1, dyldCache, otherDylibsGroupData, &dyldCacheDylibProxyGroup, "", existingGroups, realBuildTimePrefixes, envVars);
ImageProxyGroup mainClosureGroupProxy( 2, dyldCache, nullptr, &dyldCacheOtherProxyGroup, mainProg, existingGroups, realBuildTimePrefixes, envVars, false, true, true);
if ( !mainClosureGroupProxy.addInsertedDylibs(diag) )
return nullptr;
ImageProxy* proxy = mainClosureGroupProxy.findAbsoluteImage(diag, mainProg, false, true);
if ( proxy == nullptr )
return nullptr;
return mainClosureGroupProxy.makeClosureBinary(diag, proxy, includeDylibsInDir);
}
const char* sSkipPrograms_macOS[] = {
"/Applications/iBooks.app/Contents/MacOS/iBooks",
};
const char* sSkipPrograms_embeddedOSes[] = {
"/sbin/launchd",
"/usr/local/sbin/launchd.debug",
"/usr/local/sbin/launchd.development"
};
BinaryClosureData* ImageProxyGroup::makeClosureBinary(Diagnostics& diag, ImageProxy* mainProgProxy, bool includeDylibsInDir)
{
assert(mainProgProxy != nullptr);
assert(_images.size() >= 1);
if ( _platform == Platform::macOS ) {
for (const char* skipProg : sSkipPrograms_macOS) {
if ( mainProgProxy->runtimePath() == skipProg ) {
diag.error("black listed program");
return nullptr;
}
}
} else {
for (const char* skipProg : sSkipPrograms_embeddedOSes) {
if ( mainProgProxy->runtimePath() == skipProg ) {
diag.error("black listed program");
return nullptr;
}
}
}
_mainExecutableIndex = (uint32_t)_images.size() - 1;
mainProgProxy->addDependentsDeep(*this, nullptr, true);
if ( mainProgProxy->diagnostics().hasError() ) {
diag.copy(mainProgProxy->diagnostics());
return nullptr;
}
bool isAppMainExecutable = false;
std::string appDir;
std::string leafName = basePath(mainProgProxy->runtimePath());
size_t posAppX = mainProgProxy->runtimePath().rfind(std::string("/") + leafName + ".appex/");
size_t posApp = mainProgProxy->runtimePath().rfind(std::string("/") + leafName + ".app/");
if ( posAppX != std::string::npos ) {
appDir = mainProgProxy->runtimePath().substr(0, posAppX+leafName.size()+7);
isAppMainExecutable = true;
}
else if ( posApp != std::string::npos ) {
appDir = mainProgProxy->runtimePath().substr(0, posApp+leafName.size()+5);
isAppMainExecutable = true;
}
if ( isAppMainExecutable ) {
addExtraMachOsInBundle(appDir);
for (size_t i=0; i < _images.size(); ++i) {
ImageProxy* aProxy = _images[i];
ImageProxy::RPathChain base = { aProxy, nullptr, mainProgProxy->rpaths() };
aProxy->addDependentsDeep(*this, &base, false);
if ( aProxy->diagnostics().hasError() ) {
aProxy->markInvalid();
diag.warning("%s could not be added to closure because %s", aProxy->runtimePath().c_str(), aProxy->diagnostics().errorMessage().c_str());
}
}
}
else if ( includeDylibsInDir ) {
size_t pos = mainProgProxy->runtimePath().rfind('/');
if ( pos != std::string::npos ) {
std::string mainDir = mainProgProxy->runtimePath().substr(0, pos);
addExtraMachOsInBundle(mainDir);
for (size_t i=0; i < _images.size(); ++i) {
ImageProxy* aProxy = _images[i];
aProxy->addDependentsDeep(*this, nullptr, false);
}
}
}
if ( _mainExecutableIndex != 0 ) {
for (uint32_t i=0; i < _mainExecutableIndex; ++i) {
_images[i]->addDependentsDeep(*this, nullptr, true);
if ( _images[i]->diagnostics().hasError() )
return nullptr;
}
}
for (ImageProxy* proxy : _images) {
if ( !proxy->staticallyReferenced() && proxy->diagnostics().hasError() )
continue;
diag.copy(proxy->diagnostics());
if ( diag.hasError() ) {
return nullptr;
}
}
MachOParser mainExecutableParser(mainProgProxy->mh(), _dyldCache.cacheIsMappedRaw());
bool usesCRT;
uint32_t entryOffset;
mainExecutableParser.getEntry(entryOffset, usesCRT);
launch_cache::ImageGroupWriter groupWriter(_groupNum, mainExecutableParser.uses16KPages(), mainExecutableParser.is64(), _dylibsExpectedOnDisk, _inodesAreSameAsRuntime);
populateGroupWriter(diag, groupWriter);
if ( diag.hasError() )
return nullptr;
ImageRef libdyldEntryImageRef = ImageRef::makeEmptyImageRef();
uint32_t libdyldEntryOffset;
findLibdyldEntry(diag, libdyldEntryImageRef, libdyldEntryOffset);
if ( diag.hasError() )
return nullptr;
ImageRef libSystemImageRef = ImageRef::makeEmptyImageRef();
findLibSystem(diag, mainExecutableParser.isSimulatorBinary(), libSystemImageRef);
if ( diag.hasError() )
return nullptr;
__block StringPool stringPool;
__block std::vector<uint32_t> envVarOffsets;
std::vector<uint16_t> missingFileComponentOffsets;
stringPool.add(" ");
for (const std::string& path : _mustBeMissingFiles) {
size_t start = 1;
size_t slashPos = path.find('/', start);
while (slashPos != std::string::npos) {
std::string component = path.substr(start, slashPos - start);
uint16_t offset = stringPool.add(component);
missingFileComponentOffsets.push_back(offset);
start = slashPos + 1;
slashPos = path.find('/', start);
}
std::string lastComponent = path.substr(start);
uint16_t offset = stringPool.add(lastComponent);
missingFileComponentOffsets.push_back(offset);
missingFileComponentOffsets.push_back(0); }
missingFileComponentOffsets.push_back(0); if ( missingFileComponentOffsets.size() & 1 )
missingFileComponentOffsets.push_back(0); __block uint32_t envVarCount = 0;
_pathOverrides.forEachEnvVar(^(const char* envVar) {
envVarOffsets.push_back(stringPool.add(envVar));
++envVarCount;
});
stringPool.align();
uint32_t groupSize = groupWriter.size();
uint32_t missingFilesArraySize = (uint32_t)((missingFileComponentOffsets.size()*sizeof(uint16_t) + 3) & (-4));
uint32_t envVarsSize = (uint32_t)(envVarOffsets.size()*sizeof(uint32_t));
uint32_t stringPoolSize = (uint32_t)stringPool.size();
size_t allocSize = sizeof(launch_cache::binary_format::Closure)
+ groupSize
+ missingFilesArraySize
+ envVarsSize
+ stringPoolSize;
BinaryClosureData* clo = (BinaryClosureData*)malloc(allocSize);
groupWriter.finalizeTo(diag, _knownGroups, &clo->group);
launch_cache::ImageGroup cloGroup(&clo->group);
launch_cache::Image mainImage(cloGroup.imageBinary(_mainExecutableIndex));
uint32_t maxImageLoadCount = groupWriter.maxLoadCount(diag, _knownGroups, &clo->group);
if ( mainImage.isInvalid() ) {
free((void*)clo);
diag.error("depends on invalid dylib");
return nullptr;
}
clo->magic = launch_cache::binary_format::Closure::magicV1;
clo->usesCRT = usesCRT;
clo->isRestricted = mainProgProxy->isSetUID() || mainExecutableParser.isRestricted();
clo->usesLibraryValidation = mainExecutableParser.usesLibraryValidation();
clo->padding = 0;
clo->missingFileComponentsOffset = offsetof(launch_cache::binary_format::Closure, group) + groupSize;
clo->dyldEnvVarsOffset = clo->missingFileComponentsOffset + missingFilesArraySize;
clo->dyldEnvVarsCount = envVarCount;
clo->stringPoolOffset = clo->dyldEnvVarsOffset + envVarsSize;
clo->stringPoolSize = stringPoolSize;
clo->libSystemRef = libSystemImageRef;
clo->libDyldRef = libdyldEntryImageRef;
clo->libdyldVectorOffset = libdyldEntryOffset;
clo->mainExecutableIndexInGroup = _mainExecutableIndex;
clo->mainExecutableEntryOffset = entryOffset;
clo->initialImageCount = maxImageLoadCount;
_dyldCache.cacheHeader()->getUUID(clo->dyldCacheUUID);
if ( !mainExecutableParser.getCDHash(clo->mainExecutableCdHash) ) {
bzero(clo->mainExecutableCdHash, 20);
mainExecutableParser.getUuid(clo->mainExecutableCdHash);
}
if ( missingFilesArraySize != 0 )
memcpy((uint8_t*)clo + clo->missingFileComponentsOffset, &missingFileComponentOffsets[0], missingFileComponentOffsets.size()*sizeof(uint16_t));
if ( envVarsSize != 0 )
memcpy((uint8_t*)clo + clo->dyldEnvVarsOffset, &envVarOffsets[0], envVarsSize);
if ( stringPool.size() != 0 )
memcpy((uint8_t*)clo + clo->stringPoolOffset, stringPool.buffer(), stringPool.size());
return clo;
}
const BinaryImageGroupData* ImageProxyGroup::makeImageGroupBinary(Diagnostics& diag, const char* const neverEliminateStubs[])
{
const bool continueIfErrors = (_groupNum == 1);
bool uses16KPages = true;
bool is64 = true;
if ( !_images.empty() ) {
MachOParser firstParser(_images.front()->mh(), _dyldCache.cacheIsMappedRaw());
uses16KPages = firstParser.uses16KPages();
is64 = firstParser.is64();
}
launch_cache::ImageGroupWriter groupWriter(_groupNum, uses16KPages, is64, _dylibsExpectedOnDisk, _inodesAreSameAsRuntime);
populateGroupWriter(diag, groupWriter, neverEliminateStubs);
if ( diag.hasError() )
return nullptr;
BinaryImageGroupData* groupData = (BinaryImageGroupData*)malloc(groupWriter.size());
groupWriter.finalizeTo(diag, _knownGroups, groupData);
if ( !continueIfErrors && groupWriter.isInvalid(0) ) {
free((void*)groupData);
diag.error("depends on invalid dylib");
return nullptr;
}
return groupData;
}
void ImageProxyGroup::findLibdyldEntry(Diagnostics& diag, ImageRef& ref, uint32_t& vmOffsetInLibDyld)
{
Diagnostics libDyldDiag;
ImageProxy* libDyldProxy = findImage(libDyldDiag, "/usr/lib/system/libdyld.dylib", false, nullptr);
if ( libDyldProxy == nullptr ) {
diag.error("can't find libdyld.dylib");
return;
}
ref = ImageRef(0, libDyldProxy->groupNum(), libDyldProxy->indexInGroup());
Diagnostics entryDiag;
MachOParser::FoundSymbol dyldEntryInfo;
MachOParser libDyldParser(libDyldProxy->mh(), _dyldCache.cacheIsMappedRaw());
if ( !libDyldParser.findExportedSymbol(entryDiag, "__ZN5dyld318entryVectorForDyldE", nullptr, dyldEntryInfo, nullptr) ) {
diag.error("can't find dyld entry point into libdyld.dylib");
return;
}
vmOffsetInLibDyld = (uint32_t)dyldEntryInfo.value;
const LibDyldEntryVector* entry = (LibDyldEntryVector*)(libDyldParser.content(vmOffsetInLibDyld));
if ( entry == nullptr ) {
diag.error("dyld entry point at offset 0x%0X not found in libdyld.dylib", vmOffsetInLibDyld);
return;
}
if ( entry->vectorVersion != LibDyldEntryVector::kCurrentVectorVersion )
diag.error("libdyld.dylib vector version is incompatible with this dyld cache builder");
else if ( entry->binaryFormatVersion != launch_cache::binary_format::kFormatVersion )
diag.error("libdyld.dylib closures binary format version is incompatible with this dyld cache builder");
}
void ImageProxyGroup::findLibSystem(Diagnostics& diag, bool forSimulator, ImageRef& ref)
{
Diagnostics libSysDiag;
ImageProxy* libSystemProxy = findImage(libSysDiag, forSimulator ? "/usr/lib/libSystem.dylib" : "/usr/lib/libSystem.B.dylib" , false, nullptr);
if ( libSystemProxy == nullptr ) {
diag.error("can't find libSystem.dylib");
return;
}
ref = ImageRef(0, libSystemProxy->groupNum(), libSystemProxy->indexInGroup());
}
std::vector<ImageProxy*> ImageProxyGroup::flatLookupOrder()
{
std::vector<ImageProxy*> results;
for (uint32_t i=0; i <= _mainExecutableIndex; ++i)
results.push_back(_images[i]);
_images[_mainExecutableIndex]->addToFlatLookup(results);
for (uint32_t i=0; i < _mainExecutableIndex; ++i)
_images[i]->addToFlatLookup(results);
return results;
}
void ImageProxyGroup::populateGroupWriter(Diagnostics& diag, launch_cache::ImageGroupWriter& groupWriter, const char* const neverEliminateStubs[])
{
const bool buildingDylibsInCache = (_groupNum == 0);
const bool continueIfErrors = (_groupNum == 1);
std::unordered_set<std::string> neverStubEliminate;
if ( neverEliminateStubs != nullptr ) {
for (const char* const* nes=neverEliminateStubs; *nes != nullptr; ++nes)
neverStubEliminate.insert(*nes);
}
const uint64_t cacheUnslideBaseAddress = _dyldCache.cacheHeader()->unslidLoadAddress();
const uint32_t imageCount = (uint32_t)_images.size();
groupWriter.setImageCount(imageCount);
for (uint32_t i=0; i < imageCount; ++i) {
MachOParser imageParser(_images[i]->mh(), _dyldCache.cacheIsMappedRaw());
assert((imageParser.inDyldCache() == buildingDylibsInCache) && "all images must be same type");
groupWriter.setImagePath(i, _images[i]->runtimePath().c_str());
groupWriter.setImageIsBundle(i, (imageParser.fileType() == MH_BUNDLE));
bool hasObjC = imageParser.hasObjC();
groupWriter.setImageHasObjC(i, hasObjC);
bool isEncrypted = imageParser.isEncrypted();
groupWriter.setImageIsEncrypted(i, isEncrypted);
bool mayHavePlusLoad = false;
if ( hasObjC ) {
mayHavePlusLoad = isEncrypted || imageParser.hasPlusLoadMethod(diag);
groupWriter.setImageMayHavePlusLoads(i, mayHavePlusLoad);
}
groupWriter.setImageHasWeakDefs(i, imageParser.hasWeakDefs());
groupWriter.setImageMustBeThisDir(i, _images[i]->cwdMustBeThisDir());
groupWriter.setImageIsPlatformBinary(i, _images[i]->isPlatformBinary());
groupWriter.setImageOverridableDylib(i, !_stubEliminated || (neverStubEliminate.count(_images[i]->runtimePath()) != 0));
uuid_t uuid;
if ( imageParser.getUuid(uuid) )
groupWriter.setImageUUID(i, uuid);
if ( _inodesAreSameAsRuntime ) {
groupWriter.setImageFileMtimeAndInode(i, _images[i]->fileModTime(), _images[i]->fileInode());
}
else {
uint8_t cdHash[20];
if ( !imageParser.getCDHash(cdHash) )
bzero(cdHash, 20);
groupWriter.setImageCdHash(i, cdHash);
}
if ( !buildingDylibsInCache ) {
groupWriter.setImageSliceOffset(i, _images[i]->sliceFileOffset());
uint32_t fairPlayTextOffset;
uint32_t fairPlaySize;
if ( imageParser.isFairPlayEncrypted(fairPlayTextOffset, fairPlaySize) )
groupWriter.setImageFairPlayRange(i, fairPlayTextOffset, fairPlaySize);
uint32_t codeSigOffset;
uint32_t codeSigSize;
if ( imageParser.hasCodeSignature(codeSigOffset, codeSigSize) )
groupWriter.setImageCodeSignatureLocation(i, codeSigOffset, codeSigSize);
}
groupWriter.setImageDependentsCount(i, imageParser.dependentDylibCount());
groupWriter.setImageSegments(i, imageParser, cacheUnslideBaseAddress);
__block std::vector<uint32_t> initOffsets;
imageParser.forEachInitializer(diag, ^(uint32_t offset) {
initOffsets.push_back(offset);
});
groupWriter.setImageInitializerOffsets(i, initOffsets);
if ( diag.hasError() && !continueIfErrors ) {
return;
}
__block std::vector<uint32_t> dofOffsets;
imageParser.forEachDOFSection(diag, ^(uint32_t offset) {
dofOffsets.push_back(offset);
});
groupWriter.setImageDOFOffsets(i, dofOffsets);
if ( diag.hasError() && !continueIfErrors ) {
return;
}
bool neverUnload = false;
if ( buildingDylibsInCache )
neverUnload = true;
if ( _images[i]->staticallyReferenced() )
neverUnload = true;
if ( imageParser.hasObjC() && (imageParser.fileType() == MH_DYLIB) )
neverUnload = true;
if ( imageParser.hasThreadLocalVariables() )
neverUnload = true;
if ( !dofOffsets.empty() )
neverUnload = true;
groupWriter.setImageNeverUnload(i, neverUnload);
if ( _images[i]->invalid() )
groupWriter.setImageInvalid(i);
ImageRef stdRef = _images[i]->overrideOf();
if ( stdRef != ImageRef::weakImportMissing() ) {
ImageRef thisImageRef(0, _groupNum, i);
groupWriter.addImageIsOverride(stdRef, thisImageRef);
}
if ( imageParser.fileType() == MH_DYLIB ) {
const char* installName = imageParser.installName();
if ( installName[0] == '/' ) {
if ( _images[i]->runtimePath() != installName ) {
groupWriter.addImageAliasPath(i, installName);
}
}
if ( buildingDylibsInCache && (_platform != Platform::macOS) && (_images[i]->runtimePath() == "/System/Library/Frameworks/IOKit.framework/Versions/A/IOKit") ) {
groupWriter.addImageAliasPath(i, "/System/Library/Frameworks/IOKit.framework/IOKit");
}
}
}
for (uint32_t i=0; (i < imageCount) && diag.noError(); ++i) {
__block uint32_t depIndex = 0;
_images[i]->forEachDependent(^(ImageProxy* dep, LinkKind kind) {
if ( dep == nullptr ) {
if ( kind == LinkKind::weak )
groupWriter.setImageDependent(i, depIndex, launch_cache::binary_format::ImageRef::weakImportMissing());
else
groupWriter.setImageInvalid(i);
}
else {
launch_cache::binary_format::ImageRef ref((uint8_t)kind, dep->groupNum(), dep->indexInGroup());
groupWriter.setImageDependent(i, depIndex, ref);
}
++depIndex;
});
}
if ( continueIfErrors ) {
const launch_cache::binary_format::ImageRef missingRef = launch_cache::binary_format::ImageRef::weakImportMissing();
__block bool somethingInvalidated = false;
do {
somethingInvalidated = false;
for (uint32_t i=0; i < imageCount; ++i) {
if ( groupWriter.isInvalid(i) )
continue;
uint32_t depCount = groupWriter.imageDependentsCount(i);
for (uint32_t depIndex=0; depIndex < depCount; ++depIndex) {
launch_cache::binary_format::ImageRef ref = groupWriter.imageDependent(i, depIndex);
if ( ref == missingRef )
continue;
if ( ref.groupNum() == _groupNum ) {
if ( groupWriter.isInvalid(ref.indexInGroup()) ) {
groupWriter.setImageInvalid(i);
somethingInvalidated = true;
break;
}
}
}
}
} while (somethingInvalidated);
}
bool someBadFixups = false;
if ( !buildingDylibsInCache ) {
__block std::vector<ImageProxy::FixupInfo> fixupInfos;
fixupInfos.resize(imageCount);
for (uint32_t imageIndex=0; imageIndex < imageCount; ++imageIndex) {
if ( groupWriter.isInvalid(imageIndex) )
continue;
Diagnostics fixupDiag;
fixupInfos[imageIndex] = _images[imageIndex]->buildFixups(fixupDiag, cacheUnslideBaseAddress, groupWriter);
if ( fixupDiag.hasError() ) {
someBadFixups = true;
groupWriter.setImageInvalid(imageIndex);
if ( continueIfErrors ) {
diag.warning("fixup problem in %s: %s", _images[imageIndex]->runtimePath().c_str(), fixupDiag.errorMessage().c_str());
continue;
}
else {
diag.error("fixup problem in %s: %s", _images[imageIndex]->runtimePath().c_str(), fixupDiag.errorMessage().c_str());
return;
}
}
}
if ( _groupNum == 2) {
std::unordered_set<ImageProxy*> staticImagesWithWeakDefs;
ImageProxyGroup* cacheGroup = _nextSearchGroup->_nextSearchGroup;
assert(cacheGroup->_basedOn != nullptr);
launch_cache::ImageGroup dyldCacheGroup(cacheGroup->_basedOn);
for (uint32_t imageIndex=0; imageIndex < imageCount; ++imageIndex) {
if ( groupWriter.isInvalid(imageIndex) )
continue;
ImageProxy* thisProxy = _images[imageIndex];
if ( !thisProxy->staticallyReferenced() )
continue;
MachOParser imageParser(thisProxy->mh(), _dyldCache.cacheIsMappedRaw());
imageParser.forEachInterposingTuple(diag, ^(uint32_t segIndex, uint64_t replacementSegOffset, uint64_t replaceeSegOffset, uint64_t replacementContent, bool& tupleStop) {
if ( _groupNum != 2 ) {
groupWriter.setImageInvalid(imageIndex);
return;
}
TargetSymbolValue interposeReplacee = TargetSymbolValue::makeInvalid();
TargetSymbolValue interposeReplacement = TargetSymbolValue::makeInvalid();
for (const FixUp& fixup : fixupInfos[imageIndex].fixups) {
if ( fixup.segIndex != segIndex )
continue;
if ( fixup.segOffset == replacementSegOffset ) {
if ( fixup.type == launch_cache::ImageGroupWriter::FixupType::rebase ) {
uint64_t offsetInImage = replacementContent - imageParser.preferredLoadAddress();
interposeReplacement = TargetSymbolValue::makeGroupValue(2, imageIndex, offsetInImage, false);
}
else {
diag.warning("bad interposing implementation in %s", _images[imageIndex]->runtimePath().c_str());
return;
}
}
else if ( fixup.segOffset == replaceeSegOffset ) {
if ( fixup.type == launch_cache::ImageGroupWriter::FixupType::pointerBind ) {
interposeReplacee = fixup.target;
}
else {
diag.warning("bad interposing target in %s", _images[imageIndex]->runtimePath().c_str());
return;
}
}
}
for (uint32_t otherIndex=0; otherIndex < imageCount; ++otherIndex) {
if ( otherIndex == imageIndex )
continue;
for (FixUp& fixup : fixupInfos[otherIndex].fixups) {
switch ( fixup.type ) {
case launch_cache::ImageGroupWriter::FixupType::pointerBind:
case launch_cache::ImageGroupWriter::FixupType::pointerLazyBind:
if ( fixup.target == interposeReplacee )
fixup.target = interposeReplacement;
break;
case launch_cache::ImageGroupWriter::FixupType::rebase:
case launch_cache::ImageGroupWriter::FixupType::rebaseText:
case launch_cache::ImageGroupWriter::FixupType::ignore:
case launch_cache::ImageGroupWriter::FixupType::bindText:
case launch_cache::ImageGroupWriter::FixupType::bindTextRel:
case launch_cache::ImageGroupWriter::FixupType::bindImportJmpRel:
break;
}
}
}
if ( interposeReplacee.isInvalid() || interposeReplacement.isInvalid() ) {
diag.error("malformed interposing section in %s", _images[imageIndex]->runtimePath().c_str());
tupleStop = true;
return;
}
uint64_t offsetInCache;
if ( interposeReplacee.isSharedCacheTarget(offsetInCache) ) {
uint32_t patchTableIndex;
if ( dyldCacheGroup.hasPatchTableIndex((uint32_t)offsetInCache, patchTableIndex) ) {
uint32_t replacementGroupNum;
uint32_t replacementIndexInGroup;
uint64_t replacementOffsetInImage;
assert(interposeReplacement.isGroupImageTarget(replacementGroupNum, replacementIndexInGroup, replacementOffsetInImage));
assert(replacementGroupNum == 2);
assert(replacementIndexInGroup < (1 << 8));
if ( replacementOffsetInImage >= 0xFFFFFFFFULL ) {
diag.warning("bad interposing implementation in %s", _images[imageIndex]->runtimePath().c_str());
return;
}
DyldCacheOverride cacheOverride;
cacheOverride.patchTableIndex = patchTableIndex;
cacheOverride.imageIndex = replacementIndexInGroup;
cacheOverride.imageOffset = replacementOffsetInImage;
_cacheOverrides.push_back(cacheOverride);
}
}
});
if ( diag.hasError() && !continueIfErrors ) {
return;
}
ImageRef overrideOf = thisProxy->overrideOf();
if ( (overrideOf != ImageRef::makeEmptyImageRef()) && (overrideOf.groupNum() == 0) ) {
const launch_cache::Image imageInCache = dyldCacheGroup.image(overrideOf.indexInGroup());
const mach_header* imageInCacheMH = (mach_header*)((char*)(_dyldCache.cacheHeader()) + imageInCache.cacheOffset());
MachOParser inCacheParser(imageInCacheMH, _dyldCache.cacheIsMappedRaw());
inCacheParser.forEachExportedSymbol(diag, ^(const char* symbolName, uint64_t imageOffset, bool isReExport, bool &stop) {
if ( isReExport )
return;
uint32_t cacheOffsetOfSymbol = (uint32_t)(imageInCache.cacheOffset() + imageOffset);
uint32_t patchTableIndex;
if ( dyldCacheGroup.hasPatchTableIndex(cacheOffsetOfSymbol, patchTableIndex) ) {
MachOParser::FoundSymbol foundInfo;
if ( imageParser.findExportedSymbol(diag, symbolName, nullptr, foundInfo, nullptr) ) {
DyldCacheOverride cacheOverride;
assert(patchTableIndex < (1 << 24));
assert(thisProxy->indexInGroup() < (1 << 8));
assert(foundInfo.value < (1ULL << 32));
cacheOverride.patchTableIndex = patchTableIndex;
cacheOverride.imageIndex = thisProxy->indexInGroup();
cacheOverride.imageOffset = foundInfo.value;
_cacheOverrides.push_back(cacheOverride);
}
}
});
}
if ( thisProxy->mh()->flags & (MH_WEAK_DEFINES|MH_BINDS_TO_WEAK) ) {
staticImagesWithWeakDefs.insert(thisProxy);
}
}
if ( !staticImagesWithWeakDefs.empty() ) {
__block std::unordered_map<std::string, DyldCacheOverride> weakSymbols;
for (ImageProxy* proxy : staticImagesWithWeakDefs ) {
MachOParser weakDefParser(proxy->mh(), _dyldCache.cacheIsMappedRaw());
weakDefParser.forEachWeakDef(diag, ^(bool strongDef, uint32_t segIndex, uint64_t segOffset, uint64_t addend, const char* symbolName, bool& stop) {
weakSymbols[symbolName] = { 0, 0, 0 };
});
}
std::vector<ImageProxy*> flatSearchOrder = flatLookupOrder();
for (ImageProxy* proxy : flatSearchOrder) {
if ( (proxy->mh()->flags & (MH_WEAK_DEFINES|MH_BINDS_TO_WEAK)) == 0 )
continue;
if ( proxy->groupNum() == 2 ) {
MachOParser weakDefParser(proxy->mh(), _dyldCache.cacheIsMappedRaw());
for (auto& entry : weakSymbols ) {
if ( entry.second.imageOffset != 0 )
continue;
Diagnostics weakDiag;
MachOParser::FoundSymbol foundInfo;
if ( weakDefParser.findExportedSymbol(weakDiag, entry.first.c_str(), nullptr, foundInfo, nullptr) ) {
assert(proxy->indexInGroup() < (1 << 8));
if ( foundInfo.value >= (1ULL << 32) ) {
diag.warning("bad weak symbol address in %s", proxy->runtimePath().c_str());
return;
}
entry.second.imageIndex = proxy->indexInGroup();
entry.second.imageOffset = foundInfo.value;
}
}
}
}
for (ImageProxy* proxy : flatSearchOrder) {
if ( (proxy->mh()->flags & (MH_WEAK_DEFINES|MH_BINDS_TO_WEAK)) == 0 )
continue;
if ( proxy->groupNum() == 0 ) {
const launch_cache::Image imageInCache = dyldCacheGroup.image(proxy->indexInGroup());
MachOParser inCacheParser(proxy->mh(), _dyldCache.cacheIsMappedRaw());
Diagnostics cacheDiag;
for (auto& entry : weakSymbols) {
if ( entry.second.imageOffset == 0 )
continue;
Diagnostics weakDiag;
MachOParser::FoundSymbol foundInfo;
if ( inCacheParser.findExportedSymbol(weakDiag, entry.first.c_str(), nullptr, foundInfo, nullptr) ) {
uint32_t cacheOffsetOfSymbol = (uint32_t)(imageInCache.cacheOffset() + foundInfo.value);
uint32_t patchTableIndex;
if ( dyldCacheGroup.hasPatchTableIndex(cacheOffsetOfSymbol, patchTableIndex) ) {
DyldCacheOverride cacheOverride;
cacheOverride.patchTableIndex = patchTableIndex;
cacheOverride.imageIndex = entry.second.imageIndex;
cacheOverride.imageOffset = entry.second.imageOffset;
_cacheOverrides.push_back(cacheOverride);
}
}
}
}
}
}
}
for (uint32_t imageIndex=0; imageIndex < imageCount; ++imageIndex) {
groupWriter.setImageFixups(diag, imageIndex, fixupInfos[imageIndex].fixups, fixupInfos[imageIndex].hasTextRelocs);
}
}
if ( someBadFixups && continueIfErrors ) {
__block bool somethingInvalidated = false;
do {
somethingInvalidated = false;
for (uint32_t i=0; i < imageCount; ++i) {
if ( groupWriter.isInvalid(i) )
continue;
uint32_t depCount = groupWriter.imageDependentsCount(i);
for (uint32_t depIndex=0; depIndex < depCount; ++depIndex) {
launch_cache::binary_format::ImageRef ref = groupWriter.imageDependent(i, depIndex);
if ( ref.groupNum() == _groupNum ) {
if ( groupWriter.isInvalid(ref.indexInGroup()) ) {
groupWriter.setImageInvalid(i);
somethingInvalidated = true;
break;
}
}
}
}
} while (somethingInvalidated);
}
const bool log = false;
for (uint32_t imageIndex=0; imageIndex < imageCount; ++imageIndex) {
if ( groupWriter.isInvalid(imageIndex) )
continue;
auto inits = _images[imageIndex]->getInitBeforeList(*this);
if ( log && buildingDylibsInCache ) {
fprintf(stderr, "%s\n init list: ", _images[imageIndex]->runtimePath().c_str());
for (launch_cache::binary_format::ImageRef ref : inits) {
if ( ref.groupNum() == 0 ) {
std::string dep = _images[ref.indexInGroup()]->runtimePath();
size_t off = dep.rfind('/');
fprintf(stderr, "%s, ", dep.substr(off+1).c_str());
}
}
fprintf(stderr, "\n");
}
groupWriter.setImageInitBefore(imageIndex, inits);
}
for (uint32_t imageIndex=0; imageIndex < imageCount; ++imageIndex) {
if ( groupWriter.isInvalid(imageIndex) )
continue;
auto inits = _images[imageIndex]->getInitBeforeList(*this);
if ( log && buildingDylibsInCache ) {
fprintf(stderr, "%s\n DOFs: ", _images[imageIndex]->runtimePath().c_str());
for (launch_cache::binary_format::ImageRef ref : inits) {
if ( ref.groupNum() == 0 ) {
std::string dep = _images[ref.indexInGroup()]->runtimePath();
size_t off = dep.rfind('/');
fprintf(stderr, "%s, ", dep.substr(off+1).c_str());
}
}
fprintf(stderr, "\n");
}
groupWriter.setImageInitBefore(imageIndex, inits);
}
assert(buildingDylibsInCache == (_patchTable != nullptr));
if ( _patchTable != nullptr ) {
for (uint32_t i=0; i < imageCount; ++i) {
const auto pos = _patchTable->find(_images[i]->mh());
if ( pos != _patchTable->end() ) {
for (const auto& entry : pos->second ) {
uint32_t defFunctionOffset = entry.first;
groupWriter.setImagePatchLocations(i, defFunctionOffset, entry.second);
}
}
}
}
if ( !_cacheOverrides.empty() ) {
groupWriter.setGroupCacheOverrides(_cacheOverrides);
}
groupWriter.alignStringPool();
}
}