1、RET指令

指令格式为:RET
函数调用完需要返回,在函数结束时添加RET指令

  1. .text
  2. .global _test
  3. _test:
  4. ret

ARM不区分大小写

ret指令的本质是将lr(x30)的值赋值给pc寄存器。

2、MOV 指令

指令的格式为:MOV{条件} {S} 目的寄存器, 源操作数

一般大括号里的内容可以省略

MOV 指令可完成从另一个寄存器、被移位的寄存器或将一个立即数加载到目的寄存器。其中$
选项决定指令的操作是否影响 CPSR 中条件标志位的值,当没有S 时指令不更新 CPSR 中条件标志位
的值。

  1. mov x0, #0x8
  2. mov x1, x0

运行结果:x0 = #0x8,x1 = x0

3、ADD指令

指令的格式为:ADD{条件} {S} 目的寄存器, 操作数1, 操作数2
ADD 指令用于把两个操作数相加,并将结果存放到目的寄存器中。操作数1应是一个寄存器,操
作数2可以是一个寄存器,被移位的寄存器,或一个立即数。

  1. mov x0, #0x1
  2. mov x1, #0x2
  3. add x2, x0, x1

运行结果:x2 = x0 + x1

(lldb) register read x2

  1. x2 = 0x0000000000000003

或者单独创建一个add函数:

  1. // 声明add函数
  2. void add(int a, int b);
  3. ; add函数的实现
  4. _add:
  5. add x0, x0, x1
  6. ret

传入的参数默认保存在了寄存器x0和x1中,将函数的返回值存放在x0中。

4、SUB指令

指令的格式为:SUB{条件} {S} 目的寄存器, 操作数1, 操作数2
SUB 指令用于把操作数1减去操作数2,并将结果存放到日的寄存器中。操作数1应是一个寄存
器,操作数2可以是一个寄存器,被移位的寄存器,或一个立即数。该指令可用于有符号数或无符
号数的减法运算。

  1. mov x0, #0x5
  2. mov x1, #0x2
  3. sub x2, x0, x1

运行结果: x2 = x0 - x1

(lldb) register read x2

  1. x2 = 0x0000000000000003

或者单独创建一个sub函数:

  1. // 声明sub函数
  2. void sub(int a, int b);
  3. ; sub函数的实现
  4. _sub:
  5. add x0, x0, x1
  6. ret

5、CMP指令

指令的格式为:CMP{条件} 操作数1, 操作数2
CMP指令用于把一个寄存器的内容和另一个奇存器的内容或立即数进行比较,同时更新 CPSR 中
条件标志位的值。该指令进行一次减法运算,但不存储结果,只更改条件标志位。标志位表示的是
操作数1与操作数2的关系(大、小、相等),例如,当操作数1 大于操作操作数2,则此后的有GT
后缀的指令将可以执行。

  1. mov x0, #0x3
  2. mov x1, #0x2
  3. cmp x0, x1

运行结果:CPSR = x0 - x1

执行cmp指令后,查看cpsr寄存器的值:(lldb) register read cpsr

  1. cpsr = 0x20000000

截屏2022-05-26 15.11.59.png
可以观察到cpsr的31位和30位都是0,代表cmp指令的结果是非负数、非零,也就是正数,即x0大于x1。

6、跳转指令

6.1、B指令

指令的格式为:B{条件} 目标地址
B指令是最简单的跳转指令。一旦遇到一个B指令,ARM 处理器将立即跳转到给定的目标地址,
从那里继续执行。注意存储在跳转指令中的实际值是相对当前 PC 值的一个偏移量,而不是一个绝
对地址,它的值由汇编器来计算(参考寻址方式中的相对寻址)。它是24 位有符号数,左移两位
后有符号扩展为 32 位,表示的有效偏移为 26 位(前后 32MB 的地址空间)。

6.1.1、不带条件的B指令

  1. b label
  2. mov x0, #0x5
  3. label:
  4. mov x1, #0x6

当遇到b指令时,处理器会立即跳转到label处执行。

运行结果:x1 = #0x6 label是代码标记

6.1.2、带条件的B指令

  1. mov x0, #0x1
  2. mov x1, #0x1
  3. cmp x0, x1
  4. beq label
  5. mov x0, #0x5
  6. label:
  7. mov x1, #0x6

运行结果:x1 = #0x6

cmp得出结果会更新cpsr中条件标志位的值,beq就会判断cpsr寄存器的z位,eq代表相等,所以遇到beq指令后会跳转到label处继续执行。

常用条件标志码有:EQ:equal(相等)、NE:not equal(不相等)、GT:great than(大于)、GE:great equal(大于等于)、LT:less than(小于)、LE:less equal(小于等于)。 更多可参考 指令的条件域

6.1.3、指令的条件域

当处理器工作在ARM状态时,几乎所有的指令均根据CPSR中条件码的状态和指令的条件域有条件的执行。当指令的执行条件满足时,指令被执行,否则指令被忽略。
每一条ARM指令包含4位的条件码,位于指令的最高4位[31:28]。条件码共有16种,每种条件码可用两个字符表示,这两个字符可以添加在指令助记符的后面和指令同时使用。例如,跳转指令B可以加上后缀EQ变为BEQ表示“相等则跳转”,即当CPSR中的z标志置位时发生跳转。
在16种条件标志码中,只有15种可以使用,如表3-2所示,第16种(1111) 为系统保留,暂时不能使用
image.png

6.1.4、if else反汇编

使用OC编写if else代码:

  1. int a = 20;
  2. int b = 10;
  3. if (a > b) {
  4. printf(">>>>>>>>>>>>");
  5. } else {
  6. printf("<<<<<<<<<<<<");
  7. }

转换成汇编代码:

  1. 0x104c561e4 <+24>: mov w8, #0x14
  2. 0x104c561e8 <+28>: str w8, [sp, #0xc]
  3. 0x104c561ec <+32>: mov w8, #0xa
  4. 0x104c561f0 <+36>: str w8, [sp, #0x8]
  5. 0x104c561f4 <+40>: ldr w8, [sp, #0xc]
  6. 0x104c561f8 <+44>: ldr w9, [sp, #0x8]
  7. 0x104c561fc <+48>: subs w8, w8, w9
  8. 0x104c56200 <+52>: b.le 0x104c56218 ; <+76> at main.m
  9. 0x104c56204 <+56>: b 0x104c56208 ; <+60> at main.m
  10. 0x104c56208 <+60>: adrp x0, 1
  11. 0x104c5620c <+64>: add x0, x0, #0xf83 ; ">>>>>>>>>>>>"
  12. 0x104c56210 <+68>: bl 0x104c56534 ; symbol stub for: printf
  13. 0x104c56214 <+72>: b 0x104c56228 ; <+92> at main.m
  14. 0x104c56218 <+76>: adrp x0, 1
  15. 0x104c5621c <+80>: add x0, x0, #0xf90 ; "<<<<<<<<<<<<"
  16. 0x104c56220 <+84>: bl 0x104c56534 ; symbol stub for: printf
  17. 0x104c56224 <+88>: b 0x104c56228 ; <+92> at main.m
  18. 0x104c56228 <+92>: mov w0, #0x0

分析汇编代码大概可以看出,源码的if else是使用了b.le(小于等于)指令来判断w8、w9两个寄存器差值的结果,如果w8 <= w9则跳转到0x104c56218处执行if中的代码,否则就不跳转,继续执行else中的代码,并使用b指令来跳转到0x104c56228处执行,跳过if中的代码。

6.2、BL指令

指令的格式为:BL{条件} 目标地址
BL是另一个跳转指令,但跳转之前,会在寄存器 R14 中保存 PC 的当前内容,因此,可以通过将R14的内容重新加载到PC中,来返回到跳转指令之后的那个指令处执行。该指令是实现子程序调用的一个基本但常用的段。

  1. _test:
  2. bl mytest
  3. mov x3, #0x2
  4. mov x4, #0x1
  5. ret ;
  6. mytest:
  7. mov x0, #0x1
  8. mov x1, #0x2
  9. add x2, x0, x1
  10. ret

运行结果:执行_test函数 — 执行mycode函数 — 继续执行_test函数

可以通过反汇编的方式来验证,在main函数中调用myTest:

  1. void mytest() {
  2. int a = 20;
  3. int b = 10;
  4. printf("%d", a + b);
  5. }
  6. int main(int argc, char * argv[]) {
  7. mytest();
  8. return 0;
  9. }

转成汇编代码:

  1. ARMTest`main:
  2. 0x1029fe21c <+0>: sub sp, sp, #0x30
  3. 0x1029fe220 <+4>: stp x29, x30, [sp, #0x20]
  4. 0x1029fe224 <+8>: add x29, sp, #0x20
  5. 0x1029fe228 <+12>: mov w8, #0x0
  6. 0x1029fe22c <+16>: str w8, [sp, #0xc]
  7. 0x1029fe230 <+20>: stur wzr, [x29, #-0x4]
  8. 0x1029fe234 <+24>: stur w0, [x29, #-0x8]
  9. 0x1029fe238 <+28>: str x1, [sp, #0x10]
  10. -> 0x1029fe23c <+32>: bl 0x1029fe1d0 ; mytest at main.m:12
  11. 0x1029fe240 <+36>: ldr w0, [sp, #0xc]
  12. 0x1029fe244 <+40>: ldp x29, x30, [sp, #0x20]
  13. 0x1029fe248 <+44>: add sp, sp, #0x30
  14. 0x1029fe24c <+48>: ret

可以看出汇编代码是通过bl指令来跳转函数的。

6.3、B和BL指令的区别

B指令仅仅找到标记函数进行跳转。
BL指令跳转之前会将下一条指令的地址存储到lr(x30)寄存器中,然后跳转到标记处执行代码,执行到ret时,ret会把lr的值赋值给pc,cpu就会继续执行pc寄存器保存的指令地址,从而实现函数跳转后返回的目的。

7、内存操作

7.1、内存寻址

7.1.1、寄存器间接寻址

寄存器间接寻址就是以寄存器中的值作为操作数的地址,而操作数本身存放在存储器中。例如以下指令:
LDR X0, [X1]

X0 ← [X1]

指令将以 R1 的值为地址的存储器中的数据传送到R0中。

7.1.2、基址变址寻址

基址变址寻址就是将寄存器(该寄存器一般称作基址寄存器)的内容与指令中给出的地址偏移量相加,从而得到一个操作数的有效地址。变址寻址方式常用于访问某基地址附近的地址单元。采用变址寻址方式的指令常见有以下几种形式,如下所示:
LDR X0, [X1, #0x4]

X0 ← [X1 + 4]

LDR X0, [X1, #0x4]!

X0 ← [X1 + 4]、X1 = X1 + 4

在第一条指令中,将寄存器X1的内容加上4形成操作数的有效地址,从而取得操作数存入寄
存器R0中。
在第二条指令中,将寄存器R1的内容加上4形成操作数的有效地址,从而取得操作数存入寄
存器X0中,然后,X1 的内容自增4个字节。

7.2、load

从内存中读取数据

7.2.1、ldr、ldur

  1. ldr x0, [x1]

x1存放的是地址,将x1地址对应数据存储到x0中。

  1. ldr x0, [x1, #0x5]

将x1存放的地址加5,得出一个新的地址x1+5,取出x1+5地址对应的数据存储到x0中。

  1. ldr x0, [x1, #0x5]!

将x1存放的地址加5,得出一个新的地址x1+5,取出x1+5地址对应的数据存储到x0中。并且x1 = x1 +5

  1. ldur x0, [x1, -#0x5]

将x1存放的地址减5,得出一个新的地址x1-5,取出x1-5地址对应的数据存储到x0中。 ldr对应的偏移值一般是正数,ldur对应的偏移值一般是负数。

7.2.2、ldp

  1. ldp w0, w1 [x2, #0x10]

p是pair的简称,从x2 + 10地址对应的数据前4个字节存放在w0中,接着4个字节存放在w1中。

image.png

7.3、store

往内存中写入数据

7.3.1、str、stur

  1. str w0, [x1]

将w0的内容存放到x1对应的地址中

将下面OC代码转成汇编代码:

  1. int a = 0;
  2. long b = 0;
  1. 0x104ab6280 <+24>: str wzr, [sp, #0xc]
  2. 0x104ab6284 <+28>: str xzr, [sp]

wzr和xzr代表零寄存器,因为str只能操作寄存器,所以有清零操作时,需要使用零寄存器

7.3.2、stp

  1. stp w0, w1 [x1 ,#0x10]

将w0的内容存放在从x1+10开始的4个字节中,将w1的内容存放在x1+10+4开始的4个字节中

image.png