iOS汇编
- 真机:
Arm64汇编
- 寄存器
- 指令
- 堆栈
- 模拟器:
x86汇编
寄存器
通用寄存器
64bit
:x0 ~ x28
32bit
:w0 ~ w28
(属于x0 ~ x28的低32bit)x0 ~ x7
通常拿来存放函数的参数,更多的参数使用堆栈来传递x0
通常拿来存放函数的返回值int add(int a, int b); int sub(int a, int b);
1 2 3 4 5 6 7 8 9 10 11 12
.text ; 代码段 .global _add, _sub _add: ; 相加 add x0, x0, x1 ret ; sub 函数实现 _sub: ; 相减 sub x0, x0, x1 ret
程序计数器
- pc (Program Counter)
- 记录CPU当前记录的是哪一条指令
- 存储着当前CPU正在执行的指令的地址
- 类似于8086汇编的ip寄存器
堆栈指针
sp (Stack Pointer)
fp (Frame Pointer)
,也就是x29
链接寄存器
lr (Link Register)
,也就是x30
- 存储着函数的返回地址的下一条指令地址
程序状态寄存器
指令
mov指令
mov
指令的格式为:mov {条件} {s} 目的寄存器, 源操作数
mov
指令壳完成从另一个寄存、被移位的寄存器或将一个立即数加载到目的寄存器。其中 s 选项决定指令的操作是否影响CPSR中条件标志位的值,当前没有S时指令不更新CPSR中条件标志位的值。
1 2 3 4 5 6 7
.text ; 代码段 .global _test _test: ; mov 指令 mov x0, #0x8 mov x1, x0 ret
ret指令
- 函数返回
- 将
lr (x30)
寄存器的值赋值给pc寄存器
add指令
add指令
的格式为:add {条件} {s} 目的寄存器, 操作数1, 操作数2
add指令
用于把两个操作数相加,并将结构存放到目的寄存器中,操作数1应是一个寄存器,操作数2可以是一个寄存器,被移位的寄存器,或一个立即数。
1 2 3 4 5 6 7 8 9
.text ; 代码段 .global _test _test: ; add 指令 mov x0, #0x1 mov x1, #0x2 add x2, x0, x1 ret
sub指令
sub指令
的格式为:sub {条件} {s} 目的寄存器, 操作数1, 操作数2
sub指令
用于把操作数1减去操作数2,并将结果存放到目的寄存器中,操作数1应是一个寄存器,操作数2可以是一个寄存器,被移位的寄存器,或一个立即数。该指令可用于有符号数或者无符号数的减法运算。
1 2 3 4 5 6 7 8
.text ; 代码段 .global _test _test: mov x0, #0x5 mov x1, #0x2 sub x2, x0, x1 ret
cmp指令
cmp指令
格式为:cmp {条件} 操作数1, 操作数2
cmp指令
用于把一个寄存器的内容和另一个寄存的内容或者立即数进行比较,同时更新CPSR中条件标志位的值。该指令进行一次减法运算,但不存才结果,只更改条件标志位。标志位表示的是操作数1与操作数2的关系(大、小、相等),例如,当操作数1大于操作数2,则伺候的有GT后缀的指令将可以执行。- 将2个寄存器相减
- 相减的结果会影响cpsr寄存器的标志位
1 2 3 4 5 6 7 8 9 10 11 12
.text ; 代码段 .global _test _test: ; cmp 指令 mov x0, #0x2 mov x1, #0x1 cmp x0, x1 ret ; x0: #0x2 x1: #0x1 cpsr: 0010 0000 ... ; x0: #0x1 x1: #0x1 cpsr: 0110 0000 ... ; x0: #0x1 x1: #0x2 cpsr: 1010 0000 ...
b指令
b指令
的格式为:b {条件} 目标地址
b指令
是最简单的跳转指令。一旦遇到一个b指令,arm处理器将立即跳转到给定的目标地址,从哪里继续执行。注意存储在跳转指令中的实际是相对当前pc值的一个偏移量,而不是一个绝对地址,它的值由汇编器来计算(参考寻址方式中的相对地址)。它是24位有符号数,左移两位后有符号位扩展为32为,表示的有效偏移为26位(前后32MB的地址空间)。1 2 3 4 5 6 7 8 9 10
.text ; 代码段 .global _test _test: ; b 跳转指令 b mycode mov x0, #0x2 mycode: ; 跳转到这执行 mov x1, #0x6 ret
可带条件跳转,一般跟
cmp
配合使用1 2 3 4 5 6 7 8 9 10 11 12 13 14
.text ; 代码段 .global _test _test: ; b 指令带条件 mov x0, #0x1 mov x1, #0x1 cmp x0, x1 b.eq mycode ; cmp比较后相等跳mycode mov x0, #0x7 ret mycode: mov x1, #0x8 ret
bl指令
bl指令
的格式为:bl {条件} 目标地址
bl指令
是另一个带返回的跳转指令,但跳转之前,会在寄存器R14中保存PC当前内容,因此,可以通过将R14的内容重新加载到PC中,来返回到跳转指令之后的那个指令处执行。该指令是实现子程序调用的一个基本但常用的手段。- 执行的操作
- 将下一条指令的地址存储到
lr (x30)
寄存器中 - 跳转到标记处开始执行代码
- 将下一条指令的地址存储到
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
.text ; 代码段 .global _test ; 内部私有函数 mycode: mov x0, #0x2 mov x1, #0x3 add x2, x0, x1 ret _test: ; bl 带返回跳转指令 ; 如果是b指令跳转的话,执行到ret后不会跳b的下一条指令 bl mycode mov x3, #0x4 mov x4, #0x5 ret
条件域
- 当处理器工作在ARM状态时,几乎所有的指令均根据CPSR中条件码的状态和指令的条件域有条件的执行。当指令的执行条件满足是,指令被执行,否则指令被忽略。
每一条
ARM指令
包含4位
的条件码,唯一指令的最高4位[31:28]。天剑码共有16种,每种条件可用两个字符表示,这两个字符可以添加在指令助记符的后面和指令同时使用。例如,跳转指令可以加上后缀EQ变为beq或者b.eq表示”相等则跳转”,即当CPSR中的Z标志置位时发生跳转。EQ
: equal,相等NE
: not equal,不相等GT
: great than,大于GE
: great equal,大于等于LT
: less than,小于LE
: less equal,小于等于
条件码 助记符后缀 标志 含义 0000 EQ Z置位 相等 0001 NE Z清零 不相等 0010 CS C置位 无符号数大于或等于 0011 CC C清零 无符号数小于 0100 MI N置位 负数 0101 PL N清零 正数或零 0110 VS V置位 溢出 0111 VC V清零 未溢出 1000 HI C置位Z清零 无符号数大于 1001 LS C清零Z置位 无符号数小于或等于 1010 GE N等于V 带符号数大于或等于 1011 LT N不等于V 带符号数小于 1100 GT Z清零且(N等于V) 带符号大于 1101 LE Z置位或(N不等于V) 带符号数小于或等于 1110 AL 忽略 无条件执行 内存操作
load
,从内存中读取数据ldr指令
格式为:ldr {条件} 目的寄存器, <存储器地址>
ldr指令
用于从存储器中将一个32位的子数据传送到目的寄存器中。该指令通常用于从存储器总读取32的子数据到通用寄存器,然后对数据新型处理。当程序计数器PC作为目的寄存器时,指令从存储器中读取的字数据被当做目的地址,从而可以实现程序流程的跳转。该指令在程序设计中比较常用,且寻址方式灵活多样。ldr (正数)
、ldur (负数)
ldp
(p是pair的简称),从内存中读取数据放到一对寄存器中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
; x2的地址存放的是int a = 10 ldp w1, w2, [x2, #0x10] ; 意思x2地址 + 0x10,将前4个字节先赋给w1,后4个字节赋给w2 ; 示例 ; 将存储器地址为x1的字数据读入寄存器x0 ldr x0, [x1] ; 将存储器地址为x1 + x2的字数据读入到寄存器x0 ldr x0, [x1, x2] ; 将存储器地址为x1 + 8的字数据读入寄存器x0 ldr x0, [x1, #8] ; 将存储器地址为x1 + x2的字数据读入寄存器x0,并将新地址x1 <-x1+x2 ldr x0, [x1, x2]! ; 将存储器地址为x1 + 8的字数据读入寄存器x0,并将新地址x1 <-x1+8 ldr x0, [x1, #8]! ; 将存储器地址为x1的字数据读入寄存器x0,并将新地址x1+x2写入x1 ldr x0, [x1], x2 ; 将存储器地址为x1 + x2 * 4的字数据读入寄存器x0,并将新地址x1 + x2*4写入x1 ldr x0, [x1, x2, lsl#2]! ; 将存储器地址为x1的字数据读入寄存器x0,并将新地址x1+x2*4写入x1 ldr x0, [x1], x2, lsl#2
store
,往内存中写入数据str指令
的格式为:str {条件} 源寄存器, <存储器地址>
str指令
用于从源寄存器中将一个32为的字数据传送到存储器中。该指令在程序设计中比较常用,且寻址方式灵活多样,使用方式可参考指令ldr
。str
、stur
stp
零寄存器
,里面存储的值是0wzr (32bit, Word Zero Register)
,4个字节xzr (64bit)
,8个字节
1 2 3 4 5
; 示例 ; 将x0中的子数据写入以x1为地址的寄存器中,并将新地址x1 + 8写入x1 str x0, [x1], #8 ; 将x0中的字数据写入以x1 + 8为地址的寄存器中 str x0, [x1, #8]
寻址
立即寻址
立即寻址
也叫立即数寻址,这是一种特殊的寻址方式,操作数本身就在指令中给出,只要取出指令也就取到了操作数。这个操作数被称为立即数,对应的寻址方式也就叫做立即寻址。
1 2 3
add x0, x0, #1 add x0, x0, #0x3f ; 在以上两条指令中,第二个源操作数即为立即数,要求以“#”为前缀,对于以十六进制表示的立即数,还要求在“#”后加上“0x”或“&”
寄存器寻址
寄存器寻址
就是利用寄存器中的数值作为操作数,这种寻址方式是各类未处理器经常采用的一种方式,也就是一种知性效率较高的寻址方式。
1
add x0, x1, x1 ; x0 <- x1 + x2
- 该指令的执行效果是将寄存器x1和x2的内容相加,其结构存放在寄存器x0中
寄存器间接寻址
寄存器间接寻址
就是以寄存器中的值作为操作数的地址,而操作数本身存放在存储器中。
1 2 3
add x0, x1, x2 ; x0 <- x1 + x2 ldr x0, [x1] ; x0 <- [x1], 取决于x0有多少个字节 str x0, [x1] ; x1 <- [x1]
- 第一条指令中,以寄存器x2的值作为操作数的地址,在存储器中取得一个操作数后与x1相加,结果存在寄存器x0中。
- 第二条指令将以x1的值为地址的存储器中的数据传送到x0中。
- 第三条指令将x0的值传送到以x1的值为地址的存储器中。
基址变址寻址
基址变址寻址
就是将寄存器(该寄存器一般称作基址寄存器)的内容与指令中给出的地址偏移量相加,从而得到一个操作数的有效地址。变址寻址方式常用于访问某基址附近的地址单元。
1 2 3 4
ldr x0, [x1, #4] ; x0 <- [x1 + 4] ldr x0, [x1, #4]! ; x0 <- [x1 + 4], x1 <- x1 + 4 ldr x0, [x1], #4 ; x0 <- [x1], x1 <- x1 + 4 ldr x0, [x1, x2] ; x0 <- [x1 + x2]
- 第一条指令中,将寄存器x1的内容加上4形成操作数的有效地址,从而取得操作数存入寄存器x0中。
- 第二条指令中,将寄存器x1的内容加上4形成操作数的有效地址,从而取得操作数存入寄存器x0中,然后x1的内容自增4个字节。
- 第三条指令中,以寄存器x1的内容作为操作数的有效地址,从而取得操作数存入寄存器x0中,然后x1的内容自增4个字节。
- 第四个指令中,将寄存器x1的内容加上寄存器x2的内容形成操作数的有效地址,从而取得操作数存入寄存器x0中。
多寄存器寻址
- 采用
多寄存器寻址
方式,一条指令可以完成多个寄存器值的传送。中寻址方式可以用一条指令完成传送最多16个通用寄存器的值。
1 2 3 4 5
ldmia x0, {x1, x2, x3, x4} ; x1 <- [x0] ; x2 <- [x0 + 4] ; x3 <- [x0 + 8] ; x4 <- [x0 + 12]
- 该指令的后缀IA表示在每次执行完嘉爱/存储操作后,x0按字长度增加,因此,指令可将连续存储单元的值传送到
x1 ~ x4
- 采用
相对寻址
- 与基址变址寻址方式类似,
相对寻址
以程序计数器PC的当前值为基地址,指令中的地址标号作为偏移量,将两者相加后得到操作数的有效地址。
- 与基址变址寻址方式类似,
堆栈寻址
堆栈
是一种数据结构,按先进后出(First In Last Out, FILO)的方式工作,使用一个称作堆栈指针的专用寄存器知识当前的操作位置,堆栈指针总是指向栈顶。
函数的堆栈
- 函数的类型
- 叶子函数
- 什么是叶子函数? 就是函数内部不再调用其他函数就是叫叶子函数
- 非叶子函数
- 叶子函数
生成汇编文件
1
xcrun --sdk iphoneos clang -S -arch arm64 main.c -o main.s