objc_msgSend
是基于匯編實(shí)現(xiàn)的钥屈,hook objc_msgSend
和我們平時(shí) hook OC 方法不一樣歪沃,在 github 上有開源的項(xiàng)目通過(guò) hook objc_msgSend
來(lái)監(jiān)控每個(gè)函數(shù)的耗時(shí)情況撒汉。這篇文章對(duì)其 hook 邏輯的主要代碼進(jìn)行分析記錄。閱讀前建議先了解開源庫(kù) fishhook 的源碼霜旧。
主流程
先看開源 項(xiàng)目 主要代碼
#define call(b, value) \
__asm volatile ("stp x8, x9, [sp, #-16]!\n"); \
__asm volatile ("mov x12, %0\n" :: "r"(value)); \
__asm volatile ("ldp x8, x9, [sp], #16\n"); \
__asm volatile (#b " x12\n");
#define save() \
__asm volatile ( \
"stp x8, x9, [sp, #-16]!\n" \
"stp x6, x7, [sp, #-16]!\n" \
"stp x4, x5, [sp, #-16]!\n" \
"stp x2, x3, [sp, #-16]!\n" \
"stp x0, x1, [sp, #-16]!\n");
#define load() \
__asm volatile ( \
"ldp x0, x1, [sp], #16\n" \
"ldp x2, x3, [sp], #16\n" \
"ldp x4, x5, [sp], #16\n" \
"ldp x6, x7, [sp], #16\n" \
"ldp x8, x9, [sp], #16\n" );
#define link(b, value) \
__asm volatile ("stp x8, lr, [sp, #-16]!\n"); \
__asm volatile ("sub sp, sp, #16\n"); \
call(b, value); \
__asm volatile ("add sp, sp, #16\n"); \
__asm volatile ("ldp x8, lr, [sp], #16\n");
#define ret() __asm volatile ("ret\n");
__attribute__((__naked__))
static void hook_Objc_msgSend() {
// Save parameters.
/// Step 1
save()
/// Step 2
__asm volatile ("mov x2, lr\n");
__asm volatile ("mov x3, x4\n");
// Call our before_objc_msgSend.
/// Step 3
call(blr, &before_objc_msgSend)
// Load parameters.
/// Step 4
load()
// Call through to the original objc_msgSend.
/// Step 5
call(blr, orig_objc_msgSend)
// Save original objc_msgSend return value.
/// Step 6
save()
// Call our after_objc_msgSend.
/// Step 7
call(blr, &after_objc_msgSend)
// restore lr
/// Step 8
__asm volatile ("mov lr, x0\n");
// Load original objc_msgSend return value.
/// Step 9
load()
// return
/// Step 10
ret()
}
對(duì)以上代碼我們分步驟來(lái)看
-
save()
保存函數(shù)入?yún)?x0-x8)到棧內(nèi)存棚潦,因?yàn)榻酉聛?lái)你的函數(shù)調(diào)用修改原有參數(shù)。這里源碼里面看到 x9 的值也被保存了软啼,這里的原因是因?yàn)闂V羔樢苿?dòng)必須滿足SP Mod 16 = 0
的條件桑谍,而在 x8 寄存器只占用8個(gè)字節(jié),剩余8個(gè)字節(jié)控件由 x9 來(lái)填充#define save() \ __asm volatile ( \ "stp x8, x9, [sp, #-16]!\n" \ "stp x6, x7, [sp, #-16]!\n" \ "stp x4, x5, [sp, #-16]!\n" \ "stp x2, x3, [sp, #-16]!\n" \ "stp x0, x1, [sp, #-16]!\n");
-
保存 lr 到 x2祸挪,以便
call(blr, &before_objc_msgSend)
的調(diào)用,保存到 x2 是因?yàn)?before_objc_msgSend
函數(shù)第三個(gè)參數(shù)需要傳入 lr贞间,方便后續(xù)返回贿条;blr
指令會(huì)改變 lr 寄存器的值,所以調(diào)用前先保存 lr#define call(b, value) \ __asm volatile ("stp x8, x9, [sp, #-16]!\n"); \ __asm volatile ("mov x12, %0\n" :: "r"(value)); \ __asm volatile ("ldp x8, x9, [sp], #16\n"); \ __asm volatile (#b " x12\n"); void before_objc_msgSend(id self, SEL _cmd, uintptr_t lr) { push_call_record(self, object_getClass(self), _cmd, lr); } static inline void push_call_record(id _self, Class _cls, SEL _cmd, uintptr_t lr) { thread_call_stack *cs = get_thread_call_stack(); if (cs) { int nextIndex = (++cs->index); if (nextIndex >= cs->allocated_length) { cs->allocated_length += 64; cs->stack = (thread_call_record *)realloc(cs->stack, cs->allocated_length * sizeof(thread_call_record)); } thread_call_record *newRecord = &cs->stack[nextIndex]; newRecord->self = _self; newRecord->cls = _cls; newRecord->cmd = _cmd; newRecord->lr = lr; if (cs->is_main_thread && _call_record_enabled) { struct timeval now; gettimeofday(&now, NULL); newRecord->time = (now.tv_sec % 100) * 1000000 + now.tv_usec; } } }
__asm volatile ("mov x3, x4\n");
目前個(gè)人認(rèn)為是冗余代碼增热,在整個(gè)流程中貌似并沒(méi)有實(shí)際作用整以。 -
通過(guò)
blr
指令 跳轉(zhuǎn)執(zhí)行before_objc_msgSend
函數(shù)。這里會(huì)先保存 x8峻仇、x9 寄存器的值公黑,原因是__asm volatile ("mov x12, %0\n" :: "r"(value))
執(zhí)行命令過(guò)程中會(huì)通過(guò) x8 來(lái)保存函數(shù)地址,再進(jìn)行跳轉(zhuǎn)摄咆,所以這里會(huì)先要保存 x8凡蚜,和步驟1相同,棧指針移動(dòng)必須滿足SP Mod 16 = 0
的條件吭从,所以 x9 也被保存朝蜘。執(zhí)行完之后 x8、x9 恢復(fù)涩金。#define call(b, value) \ __asm volatile ("stp x8, x9, [sp, #-16]!\n"); \ __asm volatile ("mov x12, %0\n" :: "r"(value)); \ __asm volatile ("ldp x8, x9, [sp], #16\n"); \ __asm volatile (#b " x12\n");
在
__asm volatile ("mov x12, %0\n" :: "r"(value))
下斷點(diǎn)可以看到 cpu 是通過(guò)adrp
+add
2個(gè)指令結(jié)合尋址到函數(shù)的地址并執(zhí)行谱醇,過(guò)程中改變了 x8 的值image-20190713185417531 Step 4 到 Step 6,恢復(fù)原有入?yún)⒉阶觯瑘?zhí)行原函數(shù)副渴,然后保存入?yún)?/p>
-
call(blr, &after_objc_msgSend)
和步驟3相似,執(zhí)行 hook 收尾的函數(shù)全度,主要是通過(guò) TSD 返回步驟3保存的原來(lái) lr 寄存器保存的內(nèi)容煮剧,也就是hook前的 lr 寄存器值static inline uintptr_t pop_call_record() { thread_call_stack *cs = get_thread_call_stack(); int curIndex = cs->index; int nextIndex = cs->index--; thread_call_record *pRecord = &cs->stack[nextIndex]; if (cs->is_main_thread && _call_record_enabled) { struct timeval now; gettimeofday(&now, NULL); uint64_t time = (now.tv_sec % 100) * 1000000 + now.tv_usec; if (time < pRecord->time) { time += 100 * 1000000; } uint64_t cost = time - pRecord->time; if (cost > _min_time_cost && cs->index < _max_call_depth) { if (!_smCallRecords) { _smRecordAlloc = 1024; _smCallRecords = malloc(sizeof(smCallRecord) * _smRecordAlloc); } _smRecordNum++; if (_smRecordNum >= _smRecordAlloc) { _smRecordAlloc += 1024; _smCallRecords = realloc(_smCallRecords, sizeof(smCallRecord) * _smRecordAlloc); } smCallRecord *log = &_smCallRecords[_smRecordNum - 1]; log->cls = pRecord->cls; log->depth = curIndex; log->sel = pRecord->cmd; log->time = cost; } } return pRecord->lr; }
__asm volatile ("mov lr, x0\n");
將步驟5返回的值(原來(lái)lr的初始值)到lr寄存器Step 9 - Step 10 恢復(fù)寄存器值,并返回。主要目的是還原原始函數(shù)的執(zhí)行之后的狀態(tài)轿秧。
遺留問(wèn)題:
以上就是整個(gè)匯編 hook objc_msgSend
的主要過(guò)程中跌,目前遺留一個(gè)問(wèn)題是:
-
__asm volatile ("mov x3, x4\n");
這行代碼是否屬于冗余代碼呢?
參考文章: