#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <stdarg.h>
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <mach-o/dyld_priv.h>
#include <vector>
#include <tuple>
#include <set>
#include <unordered_set>
#include <string>
#include "Array.h"
#include "MachOFile.h"
#include "MachOLoaded.h"
#include "MachOAnalyzer.h"
#include "MachOAnalyzerSet.h"
#include "ClosureFileSystemPhysical.h"
#include "DyldSharedCache.h"
typedef dyld3::MachOLoaded::ChainedFixupPointerOnDisk ChainedFixupPointerOnDisk;
static void versionToString(uint32_t value, char buffer[32])
{
if ( value == 0 )
strcpy(buffer, "n/a");
else if ( value & 0xFF )
sprintf(buffer, "%d.%d.%d", value >> 16, (value >> 8) & 0xFF, value & 0xFF);
else
sprintf(buffer, "%d.%d", value >> 16, (value >> 8) & 0xFF);
}
static void printPlatforms(const dyld3::MachOAnalyzer* ma)
{
printf(" -platform:\n");
printf(" platform minOS sdk\n");
ma->forEachSupportedPlatform(^(dyld3::Platform platform, uint32_t minOS, uint32_t sdk) {
char osVers[32];
char sdkVers[32];
versionToString(minOS, osVers);
versionToString(sdk, sdkVers);
printf(" %15s %-7s %-7s\n", dyld3::MachOFile::platformName(platform), osVers, sdkVers);
});
}
static void permString(uint32_t permFlags, char str[4])
{
str[0] = (permFlags & VM_PROT_READ) ? 'r' : '.';
str[1] = (permFlags & VM_PROT_WRITE) ? 'w' : '.';
str[2] = (permFlags & VM_PROT_EXECUTE) ? 'x' : '.';
str[3] = '\0';
}
static void printSegments(const dyld3::MachOAnalyzer* ma)
{
if ( ma->inDyldCache() ) {
printf(" -segments:\n");
printf(" load-address segment section sect-size seg-size perm\n");
__block const char* lastSegName = "";
ma->forEachSection(^(const dyld3::MachOFile::SectionInfo& sectInfo, bool malformedSectionRange, bool& stop) {
if ( strcmp(lastSegName, sectInfo.segInfo.segName) != 0 ) {
char permChars[8];
permString(sectInfo.segInfo.protections, permChars);
printf(" 0x%08llX %-16s %16lluKB %s\n", sectInfo.segInfo.vmAddr, sectInfo.segInfo.segName, sectInfo.segInfo.vmSize/1024, permChars);
lastSegName = sectInfo.segInfo.segName;
}
printf(" 0x%08llX %-16s %6llu\n", sectInfo.sectAddr, sectInfo.sectName, sectInfo.sectSize);
});
}
else {
printf(" -segments:\n");
printf(" load-offset segment section sect-size seg-size perm\n");
__block const char* lastSegName = "";
__block uint64_t firstSegVmAddr = 0;
ma->forEachSection(^(const dyld3::MachOFile::SectionInfo& sectInfo, bool malformedSectionRange, bool& stop) {
if ( lastSegName[0] == '\0' )
firstSegVmAddr = sectInfo.segInfo.vmAddr;
if ( strcmp(lastSegName, sectInfo.segInfo.segName) != 0 ) {
char permChars[8];
permString(sectInfo.segInfo.protections, permChars);
printf(" 0x%08llX %-16s %6lluKB %s\n", sectInfo.segInfo.vmAddr - firstSegVmAddr, sectInfo.segInfo.segName, sectInfo.segInfo.vmSize/1024, permChars);
lastSegName = sectInfo.segInfo.segName;
}
printf(" 0x%08llX %-16s %6llu\n", sectInfo.sectAddr-firstSegVmAddr, sectInfo.sectName, sectInfo.sectSize);
});
}
}
static void printDependents(const dyld3::MachOAnalyzer* ma)
{
printf(" -dependents:\n");
printf(" attributes load path\n");
ma->forEachDependentDylib(^(const char* loadPath, bool isWeak, bool isReExport, bool isUpward, uint32_t compatVersion, uint32_t curVersion, bool &stop) {
const char* attribute = "";
if ( isWeak )
attribute = "weak_import";
else if ( isReExport )
attribute = "re-export";
else if ( isUpward )
attribute = "upward";
printf(" %-12s %s\n", attribute, loadPath);
});
}
static bool liveMachO(const dyld3::MachOAnalyzer* ma, const DyldSharedCache* dyldCache, size_t cacheLen)
{
if ( dyldCache == nullptr )
return false;
const uint8_t* cacheStart = (uint8_t*)dyldCache;
const uint8_t* cacheEnd = &cacheStart[cacheLen];
if ( (uint8_t*)ma < cacheStart)
return false;
if ( (uint8_t*)ma > cacheEnd)
return false;
return ( dyld_image_header_containing_address(ma) != nullptr );
}
static void printInitializers(const dyld3::MachOAnalyzer* ma, const DyldSharedCache* dyldCache, size_t cacheLen)
{
printf(" -inits:\n");
Diagnostics diag;
const dyld3::MachOAnalyzer::VMAddrConverter vmAddrConverter = (ma->inDyldCache() ? dyldCache->makeVMAddrConverter(true) : ma->makeVMAddrConverter(false));
ma->forEachInitializer(diag, vmAddrConverter, ^(uint32_t offset) {
uint64_t targetLoadAddr = (uint64_t)ma+offset;
const char* symbolName;
uint64_t symbolLoadAddr;
if ( ma->findClosestSymbol(targetLoadAddr, &symbolName, &symbolLoadAddr) ) {
uint64_t delta = targetLoadAddr - symbolLoadAddr;
if ( delta == 0 )
printf(" 0x%08X %s\n", offset, symbolName);
else
printf(" 0x%08X %s + 0x%llX\n", offset, symbolName, delta);
}
else
printf(" 0x%08X\n", offset);
});
if ( ma->hasPlusLoadMethod(diag) ) {
if ( liveMachO(ma, dyldCache, cacheLen) ) {
printf(" <<<cannot print objc data on live dylib>>>\n");
return;
}
const uint32_t pointerSize = ma->pointerSize();
uint64_t prefLoadAddress = ma->preferredLoadAddress();
ma->forEachObjCClass(diag, vmAddrConverter, ^(Diagnostics& diag, uint64_t classVMAddr,
uint64_t classSuperclassVMAddr, uint64_t classDataVMAddr,
const dyld3::MachOAnalyzer::ObjCClassInfo& objcClass, bool isMetaClass) {
if (!isMetaClass)
return;
dyld3::MachOAnalyzer::PrintableStringResult classNameResult;
const char* className = ma->getPrintableString(objcClass.nameVMAddr(pointerSize), classNameResult);
if ( classNameResult == dyld3::MachOAnalyzer::PrintableStringResult::CanPrint ) {
ma->forEachObjCMethod(objcClass.baseMethodsVMAddr(pointerSize), vmAddrConverter, ^(uint64_t methodVMAddr, const dyld3::MachOAnalyzer::ObjCMethod& method) {
dyld3::MachOAnalyzer::PrintableStringResult methodNameResult;
const char* methodName = ma->getPrintableString(method.nameVMAddr, methodNameResult);
if ( methodNameResult == dyld3::MachOAnalyzer::PrintableStringResult::CanPrint ) {
if ( strcmp(methodName, "load") == 0 )
printf(" 0x%08llX +[%s %s]\n", methodVMAddr-prefLoadAddress, className, methodName);
}
});
}
});
ma->forEachObjCCategory(diag, vmAddrConverter, ^(Diagnostics& diag, uint64_t categoryVMAddr,
const dyld3::MachOAnalyzer::ObjCCategory& objcCategory) {
dyld3::MachOAnalyzer::PrintableStringResult categoryNameResult;
const char* categoryName = ma->getPrintableString(objcCategory.nameVMAddr, categoryNameResult);
if ( categoryNameResult == dyld3::MachOAnalyzer::PrintableStringResult::CanPrint ) {
ma->forEachObjCMethod(objcCategory.classMethodsVMAddr, vmAddrConverter, ^(uint64_t methodVMAddr, const dyld3::MachOAnalyzer::ObjCMethod& method) {
dyld3::MachOAnalyzer::PrintableStringResult methodNameResult;
const char* methodName = ma->getPrintableString(method.nameVMAddr, methodNameResult);
if ( methodNameResult == dyld3::MachOAnalyzer::PrintableStringResult::CanPrint ) {
if ( strcmp(methodName, "load") == 0 ) {
__block const char* catOnClassName = "";
ma->forEachObjCClass(diag, vmAddrConverter, ^(Diagnostics& diag, uint64_t classVMAddr,
uint64_t classSuperclassVMAddr, uint64_t classDataVMAddr,
const dyld3::MachOAnalyzer::ObjCClassInfo& objcClass, bool isMetaClass) {
if ( objcCategory.clsVMAddr == classVMAddr ) {
dyld3::MachOAnalyzer::PrintableStringResult classNameResult;
const char* className = ma->getPrintableString(objcClass.nameVMAddr(pointerSize), classNameResult);
if ( classNameResult == dyld3::MachOAnalyzer::PrintableStringResult::CanPrint ) {
catOnClassName = className;
}
}
});
printf(" 0x%08llX +[%s(%s) %s]\n", methodVMAddr-prefLoadAddress, catOnClassName, categoryName, methodName);
}
}
});
}
});
}
}
static const char* pointerFormat(uint16_t format)
{
switch (format) {
case DYLD_CHAINED_PTR_ARM64E:
return "authenticated arm64e, 8-byte stride, target vmadddr";
case DYLD_CHAINED_PTR_ARM64E_USERLAND:
return "authenticated arm64e, 8-byte stride, target vmoffset";
case DYLD_CHAINED_PTR_ARM64E_FIRMWARE:
return "authenticated arm64e, 4-byte stride, target vmadddr";
case DYLD_CHAINED_PTR_ARM64E_KERNEL:
return "authenticated arm64e, 4-byte stride, target vmoffset";
case DYLD_CHAINED_PTR_64:
return "generic 64-bit, 4-byte stride, target vmadddr";
case DYLD_CHAINED_PTR_64_OFFSET:
return "generic 64-bit, 4-byte stride, target vmoffset ";
case DYLD_CHAINED_PTR_32:
return "generic 32-bit";
case DYLD_CHAINED_PTR_32_CACHE:
return "32-bit for dyld cache";
case DYLD_CHAINED_PTR_64_KERNEL_CACHE:
return "64-bit for kernel cache";
case DYLD_CHAINED_PTR_X86_64_KERNEL_CACHE:
return "64-bit for x86_64 kernel cache";
case DYLD_CHAINED_PTR_ARM64E_USERLAND24:
return "authenticated arm64e, 8-byte stride, target vmoffset, 24-bit bind ordinals";
}
return "unknown";
}
static void printChains(const dyld3::MachOAnalyzer* ma)
{
Diagnostics diag;
ma->withChainStarts(diag, 0, ^(const dyld_chained_starts_in_image* starts) {
for (int i=0; i < starts->seg_count; ++i) {
if ( starts->seg_info_offset[i] == 0 )
continue;
const dyld_chained_starts_in_segment* seg = (dyld_chained_starts_in_segment*)((uint8_t*)starts + starts->seg_info_offset[i]);
if ( seg->page_count == 0 )
continue;
printf("seg[%d]:\n", i);
printf(" page_size: 0x%04X\n", seg->page_size);
printf(" pointer_format: %d (%s)\n", seg->pointer_format, pointerFormat(seg->pointer_format));
printf(" segment_offset: 0x%08llX\n", seg->segment_offset);
printf(" max_pointer: 0x%08X\n", seg->max_valid_pointer);
printf(" pages: %d\n", seg->page_count);
for (int pageIndex=0; pageIndex < seg->page_count; ++pageIndex) {
uint16_t offsetInPage = seg->page_start[pageIndex];
if ( offsetInPage == DYLD_CHAINED_PTR_START_NONE )
continue;
if ( offsetInPage & DYLD_CHAINED_PTR_START_MULTI ) {
uint32_t overflowIndex = offsetInPage & ~DYLD_CHAINED_PTR_START_MULTI;
bool chainEnd = false;
while (!chainEnd) {
chainEnd = (seg->page_start[overflowIndex] & DYLD_CHAINED_PTR_START_LAST);
offsetInPage = (seg->page_start[overflowIndex] & ~DYLD_CHAINED_PTR_START_LAST);
printf(" start[% 2d]: 0x%04X\n", pageIndex, offsetInPage);
++overflowIndex;
}
}
else {
printf(" start[% 2d]: 0x%04X\n", pageIndex, offsetInPage);
}
}
}
});
}
static void printChainDetails(const dyld3::MachOAnalyzer* ma)
{
__block Diagnostics diag;
ma->withChainStarts(diag, 0, ^(const dyld_chained_starts_in_image* starts) {
ma->forEachFixupInAllChains(diag, starts, true, ^(ChainedFixupPointerOnDisk* fixupLoc, const dyld_chained_starts_in_segment* segInfo, bool& stop) {
uint64_t vmOffset = (uint8_t*)fixupLoc - (uint8_t*)ma;
switch (segInfo->pointer_format) {
case DYLD_CHAINED_PTR_ARM64E:
case DYLD_CHAINED_PTR_ARM64E_KERNEL:
case DYLD_CHAINED_PTR_ARM64E_USERLAND:
case DYLD_CHAINED_PTR_ARM64E_FIRMWARE:
case DYLD_CHAINED_PTR_ARM64E_USERLAND24:
if ( fixupLoc->arm64e.authRebase.auth ) {
uint32_t bindOrdinal = (segInfo->pointer_format == DYLD_CHAINED_PTR_ARM64E_USERLAND24) ? fixupLoc->arm64e.authBind24.ordinal : fixupLoc->arm64e.authBind.ordinal;
if ( fixupLoc->arm64e.authBind.bind ) {
printf(" 0x%08llX: raw: 0x%016llX auth-bind: (next: %03d, key: %s, addrDiv: %d, diversity: 0x%04X, ordinal: %04X)\n", vmOffset, fixupLoc->raw64,
fixupLoc->arm64e.authBind.next, fixupLoc->arm64e.keyName(),
fixupLoc->arm64e.authBind.addrDiv, fixupLoc->arm64e.authBind.diversity, bindOrdinal);
}
else {
printf(" 0x%08llX: raw: 0x%016llX auth-rebase: (next: %03d, key: %s, addrDiv: %d, diversity: 0x%04X, target: 0x%08X)\n", vmOffset, fixupLoc->raw64,
fixupLoc->arm64e.authRebase.next, fixupLoc->arm64e.keyName(),
fixupLoc->arm64e.authBind.addrDiv, fixupLoc->arm64e.authBind.diversity, fixupLoc->arm64e.authRebase.target);
}
}
else {
uint32_t bindOrdinal = (segInfo->pointer_format == DYLD_CHAINED_PTR_ARM64E_USERLAND24) ? fixupLoc->arm64e.bind24.ordinal : fixupLoc->arm64e.bind.ordinal;
if ( fixupLoc->arm64e.rebase.bind ) {
printf(" 0x%08llX: raw: 0x%016llX bind: (next: %03d, ordinal: %04X, addend: %d)\n", vmOffset, fixupLoc->raw64,
fixupLoc->arm64e.bind.next, bindOrdinal, fixupLoc->arm64e.bind.addend);
}
else {
printf(" 0x%08llX: raw: 0x%016llX rebase: (next: %03d, target: 0x%011llX, high8: 0x%02X)\n", vmOffset, fixupLoc->raw64,
fixupLoc->arm64e.rebase.next, fixupLoc->arm64e.rebase.target, fixupLoc->arm64e.rebase.high8);
}
}
break;
case DYLD_CHAINED_PTR_64:
case DYLD_CHAINED_PTR_64_OFFSET:
if ( fixupLoc->generic64.rebase.bind ) {
printf(" 0x%08llX: raw: 0x%016llX bind: (next: %03d, ordinal: %06X, addend: %d)\n", vmOffset, fixupLoc->raw64,
fixupLoc->generic64.bind.next, fixupLoc->generic64.bind.ordinal, fixupLoc->generic64.bind.addend);
}
else {
printf(" 0x%08llX: raw: 0x%016llX rebase: (next: %03d, target: 0x%011llX, high8: 0x%02X)\n", vmOffset, fixupLoc->raw64,
fixupLoc->generic64.rebase.next, fixupLoc->generic64.rebase.target, fixupLoc->generic64.rebase.high8);
}
break;
case DYLD_CHAINED_PTR_32:
if ( fixupLoc->generic32.bind.bind ) {
printf(" 0x%08llX: raw: 0x%08X bind: (next:%02d ordinal:%05X addend:%d)\n", vmOffset, fixupLoc->raw32,
fixupLoc->generic32.bind.next, fixupLoc->generic32.bind.ordinal, fixupLoc->generic32.bind.addend);
}
else if ( fixupLoc->generic32.rebase.target > segInfo->max_valid_pointer ) {
uint32_t bias = (0x04000000 + segInfo->max_valid_pointer)/2;
uint32_t value = fixupLoc->generic32.rebase.target - bias;
printf(" 0x%08llX: raw: 0x%08X nonptr: (next:%02d value: 0x%08X)\n", vmOffset, fixupLoc->raw32,
fixupLoc->generic32.rebase.next, value);
}
else {
printf(" 0x%08llX: raw: 0x%08X rebase: (next:%02d target: 0x%07X)\n", vmOffset, fixupLoc->raw32,
fixupLoc->generic32.rebase.next, fixupLoc->generic32.rebase.target);
}
break;
default:
fprintf(stderr, "unknown pointer type %d\n", segInfo->pointer_format);
break;
}
});
});
if ( diag.hasError() )
fprintf(stderr, "dyldinfo: %s\n", diag.errorMessage());
}
struct FixupInfo
{
std::string segName;
std::string sectName;
uint64_t address;
dyld3::MachOAnalyzerSet::PointerMetaData pmd;
const char* type;
uint64_t targetValue;
const char* targetDylib;
const char* targetSymbolName;
uint64_t targetAddend;
bool targetWeakImport;
};
struct SymbolicFixupInfo
{
uint64_t address;
const char* kind;
std::string target;
};
static const char* ordinalName(const dyld3::MachOAnalyzer* ma, int libraryOrdinal)
{
static int sLastOrdinal = -100;
static const char* sLastString = nullptr;
if ( libraryOrdinal > 0 ) {
if ( libraryOrdinal != sLastOrdinal ) {
const char* path = ma->dependentDylibLoadPath(libraryOrdinal-1);
if ( path == nullptr )
return "ordinal-too-large";
const char* leafName = path;
if ( const char* lastSlash = strrchr(path, '/') )
leafName = lastSlash+1;
sLastOrdinal = libraryOrdinal;
sLastString = leafName;
}
return sLastString;
}
else {
switch ( libraryOrdinal) {
case BIND_SPECIAL_DYLIB_SELF:
return "this-image";
case BIND_SPECIAL_DYLIB_MAIN_EXECUTABLE:
return "main-executable";
case BIND_SPECIAL_DYLIB_FLAT_LOOKUP:
return "flat-namespace";
case BIND_SPECIAL_DYLIB_WEAK_LOOKUP:
return "weak-coalesce";
}
}
return "unknown-ordinal";
}
class SectionFinder
{
public:
SectionFinder(const dyld3::MachOAnalyzer* ma);
const char* segmentName(uint64_t vmOffset) const;
const char* sectionName(uint64_t vmOffset) const;
uint64_t baseAddress() const { return _baseAddress; }
uint64_t currentSectionAddress() const { return _lastSection.sectAddr; }
bool isNewSection(uint64_t vmOffset) const;
private:
void updateLastSection(uint64_t vmOffset) const;
const dyld3::MachOAnalyzer* _ma;
uint64_t _baseAddress;
mutable dyld3::MachOFile::SectionInfo _lastSection;
mutable char _lastSegName[20];
mutable char _lastSectName[20];
};
SectionFinder::SectionFinder(const dyld3::MachOAnalyzer* ma)
: _ma(ma)
{
_baseAddress = ma->preferredLoadAddress();
_lastSection.sectAddr = 0;
_lastSection.sectSize = 0;
}
bool SectionFinder::isNewSection(uint64_t vmOffset) const
{
uint64_t vmAddr = _baseAddress + vmOffset;
return ( (vmAddr < _lastSection.sectAddr) || (vmAddr >= _lastSection.sectAddr+_lastSection.sectSize) );
}
void SectionFinder::updateLastSection(uint64_t vmOffset) const
{
if ( isNewSection(vmOffset) ) {
_lastSegName[0] = '\0';
_lastSectName[0] = '\0';
uint64_t vmAddr = _baseAddress + vmOffset;
_ma->forEachSection(^(const dyld3::MachOFile::SectionInfo& sectInfo, bool malformedSectionRange, bool& sectStop) {
if ( (sectInfo.sectAddr <= vmAddr) && (vmAddr < sectInfo.sectAddr+sectInfo.sectSize) ) {
_lastSection = sectInfo;
strcpy(_lastSegName, _lastSection.segInfo.segName);
strcpy(_lastSectName, _lastSection.sectName);
sectStop = true;
}
});
}
}
const char* SectionFinder::segmentName(uint64_t vmOffset) const
{
updateLastSection(vmOffset);
return _lastSegName;
}
const char* SectionFinder::sectionName(uint64_t vmOffset) const
{
updateLastSection(vmOffset);
return _lastSectName;
}
static inline std::string decimal(int64_t value) {
char buff[64];
sprintf(buff, "%lld", value);
return buff;
}
static inline std::string hex(int64_t value) {
char buff[64];
sprintf(buff, "0x%llX", value);
return buff;
}
static std::string rebaseTargetString(const dyld3::MachOAnalyzer* ma, uint64_t vmAddr)
{
uint64_t targetLoadAddr = (uint64_t)ma+vmAddr;
const char* targetSymbolName;
uint64_t targetSymbolLoadAddr;
if ( ma->findClosestSymbol(targetLoadAddr, &targetSymbolName, &targetSymbolLoadAddr) ) {
uint64_t delta = targetLoadAddr - targetSymbolLoadAddr;
if ( delta == 0 ) {
return targetSymbolName;
}
else {
if ( (delta == 1) && (ma->cputype == CPU_TYPE_ARM) )
return std::string(targetSymbolName) + std::string(" [thumb]");
else
return std::string(targetSymbolName) + std::string("+") + decimal(delta);
}
}
else {
__block std::string result;
ma->forEachSection(^(const dyld3::MachOAnalyzer::SectionInfo& sectInfo, bool malformedSectionRange, bool& stop) {
if ( (sectInfo.sectAddr <= vmAddr) && (vmAddr < sectInfo.sectAddr+sectInfo.sectSize) ) {
if ( (sectInfo.sectFlags & SECTION_TYPE) == S_CSTRING_LITERALS ) {
const char* cstring = (char*)ma + (vmAddr-ma->preferredLoadAddress());
result = std::string("\"") + cstring + std::string("\"");
}
else {
result = std::string(sectInfo.segInfo.segName) + "/" + sectInfo.sectName + "+" + decimal(vmAddr - sectInfo.sectAddr);
}
}
});
return result;
}
}
typedef dyld3::MachOAnalyzerSet MachOAnalyzerSet;
typedef dyld3::MachOAnalyzerSet::WrappedMachO WrappedMachO;
struct InfoAnalyzerSet : public MachOAnalyzerSet
{
void mas_forEachImage(void (^handler)(const WrappedMachO& anImage, bool hidden, bool& stop)) const override;
void mas_mainExecutable(WrappedMachO& anImage) const override;
void* mas_dyldCache() const override;
bool wmo_dependent(const WrappedMachO* image, uint32_t depIndex, WrappedMachO& childObj, bool& missingWeakDylib) const override;
const char* wmo_path(const WrappedMachO* image) const override;
ExportsTrie wmo_getExportsTrie(const WrappedMachO* image) const override;
bool wmo_findSymbolFrom(const WrappedMachO* fromWmo, Diagnostics& diag, int libOrdinal, const char* symbolName, bool weakImport,
bool lazyBind, uint64_t addend, CachePatchHandler ph, FixupTarget& target) const override;
bool wmo_missingSymbolResolver(const WrappedMachO* fromWmo, bool weakImport, bool lazyBind, const char* symbolName, const char* expectedInDylibPath, const char* clientPath, FixupTarget& target) const override;
};
bool InfoAnalyzerSet::wmo_dependent(const WrappedMachO* image, uint32_t depIndex, WrappedMachO& childObj, bool& missingWeakDylib) const
{
const char* depPath = image->_mh->dependentDylibLoadPath(depIndex);
childObj = WrappedMachO(nullptr, image->_set, (void*)depPath);
return true;
}
const char* InfoAnalyzerSet::wmo_path(const WrappedMachO* image) const
{
return (const char*)image->_other;
}
MachOAnalyzerSet::ExportsTrie InfoAnalyzerSet::wmo_getExportsTrie(const WrappedMachO* obj) const
{
return { nullptr, nullptr };
}
bool InfoAnalyzerSet::wmo_findSymbolFrom(const WrappedMachO* obj, Diagnostics& diag, int libOrdinal, const char* symbolName, bool weakImport,
bool lazyBind, uint64_t addend, CachePatchHandler ph, MachOAnalyzerSet::FixupTarget& target) const
{
target.requestedSymbolName = symbolName;
target.foundSymbolName = symbolName;
target.addend = addend;
target.kind = weakImport ? MachOAnalyzerSet::FixupTarget::Kind::bindMissingSymbol : MachOAnalyzerSet::FixupTarget::Kind::bindToImage;
if ( libOrdinal == BIND_SPECIAL_DYLIB_SELF ) {
target.foundInImage = WrappedMachO(nullptr, obj->_set, (void*)"self");
}
else if ( libOrdinal == BIND_SPECIAL_DYLIB_MAIN_EXECUTABLE ) {
target.foundInImage = WrappedMachO(nullptr, obj->_set, (void*)"main-executable");
}
else if ( libOrdinal == BIND_SPECIAL_DYLIB_FLAT_LOOKUP ) {
target.foundInImage = WrappedMachO(nullptr, obj->_set, (void*)"flat-namespace");
}
else if ( libOrdinal == BIND_SPECIAL_DYLIB_WEAK_LOOKUP ) {
target.foundInImage = WrappedMachO(nullptr, obj->_set, (void*)"weak-coalesce");
}
else {
int depIndex = libOrdinal - 1;
const char* depPath = obj->_mh->dependentDylibLoadPath(depIndex);
const char* lastSlash = strrchr(depPath, '/');
if (lastSlash)
++lastSlash;
else
lastSlash = depPath;
target.foundInImage = WrappedMachO(nullptr, obj->_set, (void*)lastSlash);
}
return true;
}
void InfoAnalyzerSet::mas_forEachImage(void (^handler)(const WrappedMachO& anImage, bool hidden, bool& stop)) const
{
WrappedMachO anImage(nullptr, this, (void*)"flat-namespace");
bool stop = false;
handler(anImage, false, stop);
}
bool InfoAnalyzerSet::wmo_missingSymbolResolver(const WrappedMachO* fromWmo, bool weakImport, bool lazyBind, const char* symbolName, const char* expectedInDylibPath, const char* clientPath, FixupTarget& target) const
{
return false;
}
void InfoAnalyzerSet::mas_mainExecutable(WrappedMachO& anImage) const
{
anImage = WrappedMachO(nullptr, this, (void*)"main-executable");
}
void* InfoAnalyzerSet::mas_dyldCache() const
{
return nullptr;
}
static void printFixups(const dyld3::MachOAnalyzer* ma, const char* path)
{
printf(" -fixups:\n");
Diagnostics diag;
__block std::vector<FixupInfo> fixups;
uint64_t prefLoadAddr = ma->preferredLoadAddress();
SectionFinder namer(ma);
InfoAnalyzerSet ias;
WrappedMachO wm(ma, &ias, (void*)path);
wm.forEachFixup(diag, ^(uint64_t fixupLocRuntimeOffset, MachOAnalyzerSet::PointerMetaData pmd, const MachOAnalyzerSet::FixupTarget& target, bool& stop) {
FixupInfo fixup;
fixup.segName = namer.segmentName(fixupLocRuntimeOffset);
fixup.sectName = namer.sectionName(fixupLocRuntimeOffset);
fixup.address = prefLoadAddr + fixupLocRuntimeOffset;
fixup.pmd = pmd;
fixup.targetWeakImport = false;
switch ( target.kind ) {
case MachOAnalyzerSet::FixupTarget::Kind::bindAbsolute:
fixup.type = "absolute";
break;
case MachOAnalyzerSet::FixupTarget::Kind::rebase:
fixup.type = "rebase";
fixup.targetSymbolName = nullptr;
fixup.targetDylib = nullptr;
break;
case MachOAnalyzerSet::FixupTarget::Kind::bindToImage:
fixup.type = "bind";
fixup.targetSymbolName = target.requestedSymbolName;
fixup.targetDylib = target.foundInImage.path();
break;
case MachOAnalyzerSet::FixupTarget::Kind::bindMissingSymbol:
fixup.type = "bind";
fixup.targetSymbolName = target.requestedSymbolName;
fixup.targetDylib = target.foundInImage.path();
fixup.targetWeakImport = true;
break;
}
fixup.targetValue = target.offsetInImage;
fixup.targetAddend = target.addend;
if ( pmd.high8 )
fixup.targetAddend += ((uint64_t)pmd.high8 << 56);
fixups.push_back(fixup);
},
^(uint32_t, uint32_t, const MachOAnalyzerSet::FixupTarget&) {
});
std::sort(fixups.begin(), fixups.end(), [](const FixupInfo& l, const FixupInfo& r) {
if ( &l == &r )
return false;
if ( l.address == r.address )
return (l.targetSymbolName == nullptr);
return ( l.address < r.address );
});
printf(" segment section address type target\n");
for (const FixupInfo& fixup : fixups) {
char authInfo[128];
authInfo[0] = '\0';
if ( fixup.pmd.authenticated ) {
sprintf(authInfo, " (div=0x%04X ad=%d key=%s)", fixup.pmd.diversity, fixup.pmd.usesAddrDiversity, ChainedFixupPointerOnDisk::Arm64e::keyName(fixup.pmd.key));
}
if ( fixup.targetSymbolName == nullptr )
printf(" %-12s %-16s 0x%08llX %16s 0x%08llX%s\n", fixup.segName.c_str(), fixup.sectName.c_str(), fixup.address, fixup.type, fixup.targetValue, authInfo);
else if ( fixup.targetAddend != 0 )
printf(" %-12s %-16s 0x%08llX %16s %s/%s + 0x%llX%s\n", fixup.segName.c_str(), fixup.sectName.c_str(), fixup.address, fixup.type, fixup.targetDylib, fixup.targetSymbolName, fixup.targetAddend, authInfo);
else if ( fixup.targetWeakImport )
printf(" %-12s %-16s 0x%08llX %16s %s/%s [weak-import]%s\n", fixup.segName.c_str(), fixup.sectName.c_str(), fixup.address, fixup.type, fixup.targetDylib, fixup.targetSymbolName, authInfo);
else
printf(" %-12s %-16s 0x%08llX %16s %s/%s%s\n", fixup.segName.c_str(), fixup.sectName.c_str(), fixup.address, fixup.type, fixup.targetDylib, fixup.targetSymbolName, authInfo);
}
}
static void printSymbolicFixups(const dyld3::MachOAnalyzer* ma, const char* path)
{
printf(" -symbolic_fixups:\n");
Diagnostics diag;
__block std::vector<SymbolicFixupInfo> fixups;
uint64_t prefLoadAddr = ma->preferredLoadAddress();
InfoAnalyzerSet ias;
WrappedMachO wm(ma, &ias, (void*)path);
wm.forEachFixup(diag, ^(uint64_t fixupLocRuntimeOffset, MachOAnalyzerSet::PointerMetaData pmd, const MachOAnalyzerSet::FixupTarget& target, bool& stop) {
SymbolicFixupInfo fixup;
fixup.address = prefLoadAddr + fixupLocRuntimeOffset;
switch ( target.kind ) {
case MachOAnalyzerSet::FixupTarget::Kind::bindAbsolute:
fixup.kind = "absolute";
fixup.target = "";
break;
case MachOAnalyzerSet::FixupTarget::Kind::rebase:
fixup.kind = "rebase pointer";
fixup.target = rebaseTargetString(ma, target.offsetInImage);
break;
case MachOAnalyzerSet::FixupTarget::Kind::bindToImage:
case MachOAnalyzerSet::FixupTarget::Kind::bindMissingSymbol:
fixup.kind = "bind pointer";
fixup.target = std::string(target.foundInImage.path()) + "/" + target.requestedSymbolName;
if ( target.addend != 0 )
fixup.target += std::string("+") + decimal(target.addend);
if ( pmd.high8 )
fixup.target += std::string("+") + hex(((uint64_t)pmd.high8 << 56));
break;
}
if ( pmd.authenticated ) {
char authInfo[256];
sprintf(authInfo, " (div=0x%04X ad=%d key=%s)", pmd.diversity, pmd.usesAddrDiversity, ChainedFixupPointerOnDisk::Arm64e::keyName(pmd.key));
fixup.target += authInfo;
}
fixups.push_back(fixup);
},
^(uint32_t, uint32_t, const MachOAnalyzerSet::FixupTarget&) {
});
std::sort(fixups.begin(), fixups.end(), [](const SymbolicFixupInfo& l, const SymbolicFixupInfo& r) {
if ( &l == &r )
return false;
return ( l.address < r.address );
});
SectionFinder sectionTracker(ma);
uint64_t lastSymbolVmOffset = 0;
for (const SymbolicFixupInfo& fixup : fixups) {
uint64_t vmAddr = fixup.address;
uint64_t vmOffset = vmAddr - prefLoadAddr;
if ( sectionTracker.isNewSection(vmOffset) ) {
printf(" 0x%08llX %-12s %-16s \n", vmAddr, sectionTracker.segmentName(vmOffset), sectionTracker.sectionName(vmOffset));
}
const char* symbolName;
uint64_t symbolLoadAddr = 0;
if ( ma->findClosestSymbol((uint64_t)ma+vmOffset, &symbolName, &symbolLoadAddr) ) {
uint64_t symbolVmOffset = symbolLoadAddr - (uint64_t)ma;
if ( symbolVmOffset != lastSymbolVmOffset ) {
printf(" %s:\n", symbolName);
lastSymbolVmOffset = symbolVmOffset;
}
}
printf(" +0x%04llX %16s %s\n", vmOffset - lastSymbolVmOffset, fixup.kind, fixup.target.c_str());
}
}
static void printExports(const dyld3::MachOAnalyzer* ma)
{
printf(" -exports:\n");
printf(" offset symbol\n");
Diagnostics diag;
ma->forEachExportedSymbol(diag, ^(const char* symbolName, uint64_t imageOffset, uint64_t flags, uint64_t other, const char* importName, bool& stop) {
const bool reExport = (flags & EXPORT_SYMBOL_FLAGS_REEXPORT);
const bool weakDef = (flags & EXPORT_SYMBOL_FLAGS_WEAK_DEFINITION);
const bool resolver = (flags & EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER);
const bool threadLocal = ((flags & EXPORT_SYMBOL_FLAGS_KIND_MASK) == EXPORT_SYMBOL_FLAGS_KIND_THREAD_LOCAL);
const bool abs = ((flags & EXPORT_SYMBOL_FLAGS_KIND_MASK) == EXPORT_SYMBOL_FLAGS_KIND_ABSOLUTE);
if ( reExport )
printf(" [re-export] ");
else
printf(" 0x%08llX ", imageOffset);
printf("%s", symbolName);
if ( weakDef || threadLocal || resolver || abs ) {
bool needComma = false;
printf(" [");
if ( weakDef ) {
printf("weak_def");
needComma = true;
}
if ( threadLocal ) {
if ( needComma )
printf(", ");
printf("per-thread");
needComma = true;
}
if ( abs ) {
if ( needComma )
printf(", ");
printf("absolute");
needComma = true;
}
if ( resolver ) {
if ( needComma )
printf(", ");
printf("resolver=0x%08llX", other);
needComma = true;
}
printf("]");
}
if ( reExport ) {
if ( importName[0] == '\0' )
printf(" (from %s)", ordinalName(ma, (int)other));
else
printf(" (%s from %s)", importName, ordinalName(ma, (int)other));
}
printf("\n");
});
}
static void printObjC(const dyld3::MachOAnalyzer* ma, const DyldSharedCache* dyldCache, size_t cacheLen)
{
Diagnostics diag;
const uint32_t pointerSize = ma->pointerSize();
const dyld3::MachOAnalyzer::VMAddrConverter vmAddrConverter = (ma->inDyldCache() ? dyldCache->makeVMAddrConverter(true) : ma->makeVMAddrConverter(false));
auto printMethod = ^(uint64_t methodVMAddr, const dyld3::MachOAnalyzer::ObjCMethod& method) {
const char* type = "method";
dyld3::MachOAnalyzer::PrintableStringResult methodNameResult;
const char* methodName = ma->getPrintableString(method.nameVMAddr, methodNameResult);
switch (methodNameResult) {
case dyld3::MachOAnalyzer::PrintableStringResult::CanPrint:
break;
case dyld3::MachOAnalyzer::PrintableStringResult::FairPlayEncrypted:
methodName = "### fairplay encrypted";
break;
case dyld3::MachOAnalyzer::PrintableStringResult::ProtectedSection:
methodName = "### protected section";
break;
case dyld3::MachOAnalyzer::PrintableStringResult::UnknownSection:
methodName = "### unknown section";
break;
}
printf(" %10s 0x%08llX %s\n",
type, methodVMAddr, methodName);
};
printf(" -objc:\n");
if ( liveMachO(ma, dyldCache, cacheLen) ) {
printf(" <<<cannot print objc data on live dylib>>>\n");
return;
}
printf(" type vmaddr data-vmaddr name\n");
auto printClass = ^(Diagnostics& diag, uint64_t classVMAddr,
uint64_t classSuperclassVMAddr, uint64_t classDataVMAddr,
const dyld3::MachOAnalyzer::ObjCClassInfo& objcClass, bool isMetaClass) {
const char* type = "class";
if (isMetaClass)
type = "meta-class";
dyld3::MachOAnalyzer::PrintableStringResult classNameResult;
const char* className = ma->getPrintableString(objcClass.nameVMAddr(pointerSize), classNameResult);
switch (classNameResult) {
case dyld3::MachOAnalyzer::PrintableStringResult::CanPrint:
break;
case dyld3::MachOAnalyzer::PrintableStringResult::FairPlayEncrypted:
className = "### fairplay encrypted";
break;
case dyld3::MachOAnalyzer::PrintableStringResult::ProtectedSection:
className = "### protected section";
break;
case dyld3::MachOAnalyzer::PrintableStringResult::UnknownSection:
className = "### unknown section";
break;
}
printf(" %10s 0x%08llX 0x%08llX %s\n",
type, classVMAddr, objcClass.dataVMAddr, className);
ma->forEachObjCMethod(objcClass.baseMethodsVMAddr(pointerSize), vmAddrConverter, printMethod);
};
auto printCategory = ^(Diagnostics& diag, uint64_t categoryVMAddr,
const dyld3::MachOAnalyzer::ObjCCategory& objcCategory) {
const char* type = "category";
dyld3::MachOAnalyzer::PrintableStringResult categoryNameResult;
const char* categoryName = ma->getPrintableString(objcCategory.nameVMAddr, categoryNameResult);
switch (categoryNameResult) {
case dyld3::MachOAnalyzer::PrintableStringResult::CanPrint:
break;
case dyld3::MachOAnalyzer::PrintableStringResult::FairPlayEncrypted:
categoryName = "### fairplay encrypted";
break;
case dyld3::MachOAnalyzer::PrintableStringResult::ProtectedSection:
categoryName = "### protected section";
break;
case dyld3::MachOAnalyzer::PrintableStringResult::UnknownSection:
categoryName = "### unknown section";
break;
}
printf(" %10s 0x%08llX %s\n",
type, categoryVMAddr, categoryName);
ma->forEachObjCMethod(objcCategory.instanceMethodsVMAddr, vmAddrConverter,
printMethod);
ma->forEachObjCMethod(objcCategory.classMethodsVMAddr, vmAddrConverter,
printMethod);
};
auto printProtocol = ^(Diagnostics& diag, uint64_t protocolVMAddr,
const dyld3::MachOAnalyzer::ObjCProtocol& objCProtocol) {
const char* type = "protocol";
dyld3::MachOAnalyzer::PrintableStringResult protocolNameResult;
const char* protocolName = ma->getPrintableString(objCProtocol.nameVMAddr, protocolNameResult);
switch (protocolNameResult) {
case dyld3::MachOAnalyzer::PrintableStringResult::CanPrint:
break;
case dyld3::MachOAnalyzer::PrintableStringResult::FairPlayEncrypted:
protocolName = "### fairplay encrypted";
break;
case dyld3::MachOAnalyzer::PrintableStringResult::ProtectedSection:
protocolName = "### protected section";
break;
case dyld3::MachOAnalyzer::PrintableStringResult::UnknownSection:
protocolName = "### unknown section";
break;
}
printf(" %10s 0x%08llX %s\n",
type, protocolVMAddr, protocolName);
ma->forEachObjCMethod(objCProtocol.instanceMethodsVMAddr, vmAddrConverter,
printMethod);
ma->forEachObjCMethod(objCProtocol.classMethodsVMAddr, vmAddrConverter,
printMethod);
ma->forEachObjCMethod(objCProtocol.optionalInstanceMethodsVMAddr, vmAddrConverter,
printMethod);
ma->forEachObjCMethod(objCProtocol.optionalClassMethodsVMAddr, vmAddrConverter,
printMethod);
};
ma->forEachObjCClass(diag, vmAddrConverter, printClass);
ma->forEachObjCCategory(diag, vmAddrConverter, printCategory);
ma->forEachObjCProtocol(diag, vmAddrConverter, printProtocol);
}
static void usage()
{
fprintf(stderr, "Usage: dyldinfo [-arch <arch>]* <options>* <mach-o file>+\n"
"\t-segments print segments\n"
"\t-dependents print dependent dylibs\n"
"\t-fixups print locations dyld will rebase/bind\n"
"\t-exports print addresses of all symbols this file exports\n"
"\t-objc print objc classes, categories, etc\n"
);
}
static bool inStringVector(const std::vector<const char*>& vect, const char* target)
{
for (const char* str : vect) {
if ( strcmp(str, target) == 0 )
return true;
}
return false;
}
int main(int argc, const char* argv[])
{
if ( argc == 1 ) {
usage();
return 0;
}
std::vector<const char*> files;
std::vector<const char*> cmdLineArchs;
for (int i=1; i < argc; ++i) {
const char* arg = argv[i];
if ( arg[0] == '-' ) {
if ( strcmp(arg, "-arch") == 0 ) {
if ( ++i < argc ) {
cmdLineArchs.push_back(argv[i]);
}
else {
fprintf(stderr, "-arch missing architecture name");
return 1;
}
}
}
else {
files.push_back(arg);
}
}
if ( files.size() == 0 ) {
usage();
return 0;
}
size_t cacheLen;
const DyldSharedCache* cache = (DyldSharedCache*)_dyld_get_shared_cache_range(&cacheLen);
for (const char* path : files) {
Diagnostics diag;
bool fromSharedCache = false;
dyld3::closure::FileSystemPhysical fileSystem;
dyld3::closure::LoadedFileInfo info;
__block std::vector<const char*> archesForFile;
char realerPath[MAXPATHLEN];
__block bool printedError = false;
if (!fileSystem.loadFile(path, info, realerPath, ^(const char* format, ...) {
fprintf(stderr, "dyldinfo: ");
va_list list;
va_start(list, format);
vfprintf(stderr, format, list);
va_end(list);
printedError = true;
})) {
if ( printedError )
return 1;
info.fileContent = nullptr;
if ( cache != nullptr ) {
uint32_t imageIndex;
if ( cache->hasImagePath(path, imageIndex) ) {
uint64_t mTime;
uint64_t inode;
const mach_header* mh = cache->getIndexedImageEntry(imageIndex, mTime, inode);
info.fileContent = mh;
info.path = path;
fromSharedCache = true;
archesForFile.push_back(cache->archName());
}
}
if ( !fromSharedCache ) {
fprintf(stderr, "dyldinfo: %s: file not found\n", path);
return 1;
}
}
__block dyld3::Platform platform = dyld3::Platform::unknown;
if ( dyld3::FatFile::isFatFile(info.fileContent) ) {
const dyld3::FatFile* ff = (dyld3::FatFile*)info.fileContent;
ff->forEachSlice(diag, info.fileContentLen, ^(uint32_t sliceCpuType, uint32_t sliceCpuSubType, const void* sliceStart, uint64_t sliceSize, bool& stop) {
const char* sliceArchName = dyld3::MachOFile::archName(sliceCpuType, sliceCpuSubType);
if ( cmdLineArchs.empty() || inStringVector(cmdLineArchs, sliceArchName) ) {
archesForFile.push_back(sliceArchName);
const dyld3::MachOFile* mf = (dyld3::MachOFile*)sliceStart;
mf->forEachSupportedPlatform(^(dyld3::Platform plat, uint32_t minOS, uint32_t sdk) {
if ( platform == dyld3::Platform::unknown)
platform = plat;
});
}
});
}
else if ( !fromSharedCache ) {
const dyld3::MachOFile* mo = (dyld3::MachOFile*)info.fileContent;
if ( mo->isMachO(diag, info.sliceLen) ) {
archesForFile.push_back(mo->archName());
mo->forEachSupportedPlatform(^(dyld3::Platform plat, uint32_t minOS, uint32_t sdk) {
if ( platform == dyld3::Platform::unknown)
platform = plat;
});
}
else {
fprintf(stderr, "dyldinfo: %s: %s\n", path, diag.errorMessage());
return 1;
}
}
if ( archesForFile.empty() ) {
fprintf(stderr, "dyldinfo: %s: does not contain specified arch(s)\n", path);
return 1;
}
char loadedPath[MAXPATHLEN];
for (const char* sliceArch : archesForFile) {
if ( !fromSharedCache )
info = dyld3::MachOAnalyzer::load(diag, fileSystem, path, dyld3::GradedArchs::forName(sliceArch), platform, loadedPath);
if ( diag.hasError() ) {
fprintf(stderr, "dyldinfo: %s\n", diag.errorMessage());
return 1;
}
const dyld3::MachOAnalyzer* ma = (dyld3::MachOAnalyzer*)info.fileContent;
printf("%s [%s]:\n", path, sliceArch);
bool somethingPrinted = false;
for (int i=1; i < argc; ++i) {
const char* arg = argv[i];
if ( arg[0] != '-' )
continue;
if ( strcmp(arg, "-arch") == 0 ) {
++i;
}
else if ( strcmp(arg, "-platform") == 0 ) {
printPlatforms(ma);
somethingPrinted = true;
}
else if ( strcmp(arg, "-segments") == 0 ) {
printSegments(ma);
somethingPrinted = true;
}
else if ( strcmp(arg, "-dependents") == 0 ) {
printDependents(ma);
somethingPrinted = true;
}
else if ( strcmp(arg, "-inits") == 0 ) {
printInitializers(ma, cache, cacheLen);
somethingPrinted = true;
}
else if ( strcmp(arg, "-fixups") == 0 ) {
printFixups(ma, path);
somethingPrinted = true;
}
else if ( strcmp(arg, "-exports") == 0 ) {
printExports(ma);
somethingPrinted = true;
}
else if ( strcmp(arg, "-fixup_chains") == 0 ) {
printChains(ma);
somethingPrinted = true;
}
else if ( strcmp(arg, "-fixup_chain_details") == 0 ) {
printChainDetails(ma);
somethingPrinted = true;
}
else if ( strcmp(arg, "-symbolic_fixups") == 0 ) {
printSymbolicFixups(ma, path);
somethingPrinted = true;
}
else if ( strcmp(arg, "-opcodes") == 0 ) {
}
else if ( strcmp(arg, "-shared_region") == 0 ) {
}
else if ( strcmp(arg, "-function_starts") == 0 ) {
}
else if ( strcmp(arg, "-data_in_code") == 0 ) {
}
else if ( strcmp(arg, "-objc") == 0 ) {
printObjC(ma, cache, cacheLen);
somethingPrinted = true;
}
else {
fprintf(stderr, "unknown option: %s\n", arg);
return 1;
}
}
if ( !somethingPrinted ) {
printPlatforms(ma);
printSegments(ma);
printDependents(ma);
printInitializers(ma, cache, cacheLen);
printFixups(ma, path);
printExports(ma);
printObjC(ma, cache, cacheLen);
}
}
}
return 0;
}