├── KKCallStack.h ├── KKCallStack.m ├── KKCallStackSymbol.c ├── KKCallStackSymbol.h ├── README.md └── dsc_extractor.zip /KKCallStack.h: -------------------------------------------------------------------------------- 1 | // 2 | // KKCallStack.h 3 | // KKMagicHook 4 | // 5 | // Created by 吴凯凯 on 2020/4/10. 6 | // Copyright © 2020 吴凯凯. All rights reserved. 7 | // 8 | 9 | #import 10 | #include 11 | 12 | 13 | typedef NS_ENUM(NSUInteger, KKCallStackType) { 14 | KKCallStackTypeAll, //全部线程 15 | KKCallStackTypeMain, //主线程 16 | KKCallStackTypeCurrent //当前线程 17 | }; 18 | 19 | NS_ASSUME_NONNULL_BEGIN 20 | 21 | @interface KKCallStack : NSObject 22 | 23 | + (NSString *)callStackWithType:(KKCallStackType)type; 24 | //+ (NSString *)callStackWithThread_t:(thread_t)thread; 25 | 26 | @end 27 | 28 | NS_ASSUME_NONNULL_END 29 | -------------------------------------------------------------------------------- /KKCallStack.m: -------------------------------------------------------------------------------- 1 | // 2 | // KKCallStack.m 3 | // KKMagicHook 4 | // 5 | // Created by 吴凯凯 on 2020/4/10. 6 | // Copyright © 2020 吴凯凯. All rights reserved. 7 | // 8 | 9 | #import "KKCallStack.h" 10 | #include "KKCallStackSymbol.h" 11 | 12 | #if defined(__arm64__) 13 | //#define DETAG_INSTRUCTION_ADDRESS(A) ((A) & ~(3UL)) 14 | #define KK_THREAD_STATE_COUNT ARM_THREAD_STATE64_COUNT 15 | #define KK_THREAD_STATE ARM_THREAD_STATE64 16 | #define KK_FRAME_POINTER __fp 17 | #define KK_LINKER_POINTER __lr 18 | #define KK_INSTRUCTION_ADDRESS __pc 19 | 20 | #elif defined(__arm__) 21 | //#define DETAG_INSTRUCTION_ADDRESS(A) ((A) & ~(1UL)) 22 | #define KK_THREAD_STATE_COUNT ARM_THREAD_STATE_COUNT 23 | #define KK_THREAD_STATE ARM_THREAD_STATE 24 | #define KK_FRAME_POINTER __r[7] 25 | #define KK_LINKER_POINTER __lr 26 | #define KK_INSTRUCTION_ADDRESS __pc 27 | 28 | #elif defined(__x86_64__) 29 | //#define DETAG_INSTRUCTION_ADDRESS(A) (A) 30 | #define KK_THREAD_STATE_COUNT x86_THREAD_STATE64_COUNT 31 | #define KK_THREAD_STATE x86_THREAD_STATE64 32 | #define KK_FRAME_POINTER __rbp 33 | #define KK_LINKER_POINTER __rlr 34 | #define KK_INSTRUCTION_ADDRESS __rip 35 | 36 | #elif defined(__i386__) 37 | //#define DETAG_INSTRUCTION_ADDRESS(A) (A) 38 | #define KK_THREAD_STATE_COUNT x86_THREAD_STATE32_COUNT 39 | #define KK_THREAD_STATE x86_THREAD_STATE32 40 | #define KK_FRAME_POINTER __ebp 41 | #define KK_LINKER_POINTER __elr 42 | #define KK_INSTRUCTION_ADDRESS __eip 43 | 44 | #endif 45 | 46 | typedef struct { 47 | const uintptr_t *fp; //stp fp, lr, ... 48 | const uintptr_t lr; 49 | } StackFrameFP_LR; 50 | 51 | static thread_t _main_thread; 52 | static int StackMaxDepth = 32; 53 | 54 | @implementation KKCallStack 55 | 56 | + (void)load 57 | { 58 | _main_thread = mach_thread_self(); 59 | } 60 | 61 | + (NSString *)callStackWithType:(KKCallStackType)type 62 | { 63 | NSString *result; 64 | if (type == KKCallStackTypeAll) { 65 | thread_act_array_t threads; 66 | mach_msg_type_number_t thread_count = 0; 67 | if (KERN_SUCCESS != task_threads(mach_task_self(), &threads, &thread_count)) { 68 | return @"fail get all threads!"; 69 | } 70 | NSMutableString *resultStr = [NSMutableString string]; 71 | [resultStr appendFormat:@"当前线程数量:%d\n", thread_count]; 72 | for (int i = 0; i < thread_count; i++) { 73 | [resultStr appendFormat:@"%@", [self callStackWithThread_t:threads[i]]]; 74 | } 75 | result = resultStr.copy; 76 | } 77 | else if (type == KKCallStackTypeMain) 78 | { 79 | if ([NSThread isMainThread]) { 80 | result = [self callStackSymbolsWithCurrentThread]; 81 | } 82 | else 83 | { 84 | result = [self callStackWithThread_t:_main_thread]; 85 | } 86 | } 87 | else 88 | { 89 | result = [self callStackSymbolsWithCurrentThread]; 90 | } 91 | 92 | printf("\n⚠️⚠️⚠️⚠️⚠️⚠️👇堆栈👇⚠️⚠️⚠️⚠️⚠️⚠️\n"); 93 | NSLog(@"\n%@", result); 94 | printf("⚠️⚠️⚠️⚠️⚠️⚠️👆堆栈👆⚠️⚠️⚠️⚠️⚠️⚠️\n\n"); 95 | return result; 96 | } 97 | 98 | + (NSString *)callStackSymbolsWithCurrentThread 99 | { 100 | 101 | NSArray *arr = [NSThread callStackSymbols]; 102 | NSMutableString *strM = [NSMutableString stringWithFormat:@"\ncallStack of thread: %@\n", [NSThread isMainThread]?@"main thread":[NSThread currentThread].name]; 103 | for (NSString *symbol in arr) { 104 | [strM appendFormat:@"%@\n", symbol]; 105 | } 106 | return strM.copy; 107 | } 108 | 109 | + (NSString *)callStackWithThread_t:(thread_t)thread 110 | { 111 | _STRUCT_MCONTEXT machineContext; 112 | if (!getMachineContext(thread, &machineContext)) { 113 | return [NSString stringWithFormat:@"fail get thread(%u) state", thread]; 114 | } 115 | 116 | uintptr_t pc = machineContext.__ss.KK_INSTRUCTION_ADDRESS; 117 | uintptr_t fp = machineContext.__ss.KK_FRAME_POINTER; 118 | uintptr_t lr = machineContext.__ss.KK_LINKER_POINTER; 119 | 120 | uintptr_t pcArr[StackMaxDepth]; 121 | int i = 0; 122 | pcArr[i++] = pc; 123 | StackFrameFP_LR frame = {(void *)fp, lr}; 124 | vm_size_t len = sizeof(frame); 125 | while (frame.fp && i < StackMaxDepth) { 126 | pcArr[i++] = frame.lr; 127 | bool flag = readFPMemory(frame.fp, &frame, len); 128 | if (!flag || frame.fp==0 || frame.lr==0) { 129 | break; 130 | } 131 | } 132 | return generateSymbol(pcArr, i, thread); 133 | } 134 | 135 | NSString *generateSymbol(uintptr_t *pcArr, int arrLen, thread_t thread) 136 | { 137 | CallStackInfo *csInfo = (CallStackInfo *)malloc(sizeof(CallStackInfo)); 138 | if (csInfo == NULL) { 139 | return @"malloc fail"; 140 | } 141 | csInfo->length = 0; 142 | csInfo->allocLength = arrLen; 143 | csInfo->stacks = (FuncInfo *)malloc(sizeof(FuncInfo) * csInfo->allocLength); 144 | if (csInfo->stacks == NULL) { 145 | return @"malloc fail"; 146 | } 147 | callStackOfSymbol(pcArr, arrLen, csInfo); 148 | NSMutableString *strM = [NSMutableString stringWithFormat:@"\ncallStack of thread: %u\n", thread]; 149 | for (int j = 0; j < csInfo->length; j++) { 150 | [strM appendFormat:@"%@", formatFuncInfo(csInfo->stacks[j])]; 151 | } 152 | // NSLog(@"\n%@", strM); 153 | freeMemory(csInfo); 154 | return strM.copy; 155 | } 156 | 157 | NSString *formatFuncInfo(FuncInfo info) 158 | { 159 | if (info.symbol == NULL) { 160 | return @""; 161 | } 162 | char *lastPath = strrchr(info.machOName, '/'); 163 | NSString *fname = @""; 164 | if (lastPath == NULL) { 165 | fname = [NSString stringWithFormat:@"%-30s", info.machOName]; 166 | } 167 | else 168 | { 169 | fname = [NSString stringWithFormat:@"%-30s", lastPath+1]; 170 | } 171 | return [NSString stringWithFormat:@"%@ 0x%08" PRIxPTR " %s + %llu\n", fname, (uintptr_t)info.addr, info.symbol, info.offset]; 172 | } 173 | 174 | void freeMemory(CallStackInfo *csInfo) 175 | { 176 | if (csInfo->stacks) { 177 | free(csInfo->stacks); 178 | } 179 | if (csInfo) { 180 | free(csInfo); 181 | } 182 | } 183 | 184 | bool getMachineContext(thread_t thread, _STRUCT_MCONTEXT64 *machineContext) 185 | { 186 | mach_msg_type_number_t state_count = KK_THREAD_STATE_COUNT; 187 | return KERN_SUCCESS == thread_get_state(thread, KK_THREAD_STATE, (thread_state_t 188 | )&machineContext->__ss, &state_count); 189 | } 190 | 191 | // 读取fp开始,len(16)字节长度的内存。因为stp fp, lr... , fp占8字节,然后紧接着上面8字节是lr 192 | bool readFPMemory(const void *fp, const void *dst, const vm_size_t len) 193 | { 194 | vm_size_t bytesCopied = 0; 195 | kern_return_t kr = vm_read_overwrite(mach_task_self(), (vm_address_t)fp, len, (vm_address_t)dst, &bytesCopied); 196 | return KERN_SUCCESS == kr; 197 | } 198 | 199 | @end 200 | -------------------------------------------------------------------------------- /KKCallStackSymbol.c: -------------------------------------------------------------------------------- 1 | // 2 | // KKCallStackSymbol.c 3 | // KKMagicHook 4 | // 5 | // Created by 吴凯凯 on 2020/4/9. 6 | // Copyright © 2020 吴凯凯. All rights reserved. 7 | // 8 | 9 | #include "KKCallStackSymbol.h" 10 | 11 | #import 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | typedef struct { 18 | const struct mach_header *header; 19 | const char *name; 20 | uintptr_t slide; 21 | } KKMachHeader; 22 | 23 | typedef struct { 24 | KKMachHeader *array; 25 | uint32_t allocLength; 26 | } KKMachHeaderArr; 27 | 28 | static KKMachHeaderArr *machHeaderArr = NULL; 29 | 30 | void getMachHeader() 31 | { 32 | machHeaderArr = (KKMachHeaderArr *)malloc(sizeof(KKMachHeaderArr)); 33 | machHeaderArr->allocLength = _dyld_image_count(); 34 | machHeaderArr->array = (KKMachHeader *)malloc(sizeof(KKMachHeader) * machHeaderArr->allocLength); 35 | for (uint32_t i = 0; i < machHeaderArr->allocLength; i++) { 36 | KKMachHeader *machHeader = &machHeaderArr->array[i]; 37 | machHeader->header = _dyld_get_image_header(i); 38 | machHeader->name = _dyld_get_image_name(i); 39 | machHeader->slide = _dyld_get_image_vmaddr_slide(i); 40 | } 41 | } 42 | 43 | bool pcIsInMach(uintptr_t slidePC, const struct mach_header *header) 44 | { 45 | uintptr_t cur = (uintptr_t)(((struct mach_header_64*)header) + 1); 46 | for (uint32_t i = 0; i < header->ncmds; i++) { 47 | struct load_command *command = (struct load_command *)cur; 48 | if (command->cmd == LC_SEGMENT_64) { 49 | struct segment_command_64 *segmentCommand = (struct segment_command_64 *)command; 50 | uintptr_t start = segmentCommand->vmaddr; 51 | uintptr_t end = segmentCommand->vmaddr + segmentCommand->vmsize; 52 | if (slidePC >= start && slidePC <= end) { 53 | return true; 54 | } 55 | } 56 | cur = cur + command->cmdsize; 57 | } 58 | return false; 59 | } 60 | 61 | KKMachHeader *getPCInMach(uintptr_t pc) 62 | { 63 | if (!machHeaderArr) { 64 | getMachHeader(); 65 | } 66 | for (uint32_t i = 0; i < machHeaderArr->allocLength; i++) { 67 | KKMachHeader *machHeader = &machHeaderArr->array[i]; 68 | if (pcIsInMach(pc-machHeader->slide, machHeader->header)) { 69 | return machHeader; 70 | } 71 | } 72 | return NULL; 73 | } 74 | 75 | 76 | 77 | void findPCSymbolInMach(uintptr_t pc, KKMachHeader *machHeader, CallStackInfo *csInfo) 78 | { 79 | if (!machHeader) { 80 | return; 81 | } 82 | 83 | struct segment_command_64 *seg_linkedit = NULL; 84 | struct symtab_command *sym_command = NULL; 85 | const struct mach_header *header = machHeader->header; 86 | uintptr_t cur = (uintptr_t)(((struct mach_header_64*)header) + 1); 87 | for (uint32_t i = 0; i < header->ncmds; i++) { 88 | struct load_command *command = (struct load_command *)cur; 89 | if (command->cmd == LC_SEGMENT_64) { 90 | struct segment_command_64 *segmentCommand = (struct segment_command_64 *)command; 91 | if (strcmp(segmentCommand->segname, SEG_LINKEDIT)==0) { 92 | seg_linkedit = segmentCommand; 93 | } 94 | } 95 | else if (command->cmd == LC_SYMTAB) 96 | { 97 | sym_command = (struct symtab_command*)command; 98 | } 99 | cur = cur + command->cmdsize; 100 | } 101 | if (!seg_linkedit || !sym_command) { 102 | return; 103 | } 104 | 105 | uintptr_t linkedit_base = (uintptr_t)machHeader->slide + seg_linkedit->vmaddr - seg_linkedit->fileoff; 106 | struct nlist_64 *symtab = (struct nlist_64 *)(linkedit_base + sym_command->symoff); 107 | const uintptr_t strtab = linkedit_base + sym_command->stroff; 108 | 109 | uintptr_t slidePC = pc - machHeader->slide; 110 | uint64_t offset = UINT64_MAX; 111 | int best = -1; 112 | for (uint32_t i = 0; i < sym_command->nsyms; i++) { 113 | uint64_t distance = slidePC - symtab[i].n_value; 114 | if (slidePC >= symtab[i].n_value && distance <= offset) { 115 | offset = distance; 116 | best = i; 117 | } 118 | } 119 | 120 | if (best >= 0) { 121 | FuncInfo *funcInfo = &csInfo->stacks[csInfo->length++]; 122 | funcInfo->machOName = machHeader->name; 123 | funcInfo->addr = symtab[best].n_value; 124 | funcInfo->offset = offset; 125 | funcInfo->symbol = (char *)(strtab + symtab[best].n_un.n_strx); 126 | if (*funcInfo->symbol == '_') 127 | { 128 | funcInfo->symbol++; 129 | } 130 | if (funcInfo->machOName == NULL) { 131 | funcInfo->machOName = ""; 132 | } 133 | } 134 | } 135 | 136 | void callStackOfSymbol(uintptr_t *pcArr, int arrLen, CallStackInfo *csInfo) 137 | { 138 | for (int i = 0; i < arrLen; i++) { 139 | KKMachHeader *machHeader = getPCInMach(pcArr[i]); 140 | if (machHeader) { 141 | findPCSymbolInMach(pcArr[i], machHeader, csInfo); 142 | } 143 | } 144 | } 145 | 146 | 147 | 148 | 149 | 150 | 151 | -------------------------------------------------------------------------------- /KKCallStackSymbol.h: -------------------------------------------------------------------------------- 1 | // 2 | // KKCallStackSymbol.h 3 | // KKMagicHook 4 | // 5 | // Created by 吴凯凯 on 2020/4/9. 6 | // Copyright © 2020 吴凯凯. All rights reserved. 7 | // 8 | 9 | #ifndef KKCallStackSymbol_h 10 | #define KKCallStackSymbol_h 11 | 12 | #include 13 | #include 14 | 15 | typedef struct { 16 | uint64_t addr; 17 | uint64_t offset; 18 | const char *symbol; 19 | const char *machOName; 20 | } FuncInfo; 21 | 22 | typedef struct { 23 | FuncInfo *stacks; 24 | int allocLength; 25 | int length; 26 | }CallStackInfo; 27 | 28 | void callStackOfSymbol(uintptr_t *pcArr, int arrLen, CallStackInfo *csInfo); 29 | 30 | #endif /* KKCallStackSymbol_h */ 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ## 用途 3 | 程序中获取调用堆栈。 4 | 5 | ## 用法 6 | ``` 7 | typedef NS_ENUM(NSUInteger, KKCallStackType) { 8 | KKCallStackTypeAll, //全部线程 9 | KKCallStackTypeMain, //主线程 10 | KKCallStackTypeCurrent //当前线程 11 | }; 12 | 13 | 14 | //获取所有线程的调用堆栈 15 | NSString *callStack = [KKCallStack callStackWithType:KKCallStackTypeAll]; 16 | 17 | //获取主线程的调用堆栈 18 | NSString *callStack = [KKCallStack callStackWithType:KKCallStackTypeMain]; 19 | 20 | //获取当前线程的调用堆栈 21 | NSString *callStack = [KKCallStack callStackWithType:KKCallStackTypeCurrent]; 22 | 23 | ``` 24 | ## 注意 25 | 1. 在获取当前线程的调用堆栈,别人都是通过 [NSThread setName:xx(比如时间戳)] , 然后通过pthread_getname_np()。通过比对两者 name 一样,来关联NSThread 跟 pthread_t,而我直接用 [NSThread callStackSymbols] 获取当前线程的堆栈。😂😂 26 | 2. 这个项目目前主要是用来学习,所以都是硬编码,只支持arm64~ 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /dsc_extractor.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maniackk/KKCallStack/629376c926933547982a29f3aeb620a4ae685260/dsc_extractor.zip --------------------------------------------------------------------------------