当前位置: 首页>移动开发>正文

通过LinkMap来了解Mach-O

一、ipa文件中查看可执行文件

我们打包出来的ipa,使用unzip命令去解压后,然后解压

unzip xxxx.ipa // 解压xxxx.ipa到当前文件夹
解压ipa后显示包内容看到主工程可执行文件(各个动态库的可执行文件在Framework里面)
通过LinkMap来了解Mach-O,第1张
解压ipa后的可执行文件.png

我们想知道可执行文件里面有什么?

二、查看有哪些内容链接进了可执行文件

我们可以通过LinkMap来获知各个模块链接到可执行文件中的大小,具体方法见 LinkMap初探。
在找到这个linkMap的文件后,复制到桌面,然后可以用xcode打开它,查看到这个文件的结构:

# Path // 可执行文件的路径
# Arch: arm64 // 可执行文件支持的架构
# Object files: // 链接的目标文件
# Sections: // __Text(程序代码)和__DATA(已经初始化的变量)
# Symbols: // 程序的符号(类名、变量名、方法名)
# Dead Stripped Symbols: // 裁剪掉的Dead符号
  • 关于Object files =》类表
类表
通过LinkMap来了解Mach-O,第2张
类表.png

可以看到这一部分是各个库和工程文件生成的.o文件,会包括用到的系统动态库,三方动态库.tbd。[num]指的是序号,类是按照顺序保存的,后续可以通过序号查到具体对应的哪个类。

  • 关于Sections
段表
通过LinkMap来了解Mach-O,第3张
段表.png

包含代码段(__TEXT)和数据段(__DATA),内容细节如下:

代码段(`__TEXT`)下的各个部分含义:
1. __text: 代码节,存放编译后的机器码
2. __stubs:用于辅助做动态链接代码(dyld)
3. __stub_helper: 用于辅助做动态链接(dyld)。
当引用或调用另一个函数或符号时,需要使用 __stubs 和 __stub_helper 来进行动态链接过程。
4. __objc_stubs: 包含了所有声明过的 Objective-C 类名、超类名、类方法、实例方法以及成员变量等信息,并按照名称索引存储。
5. __const: 存储代码中的常量,通常是在编译时确定并且不可修改的
6. __objc_methname: objc的方法名称
7. __swift5_typeref:存储 Swift 类型信息,每个Swift类型都被分配一个类型描述符(Type Descriptor),用于表示类型的信息,如大小、布局、函数调用约定等。
当应用程序运行时,这些类型描述符被存储在 __swift5_typeref 中以供运行时访问。
8. __swift5_capture:用于支持 Swift 闭包。存储了有关捕获变量的详细信息,包括变量的类型信息、变量的地址、变量的访问级别等。
当闭包被执行时,它会动态地捕获变量并将变量的值存储在 __swift5_capture 数据结构中,以供闭包使用。
9. __cstring: 代码中包含的字符串常量,存储 C 风格的字符串常量,即以 ‘Symbols’ 结尾的字符数组。Swift的字符串常量也是保存在这里。
10. __swift5_reflstr:用于支持 Swift 反射机制。存储了 Swift 类型名称的字符串字面量,以便在运行时通过名称来查找和操作类型。
11. __swift5_fieldmd:存储 Swift 结构体和类的成员变量信息的部分。它包含了关于每个成员变量的元数据,包括成员变量的名称、类型和访问级别等信息。
12. __swift5_assocty:用于支持 Swift 关联对象(Associated Objects)机制的部分。
 存储了关联对象所需的元数据信息。这些元数据包括关联对象的键和值的类型信息、内存管理方式、对应的属性名称等等。
13. __swift5_proto:存储 Swift 协议元数据信息。协议的名称、方法列表、关联类型、继承关系等。
14. __swift5_types:存储 Swift 类型的元数据信息。
包含了关于每个类型的相关信息,例如类型的名称、成员列表、属性、方法、协议遵循关系等。
15. __swift5_builtin:存储 Swift 内置函数(Built-in Function)的元数据信息的部分。例如函数的名称、参数类型、返回类型、内存布局等。
内置函数是一种预定义的函数库,由编译器在编译时自动生成并嵌入到应用程序的可执行文件中。这些函数库提供了高效和优化的实现,用于支持常用的操作和计算任务。
运行时系统可以通过访问 __swift5_builtin 中的元数据,了解内置函数的使用和特征,并直接调用这些函数来实现高效的计算和操作。
16. __objc_classname: objc的类名称。
17. __objc_methtype: objc的方法类型。该信息描述了方法的参数和返回值类型。
18. __gcc_except_tab:存储异常处理表(Exception Handling Table)的部分。使用GCC编译器生成的异常处理表的相关信息。
异常处理表包含了程序中的异常处理块的布局和相关信息,包括异常处理块的起始地址、结束地址以及相关的异常类型信息。
19. __ustring:存储 NSString 对象的字符串常量。
20. __swift5_mpenum: 存储 Swift 5 中关于 Mirror 的 MPEnum 值的部分。
Mirror 的设计目的是提供一种通用且强大的反射机制,可以以一种统一的方式访问各种 Swift 对象的元类型和属性信息。MPEnum 是 Mirror 中的枚举值,用于描述 Mirror 的操作类型、访问属性类型等。
21. __swift5_protos: 存储应用程序中定义的所有协议。包含了在应用程序中定义的Swift协议的相关信息,用于支持Swift代码中的协议使用。
22. __swift5_entry:存储了 Swift 应用程序的入口点的信息,被操作系统调用的第一个函数。
23. __unwind_info:存储异常处理信息。堆栈帧恢复的简化信息。包括函数调用、堆栈展开和恢复等。
它是用于处理对其所属函数的堆栈帧的恢复所需的精简版本,记录了发生异常时必要的信息,例如堆栈展开的正确路径和恢复点。在异常处理的执行过程中,__unwind_info 帮助程序正确地进行堆栈展开以及跳转到异常处理代码。
24. __eh_frame:存储异常处理信息。记录了程序中所有活动帧的信息,以及如何展开和恢复它们。活动帧是指正在运行中的所有函数所占用的堆栈空间。
__eh_frame 信息的作用在于,在发生异常或错误时,展开堆栈以查找相关的堆栈帧,然后逆序恢复堆栈帧并传递控制流到适当的异常处理器。


数据段(`__DATA`)下的各个部分含义:
1. __got:Global Offset Table,全局偏移表,存储全局变量和函数指针的地址。
通常,当应用程序中需要访问全局变量或函数指针时,编译器会生成间接的跳转指令,通过__got 来获取实际的内存地址。这种间接跳转的方式,可以保证全局数据的地址在程序运行时才能确定,并可以进行动态链接或通过动态库来进行更新。
__got 段在应用程序中是可写的,因此允许程序在运行时动态地修改__got 中存储的地址。
2. __la_symbol_ptr:(Linker-As-Needed Symbol Pointers)是用于存储动态库中未定义的符号的指针.
在 iOS 应用程序中,动态库中的符号在编译应用程序时是未定义的,因此无法直接访问和链接。为了解决这个问题,动态链接器采用了懒加载的策略。它通过在 __la_symbol_ptr 中存储符号名的指针,以在程序运行时动态地解析和链接这些未定义的符号。
当应用程序需要使用动态库中的某个函数时,如果该函数在编译时未找到定义,编译器会将其转化为对 __la_symbol_ptr 中相应符号名指针的引用。在运行时,当第一次调用这个函数时,动态链接器会根据__la_symbol_ptr 中记录的符号名指针,通过符号解析的过程找到正确的函数地址,并更新到符号名指针所指向的位置。
3. __mod_init_func:存储了模块的初始化函数的地址。__mod_init_func变量的使用可以通过编译器的选项进行控制,例如在GCC中可以使用-finit-function选项开启或关闭对该变量的使用。
通过使用__attribute__((constructor))属性标记一个函数,可以将该函数指定为模块的初始化函数。这样,当模块被加载时,编译器会自动将这些被标记的函数的地址添加到__mod_init_func变量中。当模块初始化完成后,操作系统会调用__mod_init_func中存储的函数地址,以完成模块的初始化。
4. __const:全局变量或静态变量的常量,这些常量通常是在程序运行期间可以修改的
5. __cfstring:使用core foudation字符串
6. __objc_classlist: objc类列表,保存类信息,映射了__objc_data的地址
7. __objc_nlclslist:Objective-C运行时的一个标记,用于指示非懒加载类的列表(非懒加载类分为三种情况:自己实现了+ load方法、子类实现了+
load方法(因为子类初始化会初始化父类)、分类实现了+load方法。)
8. __objc_catlist: category的列表
9. __objc_nlcatlist:非懒加载category的列表
10 __objc_protolist:协议列表
11. __objc_imageinfo :Objective-C类和元数据的版本信息。
12. __objc_const:objc常量,保存objc_classdata结构体数据,用于映射类名、方法等地址。
13. __objc_selrefs:引用到的OC方法
14. __objc_protorefs:引用到的oc协议
15.__objc_classrefs:引用到的oc类
16. __objc_superrefs:引用的超类
17. __objc_ivar: oc的ivar指针,存储属性
18. __objc_data:objc的数据。用于保存类需要的数据,最主要的内容是映射__objc_const的地址,用于找到类的相关数据。
19 __data:保存了Objective-C类和元数据的地址信息。这些信息是Objective-C运行时用于动态类型识别、消息发送等操作的关键数据。
通过这些数据,Objective-C运行时能够找到对应的类和方法,以实现动态类型识别、消息发送等功能。
20. __swift_hooks: 存储Hook函数的地址和相关信息,用于在运行时进行函数hook和方法调用转发。
Swift编译器生成的Hook函数在运行时与Swift对象模型一起使用,从虚表(vtable)或协议(protocol)中查找函数地址。
21. __swift51_hooks: Swift 5.1引入了一些新的特性,包括@propertyWrapper和Result类型等,因此__swift51_hooks很可能是与这些新特性相关的实现细节,其中包括函数hook的地址和相关信息。
22. __s_async_hook: 与异步操作的处理和监控有关,支持异步操作的钩子(hook)函数。
23. __swift56_hooks: Swift 5.6引入了一些新的特性和改进,包括优化的构造器委托、异步取消、新的方法调用语法等,因此__swift56_hooks很可能是与这些新特性相关的实现细节和运行时支持。根据应用程序代码的具体实现,它可能包含不同的hook函数地址和信息。
24. __common:用于实现全局变量的共享和函数重载。__common段中的符号是未初始化的全局变量和弱符号,在链接时再放入bss段
25. __bss: 存放程序中未初始化的全局变量和静态局部变量。bss段不占用物理文件尺寸,但占用内存空间;data段占用物理文件,也占用内存空间。
当程序读取data段的数据时,系统会发生缺页中断,从而分配相应的物理内存;当程序读取bss段的数据段时,内核会将其转到一个全零页面,不会发生缺页中断,也不会为其分配相应的物理内存1。
  • 关于Dead Stripped Symbols

    通过LinkMap来了解Mach-O,第4张
    符号表.png

    这里记录着每个方法及占用的大小。
  • 关于Mach-O

    通过LinkMap来了解Mach-O,第5张
    未使用的符号.png

Dead Stripped Symbols文件中,Dead Stripped Symbols(被标记为死代码并剔除的符号)是指在链接(linking)过程中被认定为不会被使用的符号。这些符号包括函数、变量和其他命名实体。

在编译和链接过程中,编译器和链接器会根据代码的引用关系来决定是否将符号包含在最终生成的可执行文件中。如果一个符号在整个代码中没有被引用到(即不会被使用),链接器会将其标记为死代码,并将其从可执行文件中剔除。

剔除这些死代码符号有助于减小最终生成的可执行文件的大小,优化代码的执行效率,同时减少了可执行文件的加载和运行时的资源占用。

需要注意的是,符号的剔除是由链接器根据编译器和链接器的配置和参数进行的。编译器和链接器会根据代码的引用关系进行静态分析,以确定是否将某个符号标记为死代码并剔除。因此,死代码符号的剔除是在链接阶段进行的,而不是在运行时动态进行的。

可以查看文章LinkMap结构分析

  • 关于================================================================================ demoData/TestCleanPackage-LinkMap-normal-x86_64.txt各模块体积汇总 ================================================================================ Creating Result File : demoData/BaseLinkMapResult.txt AMapNaviKit 20.86M AppDelegate.o 0.01M ViewController.o 0.00M TestCleanPackage.app.xcent 0.00M UnUsedClass.o 0.00M main.o 0.00M libobjc.tbd 0.00M linker synthesized 0.00M Foundation.tbd 0.00M UIKit.tbd 0.00M 总体积: 20.87M , 死的裁剪符号,请查看文章iOS:静态库和dead code strip

我们可以使用一个工具 LinkMapParser 来统计LinkMap文件中各个模块占用的大小。比如下面的统计:

// 查看一个LinkMap文件
python parselinkmap.py $map_link_file_path

// 查看并对比两个LinkMap文件
python parselinkmap.py $base_map_link_file_path $target_map_link_file_path

LinkMapParser工具查看LinkMap文件统计的指令:


上述文件中包含项目中的.o文件、静态库、动态库,可以根据情况进行瘦身优化, 比如上述的AMapNaviKit占用了20M,如果项目不需要的话,就可以移除这个库。


https://www.xamrdz.com/mobile/48y1882445.html

相关文章: