在iOS开发过程中,断点调试是我们常用的方法, 除了Objective-C/Swift的调试外,有时也需要用到汇编调试,比如在查看系统库的调用堆栈。许多大型的iOS工程师已经完成组件化,在使用二进制framework方式引入其他模块的时候,我们无法直接查看源码,通过汇编,我们可以看到调用堆栈,并且借助调试命令,我们可以查看调用方法的传参。本文介绍iOS工程如何查看汇编代码。
打开iOS工程,在Xcode的Debug菜单,点击Debug Workflow,勾选Always Show Disassembly
在工程代码适当位置加断点,启动项目,执行到代码断点时,即可查看当前堆栈的汇编代码
我们在ViewController的viewDidLoad方法添加如下代码
- (void)viewDidLoad {
[super viewDidLoad];
[self testMethod:@"test"];
}
- (void)testMethod:(NSString *)string
{
NSLog(@"%@", string);
}
在[self testMethod:@“test”];这一行前面添加断点,程序执行到这一行的时候,Xcode界面直接展示了汇编代码。
iOSProject`-[ViewController viewDidLoad]:
<+0>: pushq %rbp
<+1>: movq %rsp, %rbp
<+4>: subq $0x20, %rsp
<+8>: movq %rdi, -0x8(%rbp)
<+12>: movq %rsi, -0x10(%rbp)
<+16>: movq -0x8(%rbp), %rax
<+20>: movq %rax, -0x20(%rbp)
<+24>: movq 0x49711(%rip), %rax ; (void *)0x0000000105d77bc8: ViewController
<+31>: movq %rax, -0x18(%rbp)
<+35>: movq 0x47fd6(%rip), %rsi ; "viewDidLoad"
<+42>: leaq -0x20(%rbp), %rdi
<+46>: callq 0x105d5e69e ; symbol stub for: objc_msgSendSuper2
-> <+51>: movq -0x8(%rbp), %rdi
<+55>: movq 0x47fca(%rip), %rsi ; "testMethod:"
<+62>: leaq 0x3bcbb(%rip), %rdx ; @"test"
<+69>: callq *0x3b40d(%rip) ; (void *)0x00007fff20185480: objc_msgSend
<+75>: addq $0x20, %rsp
<+79>: popq %rbp
<+80>: retq
从汇编代码中可以看到,当前的实例对象是ViewController的实例,当前调用了viewDidLoad方法,并且调用了super的viewDidLoad方法,接下来调用的是testMethod:方法,参数为@“test”,Objective-C的方法底层调用的是objc_msgSend方法。
点击step into执行汇编代码,在命令行里面,输入register read指令,可以查看所有寄存器的取值。
从汇编语言的代码可以看到,里面使用了诸多的汇编指令和寄存器。iOS模拟器实际使用的MacOS的程序,如果是Intel芯片的mac电脑,那么展示的是x86风格的汇编代码。这里有汇编常用的指令和寄存器列表。
指令
数据传送
movq ra, rb
pushq r
;把寄存器压入堆栈
popq r
;把寄存器弹出堆栈
算术和逻辑运算
addq
相加
mulq ax, r
乘法运算
incq
、decq
自增,自减
cmpq ra, rb
比较
andq
、orq
、xorq
与,或,异或
转移指令
jmp label
callq label
过程调用
retq
过程返回
寄存器
ESP:堆栈指针寄存器
EBP:基址指针寄存器
EAX:累加寄存器
EBX:基址寄存器
ECX:计数寄存器
EDX:存放数据寄存器
ESI:源变址寄存器
EDI:目标变址寄存器