如何正确的hook方法objc_msgSend
前言
如果希望对 Objective-C 的方法调用进行 log, 一个很好的解决方法就是 hook 方法 objc_msgSend
, 当然想到的就是利用 InlinkHook 直接 hook 完事, 然而 objc_msgSend
是一个可变参数函数, 这就有点蛋疼了.
objc4-680
, 和目前的 objc4-709
没有有很大出入.
以下 在举例 arm 相关时使用 objc4-680
, 说明 x64 时使用 objc4-709
整个代码使用 c++, 所以有些地方需要参考 objc 的源码去造一个*, 比如 object_getClass
等.
这篇文章假设读者对以下有了解.
- OC 类的内存布局模型
- OC 实例的内存布局模型
- OC 函数调用与消息传递机制
- macho 文件格式
- 基本 x64, arm 汇编指令
- 函数调用与参数传递的 x64, arm 汇编实现机制(函数调用约定)
- inlinehook 机制
Hook 思路
这里首先明确, objc_msgSend 的第三个参数是不定参数, 无法确定 objc_msgSend
的具体函数签名, 也就无法通过传参来调用原函数, 所以只能上暴力的方法, 通过保存/恢复栈和寄存器方法调用原函数, 之后在汇编指令中实现原函数的跳转调用.
objc_msgSend
的函数声明
12 |
// runtime/message.hOBJC_EXPORT id objc_msgSend(id self, SEL op, ...) |
objc_msgSend
的函数实现, 这里以 x64 和 arm64 举例.
ARM64 下 ojbc_msgSend 实现机制
_objc_msgSend
首先会取得基本的参数, 比如 isa, class, 之后会使用宏 CacheLookup
先进行缓存查找, 如果没有命中, 会触发 JumpMiss
宏处理, 跳转到 __objc_msgSend_uncached_impcache
, 这个方法是了解如何正确 hook 的关键.
12345678910 |
// objc4-680/runtime/Messengers.subproj/objc-msg-arm64.s ENTRY _objc_msgSend MESSENGER_START cmp x0, #0 // nil check and tagged pointer check b.le LNilOrTagged // (MSB tagged pointer looks negative) ldr x13, [x0] // x13 = isa and x9, x13, #ISA_MASK // x9 = class LGetIsaDone: CacheLookup NORMAL // calls imp or objc_msgSend_uncached |
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849 |
// objc4-680/runtime/Messengers.subproj/objc-msg-arm64.s.macro JumpMiss.if $0 == NORMAL b __objc_msgSend_uncached_impcache.else b LGetImpMiss.endif.endmacro.macro CacheLookup // x1 = SEL, x9 = isa ldp x10, x11, [x9, #CACHE] // x10 = buckets, x11 = occupied|mask and w12, w1, w11 // x12 = _cmd & mask add x12, x10, x12, LSL #4 // x12 = buckets + ((_cmd & mask)<<4) ldp x16, x17, [x12] // {x16, x17} = *bucket1: cmp x16, x1 // if (bucket->sel != _cmd) b.ne 2f // scan more CacheHit $0 // call or return imp 2: // not hit: x12 = not-hit bucket CheckMiss $0 // miss if bucket->cls == 0 cmp x12, x10 // wrap if bucket == buckets b.eq 3f ldp x16, x17, [x12, #-16]! // {x16, x17} = *--bucket b 1b // loop3: // wrap: x12 = first bucket, w11 = mask add x12, x12, w11, UXTW #4 // x12 = buckets+(mask<<4) // Clone scanning loop to miss instead of hang when cache is corrupt. // The slow path may detect any corruption and halt later. ldp x16, x17, [x12] // {x16, x17} = *bucket1: cmp x16, x1 // if (bucket->sel != _cmd) b.ne 2f // scan more CacheHit $0 // call or return imp 2: // not hit: x12 = not-hit bucket CheckMiss $0 // miss if bucket->cls == 0 cmp x12, x10 // wrap if bucket == buckets b.eq 3f ldp x16, x17, [x12, #-16]! // {x16, x17} = *--bucket b 1b // loop3: // double wrap JumpMiss $0 .endmacro |
具体解析下 __objc_msgSend_uncached_impcache
这个函数, 在函数入口保存寄存器和返回地址, 在调用 __class_lookupMethodAndLoadCache3
之后进行恢复. __class_lookupMethodAndLoadCache3
函数返回需要调用函数的地址.
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950 |
// objc4-680/runtime/Messengers.subproj/objc-msg-arm64.sSTATIC_ENTRY __objc_msgSend_uncached_impcache// THIS IS NOT A CALLABLE C FUNCTION// Out-of-band x9 is the class to searchMESSENGER_START// push framestp fp, lr, [sp, #-16]!mov fp, spMESSENGER_END_SLOW// save parameter registers: x0..x8, q0..q7sub sp, sp, #(10*8 + 8*16)stp q0, q1, [sp, #(0*16)]stp q2, q3, [sp, #(2*16)]stp q4, q5, [sp, #(4*16)]stp q6, q7, [sp, #(6*16)]stp x0, x1, [sp, #(8*16+0*8)]stp x2, x3, [sp, #(8*16+2*8)]stp x4, x5, [sp, #(8*16+4*8)]stp x6, x7, [sp, #(8*16+6*8)]str x8, [sp, #(8*16+8*8)]// receiver and selector already in x0 and x1mov x2, x9bl __class_lookupMethodAndLoadCache3// imp in x0mov x17, x0// restore registers and returnldp q0, q1, [sp, #(0*16)]ldp q2, q3, [sp, #(2*16)]ldp q4, q5, [sp, #(4*16)]ldp q6, q7, [sp, #(6*16)]ldp x0, x1, [sp, #(8*16+0*8)]ldp x2, x3, [sp, #(8*16+2*8)]ldp x4, x5, [sp, #(8*16+4*8)]ldp x6, x7, [sp, #(8*16+6*8)]ldr x8, [sp, #(8*16+8*8)]mov sp, fpldp fp, lr, [sp], #16br x17END_ENTRY __objc_msgSend_uncached_impcache |
举个例子演示下, 这里可以仔细观察 lldb 的输出.
x64 下 ojbc_msgSend 实现机制
整体思路与 arm64 类似, 但几个关键部分不同, 这里主要关注 GetIsaFast
和 MethodTableLookup
, MethodTableLookup
是在缓存中没有命中时进行查找.
123456789 |
objc4-709/runtime/Messengers.subproj/objc-msg-x86_64.sENTRY _objc_msgSendUNWIND _objc_msgSend, NoFrameMESSENGER_STARTNilTest NORMALGetIsaFast NORMAL // r10 = self->isaCacheLookup NORMAL, CALL // calls IMP on success |
1234567891011121314151617 |
objc4-709/runtime/Messengers.subproj/objc-msg-x86_64.s.macro GetIsaFast.if $0 != STRET testb $$1, %a1b PN jnz LGetIsaSlow_f movq $$0x00007ffffffffff8, %r10 andq (%a1), %r10.else testb $$1, %a2b PN jnz LGetIsaSlow_f movq $$0x00007ffffffffff8, %r10 andq (%a2), %r10.endifLGetIsaDone: .endmacro |
这里需要关注 x64 保存和恢复参数的操作, 特别是这里的栈对齐.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364 |
objc4-709/runtime/Messengers.subproj/objc-msg-x86_64.s.macro MethodTableLookup push %rbp mov %rsp, %rbp sub $$0x80+8, %rsp // +8 for alignment movdqa %xmm0, -0x80(%rbp) push %rax // might be xmm parameter count movdqa %xmm1, -0x70(%rbp) push %a1 movdqa %xmm2, -0x60(%rbp) push %a2 movdqa %xmm3, -0x50(%rbp) push %a3 movdqa %xmm4, -0x40(%rbp) push %a4 movdqa %xmm5, -0x30(%rbp) push %a5 movdqa %xmm6, -0x20(%rbp) push %a6 movdqa %xmm7, -0x10(%rbp) // _class_lookupMethodAndLoadCache3(receiver, selector, class).if $0 == NORMAL // receiver already in a1 // selector already in a2.else movq %a2, %a1 movq %a3, %a2.endif movq %r10, %a3 call __class_lookupMethodAndLoadCache3 // IMP is now in %rax movq %rax, %r11 movdqa -0x80(%rbp), %xmm0 pop %a6 movdqa -0x70(%rbp), %xmm1 pop %a5 movdqa -0x60(%rbp), %xmm2 pop %a4 movdqa -0x50(%rbp), %xmm3 pop %a3 movdqa -0x40(%rbp), %xmm4 pop %a2 movdqa -0x30(%rbp), %xmm5 pop %a1 movdqa -0x20(%rbp), %xmm6 pop %rax movdqa -0x10(%rbp), %xmm7.if $0 == NORMAL cmp %r11, %r11 // set eq for nonstret forwarding.else test %r11, %r11 // set ne for stret forwarding.endif leave.endmacro |
构建 fake_objc_msgSend
在了解了 objc_msgSend 的流程, 接下来构建 fake_objc_msgSend
, 需要保存保存寄存器状态, 以正确调用原 objc_msgSend
, 这里解释下为什么需要保存寄存器状态, 因为参数个数不定, 所以可能会同时用到多个寄存器和栈来同时传参, 所以这里需要将可能会用到的所有寄存器以及 sp 都保存, 以确保多参数不被修改. 其实把这里的保护寄存器实现在 hook 层可能会更好一些. 最终通过把寄存器参数保存在栈中, 并以栈指针作为参数, 传给 hookBefore
, 实现函数参数的完全访问.
ok, 说完了函数的参数(寄存器)保存和恢复部分, 接下来谈一谈函数调用栈的问题.
- 限制函数调用栈深度
- 记录函数调用的返回值
问题 1, 比较好理解. 问题 2, 如果需要记录函数返回值, 并且过滤了部分的函数记录, 必须要保证 hookBefore
和 hookAfter
是对于同一个 函数
做的处理, 所以这里需要自建函数调用栈以及标记 函数
, 也就是说必须要保证自建的调用栈的 push 和 pop 操作必须对应, 这一点 InspectiveC 不同, 它是直接跟踪所有函数入栈(依赖系统标准函数栈的准确). 这里使用 sp
寄存器做标记区分函数(关于为何使用 sp
寄存器做标记区分, 希望读者能自己仔细思考函数调用本质), 以确保 push 和 pop 的对应正确.
这里总结一下 fake_objc_msgSend_safe 的工作
- 保存寄存器/参数
- hookBefore 工作(过滤/判断
调用的函数
) - 恢复寄存器/参数
- 如果经过判断不需要追踪, 则直接调用原 ojbc_msgSend
- 如果需要追踪, 则修改默认栈内保存的返回地址后, 继续调用 objc_msgSend (这里涉及到 trick)
- 保存寄存器/参数
- hookAfter 工作(解析返回值, 函数调用出栈, 确保两次
sp
值相同) - 恢复寄存器/参数
- ret
下面列出 fake_objc_msgSend_safe 的核心代码.
这里关于调用栈的操作主要放在 hookBefore
和 hookAfter
, 关于在内联汇编中调用 C 函数这里也使用了两种方法, 一种使用 nm
导出符号, 一种使用内联汇编传参.
这里关于寄存器的使用与污染问题, 请查阅相关资料或参考下方参考资料, 以了解寄存器使用约定.
这里关于如何获取到汇编的下一条指令地址的 trick, 可以参考 <程序员自我修养>
中地址无关代码中使用到的 get_pc_thunk
.
使用 ‘横线’ 分割了 hookBefore
和 hookAfter
相关处理代码, 大部分代码我都已经加了注释.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166 |
__attribute__((__naked__)) static void fake_objc_msgSend_safe(){ // test for direct jmp // __asm__ volatile( // "jmpq *%0n": // : "r" (orig_objc_msgSend) // :); // backup registers __asm__ volatile( "subq $(16*8+8), %%rspn" // +8 for alignment "movdqa %%xmm0, (%%rsp)n" "movdqa %%xmm1, 0x10(%%rsp)n" "movdqa %%xmm2, 0x20(%%rsp)n" "movdqa %%xmm3, 0x30(%%rsp)n" "movdqa %%xmm4, 0x40(%%rsp)n" "movdqa %%xmm5, 0x50(%%rsp)n" "movdqa %%xmm6, 0x60(%%rsp)n" "movdqa %%xmm7, 0x70(%%rsp)n" "pushq %%raxn" // stack align "pushq %%r9n" // might be xmm parameter count "pushq %%r8n" "pushq %%rcxn" "pushq %%rdxn" "pushq %%rsin" "pushq %%rdin" "pushq %%raxn" // origin rsp, contain `ret address`, how to use leaq, always wrong. "movq %%rsp, %%raxn" "addq $(16*8+8+8+7*8), %%raxn" "pushq (%%rax)n" "pushq %%raxn" :: :); // prepare args for func __asm__ volatile( "movq %%rsp, %%rdin" "callq __Z10hookBeforeP9RegState_n" :: :); // get value from `ReturnPayload` __asm__ volatile( "movq (%%rax), %%r10n" "movq 8(%%rax), %%r11n" :: :); // restore registers __asm__ volatile( "popq %%raxn" "popq (%%rax)n" "popq %%raxn" "popq %%rdin" "popq %%rsin" "popq %%rdxn" "popq %%rcxn" "popq %%r8n" "popq %%r9n" "popq %%raxn" // stack align "movdqa (%%rsp), %%xmm0n" "movdqa 0x10(%%rsp), %%xmm1n" "movdqa 0x20(%%rsp), %%xmm2n" "movdqa 0x30(%%rsp), %%xmm3n" "movdqa 0x40(%%rsp), %%xmm4n" "movdqa 0x50(%%rsp), %%xmm5n" "movdqa 0x60(%%rsp), %%xmm6n" "movdqa 0x70(%%rsp), %%xmm7n" "addq $(16*8+8), %%rspn" :: :); // go to the original objc_msgSend __asm__ volatile( // "br x9n" "cmpq $0, %%r11n" "jne Lthroughxn" "jmpq *%%r10n" "Lthroughx:n" // trick to jmp "jmp NextInstructionn" "Begin:n"< 大专栏 如何正确的hook方法objc_msgSend · jmpews/div> "popq %%r11n" "movq %%r11, (%%rsp)n" "jmpq *%%r10n" "NextInstruction:n" "call Begin" :: :); //----------------------------------------------------------------------------- // after objc_msgSend we parse the result. // backup registers __asm__ volatile( "pushq %%r10n" // stack align "push %%rbpn" "movq %%rsp, %%rbpn" "subq $(16*8), %%rspn" // +8 for alignment "movdqa %%xmm0, -0x80(%%rbp)n" "push %%r9n" // might be xmm parameter count "movdqa %%xmm1, -0x70(%%rbp)n" "push %%r8n" "movdqa %%xmm2, -0x60(%%rbp)n" "push %%rcxn" "movdqa %%xmm3, -0x50(%%rbp)n" "push %%rdxn" "movdqa %%xmm4, -0x40(%%rbp)n" "push %%rsin" "movdqa %%xmm5, -0x30(%%rbp)n" "push %%rdin" "movdqa %%xmm6, -0x20(%%rbp)n" "push %%raxn" "movdqa %%xmm7, -0x10(%%rbp)n" "pushq 0x8(%%rbp)n" "movq %%rbp, %%raxn" "addq $8, %%raxn" "pushq %%raxn" :: :); // prepare args for func __asm__ volatile( "movq %%rsp, %%rdin" // "callq __Z9hookAfterP9RegState_n" "callq *%0n" "movq %%rax, %%r10n" : : "r"(func_ptr) : "%rax"); // restore registers __asm__ volatile( "pop %%raxn" "pop 8(%%rbp)n" "movdqa -0x80(%%rbp), %%xmm0n" "pop %%raxn" "movdqa -0x70(%%rbp), %%xmm1n" "pop %%rdin" "movdqa -0x60(%%rbp), %%xmm2n" "pop %%rsin" "movdqa -0x50(%%rbp), %%xmm3n" "pop %%rdxn" "movdqa -0x40(%%rbp), %%xmm4n" "pop %%rcxn" "movdqa -0x30(%%rbp), %%xmm5n" "pop %%r8n" "movdqa -0x20(%%rbp), %%xmm6n" "pop %%r9n" "movdqa -0x10(%%rbp), %%xmm7n" "leaven" // go to the original objc_msgSend "movq %%r10, (%%rsp)n" "retn" :: :);} |
虽然现在已经可以 hook 到 objc_msgSend.
利用下面的数据结构获取之前保存在栈中的参数, ps: 这个结构是参考 InspectiveC, 实现的很精巧, 在此基础上做了一些修改.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667 |
-ARM64 http://infocenter.arm.com/help/topic/com.arm.doc.den0024a/DEN0024A_v8_architecture_PG.pdf (7.2.1 Floating-point) (4.6.1 Floating-point register organization in AArch64) use struct and union to describe diagram in the above link, nice! -X86 https://en.wikipedia.org/wiki/X86_calling_conventions RDI, RSI, RDX, RCX, R8, R9, XMM0–7*/// x86_64 is XMM, arm64 is qtypedef union FPMReg_ { __int128_t q; struct { double d1; // Holds the double (LSB). double d2; } d; struct { float f1; // Holds the float (LSB). float f2; float f3; float f4; } f;} FPReg;// just ref how to backup/restore registersstruct RegState_ { uint64_t bp; uint64_t ret; union { uint64_t arr[7]; struct { uint64_t rax; uint64_t rdi; uint64_t rsi; uint64_t rdx; uint64_t rcx; uint64_t r8; uint64_t r9; } regs; } general; uint64_t _; // for align union { FPReg arr[8]; struct { FPReg xmm0; FPReg xmm1; FPReg xmm2; FPReg xmm3; FPReg xmm4; FPReg xmm5; FPReg xmm6; FPReg xmm7; } regs; } floating;};typedef struct pa_list_ { struct RegState_ *regs; // Registers saved when function is called. unsigned char *stack; // Address of current argument. int ngrn; // The Next General-purpose Register Number. int nsrn; // The Next SIMD and Floating-point Register Number.} pa_list; |
构建 hookBefore
hookBefore
实现了函数调用前的 trace 工作, 过滤 函数
, 将 函数
压进调用栈, 解析类实例对象, 解析不定参数.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788 |
arg1: object-address(need to parse >> class) arg2: method string address arg3: method signature*/vm_address_t hookBefore(struct RegState_ *rs){ gettimeofday(&start,NULL); // TODO: parse args pa_list args = (pa_list){ rs, reinterpret_cast<unsigned char *>(rs->bp), 2, 0}; ThreadCallStack *cs = getThreadCallStack(threadKey); ReturnPayload *rp = cs->rp; vm_address_t xaddr = (uint64_t)orig_objc_msgSend; rp->addr = xaddr; rp->ret = rs->ret; rp->bp = rs->bp; rp->isTrace = 0; rp->key = rs->ret & rs->bp; if (1 || pthread_main_np()) { /* pay attention!!! as `objc_msgSend` invoked so often, the `filter` code snippet that use `c` to write it must be fast!!!. */ do { // check method string address, can be narrow the scope. char *methodSelector = reinterpret_cast<char *>(rs->general.regs.rsi); if(!(methodSelector && checkAddressRange((vm_address_t)methodSelector, macho_load_addr, macho_load_end_addr))) break; // check object's class address vm_address_t class_addr = macho_objc_getClass(rs->general.regs.rdi); if (!(class_addr && checkAddressRange(class_addr, macho_load_addr, macho_load_end_addr))) break; // check `call count filter` if(check_freq_filter((unsigned long)methodSelector)) break; // // test for wechat // if(!strncmp(methodSelector, "onRevokeMsg", 9)) // debug_break(); // check exclude // check method_name first, no need parse object. if (!checkLogFilters_MethodName(NULL, methodSelector, FT_EXINLUDE)) break; objc_class_info_t *xobjc_class_info; xobjc_class_info = mem->parse_OBJECT(rs->general.regs.rdi); if(!xobjc_class_info) break; // check class name, need parse object if (!checkLogFilters_ClassName(xobjc_class_info->class_name, NULL, FT_EXINLUDE)) break; objc_method_info_t * objc_method_info; objc_method_info = search_method_name(&(xobjc_class_info->methods), methodSelector); if (!objc_method_info) break; if(check_thread_filter(cs->thread)) break; // add to `method call count cache` add_freq_filter((unsigned long)methodSelector, objc_method_info); // may be can pass into 'objc_method_info_t' without 'class_name' and 'method_name' CallRecord *cr = pushCallRecord(xobjc_class_info->class_name, methodSelector, reinterpret_cast<void *>(rs->bp), reinterpret_cast<void *>(rs->ret), cs); if(!cr) break; printCallRcord(cr, cs); rp->isTrace = 1; } while(0); } gettimeofday(&end,NULL); time_cost += (end.tv_sec-start.tv_sec)+(end.tv_usec-start.tv_usec)/1000000.0; return reinterpret_cast<unsigned long>(rp);} |
hookBefore 的参数其实之前备份寄存器后的 sp 地址, 再通过 RegState_
格式, 就可以取得所有寄存器的值. rs->general.regs.x0
存放的是实例地址, 但是按理说应该通过 object_getClass(rs->general.regs.x0)
应该取得类地址, 但是这里避免使用 Objective-C以及 parser 内代码格式统一 就直接使用了 c++ 去实现源码中对应的函数. rs->general.regs.x1
存放的是函数签名字符串.
Macho 的文件解析
这里用到了个人之前实现的 macho 的 parser 模块, 大致介绍下这个模块, 可以通过三种状态对 macho 格式进行解析: 1. 文件 2. pid 3. 自身进程, 这里使用第三种, 对自身进程进行解析. (这个模块实现也挺有意思, 需要考虑到 ‘文件偏移’, ‘虚拟地址’, ‘内存地址’ 三种地址的处理)
对于类实例的解析, 比较复杂, 这里简单介绍下, 具体可以参照 macho-ABI 文档.
构建 hookAfter
hookAfter
实现了 objc_msgSend
执行后, 函数的出栈工作, 并解析函数返回值.
1234567891011121314151617181920 |
vm_address_t hookAfter(struct RegState_ *rs){ pa_list args = (pa_list){ rs, reinterpret_cast<unsigned char *>(rs->bp), 2, 0}; ThreadCallStack *cs = getThreadCallStack(threadKey); CallRecord *cr = popCallRecordSafe(cs, (void *)rs->bp); if (cr) { // printCallRcordReturnValue(cr, cs, rs); return reinterpret_cast<unsigned long>(cr->ret); } else { cr = popCallRecord(cs); return reinterpret_cast<unsigned long>(cr->ret); }} |
hookBefore 与 hookAfter 的数据传递.
x64 层面
关键在于函数调用栈中 函数返回地址
的传递, 也就是 (%%rsp)
, 由于需要获取 objc_msgSend
的返回值, 所以需要修改栈内默认的函数返回值为下一条指令的地址(请读者思考为什么不直接 push 内存地址? ). 这涉及到如何恢复栈中函数返回地址, 采用线程私有变量的方式, 为每一个 CallRecord
添加 ret
成员.
12345678910111213141516 |
typedef struct{ vm_address_t addr; unsigned long isTrace; vm_address_t ret; vm_address_t bp; vm_address_t key;} ReturnPayload;// Shared structures.typedef struct CallRecord_ { void *objc; void *method; void *bp; void *ret;} CallRecord; |
arm64 层面
其实关键在于 lr 寄存器值的传递, 由于需要获取 objc_msgSend
的返回值, 所以必须以 blr objc_msgSend
的方式调用, 此时之前 lr 寄存器的值被覆盖, 此时也不能进行 push 操作, 因为栈中可能已经保存了可变参数的值, 需要维持 sp 和 栈中内容不变, 那这里的 lr 如何保存恢复成了问题.
最后采用了线程私有变量的方法解决, 为每一个 CallRecord
添加 lr
成员, 在 hookAfter
是进行安全 pop, 也就是说同时需要校验 rs->fp
的值, 是否一致
12345678910111213141516 |
typedef struct{ vm_address_t addr; unsigned long isTrace; vm_address_t lr; vm_address_t fp; vm_address_t key;} ReturnPayload;// Shared structures.typedef struct CallRecord_ { void *objc; void *method; void *fp; void *lr;} CallRecord; |
如何正确过滤 objc_msgSend
目前我是通过过滤来避免 log 太复杂.
1. 过滤函数地址
默认会过滤解析地址在 self 内存区内, 因为 MachoParser 本身就是一个耗时操作.
2.1 设置不解析类/方法
需要使用 hash 表进行快速 cache 和 search.
2.2 设置只解析的类/方法
需要使用 hash 表进行快速 cache 和 search.
3. 限制函数调用栈深度
通过自建模拟函数栈, 限制函数栈调用深度.
4. 过滤频繁调用函数 (log, monitor 之类)
对于一定时间段内频繁调用的函数添加到 hash-map 中.
应用场景
目前主要关注逆向方面, 当然 APM 方向也是可以玩的.
Thunder 逆向
参考资料
12 |
// 函数调用约定, 寄存器使用约定http://abcdxyzk.github.io/blog/2012/11/23/assembly-args/ |