diff --git a/Qi_ObjcMsgHook/Pods/Pods.xcodeproj/xcuserdata/denglibing.xcuserdatad/xcschemes/xcschememanagement.plist b/Qi_ObjcMsgHook/Pods/Pods.xcodeproj/xcuserdata/denglibing.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000..951edd1 --- /dev/null +++ b/Qi_ObjcMsgHook/Pods/Pods.xcodeproj/xcuserdata/denglibing.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,24 @@ + + + + + SchemeUserState + + FMDB.xcscheme_^#shared#^_ + + orderHint + 3 + + Pods-Qi_ObjcMsgHook.xcscheme_^#shared#^_ + + orderHint + 2 + + ReactiveCocoa.xcscheme_^#shared#^_ + + orderHint + 0 + + + + diff --git a/Qi_ObjcMsgHook/Qi_ObjcMsgHook.xcodeproj/project.pbxproj b/Qi_ObjcMsgHook/Qi_ObjcMsgHook.xcodeproj/project.pbxproj index 35d90b1..3b7c563 100644 --- a/Qi_ObjcMsgHook/Qi_ObjcMsgHook.xcodeproj/project.pbxproj +++ b/Qi_ObjcMsgHook/Qi_ObjcMsgHook.xcodeproj/project.pbxproj @@ -8,6 +8,7 @@ /* Begin PBXBuildFile section */ 9164681BB0A298AD454193ED /* libPods-Qi_ObjcMsgHook.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D47E09EA3AA7D83E2DB7869D /* libPods-Qi_ObjcMsgHook.a */; }; + D9DCCA7C2B32C77A00465F0F /* fishhook.c in Sources */ = {isa = PBXBuildFile; fileRef = D9DCCA7A2B32C77A00465F0F /* fishhook.c */; }; F14ED94C23850FF400D35A9D /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = F14ED94B23850FF400D35A9D /* AppDelegate.m */; }; F14ED94F23850FF400D35A9D /* SceneDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = F14ED94E23850FF400D35A9D /* SceneDelegate.m */; }; F14ED95723850FF500D35A9D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F14ED95623850FF500D35A9D /* Assets.xcassets */; }; @@ -28,6 +29,8 @@ 4976B0886AEEBF116F87F1BF /* Pods-Qi_ObjcMsgHook.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Qi_ObjcMsgHook.debug.xcconfig"; path = "Target Support Files/Pods-Qi_ObjcMsgHook/Pods-Qi_ObjcMsgHook.debug.xcconfig"; sourceTree = ""; }; 7D50652F404D4AE35BD9BF75 /* Pods-Qi_ObjcMsgHook.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Qi_ObjcMsgHook.release.xcconfig"; path = "Target Support Files/Pods-Qi_ObjcMsgHook/Pods-Qi_ObjcMsgHook.release.xcconfig"; sourceTree = ""; }; D47E09EA3AA7D83E2DB7869D /* libPods-Qi_ObjcMsgHook.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Qi_ObjcMsgHook.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + D9DCCA7A2B32C77A00465F0F /* fishhook.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = fishhook.c; sourceTree = ""; }; + D9DCCA7B2B32C77A00465F0F /* fishhook.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = fishhook.h; sourceTree = ""; }; F14ED94723850FF400D35A9D /* Qi_ObjcMsgHook.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Qi_ObjcMsgHook.app; sourceTree = BUILT_PRODUCTS_DIR; }; F14ED94A23850FF400D35A9D /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; F14ED94B23850FF400D35A9D /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; @@ -79,6 +82,15 @@ path = Pods; sourceTree = ""; }; + D9DCCA792B32C77A00465F0F /* fishhook */ = { + isa = PBXGroup; + children = ( + D9DCCA7A2B32C77A00465F0F /* fishhook.c */, + D9DCCA7B2B32C77A00465F0F /* fishhook.h */, + ); + path = fishhook; + sourceTree = ""; + }; F14ED93E23850FF400D35A9D = { isa = PBXGroup; children = ( @@ -118,6 +130,7 @@ F14ED9632385108800D35A9D /* QiLagMonitor */ = { isa = PBXGroup; children = ( + D9DCCA792B32C77A00465F0F /* fishhook */, F14ED9762385193C00D35A9D /* QiLagMonitor.h */, F14ED9772385193C00D35A9D /* QiLagMonitor.m */, F14ED964238516FC00D35A9D /* QiCPUMonitor.h */, @@ -254,6 +267,7 @@ F14ED94F23850FF400D35A9D /* SceneDelegate.m in Sources */, F14ED97B2385196500D35A9D /* QiLagDB.m in Sources */, F14ED9752385184000D35A9D /* QiCallStackModel.m in Sources */, + D9DCCA7C2B32C77A00465F0F /* fishhook.c in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -386,8 +400,10 @@ baseConfigurationReference = 4976B0886AEEBF116F87F1BF /* Pods-Qi_ObjcMsgHook.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = 8ZX24TX624; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_STYLE = Manual; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = TKRM4J3XJV; GCC_C_LANGUAGE_STANDARD = gnu99; INFOPLIST_FILE = Qi_ObjcMsgHook/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -396,6 +412,8 @@ ); PRODUCT_BUNDLE_IDENTIFIER = com.qishare.ios.lsq; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = JingxiDev_18; TARGETED_DEVICE_FAMILY = 1; }; name = Debug; @@ -405,8 +423,10 @@ baseConfigurationReference = 7D50652F404D4AE35BD9BF75 /* Pods-Qi_ObjcMsgHook.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = 8ZX24TX624; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_STYLE = Manual; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = TKRM4J3XJV; GCC_C_LANGUAGE_STANDARD = gnu99; INFOPLIST_FILE = Qi_ObjcMsgHook/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -415,6 +435,8 @@ ); PRODUCT_BUNDLE_IDENTIFIER = com.qishare.ios.lsq; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = JingxiDev_18; TARGETED_DEVICE_FAMILY = 1; }; name = Release; diff --git a/Qi_ObjcMsgHook/Qi_ObjcMsgHook.xcodeproj/xcuserdata/denglibing.xcuserdatad/xcschemes/xcschememanagement.plist b/Qi_ObjcMsgHook/Qi_ObjcMsgHook.xcodeproj/xcuserdata/denglibing.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000..b3f678a --- /dev/null +++ b/Qi_ObjcMsgHook/Qi_ObjcMsgHook.xcodeproj/xcuserdata/denglibing.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,14 @@ + + + + + SchemeUserState + + Qi_ObjcMsgHook.xcscheme_^#shared#^_ + + orderHint + 1 + + + + diff --git a/Qi_ObjcMsgHook/Qi_ObjcMsgHook.xcworkspace/xcuserdata/denglibing.xcuserdatad/UserInterfaceState.xcuserstate b/Qi_ObjcMsgHook/Qi_ObjcMsgHook.xcworkspace/xcuserdata/denglibing.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000..28d19be Binary files /dev/null and b/Qi_ObjcMsgHook/Qi_ObjcMsgHook.xcworkspace/xcuserdata/denglibing.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/Qi_ObjcMsgHook/Qi_ObjcMsgHook/QiLagMonitor/QiCallTrace.m b/Qi_ObjcMsgHook/Qi_ObjcMsgHook/QiLagMonitor/QiCallTrace.m index 98ec038..636d317 100644 --- a/Qi_ObjcMsgHook/Qi_ObjcMsgHook/QiLagMonitor/QiCallTrace.m +++ b/Qi_ObjcMsgHook/Qi_ObjcMsgHook/QiLagMonitor/QiCallTrace.m @@ -90,6 +90,12 @@ + (void)appendRecord:(QiCallTraceTimeCostModel *)cost to:(NSMutableString *)mStr model.isClassMethod = class_isMetaClass(rd->cls); model.timeCost = (double)rd->time / 1000000.0; model.callDepth = rd->depth; + model.lr = rd->lr; + + if (rd->caller_record != NULL) { + model.callerLr = rd->caller_record->lr; + } + [arr addObject:model]; } NSUInteger count = arr.count; @@ -99,14 +105,18 @@ + (void)appendRecord:(QiCallTraceTimeCostModel *)cost to:(NSMutableString *)mStr [arr removeObjectAtIndex:i]; //Todo:不需要循环,直接设置下一个,然后判断好边界就行 for (NSUInteger j = i; j < count - 1; j++) { - //下一个深度小的话就开始将后面的递归的往 sub array 里添加 - if (arr[j].callDepth + 1 == model.callDepth) { + // 下一个深度小的话就开始将后面的递归的往 sub array 里添加 + // ⚠️⚠️ 这里的bug:不能根据 callDepth 来判断,不然所有层级相等的深度,都在一个调用链路中了 + // 需要根据调用链路来关联 + if (arr[j].lr == model.callerLr) { NSMutableArray *sub = (NSMutableArray *)arr[j].subCosts; if (!sub) { sub = [NSMutableArray new]; arr[j].subCosts = sub; } - [sub insertObject:model atIndex:0]; + if (![sub containsObject:model]) { + [sub addObject:model]; + } } } i--; diff --git a/Qi_ObjcMsgHook/Qi_ObjcMsgHook/QiLagMonitor/QiCallTraceCore.c b/Qi_ObjcMsgHook/Qi_ObjcMsgHook/QiLagMonitor/QiCallTraceCore.c index 25c2660..903dc48 100644 --- a/Qi_ObjcMsgHook/Qi_ObjcMsgHook/QiLagMonitor/QiCallTraceCore.c +++ b/Qi_ObjcMsgHook/Qi_ObjcMsgHook/QiLagMonitor/QiCallTraceCore.c @@ -11,205 +11,7 @@ #ifdef __aarch64__ #pragma mark - fishhook -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* - * 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; -} - +#include "fishhook.h" #pragma mark - Record @@ -238,12 +40,13 @@ static qiCallRecord *_qiCallRecords; static int _qiRecordNum; static int _qiRecordAlloc; -typedef struct { +typedef struct thread_call_record { id self; //通过 object_getClass 能够得到 Class 再通过 NSStringFromClass 能够得到类名 Class cls; SEL cmd; //通过 NSStringFromSelector 方法能够得到方法名 uint64_t time; //us uintptr_t lr; // link register + struct thread_call_record *caller_record; //调用该方法的信息 } thread_call_record; typedef struct { @@ -286,6 +89,9 @@ static inline void push_call_record(id _self, Class _cls, SEL _cmd, uintptr_t lr newRecord->cls = _cls; newRecord->cmd = _cmd; newRecord->lr = lr; + if (nextIndex > 0) { + newRecord->caller_record = cs->stack; + } if (cs->is_main_thread && _call_record_enabled) { struct timeval now; gettimeofday(&now, NULL); @@ -323,6 +129,14 @@ static inline uintptr_t pop_call_record() { log->depth = curIndex; log->sel = pRecord->cmd; log->time = cost; + log->lr = pRecord->lr; + if (pRecord->caller_record != NULL) { + qiCallRecord *caller_record = (qiCallRecord *)malloc(sizeof(qiCallRecord)); + caller_record->cls = pRecord->caller_record->cls; + caller_record->sel = pRecord->caller_record->cmd; + caller_record->lr = pRecord->caller_record->lr; + log->caller_record = caller_record; + } } } return pRecord->lr; @@ -413,7 +227,7 @@ void qiCallTraceStart() { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ pthread_key_create(&_thread_key, &release_thread_call_stack); - fish_rebind_symbols((struct rebinding[6]){ + rebind_symbols((struct rebinding[6]){ {"objc_msgSend", (void *)hook_Objc_msgSend, (void **)&orig_objc_msgSend}, }, 1); }); diff --git a/Qi_ObjcMsgHook/Qi_ObjcMsgHook/QiLagMonitor/QiCallTraceCore.h b/Qi_ObjcMsgHook/Qi_ObjcMsgHook/QiLagMonitor/QiCallTraceCore.h index f1a5bd8..a2be4cb 100644 --- a/Qi_ObjcMsgHook/Qi_ObjcMsgHook/QiLagMonitor/QiCallTraceCore.h +++ b/Qi_ObjcMsgHook/Qi_ObjcMsgHook/QiLagMonitor/QiCallTraceCore.h @@ -12,11 +12,17 @@ #include #include -typedef struct { +typedef struct qiCallRecord { __unsafe_unretained Class cls; SEL sel; uint64_t time; // us (1/1000 ms) int depth; + // 在计算机体系结构中,lr 通常代表 "Link Register"。Link Register 是一个特殊的寄存器,用于存储函数或子程序调用的返回地址。当一个函数或子程序被调用时,返回地址(即调用指令后的下一条指令的地址)被保存到 Link Register 中,以便在函数执行完毕后可以返回到调用点继续执行。 + // 在 ARM 架构中,lr 是一个常见的寄存器名,表示 Link Register。在其他架构中,可能有不同的寄存器或方法来保存返回地址。 + // 在多线程环境中,thread_call_record 可能是一个结构体或记录,用于保存线程调用的相关信息,包括线程的状态、调用堆栈、寄存器值等。在这个上下文中,lr 可能是该记录中的一个字段,用于保存线程当前函数调用的 Link Register 值。这对于调试、性能分析或线程状态恢复等操作是非常有用的。 + // Link Register (lr) 的值并不是每个方法唯一的。lr 存储的是函数或方法调用时的返回地址,这个地址指向的是调用该函数后应当执行的下一条指令。因此,lr 的值取决于函数被调用的具体位置。 + uintptr_t lr; // link register + struct qiCallRecord *caller_record; } qiCallRecord; extern void qiCallTraceStart(void); diff --git a/Qi_ObjcMsgHook/Qi_ObjcMsgHook/QiLagMonitor/QiCallTraceTimeCostModel.h b/Qi_ObjcMsgHook/Qi_ObjcMsgHook/QiLagMonitor/QiCallTraceTimeCostModel.h index 7d4afb6..7dd8009 100644 --- a/Qi_ObjcMsgHook/Qi_ObjcMsgHook/QiLagMonitor/QiCallTraceTimeCostModel.h +++ b/Qi_ObjcMsgHook/Qi_ObjcMsgHook/QiLagMonitor/QiCallTraceTimeCostModel.h @@ -22,6 +22,9 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, assign) NSUInteger frequency; //访问频次 @property (nonatomic, strong) NSArray *subCosts; +@property (nonatomic, assign) uintptr_t lr; // 自身的 Link Register +@property (nonatomic, assign) uintptr_t callerLr; // 调用者的 Link Register + - (NSString *)des; @end diff --git a/Qi_ObjcMsgHook/Qi_ObjcMsgHook/QiLagMonitor/fishhook/fishhook.c b/Qi_ObjcMsgHook/Qi_ObjcMsgHook/QiLagMonitor/fishhook/fishhook.c new file mode 100644 index 0000000..fb41e8e --- /dev/null +++ b/Qi_ObjcMsgHook/Qi_ObjcMsgHook/QiLagMonitor/fishhook/fishhook.c @@ -0,0 +1,264 @@ +// Copyright (c) 2013, Facebook, Inc. +// All rights reserved. +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name Facebook nor the names of its contributors may be used to +// endorse or promote products derived from this software without specific +// prior written permission. +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "fishhook.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 = (struct rebindings_entry *) malloc(sizeof(struct rebindings_entry)); + if (!new_entry) { + return -1; + } + new_entry->rebindings = (struct rebinding *) 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; +} + +#if 0 +static int get_protection(void *addr, vm_prot_t *prot, vm_prot_t *max_prot) { + mach_port_t task = mach_task_self(); + vm_size_t size = 0; + vm_address_t address = (vm_address_t)addr; + memory_object_name_t object; +#ifdef __LP64__ + mach_msg_type_number_t count = VM_REGION_BASIC_INFO_COUNT_64; + vm_region_basic_info_data_64_t info; + kern_return_t info_ret = vm_region_64( + task, &address, &size, VM_REGION_BASIC_INFO_64, (vm_region_info_64_t)&info, &count, &object); +#else + mach_msg_type_number_t count = VM_REGION_BASIC_INFO_COUNT; + vm_region_basic_info_data_t info; + kern_return_t info_ret = vm_region(task, &address, &size, VM_REGION_BASIC_INFO, (vm_region_info_t)&info, &count, &object); +#endif + if (info_ret == KERN_SUCCESS) { + if (prot != NULL) + *prot = info.protection; + + if (max_prot != NULL) + *max_prot = info.max_protection; + + return 0; + } + + return -1; +} +#endif + +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; + bool symbol_name_longer_than_1 = symbol_name[0] && symbol_name[1]; + struct rebindings_entry *cur = rebindings; + while (cur) { + for (uint j = 0; j < cur->rebindings_nel; j++) { + if (symbol_name_longer_than_1 && strcmp(&symbol_name[1], cur->rebindings[j].name) == 0) { + kern_return_t err; + + if (cur->rebindings[j].replaced != NULL && indirect_symbol_bindings[i] != cur->rebindings[j].replacement) + *(cur->rebindings[j].replaced) = indirect_symbol_bindings[i]; + + /** + * 1. Moved the vm protection modifying codes to here to reduce the + * changing scope. + * 2. Adding VM_PROT_WRITE mode unconditionally because vm_region + * API on some iOS/Mac reports mismatch vm protection attributes. + * -- Lianfu Hao Jun 16th, 2021 + **/ + err = vm_protect (mach_task_self (), (uintptr_t)indirect_symbol_bindings, section->size, 0, VM_PROT_READ | VM_PROT_WRITE | VM_PROT_COPY); + if (err == KERN_SUCCESS) { + /** + * Once we failed to change the vm protection, we + * MUST NOT continue the following write actions! + * iOS 15 has corrected the const segments prot. + * -- Lionfore Hao Jun 11th, 2021 + **/ + 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); +} + +int rebind_symbols_image(void *header, + intptr_t slide, + struct rebinding rebindings[], + size_t rebindings_nel) { + struct rebindings_entry *rebindings_head = NULL; + int retval = prepend_rebindings(&rebindings_head, rebindings, rebindings_nel); + rebind_symbols_for_image(rebindings_head, (const struct mach_header *) header, slide); + if (rebindings_head) { + free(rebindings_head->rebindings); + } + free(rebindings_head); + return retval; +} + +int 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 + 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; +} diff --git a/Qi_ObjcMsgHook/Qi_ObjcMsgHook/QiLagMonitor/fishhook/fishhook.h b/Qi_ObjcMsgHook/Qi_ObjcMsgHook/QiLagMonitor/fishhook/fishhook.h new file mode 100644 index 0000000..0d8e36a --- /dev/null +++ b/Qi_ObjcMsgHook/Qi_ObjcMsgHook/QiLagMonitor/fishhook/fishhook.h @@ -0,0 +1,76 @@ +// Copyright (c) 2013, Facebook, Inc. +// All rights reserved. +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name Facebook nor the names of its contributors may be used to +// endorse or promote products derived from this software without specific +// prior written permission. +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef fishhook_h +#define fishhook_h + +#include +#include + +#if !defined(FISHHOOK_EXPORT) +#define FISHHOOK_VISIBILITY __attribute__((visibility("hidden"))) +#else +#define FISHHOOK_VISIBILITY __attribute__((visibility("default"))) +#endif + +#ifdef __cplusplus +extern "C" { +#endif //__cplusplus + +/* + * 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. + */ +FISHHOOK_VISIBILITY +int rebind_symbols(struct rebinding rebindings[], size_t rebindings_nel); + +/* + * Rebinds as above, but only in the specified image. The header should point + * to the mach-o header, the slide should be the slide offset. Others as above. + */ +FISHHOOK_VISIBILITY +int rebind_symbols_image(void *header, + intptr_t slide, + struct rebinding rebindings[], + size_t rebindings_nel); + +#ifdef __cplusplus +} +#endif //__cplusplus + +#endif //fishhook_h + diff --git a/Qi_ObjcMsgHook/Qi_ObjcMsgHook/SceneDelegate.m b/Qi_ObjcMsgHook/Qi_ObjcMsgHook/SceneDelegate.m index 5e3d8cf..b2ace1d 100644 --- a/Qi_ObjcMsgHook/Qi_ObjcMsgHook/SceneDelegate.m +++ b/Qi_ObjcMsgHook/Qi_ObjcMsgHook/SceneDelegate.m @@ -25,12 +25,63 @@ - (void)scene:(UIScene *)scene willConnectToSession:(UISceneSession *)session op self.window.rootViewController = homeNav; [self.window makeKeyAndVisible]; + [self test1_1]; + [self test2_1]; + [self test3_1]; + + [self test1_1]; + [self test2_1]; + [self test3_1]; + [QiCallTrace stop]; [QiCallTrace save]; } } +- (void)test1_1 { + usleep(11 * 1000); + [self test1_2]; + [self test1_3]; +} + +- (void)test1_2 { + usleep(12 * 1000); +} + +- (void)test1_3 { + usleep(13 * 1000); +} + +- (void)test2_1 { + usleep(21 * 1000); + [self test2_2]; + [self test2_3]; +} + +- (void)test2_2 { + usleep(22 * 1000); +} + +- (void)test2_3 { + usleep(23 * 1000); +} + + +- (void)test3_1 { + usleep(31 * 1000); + [self test3_2]; + [self test3_3]; +} + +- (void)test3_2 { + usleep(32 * 1000); +} + +- (void)test3_3 { + usleep(33 * 1000); +} + - (void)sceneDidDisconnect:(UIScene *)scene { // Called as the scene is being released by the system. // This occurs shortly after the scene enters the background, or when its session is discarded.