任務(wù)Mach-Task
描述:一個(gè)機(jī)器無(wú)關(guān)的thread的執(zhí)行環(huán)境抽象
作用:task可以理解為一個(gè)進(jìn)程,包含它的線程列表
結(jié)構(gòu)體:
task_threads
task_threads將traget_task任務(wù)下的所有線程保存在act_list數(shù)組中,數(shù)組個(gè)數(shù)為act_listCnt
kern_return_t task_threads
(
task_t traget_task,
thread_act_array_t *act_list, //線程指針列表
mach_msg_type_number_t *act_listCnt //線程個(gè)數(shù)
)
thread_info線程信息
kern_return_t thread_info
(
thread_act_t target_act,
thread_flavor_t flavor,
thread_info_t thread_info_out,
mach_msg_type_number_t *thread_info_outCnt
);
如何獲取線程的堆棧數(shù)據(jù)
1.所有線程:調(diào)用內(nèi)核API函數(shù)task_threads獲取指定task線程列表,即act_list
2.指定線程:調(diào)用API函數(shù)thread_info獲得對(duì)應(yīng)線程信息thread_info
3.線程信息:調(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)前線程棧頂指針(sp)和最頂部的棧幀指針(frame pointer),從而獲得整個(gè)線程的調(diào)用棧`。
函數(shù)調(diào)用棧原理
指令指針
- 指令指針
IP:指令寄存器存儲(chǔ),指向處理器下條等待執(zhí)行的指令地址(代碼內(nèi)的偏移量),每次執(zhí)行完 IP會(huì)增加 - 堆棧棧頂指針
SP:堆棧指令寄存器存儲(chǔ),系統(tǒng)棧的棧頂?shù)刂?/li> - 棧幀指針
FP:棧幀基址指令寄存器存儲(chǔ),每個(gè)棧幀都有一個(gè)對(duì)應(yīng)的棧幀基地址,局部變量和函數(shù)參數(shù)都可以通過(guò)FP確定,因?yàn)樗鼈兊紽P的距離不會(huì)受到壓棧和出棧操作影響。
為了訪問(wèn)函數(shù)局部變量,必須能定位每個(gè)變量。局部變量相對(duì)于堆棧指針SP的位置在進(jìn)入函數(shù)時(shí)就已確定,理論上變量可用SP加偏移量來(lái)引用,但SP會(huì)在函數(shù)執(zhí)行期隨變量的壓棧和出棧而變動(dòng)。盡管某些情況下編譯器能跟蹤棧中的變量操作以修正偏移量,但要引入可觀的管理開(kāi)銷。而且在有些機(jī)器上(如Intel處理器),用SP加偏移量來(lái)訪問(wèn)一個(gè)變量需要多條指令才能實(shí)現(xiàn),由此設(shè)計(jì)了棧幀指針FP,FP兩側(cè)分別記錄函數(shù)參數(shù),及局部變量。
函數(shù)調(diào)用棧內(nèi)部布局
棧幀:函數(shù)(運(yùn)行中且未完成)占用的一塊獨(dú)立的連續(xù)內(nèi)存區(qū)域。
函數(shù)調(diào)用通常是嵌套的,當(dāng)調(diào)用函數(shù)時(shí)邏輯棧幀被壓入堆棧, 當(dāng)函數(shù)返回時(shí)邏輯棧幀被從堆棧中彈出。棧幀存放著函數(shù)參數(shù),局部變量及恢復(fù)前一棧幀所需要的數(shù)據(jù)等。
編譯器利用棧幀,使得函數(shù)參數(shù)和函數(shù)中局部變量的分配與釋放對(duì)程序員透明。編譯器將控制權(quán)移交函數(shù)本身之前,插入特定代碼將函數(shù)參數(shù)壓入棧幀中,并分配足夠的內(nèi)存空間用于存放函數(shù)中的局部變量。使用棧幀的一個(gè)好處是使得遞歸變?yōu)榭赡?,因?yàn)閷?duì)函數(shù)的每次遞歸調(diào)用,都會(huì)分配給該函數(shù)一個(gè)新的棧幀,這樣就巧妙地隔離當(dāng)前調(diào)用與上次調(diào)用。
棧幀的邊界由棧幀基地址指針EBP和堆棧指針ESP界定(指針存放在相應(yīng)寄存器中)。EBP指向當(dāng)前棧幀底部(高地址),在當(dāng)前棧幀內(nèi)位置固定;ESP指向當(dāng)前棧幀頂部(低地址),當(dāng)程序執(zhí)行時(shí)ESP會(huì)隨著數(shù)據(jù)的入棧和出棧而移動(dòng)。因此函數(shù)中對(duì)大部分?jǐn)?shù)據(jù)的訪問(wèn)都基于EBP進(jìn)行。
函數(shù)出入棧過(guò)程
- BP棧幀指針地址:間隔被調(diào)用函數(shù)(局部變量?jī)?nèi)存空間)和調(diào)用函數(shù)(被調(diào)函數(shù)參數(shù),調(diào)用函數(shù)地址,指令指針)
- BP棧幀指針值:上一個(gè)棧幀的地址值,便于被調(diào)函數(shù)釋放后,回到調(diào)用函數(shù)
- BP棧幀入棧時(shí)機(jī):函數(shù)被調(diào)用,申請(qǐng)內(nèi)存空間來(lái)存儲(chǔ)前一個(gè)棧幀的地址值

從圖中可以看出,函數(shù)調(diào)用時(shí)入棧順序?yàn)椋?br> 實(shí)參N-1→主調(diào)函數(shù)返回地址→主調(diào)函數(shù)幀基指針EBP→被調(diào)函數(shù)局部變量1-N 。
注意:
內(nèi)存地址降序
函數(shù)定義
- caller(主調(diào)函數(shù),紫色)
- callee(被調(diào)函數(shù),藍(lán)色)
入棧過(guò)程
1.caller未調(diào)用callee,內(nèi)存分布如下:
EBP:caller EBP
ESP:caller的LocalVariables2.caller調(diào)用callee
callee函數(shù)的參數(shù)入棧(由caller提供)
caller的函數(shù)地址(vm_add), EIP入棧(代碼偏移量offset)。備注:代碼位置=vm_add+offset3.callee棧幀指針入棧
申請(qǐng)棧幀指針空間
存儲(chǔ)caller的棧幀指針地址4.申請(qǐng)callee局部變量空間
為局部變量申請(qǐng)足夠的內(nèi)存空間
Local Variable#1,Local Variable#2,Local Variable#3...Local Variable#n
EBP:callee的EBP
ESP:Local Variable#n
出棧過(guò)程
1.callee調(diào)用完畢
callee局部變量空間釋放
EBP:callee ebp -> caller ebb
ESP:caller ebp2.caller函數(shù)執(zhí)行復(fù)原
代碼執(zhí)行復(fù)原:ip+return address =代碼位置
callee函數(shù)空間釋放:Argumne #1,Argumne #2,...,Argumne #1n
EBP:caller ebb
ESP:caller Load Variables
函數(shù)調(diào)用地址獲取
獲取thread
API函數(shù)task_thread獲取線程數(shù)組地址及線程個(gè)數(shù)
API函數(shù)task_thread聲明
kern_return_t task_threads
(
task_t traget_task,
thread_act_array_t *act_list, //線程指針列表
mach_msg_type_number_t *act_listCnt //線程個(gè)數(shù)
)
使用代碼
thread_act_array_t threads;
mach_msg_type_number_t thread_count=0;
task_threads(mach_task_self(), &thrads, &thread_count);
thread的內(nèi)存上下文
API函數(shù)thread_get_state獲取內(nèi)存上下文,上下文信息存儲(chǔ)在_struct_mcontext結(jié)構(gòu)體內(nèi)
kern_return_t thread_get_state
(
thread_act_t target_act, //thread
thread_state_flavor_t flavor,
thread_state_t old_state,
mach_msg_type_number_t *old_stateCnt
);
備注:target_act和old_stateCnt配套使用,與cpu類型相關(guān)
使用代碼
bool fillThreadStateIntoMachineContext(thread_t thread, _STRUCT_MCONTEXT * machineContext) {
mach_msg_type_number_t state_count = LSL_THREAD_STATE_COUNT;
kern_return_t kr = thread_get_state(thread, LSL_THREAD_STATE, (thread_state_t)&machineContext->__ss, &state_count);
return (kr == KERN_SUCCESS);
}
thread_get_state傳入thread,_STRUCT_MCONTEXT->__ss(寄存器指針結(jié)構(gòu)體),以及cpu相關(guān)常量(target_act,old_stateCnt),來(lái)實(shí)現(xiàn)_STRUCT_MCONTEXT賦值
堆棧指針獲取
_STRUCT_MCONTEXT結(jié)構(gòu)體獲取堆棧指針
如x86_64為_(kāi)STRUCT_MCONTEXT->__ss結(jié)構(gòu)體如下
#define DETAG_INSTRUCTION_ADDRESS(A) (A)
#define LSL_THREAD_STATE_COUNT x86_THREAD_STATE64_COUNT //thread_get_state函數(shù)參數(shù)
#define LSL_THREAD_STATE x86_THREAD_STATE64 //thread_get_state函數(shù)參數(shù)
#define LSL_FRAME_POINTER __rbp
#define LSL_STACK_POINTER __rsp
#define LSL_INSTRUCTION_ADDRESS __rip
指令指針
_STRUCT_MCONTEXT->__ss.LSL_INSTRUCTION_ADDRESS //rip 指令指針
棧頂指針
_STRUCT_MCONTEXT->__ss.LSL_STACK_POINTER //bsp 棧頂指針
棧幀指針
_STRUCT_MCONTEXT->__ss.LSL_FRAME_POINTER //rbp 棧幀指針
棧幀結(jié)構(gòu)體
棧幀結(jié)構(gòu)體StackFrameEntry
typedef struct StackFrameEntry{
const struct StackFrameEntry *const previous; //前一個(gè)棧幀地址
const uintptr_t return_address; //棧幀的函數(shù)返回地址
} StackFrameEntry;
首個(gè)棧幀結(jié)構(gòu)體賦值
API函數(shù)vm_read_overwrite
kern_return_t vm_read_overwrite
(
vm_map_t target_task, //task任務(wù)
vm_address_t address, //棧幀指針FP
vm_size_t size, //結(jié)構(gòu)體大小 sizeof(StackFrameEntry)
vm_address_t data, //結(jié)構(gòu)體指針StackFrameEntry
vm_size_t *outsize //賦值大小
);
使用代碼
//參數(shù)src:棧幀指針
//參數(shù)dst:StackFrameEntry實(shí)例指針
//參數(shù)numBytes:StackFrameEntry結(jié)構(gòu)體大小
kern_return_t lsl_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);
}
函數(shù)地址
參考上一步,完成首個(gè)棧幀結(jié)構(gòu)體賦值后
1.通過(guò)棧幀結(jié)構(gòu)體StackFrameEntry->previous,遍歷所有棧幀
2.API函數(shù)vm_read_overwrite對(duì)棧幀結(jié)構(gòu)體賦值,獲取當(dāng)前棧幀函數(shù)
偽代碼
//循環(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è)棧幀地址
lsl_mach_copyMem(frame.previous, &frame, sizeof(frame)) != KERN_SUCCESS) {
break;
}
線程函數(shù)地址獲取小結(jié)
- 1.找到目標(biāo)thread,方法:API函數(shù)
task_threads - 2.獲得thread的內(nèi)存上下文_STRUCT_CONTEXT,方法:API函數(shù)
thread_get_state - 3.獲取指針棧幀結(jié)構(gòu)體
_STRUCT_CONTEXT._ss,解析得到對(duì)應(yīng)指令指針_STRUCT_CONTEXT._ss.ip;首次個(gè)棧幀指針_STRUCT_CONTEXT._ss.bp;棧頂指針_STRUCT_CONTEXT._ss.sp -
首個(gè)棧幀結(jié)構(gòu)體賦值,方法:API函數(shù)vm_read_overwrite(_STRUCT_CONTEXT._ss.bp...),完成首個(gè)棧幀結(jié)構(gòu)體賦值StackFrameEntry
-
- 遍歷StackFrameEntry獲取所有棧幀及對(duì)應(yīng)的函數(shù)地址
代碼邏輯解析
流程圖

- Setp1:
調(diào)用API函數(shù)task_threads,獲取線程數(shù)組棧幀threads,線程個(gè)數(shù)thread_count
task_threads(mach_task_self(), &threads, &thread_count)
- Setp2:
調(diào)用API函數(shù)thread_get_state,實(shí)例化結(jié)構(gòu)體STRUCT_MCONTEXT,STRUCT_MCONTEXT->__ss包含棧幀指針fp,指令指針ip,棧頂指針sp
//thread:線程
//LSL_THREAD_STATE:cpu相關(guān)的定量
//machineContext->__ss:設(shè)備上下文,__ss結(jié)構(gòu)體存儲(chǔ)了`fp`,ip,sp
//state_count:cpu相關(guān)的定量
thread_get_state(thread, LSL_THREAD_STATE, (thread_state_t)&machineContext->__ss, &state_count)
- Setp3:
調(diào)用API函數(shù)vm_read_overwrite,實(shí)例化StackFrameEntry結(jié)構(gòu)體,StackFrameEntry存儲(chǔ)首個(gè)棧幀的函數(shù)地址,以及前一個(gè)棧幀地址從而通過(guò)遍歷堆棧所有函數(shù)地址的獲取
typedef struct StackFrameEntry{
// 前一個(gè)棧幀地址
const struct StackFrameEntry * const previous;
// 函數(shù)地址
const uintptr_t return_address;
} StackFrameEntry;
//mach_task_self:task對(duì)象
//src:fp棧幀指針
//numBytes:sizeof(StackFrameEntry)
//dst:StackFrameEntry指針
//bytesCopied://cpye字節(jié)大小
vm_read_overwrite(mach_task_self(), (vm_address_t)src, (vm_size_t)numBytes, (vm_address_t)dst, &bytesCopied)
Setp4:
遍歷StackFrameEntry(遍歷條件StackFrameEntry.previous),來(lái)獲取堆棧所有棧幀地址,及函數(shù)地址(add)并存儲(chǔ)在函數(shù)地址數(shù)組backTrackBuffer。Setp5:
獲得函數(shù)的實(shí)現(xiàn)地址,由于函數(shù)地址無(wú)法進(jìn)行閱讀,需要通過(guò)符號(hào)表(nlist)來(lái)解析為函數(shù)名(Setp6-Setp15操作目標(biāo)),從而進(jìn)行程序定位。Setp6:
調(diào)用API函數(shù)_dyld_image_count(void) ,獲取images文件總數(shù),即mach-o文件總數(shù),Setp6-Setp9遍歷獲取mach-o target index(目標(biāo)mach-o鏡像文件)。Setp7:
調(diào)用API函數(shù)_dyld_get_image_header(imageIndex)獲取mach-o文件的header對(duì)象,header對(duì)象存儲(chǔ)load command個(gè)數(shù)及大小;
調(diào)用API函數(shù)_dyld_get_image_vmadd_slide(imageIndex)的mach-o文件的隨機(jī)內(nèi)存地址偏移量Setp8:
補(bǔ)充
函數(shù)地址:add,函數(shù)真實(shí)的實(shí)現(xiàn)地址
函數(shù)虛擬地址:vm_add,
ALSR:slide函數(shù)虛擬地址加載到進(jìn)程內(nèi)存的隨機(jī)偏移量,每個(gè)mach-o的slide各不相同
關(guān)系:vm_add + slide = add
已知參數(shù):add,slide因此通過(guò)關(guān)系換算得到vm_add
Setp9:
image index:函數(shù)對(duì)應(yīng)的mach-o鏡像文件image索引index
遍歷:遍歷mach-o下所有l(wèi)oadCommand(LC_SEGMENT),循環(huán)條件header->ncmds(load command個(gè)數(shù))。
目標(biāo):函數(shù)地址對(duì)應(yīng)的mach-o鏡像文件image。
查詢條件:vm_add=[image(index).segment(i).vmadd, image(index).segment(i).vmadd+image(index).segment(i).vmsize],其中index=image index,i=cmd indexSetp10:
調(diào)用API函數(shù)_dyld_get_image_vmaddr_slide(index),獲取目標(biāo)image的slide用來(lái)?yè)Q算基址。不同的mach-o的slide不同Setp11:
獲得函數(shù)對(duì)應(yīng)的mach-o的鏡像image(index)文件后,計(jì)算程序鏈接基址,從而獲取符號(hào)表地址symbolTab_Add,字符串表地址strTab_Add。
base_add=segmet(LINKEDIR).vmadd-segment(LINKEDIT).fileoff+slide
函數(shù)對(duì)應(yīng)的鏡像文件image(index),遍歷loadcommadn,獲得cmd.segname=LINKEDIT的segment,提取vmadd(虛擬地址),fileoff(文件偏移量)Setp12:
獲得符號(hào)表地址
symbolTab_add:符號(hào)表地址,一塊連續(xù)的地址來(lái)存儲(chǔ)mach-o所有的函數(shù)符號(hào),存儲(chǔ)結(jié)構(gòu)為nlis
base_add:程序鏈接時(shí)基址,通過(guò)LINKEDIT計(jì)算得到
symoff:符號(hào)表偏移地址,存儲(chǔ)在LC_SYMTAB的cmd中,symoff為相對(duì)基址的偏移量
關(guān)系:symbolTab_add = base_add + symoffSetp13:
獲得字符串表地址
strTab_add:符號(hào)表地址,一塊連續(xù)的地址來(lái)存儲(chǔ)mach-o所有的字符串指針base_add:程序鏈接時(shí)基址,通過(guò)LINKEDIT計(jì)算得到stroff:符號(hào)表偏移地址,存儲(chǔ)在LC_SYMTAB的cmd中,stroff為相對(duì)基址的偏移量
關(guān)系:strTab_add = base_add + stroffSetp14:
符號(hào)表結(jié)構(gòu)體nlist
// 位于系統(tǒng)庫(kù) 頭文件中 struct nlist {
union {
uint32_t n_strx; //符號(hào)名在字符串表中的偏移量
} n_un;
uint8_t n_type;
uint8_t n_sect;
int16_t n_desc;
uint32_t n_value; //符號(hào)在內(nèi)存中的地址,類似于函數(shù)虛擬地址指針
};
符號(hào)表以nlist的結(jié)構(gòu)體連續(xù)存儲(chǔ)mach-o文件下所有函數(shù)符號(hào),nlist結(jié)構(gòu)體將函數(shù)虛擬地址,與函數(shù)名進(jìn)行關(guān)聯(lián)。
Setp15:
符號(hào)結(jié)構(gòu)體nlist關(guān)聯(lián)了函數(shù)虛擬地址和函數(shù)名(n_vaule函數(shù)虛擬地址,n_um_strx字符串表偏移量),目前已知函數(shù)地址,因此可以遍歷所有的nlist獲得對(duì)應(yīng)的n_um_strx。
函數(shù)虛擬地址vm_add: vm_add = add - slide
符號(hào)表注冊(cè)函數(shù)虛擬地址n_value:nlist(index).n_value
index遍歷條件:vm_add >= n_value && min(vm_add - n_value),滿足上述條件的符號(hào)index即為函數(shù)對(duì)應(yīng)的nlist(index)Setp16:
獲得函數(shù)對(duì)應(yīng)的符號(hào)表索引后,得到函數(shù)名起始地址nlist(inde).n_um.n_strx + strTab_add
至此完成函數(shù)地址與函數(shù)名的關(guān)聯(lián)~