iOS开发进阶三:MACH-O和符号

MACH-O文件格式

MACHO文件格式.png

Mach Header的最开始是 Magic Number,表示这是一个 Mach-O 文件,除此之外还包含一些Flags,这些flags会影响 Mach-O的解析。

Mach-O中的Load Command __TEXT 中记录了代码的大小、第一行代码的起始位置,dyld根据这些信息就能读取到__TEXT代码段中的代码。由于Mach-O中都是二进制数据,因此dyld根据结构体内存对齐规则逐个读取到Load Command

macho文件格式基本架构.png

作为Mac OS X程序的二进制接口(ABI),Mach-O文件格式提供中间物(构建过程中产生的)和最终存储有机器码和数据的文件。它被设计成一个灵活的BSD a.out的替代品。这种文件被编译器和静态链接器使用,并在运行时包含静态链接的可执行代码。随着Mac OS X目标的发展,动态链接的特性也被添加进来,从而为静态链接和动态链接的代码形成了单一的文件格式。

每个Mach-O文件的开始部分都有一个Header结构,这个Header标志这个文件是Mach-O文件。这个头部也包含了其他基础文件类型信息,标明目标体系结构,包含指定选项的标志,这些标志会影响对文件剩余部分的解释。

在header之后是一系列大小可变的加载命令,它们指定文件的布局和链接特征。在其他信息中,load命令可以指定:

  1. 文件在虚拟内存中的初始化布局
  2. 符号表的位置(被用来动态链接使用)
  3. 程序主线程的初始执行状态
  4. 包含主可执行文件导入符号定义的共享库的名称

在加载指令之后,所有的Mach-O文件都包含一个或多个段的数据。每个段有零个或多个section,段的每个section都包含特定类型的代码或数据。每个段定义了虚拟内存区域,被连接器用来映射到进程的地址空间中,段和section的确切数量和布局由load命令和文件类型指定。

Mach-O文件中,最后一个段是链接编辑段。此段包含链接编辑信息的表,如符号表、字符串表等,动态加载程序使用这些信息将可执行文件或Mach-O包链接到其附属库。

stabs取名于symbol table strings,因为开始的时候,调试信息是以字符串的形式存储在Unix的a.out目标文件的符号表中。 stabs以字符串的形式编码程序的信息。最开始的时候,stabs很简单,但是后来变得越来越复杂,难解,而且不一致。此外,stabs没有形成标准,文档也不够详细。

DWARF已经被广泛使用,包括GCCLLVM。DWARF也是基于嵌套结构存储调试信息。

段(segment)

段定义了Mach-O文件中的一个字节范围,以及地址和内存保护属性,当动态链接器加载应用程序时,这些字节被映射到虚拟内存中。因此,段总是与虚拟内存页对齐。一个段包含零个或多个节。

  • 静态连接器会创建一个__PAGEZERO段作为可执行文件的第一个段。这个段位于虚拟内存的0位置,而且没有分配任何保护权限,它们的组合会导致对NULL的访问,这是一种常见的C编程错误,会立即崩溃。__PAGEZERO段对于现在的架构就是一页虚拟内存页的大小(对于基于Intel和Power-PC内核的Mac计算机,一般是4096字节或者十六进制0x1000).因为__PAGEZERO段没有数据,在文件中没有占用任何空间(段命令中的文件大小是0)
  • __TEXT段包含了可执行代码和只读数据。允许内核直接从可执行文件映射到共享内存中,静态连接器设置这个段虚拟内存的权限为不允许写入。当段被映射到内存中时,它可以在所有对其内容感兴趣的进程之间共享。(这个主要用在frameworksbundles,和共享库,但是可以在Mac OS X中运行同一可执行文件的多个副本,这也适用于这种情况。)只读属性也意味着这些内存也组成的__TEXT段是不可以会写到磁盘中的。当内核需要释放物理内存的时候,他可以放弃一个或多个__TEXT,如果下次有需要,就从磁盘重新读取他们
  • __DATA段包含的是可写数据。静态连接器设置这段虚拟内存的权限为可读可写。因为是可写的,因为它是可写的,所以框架或其他共享库的数据段在逻辑上被复制到与该库链接的每个进程。当组成__DATA段的内存页可读可写时,内核将它们标记为“写时复制”;因此当一个进程写入这些页的其中一页时,该进程会收到当前进程私有的当前页备份。
    *__OBJC段包含了OC语言runtime支持库所用到的数据。
  • __IMPORT段包含了符号桩和指向可执行文件中未定义的符号的非懒加载指针。这个段仅在IA-32架构的目标可执行文件中生成。
  • __LINKEDIT段包含了动态连接器所用到的原始数据,例如符号、字符串、和重定位表记录

MACHO格式

查看MACHO的命令

1.使用objdump查看mach-header

MACH_PATH=${BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/${PRODUCT_NAME}

CMD = objdump --macho -private-header ${MACH_PATH}

2.使用otool查看mach-header

otool -l ${MACH_PATH}

otool命令执行的结果可读性要差很多。

3.查看 macho __text代码段

objdump --macho -d ${MACH_PATH}
查看代码段得到汇编看程序的执行过程.png

Xcode的编译过程

编译时做哪些工作:

  • 将代码汇编化,变成汇编代码或机器码。
  • 将代码进行归类,比如数据放在数据段,代码放到代码段;将动态库里面的符号先暂存起来。归类后,将这些信息放到重定位符号表。

为什么要放到重定位符号表?

  • 在生成.o文件的时候,整个目标文件的地址没有虚拟化,没有变成虚拟内存的地址。
  • 有些动态链接的外部符号没法找到,就在编译阶段暂时将它放到重定位符号表。

查看.o文件中需要重定位符号的命令

objdump --macho --reloc test.o
终端上显示重定向MACHO中的信息.png

重定位符号表保存了当前项目中用到的符号,可以通过检测.o文件的重定位符号表,来查看程序中对某种API的使用情况。

链接过程

处理整个编译情况,将多个目标文件.o合并到一起,就意味着每个目标文件的符号表和重定位符号表,最终都会合并到一张表中。最后再生成可执行文件EXEC

生成目标文件、链接器没有参与.png

编译链接的整个过程.png

链接的过程就是处理目标文件符号的过程。

符号

全局符号与本地符号

按全局符号和本地符号的类别来查看文件符号

CMD = objdump --macho --syms  ${MACH_PATH}

查看文件的全局符号与本地符号.png

代码中定义的全局变量,在MACHO中都是全局符号。不管是初始化或者未初始化的,都是全局符号。所有Static开头的,都变成了本地符号。全局符合和本地符号的区别在于可见性。

OC中可见性默认有两种:1、hidden 2、default

__attribute__将编译器支持的参数传递给编译器。

Visibility编译器上符号可见性设置

int hidden_y __attribute__((visibility("hidden"))) = 99;
double default_y __attribute__((visibility("default"))) = 100;


int global_init_value = 10;
double default_x __attribute__((visibility("hidden")));
//// 静态变量 -> 本地变量
static int static_init_value = 9;
static int static_uninit_value;
全局符号与本地符号.png

全局符号对整个项目可见,本地符号仅对当前文件可见。链接器默认采用二级命名空间,记录符号是属于哪个MACHO。


全局符号与本地符号含义.png

导入符号与导出符号

1.查看导出符号的命令

objdump --macho -exports-trie ${MACHO_PATH}
全局符号必定是导出符号.png

全局符号默认会生成导出符号。但是可以通过链接器管理它。

2.查看间接符号表

objdump --macho --indirect-symbols ${MACH_PATH}

间接符号表不可能被删除,也就是动态库中所有全局符号,都不能被删除。所有的全局符号变导出符号都无法被删除。再去脱符号时,也无法脱去全局符号。

间接符号.png

3.OC中的符号

可以通过执行导出符号命令,知道OC默认都是导出符号。即使没有在.h文件中声明,也一样是导出符号。

如果都是导出符号会产生什么问题?最终生成的IPA包会很大。如果要缩减包体积,我们要怎么做?

可以借助链接器,将不想暴露的符号声明为不导出符号

OTHER_LDFLAGS = $(inherited) -Xlinker -unexported_symbol -Xlinker _OBJC_CLASS_$_ViewController
OTHER_LDFLAGS = $(inherited) -Xlinker -unexported_symbol -Xlinker _OBJC_METACLASS_$_ViewController

弱引用与弱定义符号

1.弱引用符号

Weak Reference Symbol: 表示此未定义符号是弱引用。如果动态链接器找不到该符号的定义,则将其设置为0。链接器会将此符号设置弱链接标志。

// 弱引用
void weak_import_function(void) __attribute__((weak_import));

实际需求:当使用别人的符号时,这个符号有可能实现,也有可能不会实现。弱引用的作用是让编译能正常。在执行的时候再去寻找它的实现。

实际场景:使用__attribute__((weak_import))weak_import_function声明为若引用符号
此时项目中没有weak_import_function函数的实现

#import <Foundation/Foundation.h>
#import "WeakImportSymbol.h"

int main(int argc, char *argv[]) {
   if (weak_import_function) {
       weak_import_function();
   }
   return 0;
}

此时编译报错,提示未定义符号。

当导入.h头文件并使用符号时,类似于API的使用,只要找到符号的声明即可。即使函数没有被实现,也可以生成目标文件。但链接生成可执行文件时,需要知道符号的具体位置,如果函数没有被实现,会出现错误提示:未定义符号。

设置不检测弱引用符号

OTHER_LDFLAGS = $(inherited) -Xlinker -U -Xlinker _weak_import_function

通过-U参数,告诉链接器此符号是动态链接的,所以在链接阶段,即使它是未定义符号,忽略,不用管它。因为在运行时,动态链接器会自动找到它

此时项目可以正常编译成功,dyld运行起来的时候会自动寻找相应的符号。因为main函数中调用weak_import_function函数之前有if (weak_import_function)的判断;当动态链接器找不到该符号的定义,则将其设置为0。所以weak_import_function函数并不会被调用。

弱引用符号的作用

  • 将一个符号声明为弱引用符号,可以避免编译链接时报错。在调用之前增加条件判断,运行时也不会报错
  • 使用动态库的时候,可以将整个动态库声明为弱引用,此时动态库即使没有被导入,也不会出现未找到动态库的错误

2.弱定义符号

Weak defintion Symbol: 表示此符号为弱定义符号。如果静态链接器或动态链接器为此符号找到另一个(非弱)定义,则弱定义将被忽略。只能将合并部分中的符号标记为弱定义。

//弱定义
void weak_function(void)  __attribute__((weak));

使用__attribute__((weak))weak_function声明为弱定义符号。

#import "WeakSymbol.h"
#import <Foundation/Foundation.h>

void weak_function(void) {
   NSLog(@"weak_function");
}

此时weak_function是一个全局符号,同样也是导出符号。弱定义并不影响它的全局属性。

实际场景:在WeakSymbol.mmain.m中,都实现一个weak_function函数。同一个Project中,出现两个相同的全局符号,此时编译报错,提示出现重复符号。

将其中一个weak_function函数声明为弱定义符号,此时编译成功。

弱定义符号的作用:可以解决同名符号的冲突;链接器按照符号上下顺序,找到一处符号的实现后,其他地方的同名符号将被忽略。

Common Symbol

在定义时,未初始化的全局符号。例如:main.m文件中,未初始化的global_uninit_value全局变量,它就属于Common Symbol

打开main.m文件,定义两个同名的全局变量,一个初始化,另一个不进行初始化,这种操作并不会报错。

int global_init_value = 10;
int global_init_value;

Common Symbol的作用

  • 在编译和链接的过程中,如果找到定义的符号,会自动将未定义符号删掉
  • 在链接过程中,链接器默认会把未定义符号变成强制定义的符号

** 链接器设置:**

  • -d:强制定义Common Symbol
  • -commons:指定对待Common Symbol如何响应

重新导出符号

对于当前程序来说,NSLog属于系统动态库foundation的导出符号,存储在间接符号表中的未定义符号。

NSLog可以在当前程序使用,如果想让使用此程序的其他程序也能使用,就要将此符号重新导出。重新导出之后的符号会放在导出符号表中,此时才能被外界查看并使用。

-alias:只能给间接符号表中的符号创建别名,别名符号具有全局可见性。

OTHER_LDFLAGS=$(inherited) -Xlinker -alias -Xlinker _NSLog -Xlinker Cat_NSLog

_NSLog符号创建Cat_NSLog别名,使用nm -m ${MACH_PATH} | grep "Cat_NSLog"命令查看符号表,指定"Cat_NSLog"关键字。此时Cat_NSLog是一个间接外部符号,是_NSLog符号的别名。使用objdump --macho --exports-trie ${MACH_PATH}命令查看导出符号。Cat_NSLog为导出符号,并且标记为[re-export],代表重新导出符号。

重新导出符号的作用

  • 将一个间接符号表中的符号声明为重新导出符号,可以让使用此程序的其他程序也能使用
  • 当程序链接A动态库,而A动态库又链接B动态库时,B动态库对于程序来说是不可见的。此时可以使用重新导出的方式,让B动态库对程序可见

查看项目使用的三方库和符号等信息

通过链接器,可以查看当前项目中使用的三方库和符号等信息
-map:将所有符号详细信息导出到指定文件。打开xcconfig文件,添加OTHER_LDFLAGS配置项

OTHER_LDFLAGS=$(inherited) -Xlinker -map -Xlinker $(PROJECT_DIR)/export.txt

文件内包含了编译链接时生成的目标文件,项目中使用的三方库,还包含项目中的Sections和Symbols等信息

控制台断点相关的命令

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

推荐阅读更多精彩内容