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.