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

Mach-O文件

Mach-O格式全称为Mach Object文件格式的缩写

Mach-O文件类型分类:

1.Executable:应用可执行的二进制文件,如.m/.h文件经过编译后会生成对应的Mach-O文件
2.Dylib Library:动态链接库
3.Static Library:静态链接库
4.Bundle:不能被链接 Dylib,只能在运行使用dlopen()加载
5.Relocatable Object File:可重定向文件类型

Mach-O文件结构

参考苹果官方文档,Mach-O文件结构由Header,Load Commands,Data三部分组成


Mach-O结构图.jpg

Header

作用:快速确认Mach-O文件的基本信息,如运行环境,Load Commands概述。
数据结构
32位架构

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位架构

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文件运行框架,如64位/32位
cpu:CPU类型,如arm
cpusubtype:对应CPU类型的具体型号
filetype:文件类型
ncmds:加载命令条数
sizeofcmds:所有加载命令的大小
flags:保留字段
reserved:标志位

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

image.png

Mach-O文件运行基本环境

  • cpu:x86,64位
  • 文件类型:可执行二进制文件
  • LoadCommand:15个LoadCommand,占用大小位1360

Load commands

作用:
Load Commands 加载指令,告诉加载器如何处理二进制数据,处理对方分别为内核,动态链接器等。加载指令紧跟在Header后的加载命令区。Load Commands 加载指令个数及大小在Header中定义( commands 的大小总和即为 Header->sizeofcmds 字段,共有 Header->ncmds 条加载命令)。

数据结构:
通用结构

struct load_command {
    uint32_t cmd;       /* type of load command */
    uint32_t cmdsize;   /* total size of command in bytes */
};
  • cmd:指令类型
  • cmdsize: 指令长度
    Command以LC开发,不同的加载指令有不同的专有结构,但必包含cmd,cmdsize两个字段,用来定义指令类型以及指令长度

指令类型:
LC_SEGMENT/LC_SEGMENNT_64

  • 作用:将对应段中的数据加载并映射到进程的内存空间
  • 结构:
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(保留数据空间,4g),_TEXT(代码数据),_DATA(函数指针),_LINKDIT(链接数据)
vmaddr:段的虚拟内存起始地址
vmsize:段的虚拟内存大小
fileoff:段在文件中的偏移量
filesize:段在文件中的大小

  • MachOView实例:


    image.png

段数据加载并映射到内存过程:从fileo ff处加载file size大小到虚拟内存vmaddr处,并占用虚拟内存大小为vmsize,一般情况下段名_TEXT,_DATA的file size=vmsize;段名_LINKDIT的file size<vmsize(动态链接申请的内存控件要大于文件大小)

LC_SEGMENT_TEXT

  • 作用:代码段,其中_stub_helper用于关联函数bind/rebind

LC_SEGMENT_DATA

  • 作用:可读/可写的数据段,函数指针,其中_la_symbol_ptr动态函数个数,及相对动态符号表的偏移量

LC_SEGMENT_LINKEDIT

  • 作用:动态链接加载指令,支持动态链接dyld,该段长度覆盖符号表等数据(计算链接时程序的基址),符号表,动态符号表,字符串表段中定义的offset偏移量都是基于_LINKEDIT的vm_add
  • 结构:与LC_SEGMENT一致
  • MachOView实例:


    image.png

LC_SYMTAB

  • 作用:符号表信息,解析函数名
  • 结构:
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:符号表偏移量,如一个函数对应一个符号
    nsyms:符号表中表个数
    stroff:字符串表偏移量,连续的内存空间用来存储函数名
    strsize:字符串表的大小

LC_DYSYMTAB

  • 作用:动态符号表信息,地址值为动态函数相对符号表的索引,_la_symbol_ptr对应的cmd可以换算出第一个动态函数对应动态符号表的初始地址,其次存储是连续,结构长度固定的,可以通过遍历获取所有动态函数的对应的符号表索引
  • 结构:
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:动态符号表偏移量
    nindirectsyms:动态符号表中表个数

ASLR随机地址控件__

ASLR:Address space layout randomization,将可执行程序随机装载到内存中,这里的随机只是偏移,而不是打乱,具体做法就是通过内核将 Mach-O的段“平移”某个随机系数。slide 正是ASLR引入的偏移

加载指令相互关系

作用

  • LC_SEGMENT(LC_LINKEDIT):换算程序基址
  • LC_SYMBOL:符号表(symoff偏移地址,nsyms符号个数),字符串表(stroff偏移地址,strsize大小)
  • LC_DYSYMBOL:动态符号表(indirectsymoff偏移地址,nindirectsyms符号个数)
  • ASLR:加载到进程的内存偏移量

地址换算

//链接时程序基址
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)

//动态符号表地址  = 基址 + 动态符号表偏移量
uint32_t *indirect_symtab = (uint32_32 *)(linkedit_base + dysymtab_cmd->indirectsymoff)

补充

  • LC_DYSYMTAB(动态符号表)是LC_SYMTAB(符号表)的子集,LC_DYSYMTAB存储动态符号对应在LC_SYMTAB的序号
  • LC_SEGMENT(_TEXT):存储函数代码,stub_help取得绑定信息(即函数地址与符号的对应关系)
  • LC_SEGMENT(_DATA):函数地址存放在LC_SEGMENT(_DATA)内的la_symbol_ptr,第二次调用,直接通过la_symbol_ptr找到函数地址,不在需要繁琐的获取函数地址过程。
  • la_symbol_ptr对应section中的reserved1字段指明相对应的indirect symbol table起始的index

程序运行时,动态链接的函数a地址记录在LC_SEGMENT(_DATA )的la_symbol_ptr中。初始化时,程序只知道函数a的符号名而不知道函数的实现地址;首次调用,程序通过LC_SEGMENT(_TEXT)的stub_help获取绑定信息;dyld_stub_binder来更新la_symbol_ptr中的符号实现地址;再次调用,直接通过la_symbol_ptr获取函数实现

image.png

补充:

动态函数调用过程
程序初始化:
函数符号名:已存在,并以nlist结构存储,但nlist->n_value=0(函数地址没有值)
函数实现地址:已存在,存放在mach-o文件cmd加载指令(SegmentName=_DATA,SectionName=__la_symbol_ptr)
函数符号名与实现地址关联(未关联):即补全nlist信息

程序运行:
函数首次调用:
函数符号名与实现地址进行关联,nlist->n_value赋值函数地址

函数再次调用:
通过关联信息,通过函数符号直接获得函数实现地址

关联过程如下:
介绍关联过程前,简单介绍几个基础知识

  • MACH-O的加载指令(__DATA,__la_symbol_ptr)
    作用:存储了对应MACH-O文件所有的动态函数实现地址,且以连续内存地址存储
    字段解析:
    Szie:指令大小,可通过换算动态函数个数,如Szie/Szieof(void*)
    Reserved1:相对动态符号表dlsymbol_Tab偏移量,用来换算第一个动态函数对应动符号表的地址

  • Indirect Symbol Table
    作用:连续的存储动态函数对应符号表的索引
    地址寻址:第一个动态符号寻址 = 动态符号基址(vm_add+slide) + Reserved1
    地址值:对应符号Symbol的索引值index

  • Symbol Table
    作用:以nlist结构连续存储函数符号与地址的关联关系,nlist包含函数实现地址,字符串偏移地址(计算函数名)
    地址寻址:符号表基址(vm_add+slide)+ index
    地址值:nlist值

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

关联过程
1.遍历Mach-O文件下的所有LoadCommand,寻址目标cmd,搜索条件(SegmetName=__DATA,SectionName=__la_symbol_ptr),目标cmd存储了动态函数个数,及第一个动态函数相对动态符号表偏移量
2.动态函数个数及动态符号表偏移量:动态函数个数=cmd->Szie/Szieof(void*);
动态符号表偏移量=cmd->reserved1
3.动态函数符号表寻址:第一个动态符号地址=动态符号表基址_LC_ DYSYMTAB->vm_add+slide+ reserved1;由于动态符号表连续存储动态函数符号,可遍历所有动态函数符号地址;地址值存储对应动态函数的符号表索引
4.关联建立,补全nlist结构体: 函数地址 + reserved1--->动态符号表寻址--->符号表index--->nlist信息补全

Data

section对应SEGMENTD的DATA数据
Section的数据结构

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在内存的起始位置
size: 该section的大小
offset: 该section的文件偏移
align :字节大小对齐
reloff:重定位入口的文件偏移
nreloc: 需要重定位的入口数量
flags:包含section的type和attributes

附上mach-0简单的逻辑图

Mach-O.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,013评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,205评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,370评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,168评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,153评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,954评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,271评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,916评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,382评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,877评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,989评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,624评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,209评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,199评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,418评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,401评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,700评论 2 345