Apple 操作系統(tǒng)可執(zhí)行文件 Mach-O

介紹

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 &section : _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í)行了。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,797評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,179評(píng)論 3 414
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 175,628評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 62,642評(píng)論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,444評(píng)論 6 405
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 54,948評(píng)論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,040評(píng)論 3 440
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 42,185評(píng)論 0 287
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,717評(píng)論 1 333
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,602評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,794評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,316評(píng)論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,045評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 34,418評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 35,671評(píng)論 1 281
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,414評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,750評(píng)論 2 370

推薦閱讀更多精彩內(nèi)容