在我看来,最好的开始方式是
- 编写 C 代码小片段(后来的 Objective-C)
- 查看对应的汇编代码
- 找出足以理解汇编代码的内容
- Repeat!
为此,您可以使用 Xcode:
- 创建一个新的 iOS 项目(单视图应用程序就可以)
- 添加C文件scratchpad.c
- 在项目构建设置中,将“生成调试符号”设置为“否”
- 确保目标是 iOS 设备,而不是模拟器
- 打开scratchpad.c并打开助理编辑器
- 将助理编辑器设置为Assembly并选择“Release”
实施例1
将以下函数添加到scratchpad.c:
void do_nothing(void)
{
return;
}
如果您现在在助理编辑器中刷新程序集,您应该会看到许多以点(指令)开头的行,后面是
_do_nothing:
@ BB#0:
bx lr
现在让我们忽略这些指令,看看这三行。通过在互联网上进行一些搜索,您会发现这些行是:
- 标签(带有下划线前缀的函数名称)。
- 只是编译器发出的注释。
- 返回语句。这
b
表示分支,忽略x
现在(它与指令集之间的切换有关),并且lr
是链接寄存器,调用者存储返回地址。
实施例2
让我们稍微加强一下并将代码更改为:
extern void do_nothing(void);
void do_nothing_twice(void)
{
do_nothing();
do_nothing();
}
保存并刷新程序集后,您将得到以下代码:
_do_nothing_twice:
@ BB#0:
push {r7, lr}
mov r7, sp
blx _do_nothing
pop.w {r7, lr}
b.w _do_nothing
同样,通过在互联网上进行一些搜索,您就会找到每一行的含义。还需要做一些工作,因为进行了两次调用:第一个调用需要返回给我们,因此我们需要更改lr
。这是由blx
指令,它不仅分支到_do_nothing
,而且还将下一条指令的地址(返回地址)存储在lr
.
因为我们改变了返回地址,所以我们必须将它存储在某个地方,所以它被压入堆栈。第二次跳跃有一个.w
后缀,但现在让我们忽略它。为什么这个函数看起来不是这样的?
_do_nothing_twice:
@ BB#0:
push {lr}
blx _do_nothing
pop.w {lr}
b.w _do_nothing
这也可以,但在 iOS 中,惯例是将帧指针存储在r7
。帧指针指向堆栈中存储前一个帧指针和前一个返回地址的位置。
所以代码的作用是:首先,它推送r7
and lr
到堆栈,然后设置r7
指向新的堆栈帧(位于堆栈顶部,并且sp
指向栈顶),然后第一次分支,然后恢复r7
and lr
,终于第二次分支了。 Abx lr
最后不需要,因为被调用的函数将返回lr
,它指向我们的调用者。
实施例3
让我们看最后一个例子:
void swap(int *x, int *y)
{
int temp = *x;
*x = *y;
*y = temp;
}
汇编代码是:
_swap:
@ BB#0:
ldr r2, [r0]
ldr r3, [r1]
str r3, [r0]
str r2, [r1]
bx lr
通过一些搜索,您将了解到参数和返回值存储在寄存器中r0
-r3
,并且我们可以自由地使用它们进行计算。代码的作用很简单:它加载的值r0
and r1
指向r2
and r3
,然后将它们按交换的顺序存储回来,然后再分支回来。
等等
就是这样:编写小片段,获取足够的信息以大致了解每行中发生的情况,然后重复。希望有帮助!