Advanced debugging skills

前言

iOS-Debug-Hacks involves the dynamic debugging, static analysis and decompile of third-party libraries.

《Advanced Apple Debugging & Reverse Engineering》值得一看

函数

谈谈对函数的理解

一个函数调用包括将数据(以参数和返回值的形式)和控制从代码的一部分转移到另一部分。在函数调用过程中,数据传递、局部变量的分配和释放是通过栈来实现的,而为单个函数调用分配的那部分栈称为栈帧(Stack Frame)。

栈帧

  • 使用 bt 命令打印出当前线程的回溯信息
(lldb) bt
* thread #1, stop reason = trace
  * frame #0: 0x000000010e533fcf dyld`_dyld_debugger_notification + 1
    frame #1: 0x000000010e533795 dyld`gdb_image_notifier(dyld_image_mode, unsigned int, dyld_image_info const*) + 111
    frame #2: 0x000000010a36b269 dyld_sim`notifyGDB(dyld_image_states, unsigned int, dyld_image_info const*) + 40
    frame #3: 0x000000010a364142 dyld_sim`dyld::notifyBatchPartial(dyld_image_states, bool, char const* (*)(dyld_image_states, unsigned int, dyld_image_info const*), bool, bool) + 814
    frame #4: 0x000000010a36d107 dyld_sim`ImageLoader::link(ImageLoader::LinkContext const&, bool, bool, bool, ImageLoader::RPathChain const&, char const*) + 101
    frame #5: 0x000000010a364548 dyld_sim`dyld::link(ImageLoader*, bool, bool, ImageLoader::RPathChain const&, unsigned int) + 161
    frame #6: 0x000000010a365a5c dyld_sim`dyld::_main(macho_header const*, unsigned long, int, char const**, char const**, char const**, unsigned long*) + 3840
    frame #7: 0x000000010a3613d4 dyld_sim`start_sim + 136
    frame #8: 0x000000010e52bded dyld`dyld::useSimulatorDyld(int, macho_header const*, char const*, int, char const**, char const**, char const**, unsigned long*, unsigned long*) + 2200
    frame #9: 0x000000010e5297a3 dyld`dyld::_main(macho_header const*, unsigned long, int, char const**, char const**, char const**, unsigned long*) + 436
    frame #10: 0x000000010e5253d4 dyld`dyldbootstrap::start(macho_header const*, int, char const**, long, macho_header const*, unsigned long*) + 453
    frame #11: 0x000000010e5251d2 dyld`_dyld_start + 54

bt 命令是得益于栈帧才能实现的,栈帧可以看成是函数执行的上下文,其中保存了函数的返回地址和局部变量,我们知道堆是从低地址向高地址延伸的,而栈是从高地址向低地址延伸的。
每个函数的每次调用,都会分配给它一个独立的栈帧,rbp 寄存器指向当前栈帧的底部(高地址),被称作帧指针,rsp 寄存器指向栈帧的顶部(低地址),被称作栈指针

一次函数调用的过程

  • 调用函数将参数压栈,如果没有参数,或者可以直接通过寄存器完成传参,则这步可以没有。
  • 将执行完函数调用的下一条指令压栈,其实就是返回地址。
  • 跳转到被调函数的起始地址开始执行。
  • 被调函数将调用函数栈帧起始地址压栈,栈帧起始地址存放在 %rbp 寄存器中。
  • 将 %rsp 寄存器赋值给 %rbp 寄存器,使得 %rbp 寄存器指向被调函数栈帧的起始地址。
  • 将被调用者保存寄存器压栈,这步是可选的。

上述 2 和 3 步骤其实就是 call 指令的任务,而 4 和 5 通过汇编指令表示如下:

    0x1054e09c0 <+0>:  pushq  %rbp //第四步
    0x1054e09c1 <+1>:  movq   %rsp, %rbp //第五步

Call 指令

call function

参数中的 function 是 TEXT 段的程序,
call 指令其实可以拆解成两步,

  • 第一步是将执行完 call 指令之后的地址压栈,这个地址其实是执行完调用函数体之后的返回地址;
  • 第二步是将指令执行跳转到 function。

call 指令其实等价于下面的命令:

push next_instruction
jmp  function

传参和返回值

在 OSX 中,最多可以有 6 个整型(整数和指针)通过寄存器传递,这 6 个寄存器分别是 rdi, rsi, rdx, rcx, r8 和 r9(顺序和参数的顺序保持一致),

  • 如果一个函数超过 6 个参数该怎么办呢?

此时就需要借助栈了,可以将剩下的参数逆序压入栈中。OSX 允许将 8 个浮点数通过浮点数寄存器 xmm0-xmm7 进行传递。

  • 函数的返回值,

使用 rax 寄存器作为整数返回值,浮点数返回值则使用 xmm0-xmm1 寄存器

输出寄存器的值

当寄存器的值是字符串的时候,LLDB 可以通过

 po (char *) $rsi

命令输出寄存器对应的字符串值,

否则直接使用 po $rsi,只会按照整数格式输出 rsi 寄存器的值。

OC 的消息转发


消息转发流程越往后,处理消息所付出的代价也就越大。所以若非必要,应当尽早结束消息转发流程。如果消息转发的流程中都没有处理未知消息,最终会调用 doesNotRecognizeSelector: 抛出异常,表示对象无法正确识别此 SEL。

补充

@dynamic 

使用 @dynamic 告诉编译器不做处理,然后 Getter 和 Setter 方法是在运行时动态创建

打断点

  • br s -n

br s 是 breakpoint set 的意思,-n 表示根据函数名称来下断点,作用和符号断点一样,

  • 使用 br s -F
  • 直接使用 b -[TCWebViewKit open]

只不过 b 命令是 _regexp-break 的缩写,是用正则匹配的方式下断点。

-给在指定的内存地址设置断点,

下断点的命令为 br s -a 0x000000010940b24e,这种方式可以用在知道 block 内存地址的时候,给 block 设置断点。

你可能感兴趣的:(stack,函数,call)