iOS堆棧信息解析(Mach-O)

Mach-O文件

Mach-O格式全稱為Mach Object文件格式的縮寫

Mach-O文件類型分類:

1.Executable:應(yīng)用可執(zhí)行的二進(jìn)制文件,如.m/.h文件經(jīng)過編譯后會生成對應(yīng)的Mach-O文件
2.Dylib Library:動態(tài)鏈接庫
3.Static Library:靜態(tài)鏈接庫
4.Bundle:不能被鏈接 Dylib,只能在運(yùn)行使用dlopen()加載
5.Relocatable Object File:可重定向文件類型

Mach-O文件結(jié)構(gòu)

參考蘋果官方文檔,Mach-O文件結(jié)構(gòu)由Header,Load Commands,Data三部分組成


Mach-O結(jié)構(gòu)圖.jpg

Header

作用:快速確認(rèn)Mach-O文件的基本信息,如運(yùn)行環(huán)境,Load Commands概述。
數(shù)據(jù)結(jié)構(gòu)
32位架構(gòu)

struct mach_header {
    uint32_t    magic;      /* mach magic number identifier */
    cpu_type_t  cputype;    /* cpu specifier */
    cpu_subtype_t   cpusubtype; /* machine specifier */
    uint32_t    filetype;   /* type of file */
    uint32_t    ncmds;      /* number of load commands */
    uint32_t    sizeofcmds; /* the size of all the load commands */
    uint32_t    flags;      /* flags */
};

64位架構(gòu)

struct mach_header_64 {
    uint32_t    magic;      /* mach magic number identifier */
    cpu_type_t  cputype;    /* cpu specifier */
    cpu_subtype_t   cpusubtype; /* machine specifier */
    uint32_t    filetype;   /* type of file */
    uint32_t    ncmds;      /* number of load commands */
    uint32_t    sizeofcmds; /* the size of all the load commands */
    uint32_t    flags;      /* flags */
    uint32_t    reserved;   /* reserved */
};

magic:確定Mach-O文件運(yùn)行框架,如64位/32位
cpu:CPU類型,如arm
cpusubtype:對應(yīng)CPU類型的具體型號
filetype:文件類型
ncmds:加載命令條數(shù)
sizeofcmds:所有加載命令的大小
flags:保留字段
reserved:標(biāo)志位

案例說明
使用MachOView工具查看Mach-O文件,如下圖:

image.png

Mach-O文件運(yùn)行基本環(huán)境

  • cpu:x86,64位
  • 文件類型:可執(zhí)行二進(jìn)制文件
  • LoadCommand:15個LoadCommand,占用大小位1360

Load commands

作用:
Load Commands 加載指令,告訴加載器如何處理二進(jìn)制數(shù)據(jù),處理對方分別為內(nèi)核,動態(tài)鏈接器等。加載指令緊跟在Header后的加載命令區(qū)。Load Commands 加載指令個數(shù)及大小在Header中定義( commands 的大小總和即為 Header->sizeofcmds 字段,共有 Header->ncmds 條加載命令)。

數(shù)據(jù)結(jié)構(gòu):
通用結(jié)構(gòu)

struct load_command {
    uint32_t cmd;       /* type of load command */
    uint32_t cmdsize;   /* total size of command in bytes */
};
  • cmd:指令類型
  • cmdsize: 指令長度
    Command以LC開發(fā),不同的加載指令有不同的專有結(jié)構(gòu),但必包含cmd,cmdsize兩個字段,用來定義指令類型以及指令長度

指令類型:
LC_SEGMENT/LC_SEGMENNT_64

  • 作用:將對應(yīng)段中的數(shù)據(jù)加載并映射到進(jìn)程的內(nèi)存空間
  • 結(jié)構(gòu):
struct segment_command_64 { /* for 64-bit architectures */
    uint32_t    cmd;        /* LC_SEGMENT_64 */
    uint32_t    cmdsize;    /* includes sizeof section_64 structs */
    char        segname[16];    /* segment name */
    uint64_t    vmaddr;     /* memory address of this segment */
    uint64_t    vmsize;     /* memory size of this segment */
    uint64_t    fileoff;    /* file offset of this segment */
    uint64_t    filesize;   /* amount to map from the file */
    vm_prot_t   maxprot;    /* maximum VM protection */
    vm_prot_t   initprot;   /* initial VM protection */
    uint32_t    nsects;     /* number of sections in segment */
    uint32_t    flags;      /* flags */
};

主要字段介紹:
cmd:指令類型,LC_SEGMENT_64 (固定)
cmdsize:指令長度
segname:段名,如_PAGEZERO(保留數(shù)據(jù)空間,4g),_TEXT(代碼數(shù)據(jù)),_DATA(函數(shù)指針),_LINKDIT(鏈接數(shù)據(jù))
vmaddr:段的虛擬內(nèi)存起始地址
vmsize:段的虛擬內(nèi)存大小
fileoff:段在文件中的偏移量
filesize:段在文件中的大小

  • MachOView實(shí)例:


    image.png

段數(shù)據(jù)加載并映射到內(nèi)存過程:從fileo ff處加載file size大小到虛擬內(nèi)存vmaddr處,并占用虛擬內(nèi)存大小為vmsize,一般情況下段名_TEXT,_DATA的file size=vmsize;段名_LINKDIT的file size<vmsize(動態(tài)鏈接申請的內(nèi)存控件要大于文件大小)

LC_SEGMENT_TEXT

  • 作用:代碼段,其中_stub_helper用于關(guān)聯(lián)函數(shù)bind/rebind

LC_SEGMENT_DATA

  • 作用:可讀/可寫的數(shù)據(jù)段,函數(shù)指針,其中_la_symbol_ptr動態(tài)函數(shù)個數(shù),及相對動態(tài)符號表的偏移量

LC_SEGMENT_LINKEDIT

  • 作用:動態(tài)鏈接加載指令,支持動態(tài)鏈接dyld,該段長度覆蓋符號表等數(shù)據(jù)(計算鏈接時程序的基址),符號表,動態(tài)符號表,字符串表段中定義的offset偏移量都是基于_LINKEDIT的vm_add
  • 結(jié)構(gòu):與LC_SEGMENT一致
  • MachOView實(shí)例:


    image.png

LC_SYMTAB

  • 作用:符號表信息,解析函數(shù)名
  • 結(jié)構(gòu):
struct symtab_command {
    uint32_t    cmd;        /* LC_SYMTAB */
    uint32_t    cmdsize;    /* sizeof(struct symtab_command) */
    uint32_t    symoff;        /* symbol table offset */
    uint32_t    nsyms;        /* number of symbol table entries */
    uint32_t    stroff;        /* string table offset */
    uint32_t    strsize;    /* string table size in bytes */
};
  • 主要字段說明:
    symoff:符號表偏移量,如一個函數(shù)對應(yīng)一個符號
    nsyms:符號表中表個數(shù)
    stroff:字符串表偏移量,連續(xù)的內(nèi)存空間用來存儲函數(shù)名
    strsize:字符串表的大小

LC_DYSYMTAB

  • 作用:動態(tài)符號表信息,地址值為動態(tài)函數(shù)相對符號表的索引,_la_symbol_ptr對應(yīng)的cmd可以換算出第一個動態(tài)函數(shù)對應(yīng)動態(tài)符號表的初始地址,其次存儲是連續(xù),結(jié)構(gòu)長度固定的,可以通過遍歷獲取所有動態(tài)函數(shù)的對應(yīng)的符號表索引
  • 結(jié)構(gòu):
struct symtab_command {
    uint32_t cmd;    /* LC_DYSYMTAB */
    uint32_t cmdsize;    /* sizeof(struct dysymtab_command) */
    uint32_t indirectsymoff; /* file offset to the indirect symbol table */
    uint32_t nindirectsyms;  /* number of indirect symbol table entries */
    .....
}
  • 主要字段說明:
    indirectsymoff:動態(tài)符號表偏移量
    nindirectsyms:動態(tài)符號表中表個數(shù)

ASLR隨機(jī)地址控件__

ASLR:Address space layout randomization,將可執(zhí)行程序隨機(jī)裝載到內(nèi)存中,這里的隨機(jī)只是偏移,而不是打亂,具體做法就是通過內(nèi)核將 Mach-O的段“平移”某個隨機(jī)系數(shù)。slide 正是ASLR引入的偏移

加載指令相互關(guān)系

作用

  • LC_SEGMENT(LC_LINKEDIT):換算程序基址
  • LC_SYMBOL:符號表(symoff偏移地址,nsyms符號個數(shù)),字符串表(stroff偏移地址,strsize大小)
  • LC_DYSYMBOL:動態(tài)符號表(indirectsymoff偏移地址,nindirectsyms符號個數(shù))
  • ASLR:加載到進(jìn)程的內(nèi)存偏移量

地址換算

//鏈接時程序基址
uintptr_t linkedit_base = linkedit_segment->vmadd - linkedit_segment->fileoff + (unintptr)slide

//符號表地址 = 基址 + 符號表偏移量
nlist_t *symbol = (nlist_t *)(linkedit_base + symtab_cmd-> symoff)

//字符串表地址 = 基址 +字符串表偏移量
char *strtab = (char *)(linkedit_base + symtab_cmd->stroff)

//動態(tài)符號表地址  = 基址 + 動態(tài)符號表偏移量
uint32_t *indirect_symtab = (uint32_32 *)(linkedit_base + dysymtab_cmd->indirectsymoff)

補(bǔ)充

  • LC_DYSYMTAB(動態(tài)符號表)是LC_SYMTAB(符號表)的子集,LC_DYSYMTAB存儲動態(tài)符號對應(yīng)在LC_SYMTAB的序號
  • LC_SEGMENT(_TEXT):存儲函數(shù)代碼,stub_help取得綁定信息(即函數(shù)地址與符號的對應(yīng)關(guān)系)
  • LC_SEGMENT(_DATA):函數(shù)地址存放在LC_SEGMENT(_DATA)內(nèi)的la_symbol_ptr,第二次調(diào)用,直接通過la_symbol_ptr找到函數(shù)地址,不在需要繁瑣的獲取函數(shù)地址過程。
  • la_symbol_ptr對應(yīng)section中的reserved1字段指明相對應(yīng)的indirect symbol table起始的index

程序運(yùn)行時,動態(tài)鏈接的函數(shù)a地址記錄在LC_SEGMENT(_DATA )的la_symbol_ptr中。初始化時,程序只知道函數(shù)a的符號名而不知道函數(shù)的實(shí)現(xiàn)地址;首次調(diào)用,程序通過LC_SEGMENT(_TEXT)的stub_help獲取綁定信息;dyld_stub_binder來更新la_symbol_ptr中的符號實(shí)現(xiàn)地址;再次調(diào)用,直接通過la_symbol_ptr獲取函數(shù)實(shí)現(xiàn)

image.png

補(bǔ)充:

動態(tài)函數(shù)調(diào)用過程
程序初始化:
函數(shù)符號名:已存在,并以nlist結(jié)構(gòu)存儲,但nlist->n_value=0(函數(shù)地址沒有值)
函數(shù)實(shí)現(xiàn)地址:已存在,存放在mach-o文件cmd加載指令(SegmentName=_DATA,SectionName=__la_symbol_ptr)
函數(shù)符號名與實(shí)現(xiàn)地址關(guān)聯(lián)(未關(guān)聯(lián)):即補(bǔ)全nlist信息

程序運(yùn)行:
函數(shù)首次調(diào)用:
函數(shù)符號名與實(shí)現(xiàn)地址進(jìn)行關(guān)聯(lián),nlist->n_value賦值函數(shù)地址

函數(shù)再次調(diào)用:
通過關(guān)聯(lián)信息,通過函數(shù)符號直接獲得函數(shù)實(shí)現(xiàn)地址

關(guān)聯(lián)過程如下:
介紹關(guān)聯(lián)過程前,簡單介紹幾個基礎(chǔ)知識

  • MACH-O的加載指令(__DATA,__la_symbol_ptr)
    作用:存儲了對應(yīng)MACH-O文件所有的動態(tài)函數(shù)實(shí)現(xiàn)地址,且以連續(xù)內(nèi)存地址存儲
    字段解析:
    Szie:指令大小,可通過換算動態(tài)函數(shù)個數(shù),如Szie/Szieof(void*)
    Reserved1:相對動態(tài)符號表dlsymbol_Tab偏移量,用來換算第一個動態(tài)函數(shù)對應(yīng)動符號表的地址

  • Indirect Symbol Table
    作用:連續(xù)的存儲動態(tài)函數(shù)對應(yīng)符號表的索引
    地址尋址:第一個動態(tài)符號尋址 = 動態(tài)符號基址(vm_add+slide) + Reserved1
    地址值:對應(yīng)符號Symbol的索引值index

  • Symbol Table
    作用:以nlist結(jié)構(gòu)連續(xù)存儲函數(shù)符號與地址的關(guān)聯(lián)關(guān)系,nlist包含函數(shù)實(shí)現(xiàn)地址,字符串偏移地址(計算函數(shù)名)
    地址尋址:符號表基址(vm_add+slide)+ index
    地址值:nlist值

  • String Table
    作用:存儲MACH-O文件所有的函數(shù)名,char*,每個函數(shù)名以\0分隔
    地址尋址:字符串表基址(vm_add+slide)+ 偏移量(nlist-> n_un->n_str)
    地址值:函數(shù)名

關(guān)聯(lián)過程
1.遍歷Mach-O文件下的所有LoadCommand,尋址目標(biāo)cmd,搜索條件(SegmetName=__DATA,SectionName=__la_symbol_ptr),目標(biāo)cmd存儲了動態(tài)函數(shù)個數(shù),及第一個動態(tài)函數(shù)相對動態(tài)符號表偏移量
2.動態(tài)函數(shù)個數(shù)及動態(tài)符號表偏移量:動態(tài)函數(shù)個數(shù)=cmd->Szie/Szieof(void*);
動態(tài)符號表偏移量=cmd->reserved1
3.動態(tài)函數(shù)符號表尋址:第一個動態(tài)符號地址=動態(tài)符號表基址_LC_ DYSYMTAB->vm_add+slide+ reserved1;由于動態(tài)符號表連續(xù)存儲動態(tài)函數(shù)符號,可遍歷所有動態(tài)函數(shù)符號地址;地址值存儲對應(yīng)動態(tài)函數(shù)的符號表索引
4.關(guān)聯(lián)建立,補(bǔ)全nlist結(jié)構(gòu)體: 函數(shù)地址 + reserved1--->動態(tài)符號表尋址--->符號表index--->nlist信息補(bǔ)全

Data

section對應(yīng)SEGMENTD的DATA數(shù)據(jù)
Section的數(shù)據(jù)結(jié)構(gòu)

struct section { /* for 32-bit architectures */
    char        sectname[16];   /* name of this section */
    char        segname[16];    /* segment this section goes in */
    uint32_t    addr;       /* memory address of this section */
    uint32_t    size;       /* size in bytes of this section */
    uint32_t    offset;     /* file offset of this section */
    uint32_t    align;      /* section alignment (power of 2) */
    uint32_t    reloff;     /* file offset of relocation entries */
    uint32_t    nreloc;     /* number of relocation entries */
    uint32_t    flags;      /* flags (section type and attributes)*/
    uint32_t    reserved1;  /* reserved (for offset or index) */
    uint32_t    reserved2;  /* reserved (for count or sizeof) */
};

字段解釋
sectname:比如_text、stubs
segname:該section所屬的segment,比如__TEXT
addr: 該section在內(nèi)存的起始位置
size: 該section的大小
offset: 該section的文件偏移
align :字節(jié)大小對齊
reloff:重定位入口的文件偏移
nreloc: 需要重定位的入口數(shù)量
flags:包含section的type和attributes

附上mach-0簡單的邏輯圖

Mach-O.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。