iOS 获取任意线程堆栈信息
缘由: 在程序崩溃的时候很容易获取到堆栈信息,程序员很容易查看到因为哪个函数导致的崩溃,但是卡顿现象和高CPU利用率的时候要查看线程的堆栈信息,系统暂未提供方法,所有有了这篇文章
卡顿的时候首先要获取到线程,根据线程再获取堆栈信息。
1. 获取线程
在mach-o/dyld.h
和mach-o/nlist.h``pthread.h
中找到函数task_threads()
,
根据函数task_threads(mach_task_self(), &list, &count)
获取到线程个数,然后根据
_Nullable pthread_t pthread_from_mach_thread_np(mach_port_t);
获取到线程,如果想获取到所有线程的堆栈,那么遍历把每个线程的堆栈保存即可。
```
import
include
include
include
include
include
include
include
pragma -mark Convert NSThread to Mach thread
thread_t bs_machThreadFromNSThread(NSThread *nsthread) { char name[256]; mach_msg_type_number_t count; thread_act_array_t list task_threads(mach_task_self(), &list, &count);
NSTimeInterval currentTimestamp = [[NSDate date] timeIntervalSince1970]; NSString *originName = [nsthread name]; [nsthread setName:[NSString stringWithFormat:@"%f", currentTimestamp]];
if ([nsthread isMainThread]) { return (thread_t)main_thread_id; } for (int i = 0; i < count; ++i) { //获取到线程 pthread_t pt = pthread_from_mach_thread_np(list[i]); if ([nsthread isMainThread]) { if (list[i] == main_thread_id) { return list[i]; } } if (pt) { name[0] = '\0'; pthread_getname_np(pt, name, sizeof name); //根据name进行判断是否是同一个线程 if (!strcmp(name, [nsthread name].UTF8String)) { [nsthread setName:originName]; return list[i]; } } } [nsthread setName:originName]; return mach_thread_self(); } ```
2. 获取栈顶游标
根据BSStackFrameEntry
这个单项链表可以获取到所有栈信息
``` //根据这个链表可以获取到所有栈信息的地址 typedef struct BSStackFrameEntry{ // 上一个栈frame指针 const struct BSStackFrameEntry *const previous; //当前栈 地址 const uintptr_t return_address; } BSStackFrameEntry;
//; 获取到线程 堆栈信息 NSString _bs_backtraceOfThread(thread_t thread) { //; 50个堆栈 uintptr_t backtraceBuffer[50]; int i = 0; NSMutableString resultString = [[NSMutableString alloc] initWithFormat:@"Backtrace of Thread %u:\n", thread];
// macho 上下文 _STRUCT_MCONTEXT machineContext;
// ; 填充线程setate thread_get_state if(!bs_fillThreadStateIntoMachineContext(thread, &machineContext)) { return [NSString stringWithFormat:@"Fail to get information about thread: %u", thread]; } //; 获得当前上下文 return machineContext->__ss.BS_INSTRUCTION_ADDRESS; 的address 地址 //; i386: machineContext->__ss->__eip x86 : __rip //; 指令地址 const uintptr_t instructionAddress = bs_mach_instructionAddress(&machineContext); backtraceBuffer[i] = instructionAddress; ++i;
//; return machineContext->__ss.__lr; 链接寄存器 //; linkRegister = 0 uintptr_t linkRegister = bs_mach_linkRegister(&machineContext); if (linkRegister) { backtraceBuffer[i] = linkRegister; i++; }
if(instructionAddress == 0) { return @"Fail to get instruction address"; }
// BSStackFrameEntry frame = {0};
//获取当前上下文的指针 const uintptr_t framePtr = bs_mach_framePointer(&machineContext); // 将framPtr 数据同步到frame if(framePtr == 0 || bs_mach_copyMem((void *)framePtr, &frame, sizeof(frame)) != KERN_SUCCESS) { return @"Fail to get frame pointer"; }
// 根据结构体进行移动,previous指向前一个函数地址,return_address 指向当前地址
// 一共获取50个堆栈信息 当previous ==0 或者 获取 向前移动指针失败 则终止
for(; i < 50; i++) { backtraceBuffer[i] = frame.return_address; // 将vm 中的 指针frame.previous 指针数据 同步到frame上 if(backtraceBuffer[i] == 0 || frame.previous == 0 || bs_mach_copyMem(frame.previous, &frame, sizeof(frame)) != KERN_SUCCESS) { break; } }
int backtraceLength = i; Dl_info symbolicated[backtraceLength]; // 符号化 将address 翻译成 可读符号 bs_symbolicate(backtraceBuffer, symbolicated, backtraceLength, 0); for (int i = 0; i < backtraceLength; ++i) { [resultString appendFormat:@"%@", bs_logBacktraceEntry(i, backtraceBuffer[i], &symbolicated[i])]; } [resultString appendFormat:@"\n"]; return [resultString copy]; }
// 将vm中的src数据同步到dst中 kern_return_t bs_mach_copyMem(const void const src, void const dst, const size_t numBytes){ vm_size_t bytesCopied = 0; return vm_read_overwrite(mach_task_self(), (vm_address_t)src, (vm_size_t)numBytes, (vm_address_t)dst, &bytesCopied);
} ```
符号化
将backtraceBuffer
中的指针地址符号化填充到Dl_info
结构体中
首先遍历符号表 ``` //符号化
void bs_symbolicate(const uintptr_t const backtraceBuffer, Dl_info const symbolsBuffer, const int numEntries, const int skippedEntries){
int i = 0;
if(!skippedEntries && i < numEntries) { bs_dladdr(backtraceBuffer[i], &symbolsBuffer[i]); i++; } for(; i < numEntries; i++) { bs_dladdr(CALL_INSTRUCTION_FROM_RETURN_ADDRESS(backtraceBuffer[i]), &symbolsBuffer[i]); } }
// address: 内存地址 bool bs_dladdr(const uintptr_t address, Dl_info* const info) {
info->dli_fname = NULL; info->dli_fbase = NULL; info->dli_sname = NULL; info->dli_saddr = NULL; //获取imageIdex 然后可以根据idx 得到ASLR的值,还有mach_header const uint32_t idx = bs_imageIndexContainingAddress(address);
if(idx == UINT_MAX) { return false; }
// 获取当前 image header const struct mach_header* header = _dyld_get_image_header(idx); // imageVMAddrSlide: ASLR const uintptr_t imageVMAddrSlide = (uintptr_t)_dyld_get_image_vmaddr_slide(idx);
// addressWithSlide: macho 中的地址 = 内存地址 - ASLR const uintptr_t addressWithSlide = address - imageVMAddrSlide;
// segmentBase 获取到macho seg的 base地址 const uintptr_t segmentBase = bs_segmentBaseOfImageIndex(idx) + imageVMAddrSlide; if(segmentBase == 0) { return false; } info->dli_fname = _dyld_get_image_name(idx); info->dli_fbase = (void)header; // Find symbol tables and get whichever symbol is closest to the address. const BS_NLIST bestMatch = NULL; uintptr_t bestDistance = ULONG_MAX; uintptr_t cmdPtr = bs_firstCmdAfterHeader(header); if(cmdPtr == 0) { return false; }
for(uint32_t iCmd = 0; iCmd < header->ncmds; iCmd++) { const struct load_command loadCmd = (struct load_command)cmdPtr; if(loadCmd->cmd == LC_SYMTAB) { const struct symtab_command symtabCmd = (struct symtab_command)cmdPtr; // 符号表初始地址 = segmentBase + symOff (偏移量) const BS_NLIST symbolTable = (BS_NLIST)(segmentBase + symtabCmd->symoff); // stringTable 地址= segmentBase+偏移量 const uintptr_t stringTable = segmentBase + symtabCmd->stroff; // symtabCmd->nsyms 符号表个数 for(uint32_t iSym = 0; iSym < symtabCmd->nsyms; iSym++) { // If n_value is 0, the symbol refers to an external object. // 取出symbolBase 符号中在stringtable的偏移量,for循环最大的一个addressWithSlide >= symbolBase
// 就是需要的字符串,然后再stringtable中找出字符即可。 if(symbolTable[iSym].n_value != 0) { uintptr_t symbolBase = symbolTable[iSym].n_value;//符号 在stringtable的偏移量 uintptr_t currentDistance = addressWithSlide - symbolBase; if((addressWithSlide >= symbolBase) && (currentDistance <= bestDistance)) { bestMatch = symbolTable + iSym; bestDistance = currentDistance; } } } if(bestMatch != NULL) { // 内存中字符串的地址 = 符号表中的符号的偏移量+ASLR info->dli_saddr = (void)(bestMatch->n_value + imageVMAddrSlide); //stringtable + 偏移量 = 字符串地址 info->dli_sname = (char)((intptr_t)stringTable + (intptr_t)bestMatch->n_un.n_strx);
if(*info->dli_sname == '_') { info->dli_sname++; } // This happens if all symbols have been stripped. if(info->dli_saddr == info->dli_fbase && bestMatch->n_type == 3) { info->dli_sname = NULL; } break; } } cmdPtr += loadCmd->cmdsize; } return true; } ```
参考代码