場(chǎng)景需求
線上app運(yùn)行過(guò)程中有內(nèi)存突變、卡頓效五、cpu飆升地消、crash等情況,需要獲取發(fā)生這些情況時(shí)的所有堆棧信息畏妖,以此來(lái)輔助定位問(wèn)題
1. callStackSymbols
只能獲取當(dāng)前堆棧信息脉执,不能獲取指定其他線程的信息,所以不滿足要求
[NSThread callStackSymbols];
0 LXDAppFluecyMonitor 0x0000000102a30699 -[ViewController tableView:didSelectRowAtIndexPath:] + 89,
1 UIKitCore 0x0000000116721902 -[UITableView _selectRowAtIndexPath:animated:scrollPosition:notifyDelegate:isCellMultiSelect:deselectPrevious:] + 1962,
2 UIKitCore 0x000000011672113d -[UITableView _selectRowAtIndexPath:animated:scrollPosition:notifyDelegate:] + 94,
3 UIKitCore 0x0000000116721bcb -[UITableView _userSelectRowAtPendingSelectionIndexPath:] + 341,
4 UIKitCore 0x0000000116a322d5 -[_UIAfterCACommitBlock run] + 54,
5 UIKitCore 0x0000000116a327cd -[_UIAfterCACommitQueue flush] + 190,
6 libdispatch.dylib 0x000000010c7d6816 _dispatch_call_block_and_release + 12,
7 libdispatch.dylib 0x000000010c7d7a5b _dispatch_client_callout + 8,
8 libdispatch.dylib 0x000000010c7e6325 _dispatch_main_queue_drain + 1169,
9 libdispatch.dylib 0x000000010c7e5e86 _dispatch_main_queue_callback_4CF + 31,
10 CoreFoundation 0x000000010b5d6261 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 9,
2. Mach Thread
思路
- 通過(guò)內(nèi)核API獲取所有線程列表
- 遍歷每個(gè)
pthread_t
戒劫,獲取線程上下文信息_STRUCT_MCONTEXT - 通過(guò)context獲得棧幀指針半夷,然后不斷調(diào)用previous獲得當(dāng)前線程的所有調(diào)用堆棧
- 通過(guò)棧幀指針獲得函數(shù)調(diào)用地址
- 通過(guò)_dyld_image相關(guān)API遍歷所有image鏡像
- 找到load commands的LC_SEGMENT(__TEXT)中包含函數(shù)地址的鏡像
- 獲取ASLR,然后找到函數(shù)地址在符號(hào)表中對(duì)應(yīng)的位置
- 然后去字符表中查找函數(shù)名字
獲取堆棧函數(shù)調(diào)用地址
-
所有線程:調(diào)用內(nèi)核API函數(shù)
task_threads
獲取指定task線程列表迅细,即list
thread_act_array_t list; mach_msg_type_number_t count; task_threads(mach_task_self(), &list, &count);
-
指定線程:調(diào)用API函數(shù)
pthread_from_mach_thread_np
獲得對(duì)應(yīng)線程pthread_t
巫橄,非UI線程比較namefor (int idx = 0; idx < count; idx++) { pthread_t pt = pthread_from_mach_thread_np(list[idx]); if ([nsthread isMainThread] && list[idx] == main_thread_id) { return list[idx]; } if (pt) { name[0] = '\0'; pthread_getname_np(pt, name, sizeof(name)); if (!strcmp(name, [nsthread name].UTF8String)) { [nsthread setName: originName]; return list[idx]; } } }
-
線程信息:調(diào)用
thread_get_state
獲得指定線程上下問(wèn)信息_STRUCT_MCONTEXT
。thread_get_stateAPI兩個(gè)參數(shù)隨著cpu架構(gòu)不同而改變茵典。_STRUCT_MCONTEXT
結(jié)構(gòu)存儲(chǔ)當(dāng)前線程棧頂指針(stack pointer)和最頂部的棧幀指針(frame pointer)湘换,從而獲得整個(gè)線程的調(diào)用棧。
thread_get_state傳入thread,_STRUCT_MCONTEXT->__ss(寄存器指針結(jié)構(gòu)體)
彩倚,以及cpu相關(guān)常量(target_act筹我,old_stateCnt)
,來(lái)實(shí)現(xiàn)_STRUCT_MCONTEXT賦值bool lxd_fillThreadStateIntoMachineContext(thread_t thread, _STRUCT_MCONTEXT * machineContext) { mach_msg_type_number_t state_count = LXD_THREAD_STATE_COUNT; kern_return_t kr = thread_get_state(thread, LXD_THREAD_STATE, (thread_state_t)&machineContext->__ss, &state_count); return (kr == KERN_SUCCESS); }
-
棧幀結(jié)構(gòu)體賦值
vm_read_overwrite
- 棧幀結(jié)構(gòu)體
typedef struct StackFrameEntry{ const struct StackFrameEntry *const previous; //前一個(gè)棧幀地址 const uintptr_t return_address; //棧幀的函數(shù)返回地址 } StackFrameEntry;
- 通過(guò)上一步獲取的machineContext獲取第一個(gè)棧幀指針
lxd_mach_copyMem((void *)machineContext->__ss.LXD_FRAME_POINTER, &frame, sizeof(frame))
打印frame//參數(shù)src:棧幀指針 //參數(shù)dst:StackFrameEntry實(shí)例指針 //參數(shù)numBytes:StackFrameEntry結(jié)構(gòu)體大小 kern_return_t lxd_mach_copyMem(const void * src, const void * dst, const size_t numBytes) { vm_size_t bytesCopied = 0; // 調(diào)用api函數(shù)帆离,根據(jù)棧幀指針獲取該棧幀對(duì)應(yīng)的函數(shù)地址 return vm_read_overwrite(mach_task_self(), (vm_address_t)src, (vm_size_t)numBytes, (vm_address_t)dst, &bytesCopied); }
Printing description of frame: (LXDStackFrameEntry) frame = { previous = 0x0000000109f6cb68 return_address = 11598032417672659023 }
- 通過(guò)
frame.previous
獲取前一個(gè)棧幀地址蔬蕊,不斷遍歷,獲得當(dāng)前線程所有函數(shù)調(diào)用的地址//循環(huán)遍歷,停止條件MAX_FRAME_NUMBER棧幀個(gè)數(shù) for (; idx < MAX_FRAME_NUMBER; idx++) { //棧幀函數(shù)賦值 backtraceBuffer[idx] = frame.return_address; if (backtraceBuffer[idx] == FAILED_UINT_PTR_ADDRESS || frame.previous == NULL || //根據(jù)當(dāng)前的棧幀的previous哥谷,獲取前一個(gè)棧幀地址 lxd_mach_copyMem(frame.previous, &frame, sizeof(frame)) != KERN_SUCCESS) { break; } }
- 棧幀結(jié)構(gòu)體
獲得堆棧調(diào)用函數(shù)名
關(guān)于Mach-O的相關(guān)知識(shí)可以看這篇文章:https://www.coderzhou.com/2019/06/05/fishhook/#Mach-O
源碼參考:https://github.com/bestswifter/BSBacktraceLogger
- 創(chuàng)建一個(gè)和上面backtraceBuffer長(zhǎng)度一樣的Dl_info數(shù)組
Dl_info symbolicated[backtraceLength];
- 逐個(gè)遍歷backtraceBuffer岸夯,獲取對(duì)應(yīng)的符號(hào)信息添加到symbolicated中
- 找到棧幀地址對(duì)應(yīng)的image鏡像
- 遍歷鏡像,通過(guò)
_dyld_get_image_vmaddr_slide
獲取ASLR偏移地址呼巷,計(jì)算出調(diào)用函數(shù)棧幀地址在mach-O文件中的地址 - 遍歷mach-o的load commands找到
LC_SEGMENT
段 - 計(jì)算調(diào)用函數(shù)在mach-o中的地址是否包含在
LC_SEGMENT
段中 - 返回鏡像idx
uint32_t lxd_imageIndexContainingAddress(const uintptr_t address) { const uint32_t imageCount = _dyld_image_count(); const struct mach_header * header = FAILED_UINT_PTR_ADDRESS; for (uint32_t iImg = 0; iImg < imageCount; iImg++) { header = _dyld_get_image_header(iImg); if (header != NULL) { // ASLR: _dyld_get_image_vmaddr_slide獲取偏移slide uintptr_t addressWSlide = address - (uintptr_t)_dyld_get_image_vmaddr_slide(iImg); uintptr_t cmdPtr = lxd_firstCmdAfterHeader(header); if (cmdPtr == FAILED_UINT_PTR_ADDRESS) { continue; } for (uint32_t iCmd = 0; iCmd < header->ncmds; iCmd++) { const struct load_command * loadCmd = (struct load_command *)cmdPtr; if (loadCmd->cmd == LC_SEGMENT) { const struct segment_command * segCmd = (struct segment_command *)cmdPtr; if (addressWSlide >= segCmd->vmaddr && addressWSlide < segCmd->vmaddr + segCmd->vmsize) { return iImg; } } else if (loadCmd->cmd == LC_SEGMENT_64) { const struct segment_command_64 * segCmd = (struct segment_command_64 *)cmdPtr; if (addressWSlide >= segCmd->vmaddr && addressWSlide < segCmd->vmaddr + segCmd->vmsize) { char *image_name = (char *)_dyld_get_image_name(iImg); const struct mach_header *mh = _dyld_get_image_header(iImg); intptr_t vmaddr_slide = _dyld_get_image_vmaddr_slide(iImg); printf("Image name %s at address 0x%llx and ASLR slide 0x%lx.\n", image_name, (mach_vm_address_t)mh, vmaddr_slide); return iImg; } } cmdPtr += loadCmd->cmdsize; } } } return UINT_MAX; }
用MachOView查看囱修,和上面獲取的數(shù)據(jù)是一致的
打印出segCmd的虛擬內(nèi)存結(jié)束的地址,判斷函數(shù)虛擬內(nèi)存地址是否在當(dāng)前段中
- 遍歷鏡像,通過(guò)
- 找到對(duì)應(yīng)鏡像中l(wèi)oad commands的起始段地址王悍,這里正好是代碼段
__TEXT
uintptr_t lxd_segmentBaseOfImageIndex(const uint32_t idx) { const struct mach_header * header = _dyld_get_image_header(idx); uintptr_t cmdPtr = lxd_firstCmdAfterHeader(header); if (cmdPtr == FAILED_UINT_PTR_ADDRESS) { return FAILED_UINT_PTR_ADDRESS; } for (uint32_t idx = 0; idx < header->ncmds; idx++) { const struct load_command * loadCmd = (struct load_command *)cmdPtr; if (loadCmd->cmd == LC_SEGMENT) { const struct segment_command * segCmd = (struct segment_command *)cmdPtr; if (strcmp(segCmd->segname, SEG_LINKEDIT) == 0) { return segCmd->vmaddr - segCmd->fileoff; } } else if (loadCmd->cmd == LC_SEGMENT_64) { const struct segment_command_64 * segCmd = (struct segment_command_64 *)cmdPtr; if (strcmp(segCmd->segname, SEG_LINKEDIT) == 0) { return segCmd->vmaddr - segCmd->fileoff; } } cmdPtr += loadCmd->cmdsize; } return FAILED_UINT_PTR_ADDRESS; }
- 遍歷load commands破镰,找到LC_SYMTAB,里面包含了符號(hào)表和字符串表的偏移信息
struct symtab_command { uint32_t cmd; /* LC_SYMTAB */ uint32_t cmdsize; /* sizeof(struct symtab_command) */ uint32_t symoff; /* 表示符號(hào)表的偏移 */ uint32_t nsyms; /* 符號(hào)表?xiàng)l目的個(gè)數(shù) */ uint32_t stroff; /* 字符串表在文件中的偏移 */ uint32_t strsize; /* 字符串表的大小 */ };
- 遍歷符號(hào)表压储,找到函數(shù)地址對(duì)應(yīng)的符號(hào)表?xiàng)l目所在的地址
符號(hào)表單條目結(jié)構(gòu)體struct nlist_64 { union { uint32_t n_strx; /* index into the string table */ } n_un; uint8_t n_type; /* type flag, see below */ uint8_t n_sect; /* section number or NO_SECT */ uint16_t n_desc; /* see <mach-o/stab.h> */ uint64_t n_value; /* value of this symbol (or stab offset) */ };
- 通過(guò)上一步獲取的符號(hào)表數(shù)據(jù)鲜漩,獲得函數(shù)符號(hào)在字符串表中的偏移量,然后獲得對(duì)應(yīng)的字符串
函數(shù)調(diào)用地址在符號(hào)表中對(duì)應(yīng)的位置if (loadCmd->cmd == LC_SYMTAB) { //LC_SYMTAB 是符號(hào)表和字符串表的偏移信息 const struct symtab_command * symtabCmd = (struct symtab_command *)cmdPtr; //符號(hào)表在內(nèi)存中的地址(包含偏移) symoff符號(hào)表的偏移 const LXD_NLIST * symbolTable = (LXD_NLIST *)(segmentBase + symtabCmd->symoff); //字符串表在內(nèi)存中的地址(包含偏移) stroff字符串表在文件中的偏移 const uintptr_t stringTable = segmentBase + symtabCmd->stroff; //nsyms符號(hào)表?xiàng)l目的個(gè)數(shù) for (uint32_t iSym = 0; iSym < symtabCmd->nsyms; iSym++) { if (symbolTable[iSym].n_value == FAILED_UINT_PTR_ADDRESS) { continue; } //符號(hào)表每一項(xiàng)開始地址 uintptr_t symbolBase = symbolTable[iSym].n_value; //函數(shù)地址在符號(hào)表的偏移 uintptr_t currentDistance = addressWithSlide - symbolBase; if ( (addressWithSlide >= symbolBase && currentDistance <= bestDistance) ) { bestMatch = symbolTable + iSym; bestDistance = currentDistance; } } if (bestMatch != NULL) { info->dli_saddr = (void *)(bestMatch->n_value + imageVMAddressSlide); //n_un.n_strx 表示符號(hào)名在字符串表中的偏移量集惋,用于表示函數(shù)名 info->dli_sname = (char *)((intptr_t)stringTable + (intptr_t)bestMatch->n_un.n_strx); NSLog(@"%s",info->dli_sname); if (*info->dli_sname == '_') { info->dli_sname++; } if (info->dli_saddr == info->dli_fbase && bestMatch->n_type == 3) { info->dli_sname = NULL; } break; } }
0x00000001000048a0
MachOView中查看
ASLR地址是0x0000000004f31000
函數(shù)調(diào)用字符在字符串表中的地址0x0000000104f40940
去掉偏移量的地址:0x0000000104f40940 - 0x0000000004f31000 = 0x000000010000F940
在MachOView中查看
打印信息
參考文章
http://www.reibang.com/p/df5b08330afd
http://www.reibang.com/p/8b78bbbcaf89
https://blog.csdn.net/jasonblog/article/details/49909209
https://elliotsomething.github.io/2017/06/28/thread%E5%AD%A6%E4%B9%A0/