介紹
Mach-O 的全稱是 Mach Object File Format。可以是可執(zhí)行文件,目標(biāo)代碼或共享庫(kù),動(dòng)態(tài)庫(kù)。Mach 內(nèi)核的操作系統(tǒng)比如 macOS,iPadOS 和 iOS 都是用的 Mach-O。Mach-O 包含程序的核心邏輯,以及入口點(diǎn)主要功能。
通過(guò)學(xué)習(xí) Mach-O,可以了解應(yīng)用程序是如何加載到系統(tǒng)的,如何執(zhí)行的。還能了解符號(hào)查找,函數(shù)調(diào)用堆棧符號(hào)化等。更重要的是能夠了解如何設(shè)計(jì)數(shù)據(jù)結(jié)構(gòu),這對(duì)于日后開(kāi)發(fā)生涯的收益是長(zhǎng)期的。了解這些對(duì)于了解編譯和逆向工程都會(huì)有幫助,你還會(huì)了解到動(dòng)態(tài)鏈接器的內(nèi)部工作原理以及字節(jié)碼格式的信息,Leb128字節(jié)流,Mach 導(dǎo)出時(shí) Trie 二進(jìn)制 image 壓縮。
對(duì)于 Mach-O,你一定不陌生,但是對(duì)于它內(nèi)部邏輯你一定會(huì)好奇,比如它是怎么構(gòu)建出來(lái)的,組織方式如何,怎么加載的,如何工作,誰(shuí)讓它工作的,怎樣導(dǎo)入和導(dǎo)出符號(hào)的。
接下來(lái)我們先看看怎么構(gòu)建一個(gè) Mach-O 文件的吧。
構(gòu)建
構(gòu)建 Mach-O 文件,主要需要用到編譯器和靜態(tài)鏈接器,編譯器可以將編寫的高級(jí)語(yǔ)言代碼轉(zhuǎn)成中間目標(biāo)文件,然后用靜態(tài)鏈接器把中間目標(biāo)文件組合成 Mach-O。
編譯器驅(qū)動(dòng)程序使用的是 clang,有編譯、組裝和鏈接的能力,調(diào)用 Xcode Tools 里的其他工具來(lái)實(shí)現(xiàn)源碼到 Mach-O 文件生成。其他工具包括將匯編代碼創(chuàng)建為中間目標(biāo)文件的 as 匯編程序,組合中間目標(biāo)文件成 Mach-O 文件的靜態(tài)鏈接器 ld,還有創(chuàng)建靜態(tài)庫(kù)或共享庫(kù)的 libtool。
構(gòu)建成 Mach-O 包括中間對(duì)象文件、動(dòng)態(tài)共享庫(kù)、框架、靜態(tài)庫(kù)、Bundle、內(nèi)核擴(kuò)展這幾種類型。其中框架會(huì)包含共享庫(kù)和圖片、文檔、接口等相關(guān)資源。
寫個(gè) main.c 文件代碼:
#include <stdlib.h>
int main(int argc, char *argv[]) {
const char *name = argv[1];
printf("%s\n", name);
return 0;
}
通過(guò) clang 構(gòu)建成 Mach-O 文件 a.out。
xcrun clang main.c
如果有多個(gè)文件,先將多個(gè)文件生成中間目標(biāo)文件,后綴是.o,使用 clang 的選項(xiàng) -c。每個(gè)目標(biāo)文件都是模塊。使用靜態(tài)鏈接器可以把多個(gè)模塊組合成一個(gè)動(dòng)態(tài)共享庫(kù)。通過(guò) ld 可以完成這個(gè)操作。使用 libtool 的選項(xiàng) -static 可以構(gòu)建靜態(tài)庫(kù)。
組合成動(dòng)態(tài)庫(kù)可以使用 clang 的 -dynamiclib 選項(xiàng),命令如下:
xcrun clang -dynamiclib command.c header.c -fvisibility=hidden -o mac.dylib
靜態(tài)鏈接就是把各個(gè)模塊組合成一個(gè)整體,生成新的 Mach-O,鏈接的內(nèi)容就是把各個(gè)模塊間相互的引用能夠正確的鏈接好,原理就是把一些指令對(duì)其他符號(hào)的地址引用進(jìn)行修正。過(guò)程包含地址和空間分配,符號(hào)解析和圍繞符號(hào)進(jìn)行的重定位。核心是重定位,X86-64尋址方式是 RIP-relative 尋址,就是基于 RIP 來(lái)計(jì)算目標(biāo)地址,通過(guò) jumpq 跳轉(zhuǎn)目標(biāo)地址,就是當(dāng)前指令下一條指令地址來(lái)加偏移量。
構(gòu)建完 Mach-O。那你一定好奇 Mach-O 里面都有什么呢?分析 Mach-O 的工具有分析體系結(jié)構(gòu)的 lipo,顯式文件類型的 file,列 Data 內(nèi)容的 otool,分析 image 每個(gè)邏輯信息符號(hào)的 pagestuff,符號(hào)表顯示的 nm。
組成
Mach-O 會(huì)將數(shù)據(jù)流分組,每組都會(huì)有自己的意義,主要分三大部分,分別是 Mach Header、Load Command、Data。
Header
Mach Header 里會(huì)有 Mach-O 的 CPU 信息,以及 Load Command 的信息。可以使用 otool 查看內(nèi)容:
otool -v -h a.out
結(jié)果如下:
Mach header
magic cputype cpusubtype caps filetype ncmds sizeofcmds flags
MH_MAGIC_64 X86_64 ALL 0x00 EXECUTE 16 1368 NOUNDEFS DYLDLINK TWOLEVEL PIE
通過(guò) _dyld_get_image_header 函數(shù)可以獲取 mach_header 結(jié)構(gòu)體。GCDFetchFeed/SMCallStack.m at master · ming1016/GCDFetchFeed · GitHub 里這段代碼里有判斷 Mach Header 結(jié)構(gòu)體魔數(shù)的函數(shù) smCmdFirstPointerFromMachHeader,代碼如下:
uintptr_t smCmdFirstPointerFromMachHeader(const struct mach_header* const machHeader) {
switch (machHeader->magic) {
case MH_MAGIC:
case MH_CIGAM:
case MH_MAGIC_64:
case MH_CIGAM_64:
return (uintptr_t)(((machHeaderByCPU*)machHeader) + 1);
default:
return 0; // Header 不合法
}
}
還有 Fat Header,里面會(huì)包含多個(gè)架構(gòu)的 Header。
LLVM 中生成 Mach Header 的代碼如下:
void MachOFileLayout::writeMachHeader() {
auto cpusubtype = MachOLinkingContext::cpuSubtypeFromArch(_file.arch);
// dynamic x86 executables on newer OS version should also set the
// CPU_SUBTYPE_LIB64 mask in the CPU subtype.
// FIXME: Check that this is a dynamic executable, not a static one.
if (_file.fileType == llvm::MachO::MH_EXECUTE &&
cpusubtype == CPU_SUBTYPE_X86_64_ALL &&
_file.os == MachOLinkingContext::OS::macOSX) {
uint32_t version;
bool failed = MachOLinkingContext::parsePackedVersion("10.5", version);
if (!failed && _file.minOSverson >= version)
cpusubtype |= CPU_SUBTYPE_LIB64;
}
mach_header *mh = reinterpret_cast<mach_header*>(_buffer);
mh->magic = _is64 ? llvm::MachO::MH_MAGIC_64 : llvm::MachO::MH_MAGIC;
mh->cputype = MachOLinkingContext::cpuTypeFromArch(_file.arch);
mh->cpusubtype = cpusubtype;
mh->filetype = _file.fileType;
mh->ncmds = _countOfLoadCommands;
mh->sizeofcmds = _endOfLoadCommands - _startOfLoadCommands;
mh->flags = _file.flags;
if (_swap)
swapStruct(*mh);
}
Load Command
Load Command 包含 Mach-O 里命令類型信息,名稱和二進(jìn)制文件的位置。
使用 otool 命令可以查看詳細(xì):
otool -v -l a.out
遍歷 Mach Header 里的 ncmds 可以取到所有 Load Command。代碼如下:
for (uint32_t iCmd = 0; iCmd < machHeader->ncmds; iCmd++) {
const struct load_command* loadCmd = (struct load_command*)cmdPointer;
}
load_command 里的 cmd 是以 LC_ 開(kāi)頭定義的宏,可以參看 loader.h 里的定義,有50多個(gè),主要的是:
- LC_SEGMENT_64(_PAGEZERO)
- LC_SEGMENT_64(_TEXT)
- LC_SEGMENT_64(_DATA)
- LC_SEGMENT_64(_LINKEDIT)
- LC_DYLD_INFO_ONLY
- LC_SYMTAB
- LC_DYSYMTAB
- LC_LOAD_DYLINKER
- LC_UUID
- LC_BUILD_VERSION
- LC_SOURCE_VERSION
- LC_MAIN
- LC_LOAD_DYLIB(libSystem.B.dylib)
- LC_FUNCTION_STARTS
- LC_DATA_IN_CODE
每個(gè) command 的結(jié)構(gòu)都是獨(dú)立的,前兩個(gè)字段 cmd 和 cmdsize 是一樣的。
根據(jù) Load Command 可以得到 Segment 的偏移量。
生成 Load Command 的代碼如下:
llvm::Error MachOFileLayout::writeLoadCommands() {
uint8_t *lc = &_buffer[_startOfLoadCommands];
if (_file.fileType == llvm::MachO::MH_OBJECT) {
// Object files have one unnamed segment which holds all sections.
if (_is64) {
if (auto ec = writeSingleSegmentLoadCommand<MachO64Trait>(lc))
return ec;
} else {
if (auto ec = writeSingleSegmentLoadCommand<MachO32Trait>(lc))
return ec;
}
// Add LC_SYMTAB with symbol table info
symtab_command* st = reinterpret_cast<symtab_command*>(lc);
st->cmd = LC_SYMTAB;
st->cmdsize = sizeof(symtab_command);
st->symoff = _startOfSymbols;
st->nsyms = _file.stabsSymbols.size() + _file.localSymbols.size() +
_file.globalSymbols.size() + _file.undefinedSymbols.size();
st->stroff = _startOfSymbolStrings;
st->strsize = _endOfSymbolStrings - _startOfSymbolStrings;
if (_swap)
swapStruct(*st);
lc += sizeof(symtab_command);
// Add LC_VERSION_MIN_MACOSX, LC_VERSION_MIN_IPHONEOS,
// LC_VERSION_MIN_WATCHOS, LC_VERSION_MIN_TVOS
writeVersionMinLoadCommand(_file, _swap, lc);
// Add LC_FUNCTION_STARTS if needed.
if (_functionStartsSize != 0) {
linkedit_data_command* dl = reinterpret_cast<linkedit_data_command*>(lc);
dl->cmd = LC_FUNCTION_STARTS;
dl->cmdsize = sizeof(linkedit_data_command);
dl->dataoff = _startOfFunctionStarts;
dl->datasize = _functionStartsSize;
if (_swap)
swapStruct(*dl);
lc += sizeof(linkedit_data_command);
}
// Add LC_DATA_IN_CODE if requested.
if (_file.generateDataInCodeLoadCommand) {
linkedit_data_command* dl = reinterpret_cast<linkedit_data_command*>(lc);
dl->cmd = LC_DATA_IN_CODE;
dl->cmdsize = sizeof(linkedit_data_command);
dl->dataoff = _startOfDataInCode;
dl->datasize = _dataInCodeSize;
if (_swap)
swapStruct(*dl);
lc += sizeof(linkedit_data_command);
}
} else {
// Final linked images have sections under segments.
if (_is64) {
if (auto ec = writeSegmentLoadCommands<MachO64Trait>(lc))
return ec;
} else {
if (auto ec = writeSegmentLoadCommands<MachO32Trait>(lc))
return ec;
}
// Add LC_ID_DYLIB command for dynamic libraries.
if (_file.fileType == llvm::MachO::MH_DYLIB) {
dylib_command *dc = reinterpret_cast<dylib_command*>(lc);
StringRef path = _file.installName;
uint32_t size = sizeof(dylib_command) + pointerAlign(path.size() + 1);
dc->cmd = LC_ID_DYLIB;
dc->cmdsize = size;
dc->dylib.name = sizeof(dylib_command); // offset
// needs to be some constant value different than the one in LC_LOAD_DYLIB
dc->dylib.timestamp = 1;
dc->dylib.current_version = _file.currentVersion;
dc->dylib.compatibility_version = _file.compatVersion;
if (_swap)
swapStruct(*dc);
memcpy(lc + sizeof(dylib_command), path.begin(), path.size());
lc[sizeof(dylib_command) + path.size()] = '\0';
lc += size;
}
// Add LC_DYLD_INFO_ONLY.
dyld_info_command* di = reinterpret_cast<dyld_info_command*>(lc);
di->cmd = LC_DYLD_INFO_ONLY;
di->cmdsize = sizeof(dyld_info_command);
di->rebase_off = _rebaseInfo.size() ? _startOfRebaseInfo : 0;
di->rebase_size = _rebaseInfo.size();
di->bind_off = _bindingInfo.size() ? _startOfBindingInfo : 0;
di->bind_size = _bindingInfo.size();
di->weak_bind_off = 0;
di->weak_bind_size = 0;
di->lazy_bind_off = _lazyBindingInfo.size() ? _startOfLazyBindingInfo : 0;
di->lazy_bind_size = _lazyBindingInfo.size();
di->export_off = _exportTrie.size() ? _startOfExportTrie : 0;
di->export_size = _exportTrie.size();
if (_swap)
swapStruct(*di);
lc += sizeof(dyld_info_command);
// Add LC_SYMTAB with symbol table info.
symtab_command* st = reinterpret_cast<symtab_command*>(lc);
st->cmd = LC_SYMTAB;
st->cmdsize = sizeof(symtab_command);
st->symoff = _startOfSymbols;
st->nsyms = _file.stabsSymbols.size() + _file.localSymbols.size() +
_file.globalSymbols.size() + _file.undefinedSymbols.size();
st->stroff = _startOfSymbolStrings;
st->strsize = _endOfSymbolStrings - _startOfSymbolStrings;
if (_swap)
swapStruct(*st);
lc += sizeof(symtab_command);
// Add LC_DYSYMTAB
if (_file.fileType != llvm::MachO::MH_PRELOAD) {
dysymtab_command* dst = reinterpret_cast<dysymtab_command*>(lc);
dst->cmd = LC_DYSYMTAB;
dst->cmdsize = sizeof(dysymtab_command);
dst->ilocalsym = _symbolTableLocalsStartIndex;
dst->nlocalsym = _file.stabsSymbols.size() +
_file.localSymbols.size();
dst->iextdefsym = _symbolTableGlobalsStartIndex;
dst->nextdefsym = _file.globalSymbols.size();
dst->iundefsym = _symbolTableUndefinesStartIndex;
dst->nundefsym = _file.undefinedSymbols.size();
dst->tocoff = 0;
dst->ntoc = 0;
dst->modtaboff = 0;
dst->nmodtab = 0;
dst->extrefsymoff = 0;
dst->nextrefsyms = 0;
dst->indirectsymoff = _startOfIndirectSymbols;
dst->nindirectsyms = _indirectSymbolTableCount;
dst->extreloff = 0;
dst->nextrel = 0;
dst->locreloff = 0;
dst->nlocrel = 0;
if (_swap)
swapStruct(*dst);
lc += sizeof(dysymtab_command);
}
// If main executable, add LC_LOAD_DYLINKER
if (_file.fileType == llvm::MachO::MH_EXECUTE) {
// Build LC_LOAD_DYLINKER load command.
uint32_t size=pointerAlign(sizeof(dylinker_command)+dyldPath().size()+1);
dylinker_command* dl = reinterpret_cast<dylinker_command*>(lc);
dl->cmd = LC_LOAD_DYLINKER;
dl->cmdsize = size;
dl->name = sizeof(dylinker_command); // offset
if (_swap)
swapStruct(*dl);
memcpy(lc+sizeof(dylinker_command), dyldPath().data(), dyldPath().size());
lc[sizeof(dylinker_command)+dyldPath().size()] = '\0';
lc += size;
}
// Add LC_VERSION_MIN_MACOSX, LC_VERSION_MIN_IPHONEOS, LC_VERSION_MIN_WATCHOS,
// LC_VERSION_MIN_TVOS
writeVersionMinLoadCommand(_file, _swap, lc);
// Add LC_SOURCE_VERSION
{
// Note, using a temporary here to appease UB as we may not be aligned
// enough for a struct containing a uint64_t when emitting a 32-bit binary
source_version_command sv;
sv.cmd = LC_SOURCE_VERSION;
sv.cmdsize = sizeof(source_version_command);
sv.version = _file.sourceVersion;
if (_swap)
swapStruct(sv);
memcpy(lc, &sv, sizeof(source_version_command));
lc += sizeof(source_version_command);
}
// If main executable, add LC_MAIN.
if (_file.fileType == llvm::MachO::MH_EXECUTE) {
// Build LC_MAIN load command.
// Note, using a temporary here to appease UB as we may not be aligned
// enough for a struct containing a uint64_t when emitting a 32-bit binary
entry_point_command ep;
ep.cmd = LC_MAIN;
ep.cmdsize = sizeof(entry_point_command);
ep.entryoff = _file.entryAddress - _seg1addr;
ep.stacksize = _file.stackSize;
if (_swap)
swapStruct(ep);
memcpy(lc, &ep, sizeof(entry_point_command));
lc += sizeof(entry_point_command);
}
// Add LC_LOAD_DYLIB commands
for (const DependentDylib &dep : _file.dependentDylibs) {
dylib_command* dc = reinterpret_cast<dylib_command*>(lc);
uint32_t size = sizeof(dylib_command) + pointerAlign(dep.path.size()+1);
dc->cmd = dep.kind;
dc->cmdsize = size;
dc->dylib.name = sizeof(dylib_command); // offset
// needs to be some constant value different than the one in LC_ID_DYLIB
dc->dylib.timestamp = 2;
dc->dylib.current_version = dep.currentVersion;
dc->dylib.compatibility_version = dep.compatVersion;
if (_swap)
swapStruct(*dc);
memcpy(lc+sizeof(dylib_command), dep.path.begin(), dep.path.size());
lc[sizeof(dylib_command)+dep.path.size()] = '\0';
lc += size;
}
// Add LC_RPATH
for (const StringRef &path : _file.rpaths) {
rpath_command *rpc = reinterpret_cast<rpath_command *>(lc);
uint32_t size = pointerAlign(sizeof(rpath_command) + path.size() + 1);
rpc->cmd = LC_RPATH;
rpc->cmdsize = size;
rpc->path = sizeof(rpath_command); // offset
if (_swap)
swapStruct(*rpc);
memcpy(lc+sizeof(rpath_command), path.begin(), path.size());
lc[sizeof(rpath_command)+path.size()] = '\0';
lc += size;
}
// Add LC_FUNCTION_STARTS if needed.
if (_functionStartsSize != 0) {
linkedit_data_command* dl = reinterpret_cast<linkedit_data_command*>(lc);
dl->cmd = LC_FUNCTION_STARTS;
dl->cmdsize = sizeof(linkedit_data_command);
dl->dataoff = _startOfFunctionStarts;
dl->datasize = _functionStartsSize;
if (_swap)
swapStruct(*dl);
lc += sizeof(linkedit_data_command);
}
// Add LC_DATA_IN_CODE if requested.
if (_file.generateDataInCodeLoadCommand) {
linkedit_data_command* dl = reinterpret_cast<linkedit_data_command*>(lc);
dl->cmd = LC_DATA_IN_CODE;
dl->cmdsize = sizeof(linkedit_data_command);
dl->dataoff = _startOfDataInCode;
dl->datasize = _dataInCodeSize;
if (_swap)
swapStruct(*dl);
lc += sizeof(linkedit_data_command);
}
}
return llvm::Error::success();
}
Data
Data 由 Segment 的數(shù)據(jù)組成,是 Mach-O 占比最多的部分,有代碼有數(shù)據(jù),比如符號(hào)表。Data 共三個(gè) Segment,__TEXT、__DATA、__LINKEDIT。其中 __TEXT 和 __DATA 對(duì)應(yīng)一個(gè)或多個(gè) Section,__LINKEDIT 沒(méi)有 Section,需要配合 LC_SYMTAB 來(lái)解析 symbol table 和 string table。這些里面是 Mach-O 的主要數(shù)據(jù)。
生成 __LINKEDIT 的代碼如下:
void MachOFileLayout::buildLinkEditInfo() {
buildRebaseInfo();
buildBindInfo();
buildLazyBindInfo();
buildExportTrie();
computeSymbolTableSizes();
computeFunctionStartsSize();
computeDataInCodeSize();
}
void MachOFileLayout::writeLinkEditContent() {
if (_file.fileType == llvm::MachO::MH_OBJECT) {
writeRelocations();
writeFunctionStartsInfo();
writeDataInCodeInfo();
writeSymbolTable();
} else {
writeRebaseInfo();
writeBindingInfo();
writeLazyBindingInfo();
// TODO: add weak binding info
writeExportInfo();
writeFunctionStartsInfo();
writeDataInCodeInfo();
writeSymbolTable();
}
}
通過(guò)生成 __LINKEDIT 的代碼可以看出 __LINKEDIT 里包含 dyld 所需各種數(shù)據(jù),比如符號(hào)表、間接符號(hào)表、rebase 操作碼、綁定操作碼、導(dǎo)出符號(hào)、函數(shù)啟動(dòng)信息、數(shù)據(jù)表、代碼簽名等。
__DATA 包含 lazy 和 non lazy 符號(hào)指針,還會(huì)包含靜態(tài)數(shù)據(jù)和全局變量等。可重定位的 Mach-O 文件還會(huì)有一個(gè)重定位的區(qū)域用來(lái)存儲(chǔ)重定位信息,如果哪個(gè) section 有重定位字節(jié),就會(huì)有一個(gè) relocation table 對(duì)應(yīng)。
生成 relocation 的代碼如下:
void MachOFileLayout::writeRelocations() {
uint32_t relOffset = _startOfRelocations;
for (Section sect : _file.sections) {
for (Relocation r : sect.relocations) {
any_relocation_info* rb = reinterpret_cast<any_relocation_info*>(
&_buffer[relOffset]);
*rb = packRelocation(r, _swap, _bigEndianArch);
relOffset += sizeof(any_relocation_info);
}
}
}
使用 size 命令可以看到內(nèi)容的分布,使用前面生成的 a.out 來(lái)看:
xcrun size -x -l -m a.out
結(jié)果如下:
Segment __PAGEZERO: 0x100000000 (vmaddr 0x0 fileoff 0)
Segment __TEXT: 0x1000 (vmaddr 0x100000000 fileoff 0)
Section __text: 0x41 (addr 0x100000f50 offset 3920)
Section __stubs: 0x6 (addr 0x100000f92 offset 3986)
Section __stub_helper: 0x1a (addr 0x100000f98 offset 3992)
Section __cstring: 0x4 (addr 0x100000fb2 offset 4018)
Section __unwind_info: 0x48 (addr 0x100000fb8 offset 4024)
total 0xad
Segment __DATA_CONST: 0x1000 (vmaddr 0x100001000 fileoff 4096)
Section __got: 0x8 (addr 0x100001000 offset 4096)
total 0x8
Segment __DATA: 0x1000 (vmaddr 0x100002000 fileoff 8192)
Section __la_symbol_ptr: 0x8 (addr 0x100002000 offset 8192)
Section __data: 0x8 (addr 0x100002008 offset 8200)
total 0x10
Segment __LINKEDIT: 0x1000 (vmaddr 0x100003000 fileoff 12288)
total 0x100004000
其中__TEXT Segment 的內(nèi)容有:
- Section64(__TEXT,__text)
- Section64(__TEXT,__stubs)
- Section64(__TEXT,__stub_helper)
- Section64(__TEXT,__cstring)
- Section64(__TEXT,__unwind_info)
__DATA Segment 的內(nèi)容有:
- Section64(__DATA,__nl_symbol_ptr)
- Section64(__DATA,__la_symbol_ptr)
__LINKEDIT 的內(nèi)容是:
- Dynamic Loader Info
- Function Starts
- Symbol Table
- Data in Code Entries
- Dynamic Symbol Table
- String Table
如果是 Objective-C 代碼生成的 Mach-O 會(huì)多出很多和 Objective-C 相關(guān)的 Section ,我拿已閱項(xiàng)目生成的 Mach-O 來(lái)看。
xcrun size -x -l -m GCDFetchFeed
結(jié)果如下:
Segment __PAGEZERO: 0x100000000 (vmaddr 0x0 fileoff 0)
Segment __TEXT: 0xa8000 (vmaddr 0x100000000 fileoff 0)
Section __text: 0x89084 (addr 0x1000020e0 offset 8416)
Section __stubs: 0x588 (addr 0x10008b164 offset 569700)
Section __stub_helper: 0x948 (addr 0x10008b6ec offset 571116)
Section __gcc_except_tab: 0x1318 (addr 0x10008c034 offset 573492)
Section __cstring: 0xbebd (addr 0x10008d34c offset 578380)
Section __objc_methname: 0xa20f (addr 0x100099209 offset 627209)
Section __objc_classname: 0x11d9 (addr 0x1000a3418 offset 668696)
Section __objc_methtype: 0x2185 (addr 0x1000a45f1 offset 673265)
Section __const: 0x23c (addr 0x1000a6780 offset 681856)
Section __ustring: 0x23e (addr 0x1000a69bc offset 682428)
Section __entitlements: 0x184 (addr 0x1000a6bfa offset 683002)
Section __unwind_info: 0x1274 (addr 0x1000a6d80 offset 683392)
total 0xa5f08
Segment __DATA: 0x2f000 (vmaddr 0x1000a8000 fileoff 688128)
Section __nl_symbol_ptr: 0x8 (addr 0x1000a8000 offset 688128)
Section __got: 0x258 (addr 0x1000a8008 offset 688136)
Section __la_symbol_ptr: 0x760 (addr 0x1000a8260 offset 688736)
Section __const: 0x4238 (addr 0x1000a89c0 offset 690624)
Section __cfstring: 0x9d80 (addr 0x1000acbf8 offset 707576)
Section __objc_classlist: 0x510 (addr 0x1000b6978 offset 747896)
Section __objc_nlclslist: 0x40 (addr 0x1000b6e88 offset 749192)
Section __objc_catlist: 0x90 (addr 0x1000b6ec8 offset 749256)
Section __objc_nlcatlist: 0x10 (addr 0x1000b6f58 offset 749400)
Section __objc_protolist: 0x80 (addr 0x1000b6f68 offset 749416)
Section __objc_imageinfo: 0x8 (addr 0x1000b6fe8 offset 749544)
Section __objc_const: 0x182e8 (addr 0x1000b6ff0 offset 749552)
Section __objc_selrefs: 0x2bf8 (addr 0x1000cf2d8 offset 848600)
Section __objc_protorefs: 0x8 (addr 0x1000d1ed0 offset 859856)
Section __objc_classrefs: 0x858 (addr 0x1000d1ed8 offset 859864)
Section __objc_superrefs: 0x370 (addr 0x1000d2730 offset 862000)
Section __objc_ivar: 0xb48 (addr 0x1000d2aa0 offset 862880)
Section __objc_data: 0x32a0 (addr 0x1000d35e8 offset 865768)
Section __data: 0x604 (addr 0x1000d6888 offset 878728)
Section __bss: 0x158 (addr 0x1000d6e90 offset 0)
total 0x2efe4
Segment __LINKEDIT: 0xae000 (vmaddr 0x1000d7000 fileoff 880640)
total 0x100185000
可以看到 __objc 前綴的都是為了支持 Objective-C 語(yǔ)言新增加的。
那么 Swift 語(yǔ)言代碼構(gòu)建的 Mach-O 是怎樣的呢?
使用我做啟動(dòng)優(yōu)化時(shí)用 Swift 寫的工具 MethodTraceAnalyze 看下內(nèi)容有什么。結(jié)果如下:
Segment __PAGEZERO: 0x100000000 (vmaddr 0x0 fileoff 0)
Segment __TEXT: 0x115000 (vmaddr 0x100000000 fileoff 0)
Section __text: 0xfd540 (addr 0x1000019b0 offset 6576)
Section __stubs: 0x6f6 (addr 0x1000feef0 offset 1044208)
Section __stub_helper: 0xbaa (addr 0x1000ff5e8 offset 1045992)
Section __swift5_typeref: 0xf56 (addr 0x100100192 offset 1048978)
Section __swift5_capture: 0x3b4 (addr 0x1001010e8 offset 1052904)
Section __cstring: 0x7011 (addr 0x1001014a0 offset 1053856)
Section __const: 0x4754 (addr 0x1001084c0 offset 1082560)
Section __swift5_fieldmd: 0x2bf4 (addr 0x10010cc14 offset 1100820)
Section __swift5_types: 0x1f0 (addr 0x10010f808 offset 1112072)
Section __swift5_builtin: 0x78 (addr 0x10010f9f8 offset 1112568)
Section __swift5_reflstr: 0x2740 (addr 0x10010fa70 offset 1112688)
Section __swift5_proto: 0x154 (addr 0x1001121b0 offset 1122736)
Section __swift5_assocty: 0x120 (addr 0x100112304 offset 1123076)
Section __objc_methname: 0x7a5 (addr 0x100112424 offset 1123364)
Section __swift5_protos: 0x8 (addr 0x100112bcc offset 1125324)
Section __unwind_info: 0x1c70 (addr 0x100112bd4 offset 1125332)
Section __eh_frame: 0x7b0 (addr 0x100114848 offset 1132616)
total 0x11362c
Segment __DATA_CONST: 0x4000 (vmaddr 0x100115000 fileoff 1134592)
Section __got: 0x4a8 (addr 0x100115000 offset 1134592)
Section __const: 0x32f8 (addr 0x1001154a8 offset 1135784)
Section __objc_classlist: 0xd0 (addr 0x1001187a0 offset 1148832)
Section __objc_protolist: 0x10 (addr 0x100118870 offset 1149040)
Section __objc_imageinfo: 0x8 (addr 0x100118880 offset 1149056)
total 0x3888
Segment __DATA: 0x8000 (vmaddr 0x100119000 fileoff 1150976)
Section __la_symbol_ptr: 0x948 (addr 0x100119000 offset 1150976)
Section __objc_const: 0x2018 (addr 0x100119948 offset 1153352)
Section __objc_selrefs: 0xb0 (addr 0x10011b960 offset 1161568)
Section __objc_protorefs: 0x10 (addr 0x10011ba10 offset 1161744)
Section __objc_classrefs: 0x38 (addr 0x10011ba20 offset 1161760)
Section __objc_data: 0x98 (addr 0x10011ba58 offset 1161816)
Section __data: 0x1f88 (addr 0x10011baf0 offset 1161968)
Section __bss: 0x2a68 (addr 0x10011da80 offset 0)
Section __common: 0x50 (addr 0x1001204e8 offset 0)
total 0x7530
Segment __LINKEDIT: 0x152000 (vmaddr 0x100121000 fileoff 1171456)
total 0x100273000
可以看到 __DATA Segment 部分還是有 __objc 前綴的 Section,__TEXT Segment 里已經(jīng)都是 __swift5 為前綴的 Section 了。
使用 otool 可以查看某個(gè) Section 內(nèi)容。比如查看 __TEXT Segment 的 __text Section 的內(nèi)容,使用如下命令:
xcrun otool -s __TEXT __text a.out
使用 otool 可以直接看 Mach-O 匯編內(nèi)容 :
xcrun otool -v -t a.out
結(jié)果如下:
a.out:
(__TEXT,__text) section
_main:
0000000100000f50 pushq %rbp
0000000100000f51 movq %rsp, %rbp
0000000100000f54 subq $0x20, %rsp
0000000100000f58 movl $0x0, -0x4(%rbp)
0000000100000f5f movl %edi, -0x8(%rbp)
0000000100000f62 movq %rsi, -0x10(%rbp)
0000000100000f66 movq -0x10(%rbp), %rax
0000000100000f6a movq 0x8(%rax), %rax
0000000100000f6e movq %rax, -0x18(%rbp)
0000000100000f72 movq -0x18(%rbp), %rsi
0000000100000f76 leaq 0x35(%rip), %rdi
0000000100000f7d movb $0x0, %al
0000000100000f7f callq 0x100000f92
0000000100000f84 xorl %ecx, %ecx
0000000100000f86 movl %eax, -0x1c(%rbp)
0000000100000f89 movl %ecx, %eax
0000000100000f8b addq $0x20, %rsp
0000000100000f8f popq %rbp
0000000100000f90 retq
構(gòu)建中查看代碼生成匯編可以使用 clang 以下選項(xiàng):
xcrun clang -S -o - main.c
生成匯編如下:
.section __TEXT,__text,regular,pure_instructions
.build_version macos, 10, 15 sdk_version 10, 15, 4
.globl _main ## -- Begin function main
.p2align 4, 0x90
_main: ## @main
.cfi_startproc
## %bb.0:
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset %rbp, -16
movq %rsp, %rbp
.cfi_def_cfa_register %rbp
subq $32, %rsp
movl $0, -4(%rbp)
movl %edi, -8(%rbp)
movq %rsi, -16(%rbp)
movq -16(%rbp), %rax
movq 8(%rax), %rax
movq %rax, -24(%rbp)
movq -24(%rbp), %rsi
leaq L_.str(%rip), %rdi
movb $0, %al
callq _printf
xorl %ecx, %ecx
movl %eax, -28(%rbp) ## 4-byte Spill
movl %ecx, %eax
addq $32, %rsp
popq %rbp
retq
.cfi_endproc
## -- End function
.section __TEXT,__cstring,cstring_literals
L_.str: ## @.str
.asciz "%s\n"
可以發(fā)現(xiàn)兩者匯編邏輯是一樣的。點(diǎn)符號(hào)開(kāi)頭的都是匯編指令,比如.section 就是告知會(huì)執(zhí)行哪個(gè) segment,.p2align 指令明確后面代碼對(duì)齊方式,這里是16(2^4) 字節(jié)對(duì)齊,0x90 補(bǔ)齊。在 __TEXT Segment 的 text Section 里會(huì)創(chuàng)建一個(gè)調(diào)用幀堆棧,進(jìn)行函數(shù)調(diào)用,callq printf 函數(shù)前會(huì)用到 L.str(%rip),L.str 標(biāo)簽會(huì)指向字符串,leaq 會(huì)把字符串的指針加載到 rdi 寄存器。最后會(huì)銷毀調(diào)用幀堆棧,進(jìn)行 retq 返回。
主要 Section:
- __nl_symbol_ptr:包含 non-lazy 符號(hào)指針,mach-o/loader.h 里有詳細(xì)說(shuō)明。服務(wù) dyld_stub_binder 處理的符號(hào)。
- __la_symbol_ptr:__stubs 第一個(gè) jump 目標(biāo)地址。動(dòng)態(tài)庫(kù)的符號(hào)指針地址。
- __got:二進(jìn)制文件的全局偏移表 GOT,也包含 S_NON_LAZY_SYMBOL_POINTERS 標(biāo)記的 non-lazy 符號(hào)指針。服務(wù)于 __TEXT Segment 里的符號(hào)。可以將__got 看作一個(gè)表,里面每項(xiàng)都是一個(gè)地址值。__got 的每項(xiàng)在加載期間都會(huì)被 dyld 重寫,所以會(huì)在 __DATA Segment 中。__got 用來(lái)存放 non-lazy 符號(hào)最終地址,為 dyld 所用。dylib 外部符號(hào)對(duì)于全局變量和常量引用地址會(huì)指到 __got。
- __lazy_symbol:包含 lazy 符號(hào),首次使用時(shí)綁定。
- __stubs:跳轉(zhuǎn)表,重定向到 lazy 和 non-lazy 符號(hào)的 section。被標(biāo)記為 S_SYMBOL_STUBS。__TEXT Segment 里代碼和 dylib 外部符號(hào)的引用地址對(duì)函數(shù)符號(hào)的引用都指向了 __stubs。其中每項(xiàng)都是 jmp 代碼間接尋址,可跳到 __la_symbol_ptr Section 中。
- __stub_helper:lazy 動(dòng)態(tài)綁定符號(hào)的輔助函數(shù)。可跳到 __nl_symbol_ptr Section 中。
- __text:機(jī)器碼,也是實(shí)際代碼,包含所有功能。
- __cstring:常量。只讀 C 字符串。
- __const:初始化過(guò)的常量。
- _objc:Objective-C 語(yǔ)言 runtime 的支持。
- __data:初始化過(guò)的變量。
- __bss:未初始化的靜態(tài)變量。
- __unwind_info:生成異常處理信息。
- __eh_frame:DWARF2 unwind 可執(zhí)行文件代碼信息,用于調(diào)試。
- string table:以空值終止的字符串序列。
- symbol table:通過(guò) LC_SYMTAB 命令找到 symbol table,其包含所有用到的符號(hào)信息。結(jié)構(gòu)體 nlist_64描述了符號(hào)的基本信息。nlist_64 結(jié)構(gòu)體中 n_type 字段是一個(gè)8位復(fù)合字段,其中bit[0:1]表示是外部符號(hào),bit[5:8]表調(diào)試符號(hào),bit[4:5]表示私有 external 符號(hào),bit[1:4]是符號(hào)類型,有 N_UNDF 未定義、N_ABS 絕對(duì)地址、N_SECT 本地符號(hào)、N_PBUD 預(yù)綁定符號(hào)、N_INDR 同名符號(hào)幾種類型。
- indirect symbol table:每項(xiàng)都是一個(gè) index 值,指向 symbol table 中的項(xiàng)。由 LC_DYSYMTAB 定義,和__nl_symbol_ptr 和 __lazy_symbol 一起為 __stubs 和 __got 等 Section 服務(wù)。
生成 Section 的代碼如下:
void MachOFileLayout::writeSectionContent() {
for (const Section &s : _file.sections) {
// Copy all section content to output buffer.
if (isZeroFillSection(s.type))
continue;
if (s.content.empty())
continue;
uint32_t offset = _sectInfo[&s].fileOffset;
uint8_t *p = &_buffer[offset];
memcpy(p, &s.content[0], s.content.size());
p += s.content.size();
}
}
其中 symble table 生成的代碼如下:
void MachOFileLayout::writeSymbolTable() {
// Write symbol table and symbol strings in parallel.
uint32_t symOffset = _startOfSymbols;
uint32_t strOffset = _startOfSymbolStrings;
// Reserve n_strx offset of zero to mean no name.
_buffer[strOffset++] = ' ';
_buffer[strOffset++] = '\0';
appendSymbols(_file.stabsSymbols, symOffset, strOffset);
appendSymbols(_file.localSymbols, symOffset, strOffset);
appendSymbols(_file.globalSymbols, symOffset, strOffset);
appendSymbols(_file.undefinedSymbols, symOffset, strOffset);
// Write indirect symbol table array.
uint32_t *indirects = reinterpret_cast<uint32_t*>
(&_buffer[_startOfIndirectSymbols]);
if (_file.fileType == llvm::MachO::MH_OBJECT) {
// Object files have sections in same order as input normalized file.
for (const Section §ion : _file.sections) {
for (uint32_t index : section.indirectSymbols) {
if (_swap)
*indirects++ = llvm::sys::getSwappedBytes(index);
else
*indirects++ = index;
}
}
} else {
// Final linked images must sort sections from normalized file.
for (const Segment &seg : _file.segments) {
SegExtraInfo &segInfo = _segInfo[&seg];
for (const Section *section : segInfo.sections) {
for (uint32_t index : section->indirectSymbols) {
if (_swap)
*indirects++ = llvm::sys::getSwappedBytes(index);
else
*indirects++ = index;
}
}
}
}
}
獲取 Segment 信息的代碼如下:
int segmentWalk(void *segment_command) {
uint32_t nsects;
void *section;
section = segment_command + sizeof(struct segment_command);
nsects = ((struct segment_command *) segment_command)->nsects;
while (nsects--) {
section += sizeof(struct s_section);
}
}
獲取對(duì)應(yīng)符號(hào)的方法代碼如下:
// 定義參看 <mach-o/nlist.h>
#define N_UNDF 0x0 // 未定義
#define N_ABS 0x2 // 絕對(duì)地址
#define N_SECT 0xe // 本地符號(hào)
#define N_PBUD 0xc // 預(yù)定義符號(hào)
#define N_INDR 0xa // 同名符號(hào)
#define N_STAB 0xe0 // 調(diào)試符號(hào)
#define N_PEXT 0x10 // 私有 external 符號(hào)
#define N_TYPE 0x0e // 類型位的掩碼
#define N_EXT 0x01 // external 符號(hào)
char symbolical(sym) {
if (N_STAB & sym->type)
return '-';
else if ((N_TYPE & sym->type) == N_UNDF) {
if (sym->name_not_found)
return 'C';
else if (sym->type & N_EXT)
return = 'U';
else
return = '?';
} else if ((N_TYPE & sym->type) == N_SECT) {
return matched(saved_sections, sym);
} else if ((N_TYPE & sym->type) == N_ABS) {
return = 'A';
} else if ((N_TYPE & sym->type) == N_INDR) {
return = 'I';
}
}
char matched(saved_sections, symbol)
{
if (sect = find_mysection(saved_sections, symbol->n_sect)) #
{
if (!ft_strcmp(sect->name, SECT_TEXT))
ret = 'T';
else if (!ft_strcmp(sect->name, SECT_DATA))
ret = 'D';
else if (!ft_strcmp(sect->name, SECT_BSS))
ret = 'B';
else
ret = 'S';
if (!(mysym->type & N_EXT))
ret -= 'A' - 'a';
}
}
加載運(yùn)行
程序要和其他庫(kù)還有模塊一起運(yùn)行,需要在運(yùn)行時(shí)對(duì)這些庫(kù)和模塊的符號(hào)引用進(jìn)行解析,運(yùn)行時(shí),你應(yīng)用程序使用的模塊符號(hào)都在共享名稱空間。macOS 使用的是兩級(jí)名稱空間來(lái)確保不同模塊符號(hào)名不會(huì)沖突,同時(shí)增強(qiáng)向前兼容。
選擇要加載的 Mach-O 后,系統(tǒng)內(nèi)核會(huì)先確定該文件是否是 Mach-O 文件。
文件的第一個(gè)字節(jié)是魔數(shù),通過(guò)魔數(shù)可以推斷是不是 Mach-O,mach-o/loader.h 里定義了四個(gè)魔數(shù)標(biāo)識(shí)。
#define MH_MAGIC 0xfeedface
#define MH_CIGAM NXSwapInt(MH_MAGIC)
#define MH_MAGIC_64 0xfeedfacf
#define MH_CIGAM_64 NXSwapInt(MH_MAGIC_64)
以上四個(gè)魔數(shù)標(biāo)識(shí)是 Mach-O 文件。
然后內(nèi)核系統(tǒng)會(huì)用 fork 函數(shù)創(chuàng)建一個(gè)進(jìn)程,然后通過(guò) execve 函數(shù)開(kāi)始程序加載過(guò)程,execve 有多個(gè)種類,比如 execl、execv 等,只是在參數(shù)和環(huán)境變量上有不同,最終都會(huì)到內(nèi)核的 execve 函數(shù)。
接著會(huì)檢查 Mach-O header,加載 dyld 和程序到 Load Command 指定的地址空間。執(zhí)行動(dòng)態(tài)鏈接器。動(dòng)態(tài)鏈接器通過(guò) dyld_stub_binder 調(diào)用,這個(gè)函數(shù)的參數(shù)不直接指定要綁定的符號(hào),而是通過(guò)給 dyld_stub_binder 偏移量到 dyld 解釋的特殊字節(jié)碼 Segment 中。dyld_stub_binder 函數(shù)的代碼在這里:dyld_stub_binder.s。dyld 分為 rebase、binding、lazy binding、導(dǎo)出幾個(gè)部分。dyld 可以 hook,使用 DYLD_INSERT_LIBRARIES,類似 ld 的 LD_PRELOAD 還有 DYLD_LIBRARY_PATH。
__text 里需要被 lazy binding 的符號(hào)引用,訪問(wèn)時(shí)回到 stub 中,目標(biāo)地址在 __la_symbol_ptr,對(duì)應(yīng) __la_symbol_ptr 的內(nèi)容會(huì)指向 __stub_helper,其中邏輯會(huì)調(diào)到 dyld_stub_binder 函數(shù),這個(gè)函數(shù)會(huì)通過(guò) dyld 找到符號(hào)的真實(shí)地址,最后 dyld_stub_binder 會(huì)把得到的地址寫入 __la_symbol_ptr 里后,會(huì)跳轉(zhuǎn)到符號(hào)的真實(shí)地址。由于地址已經(jīng)在 __la_symbol_ptr 里了,所以再訪問(wèn)符號(hào)時(shí)會(huì)通過(guò) stub 的 jum 指令直接跳轉(zhuǎn)到真實(shí)地址。
通過(guò) dyld 加載主程序鏈接到的所有依賴庫(kù),執(zhí)行符號(hào)綁定也就是non lazy binding。綁定解析其他模塊的功能和數(shù)據(jù)的引用過(guò)程,也叫導(dǎo)入符號(hào)。
導(dǎo)入導(dǎo)出符號(hào)
執(zhí)行綁定時(shí),鏈接程序會(huì)用實(shí)際定義的地址替換程序的每個(gè)導(dǎo)入引用。通過(guò)構(gòu)建時(shí)的選項(xiàng)設(shè)置,dyld 可以即時(shí)綁定,也叫延遲綁定,首次使用引用時(shí)的綁定,在使用符號(hào)前不會(huì)將程序的引用綁定到共享庫(kù)的符號(hào)。使用 -bind_at_load 可以加載時(shí)綁定,動(dòng)態(tài)鏈接程序在加載程序時(shí)立即綁定所有導(dǎo)入的引用,如果沒(méi)有設(shè)置這個(gè)選項(xiàng),默認(rèn)按即時(shí)綁定來(lái)。設(shè)置 -prebind,程序引用的共享庫(kù)都會(huì)在指定的地址預(yù)先綁定。
根據(jù) Code Fragment Manager 設(shè)計(jì)的弱引用允許程序有選擇的綁定到指定的共享庫(kù),如果 dyld 找不到弱引用的定義,會(huì)設(shè)置為 NULL,然后可以繼續(xù)加載程序。代碼上可以寫判斷,如果引用為空進(jìn)行相應(yīng)的處理。
過(guò)程鏈接表 PLT,會(huì)在運(yùn)行時(shí)確定函數(shù)地址。callq 指令在 dyld_stub 調(diào)用 PLT 條目,符號(hào) stub 位于 __TEXT Segment 的 __stubs Section 中。每個(gè) Mach-O 符號(hào) stub 都是一個(gè) jumpq 指令,它會(huì)調(diào)用 dyld 找到符號(hào),然后執(zhí)行。
Mach-O 的導(dǎo)入和導(dǎo)出都會(huì)存在 __LINKEDIT 里。使用 FSA 接受 Leb128 參數(shù),也就是綁定操作碼。LEB 會(huì)把整數(shù)值編碼成可變長(zhǎng)度的字節(jié)序列,最后一個(gè)字節(jié)才設(shè)置最高有效位。
當(dāng) FSA 循環(huán)或遞歸時(shí),會(huì)用0xF0對(duì)其進(jìn)行掩碼獲得操作碼,所有導(dǎo)入綁定操作碼都會(huì)對(duì)應(yīng)有宏名稱和對(duì)應(yīng)的功能。比如 0xb0 對(duì)應(yīng)宏是 BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED,功能是將記錄放到導(dǎo)入堆棧中,然后把當(dāng)前記錄的地址偏移量設(shè)為 seg_offset = seg_offset + (scale * sizeofptr) + sizeofptr ,其中 scale 是立即數(shù)中包含的值,sizeofptr 是指針對(duì)應(yīng)平臺(tái)的大小。
Mach-O 導(dǎo)出符號(hào)是 trie 的數(shù)據(jù)結(jié)構(gòu),trie 節(jié)點(diǎn)最多有一個(gè)終端字符串信息,如果沒(méi)有終端信息,就以0x00字節(jié)標(biāo)記。有的化,就用 Leb128 代替該節(jié)點(diǎn)的終端字符串信息大小。節(jié)點(diǎn)導(dǎo)出信息后,類型信息類型使用0x3對(duì)標(biāo)志進(jìn)行位掩碼獲得。0x00表示常規(guī)符號(hào),0x01表示線程本地符號(hào),0x02標(biāo)識(shí)絕對(duì)符號(hào),0x4表示弱引用符號(hào),0x8表示重新導(dǎo)出,0x10是 stub,具有 Leb128的 stub 偏移量。大部分符號(hào)都是常規(guī)符號(hào),會(huì)將 Mach-O 的偏移量給符號(hào)。
生成 trie 數(shù)據(jù)結(jié)構(gòu)的代碼如下:
void MachOFileLayout::buildExportTrie() {
if (_file.exportInfo.empty())
return;
// For all temporary strings and objects used building trie.
BumpPtrAllocator allocator;
// Build trie of all exported symbols.
auto *rootNode = new (allocator) TrieNode(StringRef());
std::vector<TrieNode*> allNodes;
allNodes.reserve(_file.exportInfo.size()*2);
allNodes.push_back(rootNode);
for (const Export& entry : _file.exportInfo) {
rootNode->addSymbol(entry, allocator, allNodes);
}
std::vector<TrieNode*> orderedNodes;
orderedNodes.reserve(allNodes.size());
for (const Export& entry : _file.exportInfo)
rootNode->addOrderedNodes(entry, orderedNodes);
// Assign each node in the vector an offset in the trie stream, iterating
// until all uleb128 sizes have stabilized.
bool more;
do {
uint32_t offset = 0;
more = false;
for (TrieNode* node : orderedNodes) {
if (node->updateOffset(offset))
more = true;
}
} while (more);
// Serialize trie to ByteBuffer.
for (TrieNode* node : orderedNodes) {
node->appendToByteBuffer(_exportTrie);
}
_exportTrie.align(_is64 ? 8 : 4);
}
對(duì)于動(dòng)態(tài)庫(kù),有幾個(gè)易于理解的公共符號(hào)比導(dǎo)出所有符號(hào)更易于使用,讓公共符號(hào)集少,私有符號(hào)集豐富,維護(hù)起來(lái)更加方便。更新時(shí)也不會(huì)影響較早版本。導(dǎo)出最少數(shù)量的符號(hào),還能夠優(yōu)化動(dòng)態(tài)加載程序到進(jìn)程的時(shí)間,動(dòng)態(tài)庫(kù)導(dǎo)出符號(hào)越少,dyld 加載就越快。
靜態(tài)存儲(chǔ)類是表明不想導(dǎo)出符號(hào)的最簡(jiǎn)單的方法。將可見(jiàn)性屬性放置在實(shí)現(xiàn)文件中的符號(hào)定義里,設(shè)置符號(hào)可見(jiàn)性也能夠更精確的控制哪些符號(hào)是公共符號(hào)還是私有符號(hào)。在編譯選項(xiàng) -fvisbility 可以指定未指定可見(jiàn)性符號(hào)的可見(jiàn)性。使用 -weak_library 選項(xiàng)會(huì)告訴編譯器將庫(kù)里所有導(dǎo)出符號(hào)都設(shè)為弱鏈接符號(hào)。使用 nm 的 -gm 選項(xiàng)可以查看 Mach-O 導(dǎo)出的符號(hào):
nm -gm header.dylib
結(jié)果如下:
(undefined) external ___cxa_atexit (from libSystem)
(undefined) external _printf (from libSystem)
(undefined) external dyld_stub_binder (from libSystem)
另外可以通過(guò)導(dǎo)出的符號(hào)文件,列出要導(dǎo)出的符號(hào)來(lái)控制導(dǎo)出符號(hào)數(shù)量,其他符號(hào)都會(huì)被隱藏。導(dǎo)出符號(hào)文件 list 如下:
_foo
_header
使用 -exported_symbols_list 選項(xiàng)編譯就可以僅導(dǎo)出文件中指定的符號(hào):
clang -dynamiclib header.c -exported_symbols_list list -o header.dylib
符號(hào)綁定范圍
符號(hào)可能存在與多個(gè)作用域級(jí)別。未定義的外部符號(hào)是在當(dāng)前文件之外的文件中,如下:
extern int count;
extern void foo(void);
私有定義符號(hào),其他模塊不可見(jiàn)
static int count;
私有外部符號(hào)可以使用 private_extern關(guān)鍵字:
__private_extern__ int count = 0;
指定一個(gè)函數(shù)為弱引用,可以使用 weak_import 屬性:
void foo(void) __attribute__((weak_import));
在符號(hào)聲明中添加 weak 屬性來(lái)指定將符號(hào)設(shè)置為合并的弱引用:
void foo(void) __attribute__((weak));
入口點(diǎn)
符號(hào)綁定結(jié)果放到 LC_DYSYMTAB 指定的 section,解析后的地址會(huì)放到 __DATA segment 的 __nl_symbol_ptr 和 __got 里。dyld 使用 Load Command 指定 Mach-O 中的數(shù)據(jù)以各種方式鏈接依賴項(xiàng)。Mach-O 的 Segment 按照 Load Command 中指定映射到內(nèi)存中。 初始化后,會(huì)調(diào)用 LC_MAIN 指定的入口點(diǎn),這個(gè)點(diǎn)是 __TEXT Segment 的 __text Section 的開(kāi)始。使用 __stubs 將 __la_symbol_ptr 指向 __stub_helpers,dyld_stub_binder 執(zhí)行解析,然后更新 __la_symbol_ptr 的地址。
Mach-O 和鏈接器之間是通過(guò) assembly trampoline 進(jìn)行的橋接,Mach-O 接口的 ABI 和 ELF 相同,但策略不同。macOS 在調(diào)用 dyld 前后都會(huì)保存和恢復(fù) SSE 寄存器。
動(dòng)態(tài)庫(kù)構(gòu)造函數(shù)和析構(gòu)函數(shù)
動(dòng)態(tài)庫(kù)加載可能需要執(zhí)行特殊的初始化或者需要做些準(zhǔn)備工作,這里可以使用初始化函數(shù)也就是構(gòu)造函數(shù)。結(jié)束的時(shí)候可以加析構(gòu)函數(shù)。
舉個(gè)例子,先定義一個(gè) header.c,在里面加上構(gòu)造函數(shù)和析構(gòu)函數(shù):
#include <stdio.h>
__attribute__((constructor))
static void prepare() {
printf("%s\n", "prepare");
}
__attribute__((destructor))
static void end() {
printf("%s\n", "end");
}
void showHeader() {
printf("%s\n", "header");
}
將 header.c 構(gòu)建成一個(gè)動(dòng)態(tài)庫(kù) header.dylib。
xcrun clang -dynamiclib header.c -fvisibility=hidden -o header.dylib
將 header.dylib 和 main.c 構(gòu)建成一個(gè)中間目標(biāo)文件 main.o。
xcrun clang main.c header.dylib -o main
運(yùn)行看結(jié)果
ming@mingdeMacBook-Pro macho_demo % ./main "hi"
prepare
hi
end
可以看到,動(dòng)態(tài)庫(kù)的構(gòu)造函數(shù) prepare 和析構(gòu)函數(shù) end 都執(zhí)行了。