iOS 獲取任意執行緒堆疊資訊

語言: CN / TW / HK

緣由: 在程式崩潰的時候很容易獲取到堆疊資訊,程式設計師很容易檢視到因為哪個函式導致的崩潰,但是卡頓現象和高CPU利用率的時候要檢視執行緒的堆疊資訊,系統暫未提供方法,所有有了這篇文章

卡頓的時候首先要獲取到執行緒,根據執行緒再獲取堆疊資訊。

1. 獲取執行緒

mach-o/dyld.hmach-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; } ```

參考程式碼

BSBacktraceLogger