一、前言
上一篇的办法给到的是个数,但不是符号,个数并没有什么作用,甚至给了全部的符号也没什么用,由于二进制重排仅仅需求的是发动阶段所需求的符号,这就需求下面这个函数:
void __sanitizer_cov_trace_pc_guard(uint32_t *guard) {
}
增加断点,点击箭头,能够看到绿色框中的函数调用栈:
函数调用栈跟之前讲到的
app称号.LinkMap-normal-arm64.txt
文件里边的数据格式相同。
把上面断点过掉,给touchBegan
办法增加断点::
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
///
}
点击上面的绿色箭头:
这儿就出现了touchesBegan
,那大概推出下面这个函数是系统每调用一个办法,都会调用这个__sanitizer_cov_trace_pc_guard
函数:
void __sanitizer_cov_trace_pc_guard(uint32_t *guard) {
}
二、调试验证
验证是否为真给下面两个函数增加打印办法:
void test(void) {
NSLog(@"%s",__func__);
}
void (^block) (void) = ^{
NSLog(@"%s",__func__);
};
一起在touchesBegan
办法里边为增加打印和调用两个函数:
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
NSLog(@"%s",__func__);
test();
block();
}
这个追寻办法也增加打印:
void __sanitizer_cov_trace_pc_guard(uint32_t *guard) {
NSLog(@"%s",__func__);
}
cmd + r
发动,先清空打印,点击屏幕:
TraceDemo[20273:399962] __sanitizer_cov_trace_pc_guard
TraceDemo[20273:399962] -[ViewController touchesBegan:withEvent:]
TraceDemo[20273:399962] __sanitizer_cov_trace_pc_guard
TraceDemo[20273:399962] test
TraceDemo[20273:399962] __sanitizer_cov_trace_pc_guard
TraceDemo[20273:399962] block_block_invoke
能够看出先__sanitizer_cov_trace_pc_guard
再调用办法、函数、block
,这说明不管是办法、函数、block
它都会去回调这个函数,而且这个函数的调用是咱们在调用这个函数之前,也便是这个函数阻拦或许hook
了一切的办法、函数包含block
, 这就搞定了咱们没有其他操作就能阻拦到app发动时分调用了那些办法和函数
void __sanitizer_cov_trace_pc_guard(uint32_t *guard) {
NSLog(@"%s",__func__);
}
咱们在程序发动时分__sanitizer_cov_trace_pc_guard
阻拦到的办法函数:
把这些写入到
.order
文件里边这样二进制重排就搞定了
而_sanitizer_cov_trace_pc_guard
,这个函数是怎么做到这一点的?给_sanitizer_cov_trace_pc_guard
增加断点,在Xcode的Debug
挑选Debug WorkFlow
挑选显现汇编,挑选main
:
能够看到在main
之前,系统插入了_sanitizer_cov_trace_pc_guard
这个符号:
在AppDelegate
页面也是插入了这个符号:
在SceneDelegate
相同如此:
也便是说,在编译器clang
增加下面这个标记后,编译器会给函数办法前面都会调用_sanitizer_cov_trace_pc_guard
这个函数:
这样,咱们确实在打断点看到发动阶段的一切符号。
三、怎么把符号都打印出来
先翻开注释的代码:
void __sanitizer_cov_trace_pc_guard(uint32_t *guard) {
NSLog(@"%s",__func__);
if (!*guard) return;
void *PC = __builtin_return_address(0);
// char PcDescr[1024];
// printf("guard: %p %x PC %s\n", guard, *guard, PcDescr);
}
上面的PC
指的是上一个函数的地址,有了这个函数地址,就能够拿到这个函数的符号,这儿需求用到dladdr
函数:
#import <dlfcn.h>
dladdr(const void *, Dl_info *)
,这个函数能够获得一个函数的称号以及地址,第一个参数传入PC,第二个参数界说一个变量 Dl_info info
传进去:
Dl_info info;
dladdr(PC, &info);
检查Dl_info
,它是一个结构体:
typedef struct dl_info {
const char *dli_fname; /* Pathname of shared object */
void *dli_fbase; /* Base address of shared object */
const char *dli_sname; /* Name of nearest symbol */
void *dli_saddr; /* Address of nearest symbol */
} Dl_info;
dli_sname
便是所需求符号
删去其他打印,把调试代码改为如下:
void __sanitizer_cov_trace_pc_guard_init(uint32_t *start,
uint32_t *stop) {
static uint64_t N;
if (start == stop || *start) return;
printf("INIT: %p %p\n", start, stop);
for (uint32_t *x = start; x < stop; x++)
*x = ++N;
NSLog(@"%d",N);
}
void __sanitizer_cov_trace_pc_guard(uint32_t *guard) {
if (!*guard) return;
void *PC = __builtin_return_address(0);
Dl_info info;
dladdr(PC, &info);
printf("dli_sname -- %s\n",info.dli_sname);
}
运转:
dli_sname -- main
dli_sname -- -[AppDelegate application:didFinishLaunchingWithOptions:]
dli_sname -- -[SceneDelegate window]
dli_sname -- -[SceneDelegate setWindow:]
dli_sname -- -[SceneDelegate window]
dli_sname -- -[SceneDelegate scene:willConnectToSession:options:]
dli_sname -- -[SceneDelegate window]
dli_sname -- -[ViewController viewDidLoad]
dli_sname -- -[SceneDelegate sceneWillEnterForeground:]
dli_sname -- -[SceneDelegate sceneDidBecomeActive:]
得到了发动时分所需求的一切符号和发动次序,拿到这些符号之后,把它复制粘贴到.order
文件中,就能够实现之前需求的方针,便是拿到把发动所需求的符号和次序加载在前面的page
里边,就实现了二进制重排。在放进.order
文件之前,需求把重复的符号删掉,而且关于函数或许block
,需求在前面加个“_”
,这样整个clang
插桩就现已完结。
四、获取符号方式优化
可是,上面手动的办法不够灵活,应该让计算机去做这些操作,用代码完结上面的操作。
方针:
- 去掉重复的符号
- 如果是函数就前面加上
“_”
- 生成一个
.order
文件
那第一步便是要对下面的符号进行搜集,然后存储起来:
void __sanitizer_cov_trace_pc_guard(uint32_t *guard) {
// NSLog(@"%s",__func__);
if (!*guard) return;
void *PC = __builtin_return_address(0);
Dl_info info;
dladdr(PC, &info);
printf("dli_sname -- %s\n",info.dli_sname);
}
这就需求有一个全局的容器来寄存,而且这儿会涉及到多线程的状况,由于一个app发动不大可能只要一个线程在跑,由于函数、办法和
block
在哪个线程跑,这个获取符号的回调函数也在那个线程运转,所以在符号搜集的时分,需求考虑线程安全问题。
所以,这边运用线程安全的原子行列,导入头文件:
#import <libkern/OSAtomic.h>
界说一个全局容器结构体:
typedef struct {
void * pc;
void *next;
} SYNode;
然后在回调函数里边进行操作:
void __sanitizer_cov_trace_pc_guard(uint32_t *guard) {
if (!*guard) return;
void *PC = __builtin_return_address(0);
//拓荒空间
SYNode *node = malloc(sizeof(SYNode));
//赋值
*node = (SYNode){PC,NULL};
//结构体存入原子行列
OSAtomicEnqueue(&symbolList, node, offsetof(SYNode, next ));
}
offsetof
里边的next
是下一个存储方位的偏移量,这样咱们就把符号都放进了symbolList
里边,在合适的方位把它取出来就行。