前言
APP的性能决定一个应用的成功与否,我们都比较关注APP的启动性能,那么咱们自己的APP性能如何呢?该怎样优化?
分析APP启动时长
通过添加环境变量可以打印出APP的启动时间,Edit Scheme->Run->Argument,在Environment Variables中加入DYLD_PRINT_STATISTICS变量,值为1,和DYLD_PRINT_STATISTICS_DETAILS变量,值为1,然后运行APP,在控制台可以看到APP启动消耗的时间。
启动时间
- dylib loading time : 加载动态链接库,每个动态库都有自己的依赖关系,所以会消耗时间去查找和读取。
- rebase/binding time : 进行 rebase 指针调整和 bind 符号绑定。
- Objc setup time : OC运行时初始化处理,包括OC类的注册,category注册,selector唯一性检查。
- initializer time : 执行+initialize方法的时间。
- slowest intializers : 这个列出的是最慢的几个dylib文件。
在APP的main函数执行前可以优化的工作
- 减少动态库的加载,尽量将多个动态库进行合并。
- 减少加载启动后不会去使用的类和方法。
- 使用initialze()替换load()方法。
使用otool命名打印系统引入的动态库:otool - l Full path + 应用名称
例如 : otool - l /Users/XXX(电脑名)/Library/Developer/Xcode/DerivedData/yidaikuan-ebwboxfjsiomsybixwggdjmsiphk/Build/Products/Debug-iphoneos/yidaikuan.app/yidaikuan
main函数执行后优化
我们在屏幕上看到文字和图片都是由CPU和GPU处理过显示在屏幕上的,屏幕成像原理:CPU处理对象的创建和销毁,布局计算,文本排版,图片格式转换和解码等工作,然后把处理后的内容提交给GPU,等待GPU渲染完成后放入帧缓存区,随后由视频播放器发出Vsync信号逐行读取缓存的数据,经过数模转换给显示器显示。
Vsync(垂直同步信号)是按照固定频率发出的,每隔16ms会发出一次信号,发出信号后就要显示当前的画面,如果CPU和GPU消耗的时间太久,这个时候就只能显示上一帧的画面,造成卡顿现象。
离屏渲染消耗性能的原因:需要创建新的缓冲区,离屏渲染的整个过程,需要多次切换上下文环境,先是从当前屏幕(On-Screen)切换到离屏(Off-Screen);等到离屏渲染结束以后,将离屏缓冲区的渲染结果显示到屏幕上,又需要将上下文环境从离屏切换到当前屏幕
优化
- 复杂的页面布局手动计算frame,Autolayout需要消耗跟多的CPU资源。
- 不要频繁地调用UIView的frame、bounds、transform等属性。
- 图片的size与控件size的大小保持一致。
- 尽量减少视图数量和层次。
- 不要使用透明的视图。
- 尽量不要离屏渲染。
对APP方法执行耗时监控是很有必要的,可以使我们清楚的知道APP产生的耗时在那一块,可以有针对性的进行优化。
方法一:定时抓取主线程上的方法调用堆栈,计算一段时间里各个方法的耗时。xcode工具里集成的有相应的工具Time Profiler。使用可以百度。
方法二:对objc_msgSend方法进行hook,来精确计算方法的耗时。
hook 方法的意思是在原有方法执行前后执行你指定的方法,来达到掌握和改变指定方法的目的。此方法只能针对OC的方法,不能hook C语言的方法和block方法。但是可以使用fishhook三方进行扩展,实现对C方法的监听耗时
那怎样对objc_msgSend方法进行hook呢?
写一个VC的分类,对系统方法监听并替换自己的方法。
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
//需要拦截的方法
SEL fromSelectorAppear = @selector(viewWillAppear:);
//替换的方法
SEL toSelectorAppear = @selector(clsCallHookViewWillAppear:);
Method fromMethod = class_getInstanceMethod(self, fromSelectorAppear);
Method toMethod = class_getInstanceMethod(self, toSelectorAppear);
if(class_addMethod(self, fromSelectorAppear, method_getImplementation(toMethod), method_getTypeEncoding(toMethod))) {
class_replaceMethod(self, toSelectorAppear, method_getImplementation(fromMethod), method_getTypeEncoding(fromMethod));
} else {
method_exchangeImplementations(fromMethod, toMethod);
}
SEL fromSelectorDisappear = @selector(viewDidDisappear:);
SEL toSelectorDisappear = @selector(clsCallHookViewWillDisappear:);
Method fromMethodD = class_getInstanceMethod(self, fromSelectorDisappear);
Method toMethodD = class_getInstanceMethod(self, toSelectorDisappear);
if(class_addMethod(self, fromSelectorDisappear, method_getImplementation(toMethodD), method_getTypeEncoding(toMethodD))) {
class_replaceMethod(self, toSelectorDisappear, method_getImplementation(fromMethodD), method_getTypeEncoding(fromMethodD));
} else {
method_exchangeImplementations(fromMethodD, toMethodD);
}
});
}
#pragma mark - Method Hook
- (void)clsCallHookViewWillAppear:(BOOL)animated {
//执行插入代码
[self clsCallInsertToViewWillAppear];
[self clsCallHookViewWillAppear:animated];
}
- (void)clsCallHookViewWillDisappear:(BOOL)animated {
//执行插入代码
[self clsCallInsertToViewWillDisappear];
[self clsCallHookViewWillDisappear:animated];
}
- (void)clsCallInsertToViewWillAppear {
//显示
[HookTrace startWithMaxDepth:0];
}
- (void)clsCallInsertToViewWillDisappear {
//消失
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
[HookTrace stopSaveAndClean];
});
}
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface HookTrace : NSObject
+ (void)start; //开始记录
+ (void)startWithMaxDepth:(int)depth;//开始记录的深度
+ (void)startWithMinCost:(double)ms;//开始记录的时间
+ (void)startWithMaxDepth:(int)depth minCost:(double)ms;
+ (void)stop; //停止记录
+ (void)save; //保存和打印记录,如果不是短时间 stop 的话使用 saveAndClean
+ (void)stopSaveAndClean; //停止保存打印并进行内存清理
@end
NS_ASSUME_NONNULL_END
#import "HookTrace.h"
#include "HookTraceCore.h"
#import "HookTraceTimeCostModel.h"
#import <objc/runtime.h>
@implementation HookTrace
#pragma mark - Trace
#pragma mark - OC Interface
+ (void)start {
smCallTraceStart();
}
+ (void)startWithMaxDepth:(int)depth {
smCallConfigMaxDepth(depth);
[HookTrace start];
}
+ (void)startWithMinCost:(double)ms {
smCallConfigMinTime(ms * 1000);
[HookTrace start];
}
+ (void)startWithMaxDepth:(int)depth minCost:(double)ms {
smCallConfigMaxDepth(depth);
smCallConfigMinTime(ms * 1000);
[HookTrace start];
}
+ (void)stop {
smCallTraceStop();
}
+ (void)save {
NSMutableString *mStr = [NSMutableString new];
NSArray<HookTraceTimeCostModel *> *arr = [self loadRecords];
for (HookTraceTimeCostModel *model in arr) {
//记录方法路径
model.path = [NSString stringWithFormat:@"[%@ %@]",model.className,model.methodName];
[self appendRecord:model to:mStr];
}
// NSLog(@"%@",mStr);
}
+ (void)stopSaveAndClean {
[HookTrace stop];
[HookTrace save];
smClearCallRecords();
}
+ (void)appendRecord:(HookTraceTimeCostModel *)cost to:(NSMutableString *)mStr {
// [mStr appendFormat:@"%@\n path%@\n",[cost des],cost.path];
if (cost.subCosts.count < 1) {
cost.lastCall = YES;
NSLog(@"------%@",cost);
//记录到数据库中
// [[SMLagDB shareInstance] addWithClsCallModel:cost];
} else {
for (HookTraceTimeCostModel *model in cost.subCosts) {
if ([model.className isEqualToString:@"SMCallTrace"]) {
break;
}
//记录方法的子方法的路径
model.path = [NSString stringWithFormat:@"%@ - [%@ %@]",cost.path,model.className,model.methodName];
[self appendRecord:model to:mStr];
}
}
}
+ (NSArray<HookTraceTimeCostModel *>*)loadRecords {
NSMutableArray<HookTraceTimeCostModel *> *arr = [NSMutableArray new];
int num = 0;
smCallRecord *records = smGetCallRecords(&num);
for (int i = 0; i < num; i++) {
smCallRecord *rd = &records[i];
HookTraceTimeCostModel *model = [HookTraceTimeCostModel new];
model.className = NSStringFromClass(rd->cls);
model.methodName = NSStringFromSelector(rd->sel);
model.isClassMethod = class_isMetaClass(rd->cls);
model.timeCost = (double)rd->time / 1000000.0;
model.callDepth = rd->depth;
[arr addObject:model];
}
NSUInteger count = arr.count;
for (NSUInteger i = 0; i < count; i++) {
HookTraceTimeCostModel *model = arr[i];
if (model.callDepth > 0) {
[arr removeObjectAtIndex:i];
//Todo:不需要循环,直接设置下一个,然后判断好边界就行
for (NSUInteger j = i; j < count - 1; j++) {
//下一个深度小的话就开始将后面的递归的往 sub array 里添加
if (arr[j].callDepth + 1 == model.callDepth) {
NSMutableArray *sub = (NSMutableArray *)arr[j].subCosts;
if (!sub) {
sub = [NSMutableArray new];
arr[j].subCosts = sub;
}
[sub insertObject:model atIndex:0];
}
}
i--;
count--;
}
}
return arr;
}
@end
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface HookTraceTimeCostModel : NSObject
@property (nonatomic, strong) NSString *className; //类名
@property (nonatomic, strong) NSString *methodName; //方法名
@property (nonatomic, assign) BOOL isClassMethod; //是否是类方法
@property (nonatomic, assign) NSTimeInterval timeCost; //时间消耗
@property (nonatomic, assign) NSUInteger callDepth; //Call 层级
@property (nonatomic, copy) NSString *path; //路径
@property (nonatomic, assign) BOOL lastCall; //是否是最后一个 Call
@property (nonatomic, assign) NSUInteger frequency; //访问频次
@property (nonatomic, strong) NSArray <HookTraceTimeCostModel *> *subCosts;
- (NSString *)des;
@end
#import "HookTraceTimeCostModel.h"
@implementation HookTraceTimeCostModel
- (NSString *)des {
NSMutableString *str = [NSMutableString new];
[str appendFormat:@"%2d| ",(int)_callDepth];
[str appendFormat:@"%6.2f|",_timeCost * 1000.0];
for (NSUInteger i = 0; i < _callDepth; i++) {
[str appendString:@" "];
}
[str appendFormat:@"%s[%@ %@]", (_isClassMethod ? "+" : "-"), _className, _methodName];
return str;
}
@end
创建一个C的类,写入一下代码,这里代码是用汇编语言进行时间统计和对C语言的方法进行hook
#ifndef HookTraceCore_h
#define HookTraceCore_h
#include <stdio.h>
#include <objc/objc.h>
typedef struct {
__unsafe_unretained Class cls;
SEL sel;
uint64_t time; // us (1/1000 ms)
int depth;
} smCallRecord;
extern void smCallTraceStart();
extern void smCallTraceStop();
extern void smCallConfigMinTime(uint64_t us); //default 1000
extern void smCallConfigMaxDepth(int depth); //default 3
extern smCallRecord *smGetCallRecords(int *num);
extern void smClearCallRecords();
#endif /* HookTraceCore_h */
#include "HookTraceCore.h"
#ifdef __aarch64__
#pragma mark - fishhook
#include <stddef.h>
#include <stdint.h>
#include <dlfcn.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <mach-o/dyld.h>
#include <mach-o/loader.h>
#include <mach-o/nlist.h>
/*
* A structure representing a particular intended rebinding from a symbol
* name to its replacement
*/
struct rebinding {
const char *name;
void *replacement;
void **replaced;
};
/*
* For each rebinding in rebindings, rebinds references to external, indirect
* symbols with the specified name to instead point at replacement for each
* image in the calling process as well as for all future images that are loaded
* by the process. If rebind_functions is called more than once, the symbols to
* rebind are added to the existing list of rebindings, and if a given symbol
* is rebound more than once, the later rebinding will take precedence.
*/
static int fish_rebind_symbols(struct rebinding rebindings[], size_t rebindings_nel);
#ifdef __LP64__
typedef struct mach_header_64 mach_header_t;
typedef struct segment_command_64 segment_command_t;
typedef struct section_64 section_t;
typedef struct nlist_64 nlist_t;
#define LC_SEGMENT_ARCH_DEPENDENT LC_SEGMENT_64
#else
typedef struct mach_header mach_header_t;
typedef struct segment_command segment_command_t;
typedef struct section section_t;
typedef struct nlist nlist_t;
#define LC_SEGMENT_ARCH_DEPENDENT LC_SEGMENT
#endif
#ifndef SEG_DATA_CONST
#define SEG_DATA_CONST "__DATA_CONST"
#endif
struct rebindings_entry {
struct rebinding *rebindings;
size_t rebindings_nel;
struct rebindings_entry *next;
};
static struct rebindings_entry *_rebindings_head;
static int prepend_rebindings(struct rebindings_entry **rebindings_head,
struct rebinding rebindings[],
size_t nel) {
struct rebindings_entry *new_entry = malloc(sizeof(struct rebindings_entry));
if (!new_entry) {
return -1;
}
new_entry->rebindings = malloc(sizeof(struct rebinding) * nel);
if (!new_entry->rebindings) {
free(new_entry);
return -1;
}
memcpy(new_entry->rebindings, rebindings, sizeof(struct rebinding) * nel);
new_entry->rebindings_nel = nel;
new_entry->next = *rebindings_head;
*rebindings_head = new_entry;
return 0;
}
static void perform_rebinding_with_section(struct rebindings_entry *rebindings,
section_t *section,
intptr_t slide,
nlist_t *symtab,
char *strtab,
uint32_t *indirect_symtab) {
uint32_t *indirect_symbol_indices = indirect_symtab + section->reserved1;
void **indirect_symbol_bindings = (void **)((uintptr_t)slide + section->addr);
for (uint i = 0; i < section->size / sizeof(void *); i++) {
uint32_t symtab_index = indirect_symbol_indices[i];
if (symtab_index == INDIRECT_SYMBOL_ABS || symtab_index == INDIRECT_SYMBOL_LOCAL ||
symtab_index == (INDIRECT_SYMBOL_LOCAL | INDIRECT_SYMBOL_ABS)) {
continue;
}
uint32_t strtab_offset = symtab[symtab_index].n_un.n_strx;
char *symbol_name = strtab + strtab_offset;
if (strnlen(symbol_name, 2) < 2) {
continue;
}
struct rebindings_entry *cur = rebindings;
while (cur) {
for (uint j = 0; j < cur->rebindings_nel; j++) {
if (strcmp(&symbol_name[1], cur->rebindings[j].name) == 0) {
if (cur->rebindings[j].replaced != NULL &&
indirect_symbol_bindings[i] != cur->rebindings[j].replacement) {
*(cur->rebindings[j].replaced) = indirect_symbol_bindings[i];
}
indirect_symbol_bindings[i] = cur->rebindings[j].replacement;
goto symbol_loop;
}
}
cur = cur->next;
}
symbol_loop:;
}
}
static void rebind_symbols_for_image(struct rebindings_entry *rebindings,
const struct mach_header *header,
intptr_t slide) {
Dl_info info;
if (dladdr(header, &info) == 0) {
return;
}
segment_command_t *cur_seg_cmd;
segment_command_t *linkedit_segment = NULL;
struct symtab_command* symtab_cmd = NULL;
struct dysymtab_command* dysymtab_cmd = NULL;
uintptr_t cur = (uintptr_t)header + sizeof(mach_header_t);
for (uint i = 0; i < header->ncmds; i++, cur += cur_seg_cmd->cmdsize) {
cur_seg_cmd = (segment_command_t *)cur;
if (cur_seg_cmd->cmd == LC_SEGMENT_ARCH_DEPENDENT) {
if (strcmp(cur_seg_cmd->segname, SEG_LINKEDIT) == 0) {
linkedit_segment = cur_seg_cmd;
}
} else if (cur_seg_cmd->cmd == LC_SYMTAB) {
symtab_cmd = (struct symtab_command*)cur_seg_cmd;
} else if (cur_seg_cmd->cmd == LC_DYSYMTAB) {
dysymtab_cmd = (struct dysymtab_command*)cur_seg_cmd;
}
}
if (!symtab_cmd || !dysymtab_cmd || !linkedit_segment ||
!dysymtab_cmd->nindirectsyms) {
return;
}
// Find base symbol/string table addresses
uintptr_t linkedit_base = (uintptr_t)slide + linkedit_segment->vmaddr - linkedit_segment->fileoff;
nlist_t *symtab = (nlist_t *)(linkedit_base + symtab_cmd->symoff);
char *strtab = (char *)(linkedit_base + symtab_cmd->stroff);
// Get indirect symbol table (array of uint32_t indices into symbol table)
uint32_t *indirect_symtab = (uint32_t *)(linkedit_base + dysymtab_cmd->indirectsymoff);
cur = (uintptr_t)header + sizeof(mach_header_t);
for (uint i = 0; i < header->ncmds; i++, cur += cur_seg_cmd->cmdsize) {
cur_seg_cmd = (segment_command_t *)cur;
if (cur_seg_cmd->cmd == LC_SEGMENT_ARCH_DEPENDENT) {
if (strcmp(cur_seg_cmd->segname, SEG_DATA) != 0 &&
strcmp(cur_seg_cmd->segname, SEG_DATA_CONST) != 0) {
continue;
}
for (uint j = 0; j < cur_seg_cmd->nsects; j++) {
section_t *sect =
(section_t *)(cur + sizeof(segment_command_t)) + j;
if ((sect->flags & SECTION_TYPE) == S_LAZY_SYMBOL_POINTERS) {
perform_rebinding_with_section(rebindings, sect, slide, symtab, strtab, indirect_symtab);
}
if ((sect->flags & SECTION_TYPE) == S_NON_LAZY_SYMBOL_POINTERS) {
perform_rebinding_with_section(rebindings, sect, slide, symtab, strtab, indirect_symtab);
}
}
}
}
}
static void _rebind_symbols_for_image(const struct mach_header *header,
intptr_t slide) {
rebind_symbols_for_image(_rebindings_head, header, slide);
}
static int fish_rebind_symbols(struct rebinding rebindings[], size_t rebindings_nel) {
int retval = prepend_rebindings(&_rebindings_head, rebindings, rebindings_nel);
if (retval < 0) {
return retval;
}
// If this was the first call, register callback for image additions (which is also invoked for
// existing images, otherwise, just run on existing images
//首先是遍历 dyld 里的所有的 image,取出 image header 和 slide。注意第一次调用时主要注册 callback
if (!_rebindings_head->next) {
_dyld_register_func_for_add_image(_rebind_symbols_for_image);
} else {
uint32_t c = _dyld_image_count();
for (uint32_t i = 0; i < c; i++) {
_rebind_symbols_for_image(_dyld_get_image_header(i), _dyld_get_image_vmaddr_slide(i));
}
}
return retval;
}
#pragma mark - Record
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stddef.h>
#include <stdint.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <objc/message.h>
#include <objc/runtime.h>
#include <dispatch/dispatch.h>
#include <pthread.h>
static bool _call_record_enabled = true;
static uint64_t _min_time_cost = 1000; //us
static int _max_call_depth = 3;
static pthread_key_t _thread_key;
__unused static id (*orig_objc_msgSend)(id, SEL, ...);
static smCallRecord *_smCallRecords;
//static int otp_record_num;
//static int otp_record_alloc;
static int _smRecordNum;
static int _smRecordAlloc;
typedef struct {
id self; //通过 object_getClass 能够得到 Class 再通过 NSStringFromClass 能够得到类名
Class cls;
SEL cmd; //通过 NSStringFromSelector 方法能够得到方法名
uint64_t time; //us
uintptr_t lr; // link register
} thread_call_record;
typedef struct {
thread_call_record *stack;
int allocated_length;
int index;
bool is_main_thread;
} thread_call_stack;
static inline thread_call_stack * get_thread_call_stack() {
thread_call_stack *cs = (thread_call_stack *)pthread_getspecific(_thread_key);
if (cs == NULL) {
cs = (thread_call_stack *)malloc(sizeof(thread_call_stack));
cs->stack = (thread_call_record *)calloc(128, sizeof(thread_call_record));
cs->allocated_length = 64;
cs->index = -1;
cs->is_main_thread = pthread_main_np();
pthread_setspecific(_thread_key, cs);
}
return cs;
}
static void release_thread_call_stack(void *ptr) {
thread_call_stack *cs = (thread_call_stack *)ptr;
if (!cs) return;
if (cs->stack) free(cs->stack);
free(cs);
}
static inline void push_call_record(id _self, Class _cls, SEL _cmd, uintptr_t lr) {
thread_call_stack *cs = get_thread_call_stack();
if (cs) {
int nextIndex = (++cs->index);
if (nextIndex >= cs->allocated_length) {
cs->allocated_length += 64;
cs->stack = (thread_call_record *)realloc(cs->stack, cs->allocated_length * sizeof(thread_call_record));
}
thread_call_record *newRecord = &cs->stack[nextIndex];
newRecord->self = _self;
newRecord->cls = _cls;
newRecord->cmd = _cmd;
newRecord->lr = lr;
if (cs->is_main_thread && _call_record_enabled) {
struct timeval now;
gettimeofday(&now, NULL);
newRecord->time = (now.tv_sec % 100) * 1000000 + now.tv_usec;
}
}
}
static inline uintptr_t pop_call_record() {
thread_call_stack *cs = get_thread_call_stack();
int curIndex = cs->index;
int nextIndex = cs->index--;
thread_call_record *pRecord = &cs->stack[nextIndex];
if (cs->is_main_thread && _call_record_enabled) {
struct timeval now;
gettimeofday(&now, NULL);
uint64_t time = (now.tv_sec % 100) * 1000000 + now.tv_usec;
if (time < pRecord->time) {
time += 100 * 1000000;
}
uint64_t cost = time - pRecord->time;
if (cost > _min_time_cost && cs->index < _max_call_depth) {
if (!_smCallRecords) {
_smRecordAlloc = 1024;
_smCallRecords = malloc(sizeof(smCallRecord) * _smRecordAlloc);
}
_smRecordNum++;
if (_smRecordNum >= _smRecordAlloc) {
_smRecordAlloc += 1024;
_smCallRecords = realloc(_smCallRecords, sizeof(smCallRecord) * _smRecordAlloc);
}
smCallRecord *log = &_smCallRecords[_smRecordNum - 1];
log->cls = pRecord->cls;
log->depth = curIndex;
log->sel = pRecord->cmd;
log->time = cost;
}
}
return pRecord->lr;
}
void before_objc_msgSend(id self, SEL _cmd, uintptr_t lr) {
push_call_record(self, object_getClass(self), _cmd, lr);
}
uintptr_t after_objc_msgSend() {
return pop_call_record();
}
//replacement objc_msgSend (arm64)
// https://blog.nelhage.com/2010/10/amd64-and-va_arg/
// http://infocenter.arm.com/help/topic/com.arm.doc.ihi0055b/IHI0055B_aapcs64.pdf
// https://developer.apple.com/library/ios/documentation/Xcode/Conceptual/iPhoneOSABIReference/Articles/ARM64FunctionCallingConventions.html
#define call(b, value) \
__asm volatile ("stp x8, x9, [sp, #-16]!\n"); \
__asm volatile ("mov x12, %0\n" :: "r"(value)); \
__asm volatile ("ldp x8, x9, [sp], #16\n"); \
__asm volatile (#b " x12\n");
#define save() \
__asm volatile ( \
"stp x8, x9, [sp, #-16]!\n" \
"stp x6, x7, [sp, #-16]!\n" \
"stp x4, x5, [sp, #-16]!\n" \
"stp x2, x3, [sp, #-16]!\n" \
"stp x0, x1, [sp, #-16]!\n");
#define load() \
__asm volatile ( \
"ldp x0, x1, [sp], #16\n" \
"ldp x2, x3, [sp], #16\n" \
"ldp x4, x5, [sp], #16\n" \
"ldp x6, x7, [sp], #16\n" \
"ldp x8, x9, [sp], #16\n" );
#define link(b, value) \
__asm volatile ("stp x8, lr, [sp, #-16]!\n"); \
__asm volatile ("sub sp, sp, #16\n"); \
call(b, value); \
__asm volatile ("add sp, sp, #16\n"); \
__asm volatile ("ldp x8, lr, [sp], #16\n");
#define ret() __asm volatile ("ret\n");
__attribute__((__naked__))
static void hook_Objc_msgSend() {
// Save parameters.
save()
__asm volatile ("mov x2, lr\n");
__asm volatile ("mov x3, x4\n");
// Call our before_objc_msgSend.
call(blr, &before_objc_msgSend)
// Load parameters.
load()
// Call through to the original objc_msgSend.
call(blr, orig_objc_msgSend)
// Save original objc_msgSend return value.
save()
// Call our after_objc_msgSend.
call(blr, &after_objc_msgSend)
// restore lr
__asm volatile ("mov lr, x0\n");
// Load original objc_msgSend return value.
load()
// return
ret()
}
#pragma mark public
void smCallTraceStart() {
_call_record_enabled = true;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
pthread_key_create(&_thread_key, &release_thread_call_stack);
fish_rebind_symbols((struct rebinding[6]){
{"objc_msgSend", (void *)hook_Objc_msgSend, (void **)&orig_objc_msgSend},
}, 1);
});
}
void smCallTraceStop() {
_call_record_enabled = false;
}
void smCallConfigMinTime(uint64_t us) {
_min_time_cost = us;
}
void smCallConfigMaxDepth(int depth) {
_max_call_depth = depth;
}
smCallRecord *smGetCallRecords(int *num) {
if (num) {
*num = _smRecordNum;
}
return _smCallRecords;
}
void smClearCallRecords() {
if (_smCallRecords) {
free(_smCallRecords);
_smCallRecords = NULL;
}
_smRecordNum = 0;
}
#else
void smCallTraceStart() {}
void smCallTraceStop() {}
void smCallConfigMinTime(uint64_t us) {
}
void smCallConfigMaxDepth(int depth) {
}
smCallRecord *smGetCallRecords(int *num) {
if (num) {
*num = 0;
}
return NULL;
}
void smClearCallRecords() {}
#endif