文件项目mach(文件函数项目代码加载)「项目文件包含toolsversion=15.0」

01认识mach-o的必要性了解mach-o的结构可以帮助认识系统加载二进制文件的动态链接和静态链接
应用层面,使用initialize的c++函数计算启动时间耗时也需要以mach-o的结构知识为铺垫
还可以用在使用clang自注册启动任务上
后续会一一展开说明
02mach-o的定义mach-o是mach object的缩写,是存储程序或库的标准格式
app的mach-o又称为可执行文件,静态库的.a文件也为mach-o文件,还有诸如此类的一些文件
.o目标文件:MH_OBJECT静态库文件.a : MH_OBJECT可执行文件:MH_EXECUTE动态库:MH_DYLIBdyld:MH_DYLINKER符号信息:MH_DSYM
可在loader.h的源码中看到全部的mach-o文件
想要深入了解mach-o,可以自己创建一个,在xocde的build setting --> mach-o type 下选择类型,依据xcode的提示步骤可以创建出
如果已经有了文件,想知道是否为mach-o,也可以使用xcode打开文件,在build setting --> mach-o type下查看属于哪种类型
xcode的查看示意见下图:03mach-o的结构查看mach-o的内部结构需要借助于工具mach-o View下载地址
目前下载下来之后需要在mac上运行使用
举例,使用mach-o View查看app的可执行文件,首先需要编译项目,这样会生成.app文件,然后项目中搜索.app:右键show in finder,就会找到app包
如下图:查找可执行文件,文件以项目名称命名的
下图中第一个就是:通过打开mach-o View可以看到,mach-o分为三大部分,无论是什么类型的mach-o文件,都分为3大部分
mach-o header 描述了Mach-o的cpu框架以及加载命令等信息;load Commands 记录虚拟内存中的布局例如有哪些段,段从哪开始,段占用多大空间;data 记录段的具体数据
如下图,三大部分在mach-o中分布:1、第一部分:mach-o Header 详解mach-o header 的结构如下图中红框展示:magic number :系统加载器通过该字段判断文件适用于32位还是64位;cpu type:cpu类型,该字段确保系统可以将合适的二进制文件在当下架构下进行,为x86,arm64等;file type :说明文件类型(可执行文件、库文件、核心转储文件、内核扩展文件、DYSM文件、动态库等)mach-o为MH-EXECUTE.;number of load command 说明加载命令的条数;size of load commands 表示加载命令的大小;如上所述,header介绍了文件的基础信息
2、第二部分:mach-o内容mach-o的内容部分分为load commands和 data
load commands 如图所示:每一个命令的含义下表:命令名称命令含义LC_SEGMENT_64将文件中的段映射到进程地址空间LC_DYLD_INFO_ONLYdyld相关信息LC_SYMTAB加载全局符号表信息LC_DYSYMTAB动态链接符号表信息LC_DYLD_INFO_ONLYdyld相关信息LC_LOAD_DYLINKER加载一个动态链接器,也就是加载dyldLC_UUIDapp的uuidLC_VERSION_MIN_IPHONEOS支持最低系统版本LC_MAIN设置程序主线程的入口地址LC_LOAD_DYLIB(动态库名称)加载相应的动态库LC_FUNCTION_STARTS函数启示地址表LC_CODE_SIGNATURE代码签名loader.h文件中可查看命令的官方注释
data部分的内容如图所示:如图所示,data有2种段数据,一种为__TEXT段,一种为__DATA段
__text段是Mach-O文件中存储代码的一个特定段,它包含了程序的实际可执行代码
在__text段中,存储了程序的实际指令和函数定义
当程序被加载到内存中并执行时,操作系统会将__text段中的代码加载到内存中,并按照指令逐条执行,从而实现程序的功能
__text段通常是以只读方式存储在Mach-O文件中,以确保代码的完整性和安全性
这意味着在程序运行时,__text段中的代码是不可被修改的,这有助于防止恶意软件对程序代码进行篡改
__data段是用来存储程序的静态数据的一个特定段
__data段包含了程序中的静态全局变量、静态局部变量和其他静态数据,这些数据在程序运行时需要被初始化和使用
与代码段__text不同,__data段存储的是程序运行时需要进行读写操作的数据
__text各个段的含义:名称作用TEXT.text只有可执行的机器码TEXT.cstring去重后的C字符串TEXT.const初始化过的常量TEXT.stubs符号桩
本质上是一小段会直接跳入lazybinding的表对应项指针指向的地址的代码
TEXT.stub_helper辅助函数
上述提到的lazybinding的表中对应项的指针在没有找到真正的符号地址的时候,都指向这
TEXT.unwind_info用于存储处理异常情况信息TEXT.eh_frame调试辅助信息_objc_classname类名称objc_methlist方法列表__text段在mach-o中的释义:__data 各个段的含义:名称作用DATA.data初始化过的可变的数据DATA.nl_symbol_ptr非lazy-binding的指针表,dyld 加载会立即绑定DATA.la_symbol_ptrlazy-binding的指针表,每个表项中的指针一开始指向stub_helperDATA.const没有初始化过的常量DATA.mod_init_func初始化函数,在main之前调用DATA.mod_term_func终止函数,在main返回之后调用DATA.bss没有初始化的静态变量DATA.common没有初始化过的符号声明DATA.__objc_nlclslist实现了 load 方法的类__data 在mach-o中的展示:04mach-o的应用认识了mach-o,可以将其运用在统计启动时期c++ static initializer 阶段耗时
c++ static initializer 阶段系统做了什么,一个是c++的构造函数属性函数,一个是非基础类型的c++静态全局变量的创建(通常是类或结构体)
在构造函数上打断点,可以得到如图:从dyld的源码中可以看到doModeInitFunction()的具体执行
如下图所示:从dyld的源码中可以看出,取出mod_init_func section 中的元素并执行
可以看出,mod_init_func section中存储的是函数地址,类型为initialize.了解这些之后,自己写一个带有计时的start和end函数,并在中间调用源函数地址
然后hook mod_init_func 中的所有地址,并替换执行自己的函数
步骤如下:hook mod_init_func中的所有地址
因为__mod_init_func section 位于__DATAsegment.__DATA segment 是数据段,是可以在运行时被修改的
并且,+load方法的执行是在dyld读取这些initializer之前
所以hook mod_init_func中的所有地址是可行的;修改mod_init_func数据
利用getsectiondata获取到segment的每一个数据,将自己写的方法替换表中的方法;调用原来的initializer
自己的Initializer中逐个获取每一个原函数地址,调用并计算耗时获取
通过以上步骤,我们可以得出这一项的耗时,从而做出优化
认识mach-o,是注册启动任务的必备知识
做注册启动任务的必要性有两点
启动代码集中在AppDelegate中,代码逐渐臃肿,易读性降低,且代码之间耦合度高;各个业务方加启动任务,都需要启动业务配合
我们利用mach-o结构的__DATA可读写性
所以可以通过clang的section函数在编译阶段写入macho文件中一个__DATA段
__DATA段存储函数指针的指针
具体的使用步骤为:编写注册方法的宏,提供给外部使用;业务方注册任务,注册的每个时机都会在编译期间新增一个__DATA类型section,存储任务函数;App运行,在注册的时机函数中使用getsectbynamefromheader_64遍历取出相应Section中的函数,并依次执行
代码如下:static void Launch_Func_(void);\__attribute__((used, section("__DATA, "#period""))) static const void __Func__= Launch_Func_;\static void Launch_Func_(void)#define RegisterLaunchTaskOnWillFinishLaunchPeriod\ RegisterLaunchTask(willFinishLaunch)#define RegisterLaunchTaskOnDidFinishLaunchPeriod\ RegisterLaunchTask(didFinishLaunch)#define RegisterLaunchTaskOnDidFinishADPeriod\ RegisterLaunchTask(didFinishAD)#define RegisterLaunchTaskOnDidFinishHomepagePeriod\ RegisterLaunchTask(didFinishHome) 各个业务的使用代码如下:RegisterLaunchTaskOnDidFinishHomepagePeriod{ /do sth/}参考资料:1.https://everettjf.github.io/2017/02/06/a-method-of-hook-static-2.initializers/# https://github.com/fangshufeng/MachOView作者:李赞 来源:微信公众号:搜狐技术产品出处:https://mp.weixin.qq.com/s/c3bgebQUcXXetiFAdiXjsQ
文件项目mach(文件函数项目代码加载)
(图片来源网络,侵删)

联系我们

在线咨询:点击这里给我发消息