二进制重排

news/2024/11/22 21:25:22/

二进制重排作用

  二进制重排的主要目的是将连续调用的函数连接到相邻的虚拟内存地址,这样在启动时可以减少缺页中断的发生,提升启动速度。目前网络上关于ios应用启动优化,通过XCode实现的版本比较多。MacOS上的应用也是通过clang进行编译的,理论上也可以进行二进制重排,主要分为两步。
  首先是获取启动过程调用的函数符号,需要通过clang插桩方式实现,对于其它编译器目前没有找到类似的功能。

编译选项

set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize-coverage=func,trace-pc-guard")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize-coverage=func,trace-pc-guard")

入口函数

  然后是入口函数实现,收集调用函数符号序列,通过下面的代码可以实现生成。

#ifndef APPCALLCOLLECTOR_H_
#define APPCALLCOLLECTOR_H_#import <Foundation/Foundation.h>//! Project version number for AppCallCollecter.
FOUNDATION_EXPORT double AppCallCollecterVersionNumber;//! Project version string for AppCallCollecter.
FOUNDATION_EXPORT const unsigned char AppCallCollecterVersionString[];/// 与CLRAppOrderFile只能二者用其一
extern NSArray <NSString *> *getAppCalls(void);/// 与getAppCalls只能二者用其一
extern void appOrderFile(NSString* orderFilePath);// In this header, you should import all the public headers of your framework using statements like #import <AppCallCollecter/PublicHeader.h>
#endif
#import "appcallcollector.h"
#import <dlfcn.h>
#import <libkern/OSAtomicQueue.h>
#import <pthread.h>static OSQueueHead qHead = OS_ATOMIC_QUEUE_INIT;
static BOOL stopCollecting = NO;typedef struct {void *pointer;void *next;
} PointerNode;// dyld链接dylib时调用,start和stop地址之间的保存该dylib的所有符号的个数
// 可以不实现具体内容,不影响后续调用
extern "C" void __sanitizer_cov_trace_pc_guard_init(uint32_t *start,uint32_t *stop) {static uint32_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.printf("totasl count %i\n", N);
}// This callback is inserted by the compiler on every edge in the
// control flow (some optimizations apply).
// Typically, the compiler will emit the code like this:
//    if(*guard)
//      __sanitizer_cov_trace_pc_guard(guard);
// But for large functions it will emit a simple call:
//    __sanitizer_cov_trace_pc_guard(guard);
/* 通过汇编可发现,每个函数调用前都被插入了bl     0x102b188c0               ; symbol stub for: __sanitizer_cov_trace_pc_guard所以在每个函数调用时都会先跳转执行该函数
*/
extern "C" void __sanitizer_cov_trace_pc_guard(uint32_t *guard) {// If initialization has not occurred yet (meaning that guard is uninitialized), that means that initial functions like +load are being run. These functions will only be run once anyways, so we should always allow them to be recorded and ignore guard// +load方法先于guard_init调用,此时guard为0if(!*guard) { return; }if (stopCollecting) {return;}// __builtin_return_address 获取当前调用栈信息,取第一帧地址(即下条要执行的指令地址,被插桩的函数地址)void *PC = __builtin_return_address(0);PointerNode *node = (PointerNode *)malloc(sizeof(PointerNode));*node = (PointerNode){PC, NULL};// 使用原子队列要存储帧地址OSAtomicEnqueue(&qHead, node, offsetof(PointerNode, next));
}extern NSArray <NSString *> *getAllFunctions(NSString *currentFuncName) {NSMutableSet<NSString *> *unqSet = [NSMutableSet setWithObject:currentFuncName];NSMutableArray <NSString *> *functions = [NSMutableArray array];while (YES) {PointerNode *front = (PointerNode *)OSAtomicDequeue(&qHead, offsetof(PointerNode, next));if(front == NULL) {break;}Dl_info info = {0};// dladdr获取地址符号信息dladdr(front->pointer, &info);NSString *name = @(info.dli_sname);// 去除重复调用if([unqSet containsObject:name]) {continue;}BOOL isObjc = [name hasPrefix:@"+["] || [name hasPrefix:@"-["];// order文件格式要求C函数和block前需要添加_NSString *symbolName = isObjc ? name : [@"_" stringByAppendingString:name];[unqSet addObject:name];[functions addObject:symbolName];}// 取反得到正确调用排序return [[functions reverseObjectEnumerator] allObjects];;
}#pragma mark - publicextern NSArray <NSString *> *getAppCalls(void) {stopCollecting = YES;// 内存屏障,防止cpu的乱序执行调度内存(原子锁)__sync_synchronize();NSString* curFuncationName = [NSString stringWithUTF8String:__FUNCTION__];return getAllFunctions(curFuncationName);
}extern void appOrderFile(NSString* orderFilePath) {stopCollecting = YES;__sync_synchronize();NSString* curFuncationName = [NSString stringWithUTF8String:__FUNCTION__];NSArray *functions = getAllFunctions(curFuncationName);NSString *orderFileContent = [functions.reverseObjectEnumerator.allObjects componentsJoinedByString:@"\n"];NSLog(@"[orderFile]: %@",orderFileContent);NSString *filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"orderFile.order"];NSData * fileContents = [orderFileContent dataUsingEncoding:NSUTF8StringEncoding];// NSArray *functions = getAllFunctions(curFuncationName);// NSString * funcString = [symbolAry componentsJoinedByString:@"\n"];// NSString * filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"lb.order"];// NSData * fileContents = [funcString dataUsingEncoding:NSUTF8StringEncoding];BOOL result = [[NSFileManager defaultManager] createFileAtPath:filePath contents:fileContents attributes:nil];if (result) {NSLog(@"%@",filePath);}else{NSLog(@"文件写入出错");}
}

链接器配置

  拿到函数符号列表后,需要通过链接选项将列表文件传递给链接器,也可以通过链接选项输出link map,查看重排前后的符号顺序。

-order_file_statistics
  Logs information about the processing of a -order_file.

-map map_file_path
  Writes a map file to the specified path which details all symbols and their addresses in the output image.

-order_file file
  Alters the order in which functions and data are laid out. For each section in the outputfile, any symbol in that section that are specified in the order file file is moved to the start of its section and laid out in the same order as in the order file file. Order files are text files with one symbol name per line. Lines starting with a # are comments. A symbol name may be optionally preceded with its object file leaf name and a colon (e.g. foo.o:_foo). This is useful for static functions/data that occur in multiple files. A symbol name may also be optionally preceded with the architecture (e.g. ppc:_foo or ppc:foo.o:_foo). This enables you to have one order file that works for multiple architec-tures. Literal c-strings may be ordered by by quoting the string (e.g. “Hello, world\n”) in the order file.

可执行程序模块重排

set(CMAKE_CXX_LINK_FLAGS "-Xlinker -map -Xlinker /Users/Desktop/out/out001.txt -Xlinker -order_file_statistics -Xlinker -order_file -Xlinker /Users/Desktop/out/orderFile_cpp.order ${CMAKE_CXX_LINK_FLAGS}")

动态库重排

set(CMAKE_SHARED_LINKER_FLAGS "-Xlinker -map -Xlinker /Users/Desktop/out/out002.txt -Xlinker -order_file_statistics -Xlinker -order_file -Xlinker /Users/Desktop/out/orderFile_add.order ${CMAKE_SHARED_LINKER_FLAGS}")

http://www.ppmy.cn/news/986024.html

相关文章

刘铁猛C#语言教程——语句1

语句的定义 以下是对该文档的翻译 一条语句对应着一条汇编语言指令或者一条语句对应着一系列有着内在逻辑关联的汇编指令&#xff0c;对于这句话的理解&#xff0c;我们可以观察C#编译器编译的C#程序后得到的汇编语言代码&#xff0c;这样便可以看到语句与指令的关系&#xff…

黑马点评项目学习笔记(15w字详解,堪称史上最详细,欢迎收藏)

黑马点评项目学习笔记 文章目录 黑马点评项目学习笔记前言项目搭建导入数据库初始化项目启动项目启动前端项目启动后端项目 基于Session实现短信验证码登录短信验证码登录配置登录拦截器数据脱敏 Session集群共享问题基于Redis实现短信验证码登录短信验证登录配置登录拦截器 店…

flutter开发实战-父子Widget组件调用方法

flutter开发实战-父子Widget组件调用方法 在最近开发中遇到了需要父组件调用子组件方法&#xff0c;子组件调用父组件的方法。这里记录一下方案。 一、使用GlobalKey 父组件使用globalKey.currentState调用子组件具体方法&#xff0c;子组件通过方法回调callback方法调用父组…

Android进阶之微信扫码登录

遇到新需求要搭建微信扫码登录功能,这篇文章是随着我的编码过程一并写的,希望能够帮助有需求的人和以后再次用到此功能的自己。 首先想到的就是百度各种文章,当然去开发者平台申请AppID和密钥是必不可少的,等注册好发现需要创建应用以及审核(要官网,流程图及其他信息),想着先写…

美国过境签证申请也要面签吗?

随着人们出国旅行的增加&#xff0c;美国过境签证成为了一个热门话题。对于许多人来说&#xff0c;了解美国过境签证的流程和要求非常重要。在这篇文章中&#xff0c;知识人网小编将介绍美国过境签证是否需要面签&#xff0c;以及相关的注意事项。 首先&#xff0c;让我们来了解…

css中flex后文本溢出的问题

原因&#xff1a; 为了给flex item提供一个合理的默认最小尺寸&#xff0c;flex将flex item的min-width 和 min-height属性设置为了auto flex item的默认设置为&#xff1a; min-width&#xff1a; auto 水平flex布局 min-height&#xff1a;auto 垂直flex布局 解决办法&…

flutter开发实战-自定义相机camera功能

flutter开发实战-自定义相机camera功能。 Flutter 本质上只是一个 UI 框架&#xff0c;运行在宿主平台之上&#xff0c;Flutter 本身是无法提供一些系统能力&#xff0c;比如使用蓝牙、相机、GPS等&#xff0c;因此要在 Flutter 中调用这些能力就必须和原生平台进行通信。 实现…

综合案例(面向对象)

使用面向对象思想完成数据读取和处理基于面向对象思想重新认知第三方库使用&#xff08;PyEcharts&#xff09; 数据分析案例 某公司&#xff0c;有2份数据文件&#xff0c;现需要对其进行分析处理&#xff0c;计算每日的销售额并以柱状图表的形式进行展示。 数据内容 综合案…