iOS App启动优化(四):编译期插桩 && 获取方法符号工具
iOS App启动优化(五):收集符号 && 生成 Order Filepost
编译器插桩就是在代码编译期间修改已有的代码或生成新代码。ui
编译期时,在每个函数内部二进制源数据添加 hook
代码来实现全局 hook
效果。spa
编译期插桩会涉及关于 LLVM
的内容,可是对代码具体实现影响不大,想了解相关概念能够看看翻译
说白了咱们要跟踪到 每一个方法的执行,从而获取到启动时 方法执行的顺序,而后再按照这个顺序去编写order file
。
跟踪的具体实现会用到 clang
的 SanitizerCoverage
,这是什么东西??
遇到事情不要慌,先打开文档看一看 ~ clang文档
文档很重要,万一里面有 demo
呢?
LLVM
具备内置的简单代码覆盖率检测工具(
SanitizerCoverage
)
文档是个好东西~里面就有 example
。
没明白怎么用?问题不大~
Target -> Build Setting -> Custom Complier Flags -> Other C Flags
添加 -fsanitize-coverage=trace-pc-guard
我是在 viewController
里面进行的,把这两个方法复制进去
void __sanitizer_cov_trace_pc_guard_init(uint32_t *start,
uint32_t *stop) {
static uint64_t N; // Counter for the guards.
if (start == stop || *start) return; // Initialize only once.
printf("INIT: %p %p\n", start, stop);
for (uint32_t *x = start; x < stop; x++)
*x = ++N; // Guards should start from 1.
}
void __sanitizer_cov_trace_pc_guard(uint32_t *guard) {
if (!*guard) return;
void *PC = __builtin_return_address(0);
char PcDescr[1024];
printf("guard: %p %x PC %s\n", guard, *guard, PcDescr);
}
复制代码
执行代码看一看
这是什么...没看懂,仍是打个断点看一看吧
start
里面存的是一堆序号,stop
里面会不会也是序号呢?
看看 stop
验证一下,发现里面的并非序号,这就尴尬了...
思考了一会发现想知道最后一个序号,应该把 stop
地址往前移动了再查看!
向前挪4个字节看看
真找到了,start
和 stop
中间是 01 ~ 0e
,十进制的 1~14
。
这会不会是函数的序号呢?给他安排个函数试试看
0e
变成了
0f
,真的增长了一个。
看到这里可能以为~ hook
一下都能拿到这有啥了不得。来点新花样
block
c函数
0xf + 2 = 0x11
,数量增长了2~验证成功
可是还不够,我万一是混编咋办呢?得添加个 swfit
函数试试。
Target -> Build Setting -> Custom Complier Flags -> Other Swift Flags
添加
-sanitize-coverage=func
-sanitize=undefined
运行代码,输出地址
数量一会儿增长到了0x1a
,我猜想生成文件同时生成了很多自带方法,那么咱们屏蔽掉刚刚新建的block
、c函数
、oc函数
看看数量是否变成1a - 0x3
便可。
1a - 0x3 = 0x17
验证成功
上述 guard_init
方法里面能够获取到全部方法的数量,那么确定也有办法获取方法具体的相关信息。重点就是接下来要分析的__sanitizer_cov_trace_pc_guard
。
添加点击方法,调用一下刚刚添加的方法
运行代码,点击屏幕两次
每次点击最终输出 test
, 以此为界能够看到每次点击会进入 guard方法
8次,在[ViewController touchesBegan:withEvent:]
断点查看一下。
能够看到在调用方法的时候插入了 __sanitizer_cov_trace_pc_guard
方法。
这里实际上是方法内部代码执行的第一句,在此以前的代码行是在栈平衡和准备寄存器数据。
在执行了 __sanitizer_cov_trace_pc_guard
后断点,读取一下 lr
寄存器的内容
这里能够看到lr里面存储的是 [ViewController touchesBegan:withEvent:]
。
这就太神奇了,难道从新执行了一次吗?不是的,若是从新执行就会陷入死循环,很明显代码并无。
函数嵌套时,跳转函数 bl
会保存下一条指令的地址在 X30
,也就是lr
寄存器。
funcA
调用了 funcB
,在汇编里面会被翻译成 bl + 0x????
, 该指令会首先将下一条汇编指令的地址保存在 x30
寄存器, 而后在跳转到 bl
后面传递的指定地址去执行。
bl
跳转到某个地址的原理就是修改 pc
寄存器的值来指向到要跳转的地址。
并且实际上 funcB
中也会对 x29
、x30
寄存器的值作保护,防止子函数跳转其余函数覆盖 x30
的值。
当 funcB
执行返回指令 ret
时 , 会读取 x30
的地址跳回去, 就返回到上一层函数的下一步。
因此在这里实际上是执行了 __sanitizer_cov_trace_pc_guard
后返回到了原来 [ViewController touchesBegan:withEvent:]
的首行。
也就是说,咱们能够在 __sanitizer_cov_trace_pc_guard
函数里面拿到原方法的地址!来看看是怎么作到的。
重点在这个 __builtin_return_address
函数,它的做用其实就是去读取 x30
中所存储的要返回时下一条指令的地址。那么这个 PC
就是咱们要的方法地址!
导入头文件
#import <dlfcn.h>
复制代码
dlfcn.h
中有一个 dladdr()
方法,能够经过函数内部地址找到函数符号。
该方法须要用到结构体Dl_info
,里面还有一些其余信息~
获取到一个个 Dl_info
,打印出来看看。
sname
咱们要的方法符号了,并且顺序正是调用函数的顺序!
到这里经过编译器插桩获取方法符号已经成功了,那立刻到项目写 order file
就大功告成了?
no~too young too simple~ T.T