1. 程式人生 > >趣探 Mach-O:符號解析

趣探 Mach-O:符號解析

這是 Mach-O 系列的第二篇,這篇文章我理解起來感覺不是特別深入,如有問題還望指出,目前也在做更多 Mach-O 的總結,如有新的理解,我也會及時更新文章

符號解析中會遇到很多名詞和函式,首先介紹一下這些知識點,然後符號解析會參照 KSCrash的原始碼來進行分析

Dsym

debugging SYMbols:除錯符號表

當我們build的時候,就會在.app檔案中同時生成一個 Dsym 檔案,我們在後期捕獲到線上 Crash 或者 卡頓 堆疊的地址資訊時,會結合 Dsym 進行符號還原,進而確認卡頓、崩潰的具體位置

ASLR

這個在 趣探 Mach-O:檔案格式分析 這篇文章中也有提到,這裡再深入闡述一下。

ASLR:Address space layout randomization,將可執行程式隨機裝載到記憶體中,這裡的隨機只是偏移,而不是打亂,具體做法就是通過核心將 Mach-O 的段“平移”某個隨機係數

我們再來看一下crash堆疊資訊

1234567 Thread0:0libsystem_kernel.dylib0x10e58110a0x10e56a000+944741libsystem_c.dylib0x10e303b0b0x10e285000+5189232QYPerformanceMonitor0x10afcdf820x10afcc000+80663UIKit0x10bf764f40x10bdf3000+15864204UIKit0x10bf7662c0x10bdf3000+15867325UIKit0x10bf4ad4f0x10bdf3000+1408335

我們先關注第三列,這個是函式呼叫完的返回地址,我們也叫做

Frame Pointer Addreass。第四列,是共享物件的的起始地址(Base address of shared object,下面叫做基地址,比如上面的 UIKit)

在終端下,如果我們配合 Dsym 進行符號解析的話,需要這樣子寫指令

1 atos-oYour.app.dSYM/Contents/Resources/DWARF/Your0x10bf764f4-arch arm64-l0x10bdf3000

可以發現僅僅依靠 Frame Pointer 和 Dsym 並不能夠進行符號化解析,還需要基地址的配合。為什麼呢?

因為ASLR 引入了一個 slide(偏移),可以通過dyld_get_image_vmaddr_slide() 來進行獲取,函式對應在符號表的地址、slideframe Pointer address滿足下面這個公式。slide可以通過程式的 api 獲取,也可以通過 Dsym 檔案拿到

1 symbol addressframe pointer addressslide

Dl_info

123456789 /* * Structure filled in by dladdr(). */typedefstructdl_info{constchar*dli_fname;/* Pathname of shared object */void*dli_fbase;/* Base address of shared object */constchar*dli_sname;/* Name of nearest symbol */void*dli_saddr;/* Address of nearest symbol */}Dl_info;

我們一會經過 dladdr()處理後的有效資訊都會放進這個結構體中

  • fname:路徑名,例如
1 /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation
  • dli_fbase:剛才講到的共享物件的的起始地址(Base address of shared object,下面叫做基地址,比如上面的 CoreFoundation)
  • dli_saddr :符號的地址
  • dli_sname:符號的名字,即下面的第四列的函式資訊
    12345 Thread0:0libsystem_kernel.dylib0x11135810a__semwait_signal+944741libsystem_c.dylib0x1110dab0bsleep+5189232QYPerformanceMonitor0x10dda4f1b-[ViewController tableView:cellForRowAtIndexPath:]+79633UIKit0x10ed4d4f4-[UITableView _createPreparedCellForGlobalRow:withIndexPath:willDisplay:]+1586420

LC_SYMTAB

12345678 structsymtab_command{uint32_t    cmd;/* LC_SYMTAB */uint32_t    cmdsize;/* sizeof(struct symtab_command) */uint32_t    symoff;/* symbol table offset */uint32_t    nsyms;/* number of symbol table entries */uint32_t    stroff;/* string table offset */uint32_t    strsize;/* string table size in bytes */};

符號表在 Mach-O目標檔案中的地址可以通過LC_SYMTAB載入命令指定的 symoff找到,對應的符號名稱在stroff,總共有nsyms條符號資訊

nlist

nlist的資料結構看似比較簡單,但是裡面具有很多的學問,這裡先只簡單介紹一下它的資料結構,足夠我們接下來的分析原始碼即可

123456789101112 /* * This is the symbol table entry structure for 32-bit architectures. */structnlist{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 */int16_t n_desc;/* see  */uint32_t n_value;/* value of this symbol (or stab offset) */};

符號解析

符號解析基本思路如下

  • 根據 Frame Pointer 拿到函式呼叫的地址(address)
  • 尋找包含地址 (address) 的目標映象(image)
  • 拿到映象檔案的符號表、字串表
  • 根據 address 、符號表、字串表的對應關係找到對應的函式名

下面分析的主要是ksdl_dladdr函式

首先根據 address,找到目標映象

12345678 boolksdl_dladdr(constuintptr_t address,Dl_info*constinfo){// 初始 Dl_infoinfo->dli_fname=NULL;info->dli_fbase=NULL;info->dli_sname=NULL;info->dli_saddr=NULL;// image indexconstuint32_t idx=ksdl_imageIndexContainingAddress(address);

後面兩步驟是關鍵

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152 conststructmach_header*header=_dyld_get_image_header(idx);// slideconstuintptr_t imageVMAddrSlide=(uintptr_t)_dyld_get_image_vmaddr_slide(idx);constuintptr_t addressWithSlide=address-imageVMAddrSlide;// 段基址constuintptr_t segmentBase=ksdl_segmentBaseOfImageIndex(idx)+imageVMAddrSlide;// 拿到了 Pathnameinfo->dli_fname=_dyld_get_image_name(idx);// 拿到了基地址info->dli_fbase=(void*)header;// Find symbol tables and get whichever symbol is closest to the address// 在符號表中查詢哪個符號最接近這個指令的地址// nlistconstSTRUCT_NLIST*bestMatch=NULL;uintptr_t bestDistance=ULONG_MAX;// load commonduintptr_t cmdPtr=ksdl_firstCmdAfterHeader(header);// header->ncmds 代表所有的載入命令,這裡進行遍歷,查詢 LC_SYMTABfor(uint32_t iCmd=0;iCmd ncmds;iCmd++){conststructload_command*loadCmd=(structload_command*)cmdPtr;if(loadCmd->cmd==LC_SYMTAB){// symtab_command  LC_SYMTABconststructsymtab_command*symtabCmd=(structsymtab_command*)cmdPtr;// 找到符號表constSTRUCT_NLIST*symbolTable=(STRUCT_NLIST*)(segmentBase+symtabCmd->symoff);//  找到字串表constuintptr_t stringTable=segmentBase+symtabCmd->stroff;// 遍歷符號表for(uint32_t iSym=0;iSym nsyms;iSym++){// If n_value is 0, the symbol refers to an external object.if(symbolTable[iSym].n_value!=0){uintptr_t symbolBase=symbolTable[iSym].n_value;// addr >= symbol.value 說明這個指令在這個函式入口中// 獲取和address的距離找到最接近的一個// 離指令地址addr更近的函式入口地址,才是更準確的匹配項uintptr_t currentDistance=addressWithSlide-symbolBase;if((addressWithSlide>=symbolBase)&&(currentDistance dli_saddr=(void*)(bestMatch->n_value+imageVMAddrSlide);// 符號的名字info->dli_sname=(char*)((intptr_t)stringTable+(intptr_t)bestMatch->n_un.n_strx);break;}}cmdPtr+=loadCmd->cmdsize;}returntrue;}

參考連結