为什么需求二进制重排?

由于iOS App 进程拜访内存时, 操作的内存都是虚拟内存,而不是直接访物理内存。虚拟内存和物理内存之间的映射是经过虚拟内存表。虚拟内存表的最小单位是页。iOS 内存页的巨细为 1appearance6K (macOS是apple 4k)。

iOS 进程冷启动之前, 一切的办法和数据都没有加载进内存, 在启动时拜访具体办二进制八进制十进制十六进制转换法时,经过虚拟内存表查询到相关的办法未物理内存中,这个时会产生缺页中止, 也便是所说的 (page fault)。这个缺页中止如果产生的次数太多, 就会影响启动时间。 (内存置换:产生在iOS体系杀死不活跃的后台App时)

如果启动时多线程是什么意思用到的办法分配到大量不同的内存页中, 产多线程是什么意思生缺页中止的次数就会越大, 为了减少这个次数, 咱们能够将启动时用到的办法优先排在前面。 这便是要说的二进制重排。

检查 page fault 次数

翻开Instruments二进制转八进制ystem trace 运转相应的appear项目在真机上, (模拟器和真机一页的内存巨细不一样, 4k, 16k)。

找到项目下的主线程, 检查Summary: Virtual Memory ->Page Cache Hint 的count 数 和 duration, 依此来对比优二进制转化为十进制化前后的作安全教育日

默认的办法排多线程的应用场景

创建一个例如 OCDemo01 项目

Build Settings 查找 link map, 找到 W多线程面试题rite Link Map File 设置为 YES。

编译项目,找到 Products 目录下的 OCDemo01.app show in finder, 往上上级目录找, 找到 Intermappointmentediates.noindex , 找到 OCDemo01.bui字符常量ld/Debug-iphonesimulator/OCDe字符间距加宽2磅怎么设置mo01.build 这个目录下:名为 OCDemo01-LinkMap-normal-x86_64.txt 的文件。

由于是模拟安全教育平台器, 所以是x86_64. 翻开文件, 能够看到办法的次序:

x1000013C0	0x00000090	[  2] -[AppDelegate application:didFinishLaunchingWithOptions:]
0x100001450	0x00000130	[  2] -[AppDelegate application:configurationForConnectingSceneSession:options:]
0x100001580	0x00000080	[  2] -[AppDelegate application:didDiscardSceneSessions:]
0x100001600	0x00000016	[  2] _sancov.module_ctor_trace_pc_guard
0x100001620	0x000000B0	[  3] _main
0x1000016D0	0x00000016	[  3] _sancov.module_ctor_trace_pc_guard
0x1000016F0	0x000000B0	[  4] -[SceneDelegate scene:willConnectToSession:options:]
0x1000017A0	0x00000060	[  4] -[SceneDelegate sceneDidDisconnect:]
0x100001800	0x00000060	[  4] -[SceneDelegate sceneDidBecomeActive:]
0x100001860	0x00000060	[  4] -[SceneDelegate sceneWillResignActive:]
0x1000018C0	0x00000060	[  4] -[SceneDelegate sceneWillEnterForeground:]
0x100001920	0x00000060	[  4] -[SceneDelegate sceneDidEnterBackground:]
0x100001980	0x00000050	[  4] -[SceneDelegate window]
0x1000019D0	0x00000060	[  4] -[SceneDelegate setWindow:]
0x100001A30	0x00000050	[  4] -[SceneDelegate .cxx_destruct]
0x100001A80	0x00000016	[  4] _sancov.module_ctor_trace_pc_guard
0x100001AA0	0x00000060	[  5] -[ViewController viewDidLoad]

APP果是Xcode 13, 找不到 Products 目录

翻开 project.pbxpro 这个文件,字符串是什么意思 找到 productRefGroup 将 mainGroup 的值赋值给它, 就能够看到 Products 目录了

mainGroup = 97DABBF6286EFD4500F8A0CA;
productRefGroup = 87DABBF6286EFD4511F8A0CB;
projectDirPath = "";
projectRoot = "";
targets = (
97DABBFE286EFD4500F8A0CA /* OCDemo01 */,
);

order file 修apple正办法次序

  • 其实经过手动, 在 Bu字符间距怎么加宽ild Phases -> Compile Sources 中拖拽文件也能够修办法的次序。能够拖拽完编译调试测验。

  • 还能够经过修正一个文件内, 比如 ViewContro字符是什么ller.m 中办法前后的排序, 能够更改 link map 文件中办法的次序。

上面两种方式appetite都很麻烦,不推荐

咱们能够经过在 Build Settings 查找 order file, 找到 Order File 增加 order file 的文件路径, 例如咱们在当时项目目录下 增加 ./yong.order

咱们经过copy link map file 中的办法排序, 在 yong.order 中增加并修正办法次序, 完成之后编译,再次检查link file map 文件, 看办法次序是approve否是 yong.order 文件中界说的次序。

怎么获取启动时用到的办法?
咱们想到的首要肯定是 hook objc_msgS多线程并发end 办法, 但安全教育日是这只能获取到 OC 办法, 关于C函数和 block, 无法获取到, 所以这个计划不完美。Clang 插桩 能够解决这个问题。

Clang 插桩

1.装备 Clang 插桩

Build Settings 查找 other C安全教育平台作业登录 找到 Other Capple Flags 中增加二进制的运算规则 -fsanitize-coverage=trace-pc-guard

装备完二进制的运算规则成之后咱们编译项目会发现报错,便是告知咱们找不到__sanitizer_cov_trace_pc_guard_ini字符间距t__sanitizer_cov_trace_pc_guard这两个函数的完成。所以咱们下面完成这两个函数。

咱们在 ViewController.m 文件中(其他文件也能够)导入头文多线程是什么意思

#include <stdint.h>
#include <stdio.h>
#include <sanitizer/coverage_interface.h>

增加两个办法的完成


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 %pn", 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) {
 
}

这次编译, 就不会报错了。

2.获取办法个数

多线程面试题转项目, 会输出两个16apple 进制数

**INIT: 0x10dc1f680 0x10dc1f6b8**

代表开端方位与结束方位,x 结束方二进制转八进制位 - 4 就代表符号个数。
经过打断点, 能够验证:

**(lldb) x (0x10dc1f6b8-4)**
0x10dc1f6b4: 0e 00 00 00 00 00 00 00 00 00 00 00 0e 00 00 00

0e 代表有 14 个符号。咱们再增加两个符号, 例如

void test(void) {
  NSLog(@"test 函数履行");
  block();
}
void(^block)(void) = ^(void){
  NSLog(@"block 函数履行");
};

再次运转, 断点调试之后, 咱们发现符号个数增加了两个, 变为 0x10, 也便是16.

在这儿咱们现已知道办法和函数还有block 的个数能够经过这个__sanitizer_cov_trace_pc_guapproachard_init 办法获取到。

3.获取符号的称号

经过debug 汇编代码, 发现调用 ViewController vie二进制转化为十进制wDidLoad 的时分, 会刺进__sanitizer_cov_trace_pc_guard 这个C 函数的调用。

// 这儿咱们导入 `#import <dlfcn.h>` 头文件
// HOOK 一切的函数调用
void __sanitizer_cov_trace_pc_guard(uint32_t *guard) {
  void *PC = __builtin_return_address(0);
    Dl_info info; dladdr(PC, &info);
    NSLog(@"fname:%sn fbase:%pn sname:%sn saddr:%pn",
                        info.dli_fname, 
                        info.dli_fbase, 
                        info.dli_sname,
                        info.dli_saddr);
}

这儿 __builtin_return_address 函数的意思便是获取到当时函数的内部返回地址,也便是上一个函数的地址,当时函数的字符间距怎么加宽调用者。所以这儿安全生产法 *PC 便是上一个函数的第 0 句代码的地址。这儿咱们就有机会拿到上一个函数的符号称号。dladdr(PC, &info) 这一句相当于把 PC 指向的地址数据信息赋值给 in安全教育手抄报fo 这个结构体。最终经过打印也输出了对应的信息。二进制换成十进制算法这儿就能拿到二进制转化为十进制一切符号的称号。

结构体说明

typedef struct dl_info {
    const char *dli_fname; /* 文件称号,也便是 mach-o 文件 */ 
    void *dli_fbase; /* 文件地址 */ 
    const char *dli_sname; /* 函数称号 */ 
    void *dli_saddr; /* 函数地址 */ 
} Dl_info;

4.生成order 文件

多线程的问题二进制的运算规则: 如果是子线程中的办法,那么__sanitizer_cov_trace_pc_guard函数也会在子线程中履行,所以这儿写入的时二进制怎么算分要注意多线程影响,这儿经过原子队列保存。

while 循环的影响: 由于 __sanitizer_cov_trace_pc_guard 函数也安全教育平台登录入口会阻拦到 while 循环,这样写入的时分就会造多线程成递归调用,所以装备 other c flags 参数的安全期计算器时分要多加一个条件 -fsanitize-coverage=func,trace-pc-guar多线程的实现方式d,只阻拦办法。

生成order文件的时分要对符号办法进行取反,由于写入的时分是倒序的,以及对符号称号去重。

函数称号前面要增加_

增加原子队二进制八进制十进制十六进制转换

// 导入头文件 #import <libkern/OSAtomic.h>
//界说原子队列
static OSQueueHead symbolList = OS_ATOMIC_QUEUE_INIT;
//界说符号结构体
typedef struct {
  void * pc;
  void * next;
} SYNode;

二进制符号信息赋值给结构体, 生成队列字符间距加宽2磅怎么设置

void __sanitizer_cov_trace_pc_guard(uint32_t *guard) {
  void *PC = __builtin_return_address(0);
  //创建结构体
    SYNode * node = malloc(sizeof(SYNode));
    *node = (SYNode){PC,NULL};
    //把结构体 node 写入到 symbolList,offsetof(SYNode, next) 表明设置 node 的 next,下一个节点,便是在上一个 next 的基础上加上 SYNode 巨细
    OSAtomicEnqueue(&symbolList, node, offsetof(SYNode, next));
}

生成order 文件


- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
  //界说数组
    NSMutableArray<NSString *> * symbleNames = [NSMutableArray array];
    while (YES) {//循环体内!进行了阻拦!!这儿需求注意的是 while 也会被阻拦,要做工程中做装备只阻拦办法
      SYNode * node = OSAtomicDequeue(&symbolList, offsetof(SYNode,next));
      if (node == NULL) {
        break;
      }
      Dl_info info;
      dladdr(node->pc, &info);
      NSString * name = @(info.dli_sname);//转字符串
      //给函数称号增加 _,这儿要判别是否是函数,是函数的话要增加_
      BOOL isObjc = [name hasPrefix:@"+["] || [name hasPrefix:@"-["];
      NSString * symbolName = isObjc ? name : [@"_" stringByAppendingString:name];
      [symbleNames addObject:symbolName];
    }
    //反向遍历数组,由于入栈函数调用次序是反的,所以这儿要取反
    NSEnumerator * em = [symbleNames reverseObjectEnumerator];
    NSMutableArray * funcs = [NSMutableArray arrayWithCapacity:symbleNames.count];
    NSString * name;
    // 这儿要对数组进行去重,由于有的办法会屡次调用
    while (name = [em nextObject]) {
      if (![funcs containsObject:name]) {//数组没有name
        [funcs addObject:name];
      }
    }
    //去掉自己!也便是当时办法
    [funcs removeObject:[NSString stringWithFormat:@"%s",__func__]];
    //写入文件
    //1.编成字符串
    NSString * funcStr = [funcs componentsJoinedByString:@"n"];
    NSString * filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"chenxi.order"];
    NSData * file = [funcStr dataUsingEncoding:NSUTF8StringEncoding];
    //写入到沙盒路径
    [[NSFileManager defaultManager] createFileAtPath:filePath contents:file attributes:nil];
    // 打印数组字符串
    NSLog(@"%@",funcStr);
    // 打印文件地址
    NSLog(@"order 文件地址%@",filePath);
}

这时分咱们就能够把order 文件装备到工程

Swift 符号覆盖

增加 swift 办法到项目appearance

需求在other swift flags这儿增加-sanitize-coverage=func-sanitize-undefinedapproach多线程的实现方式两个参数。

这时分再打印能够看到swift办法被搜集到了,而且编译器对swift符号称号做了混杂,这也能够看出swift相关于OC会更安全。安全

参阅:
juejin.cn/post/700445…