ARM64汇编

ARM64汇编

寄存器

  • 通用寄存器

    • 64bit: x0 - x28

    29个 通用寄存器,8位(bit)=1字节(Byte),每个寄存器64bit能存放8个字节

    注:

    1位表示1个2进制位

    一位十六进制位=4个二进制位

    32位=8位16进制 0x00000000

    64位=16个16进制位0x0000000000000000

    一个字节包含8个二进制位,一个字节可以由2个十六进制表示

    • 32bit: w0 - w28 (属于x0 - x28的低32bit)
    • x0 - x7 通常拿来存放函数的参数,更多的参数使用堆栈来传递
    • x0 通常拿来存放函数的返回值
  • 程序计数器

    • pc (Program Counter)

    用来记录CUP当前执行的是哪一条指令

    存储着当前CPU正在执行的指令的地址

    类似8086汇编的ip寄存器

  • 链接寄存器

    • sp (Stack Pointer) 栈顶指针
    • fp (Frame Pointer) 也就是x29 栈底指针
  • 堆栈指针

    • lr (Link Register) 也就是x30

    存储着函数的返回地址

  • 程序状态寄存器

    • cpsr (Current Program Status Register)
    • spsr (Saved Program Status Register) , 异常状态下使用

cpsr

31 30 29 28 27 ~ 8 7 6 5 4 3 2 1 0
N Z C V 保留 I F T M4 M3 M2 M1 M0

N: 带符号的数进行运算时,N=1表示运行结果为负;N=0表示运行结果为正或0

Z:Z=1表示运算结果为0,Z=0表示运行结果非零

C: 可以有4中方法设置C位的值:

- 加法运算(包括比较指令CMN): 当运算结果产生了进位时(无符号数溢出),C=1,否则C=0。
- 减法运算(包括比较指令CMP):当运算符产生了错位(无符号数溢出),C=0,否则C=1。
- 对于包含移位操作的非加/减运算指令,C为移出值的最后移位。
- 对于其他的非加/减运算指令,C的值通常不改变。

V:可以有2种方法设置V的值:

- 对于加/减法运算指令,当操作数和运算结果为二进制的补码表示的带符号数时,V=1表示符号位溢出。
- 对于其他的非加/减运算指令,V的值通常不改变。

0-7: 控制位,软件开发基本用不到

指令

  • ret

    • 函数返回
    • lr(x30)的值赋值给pc寄存器
  • mov

    MOV {条件} {s} 目的寄存器, 源操作数

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

    1
    2
    3
    4
    ;指令示例
    MOV x1, x0 ; 将寄存器x0的值传送到寄存器x1
    MOV x2, x1 ; 将寄存器x1的值传送到x2
    MOV x1, x0, LSL#3 ; 将寄存器x0的值左移3位后传送到x1[暂不确定是否在arm汇编可用]
  • add

    ADD {条件} {s} 目的寄存器, 操作数1, 操作数2

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

    1
    2
    3
    4
    ;指令示例
    ADD x0, x1, x2 ; x0 = x1 + x2
    ADD x0, x1, #0x11 ; x0 = x1 + 0x11
    ADD x0, x2, x3, LSL#1 ; x0 = x2 + (x3 << 1) [暂不确定是否在arm汇编可用]
  • sub

    SUB {条件} {s} 目的寄存器, 操作数1, 操作数2

    ADD指令用于把操作数1减去操作数2,并将结果存放到目的寄存器中。操作数1应是一个寄存器,操作数2可以是一个寄存器,被移位的寄存器,或一个立即数。该指令可用于有符号数或无符号数的减法运算。

    1
    2
    3
    4
    ;指令示例
    SUB x0, x1, x2 ; x0 = x1 - x2
    SUB x0, x1, #0x11 ; x0 = x1 - 0x11
    SUB x0, x2, x3, LSL#1 ; x0 = x2 - (x3 << 1)[暂不确定是否在arm汇编可用]
  • cmp

    CMP {条件} 操作数1, 操作数2

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

    1
    2
    3
    ;指令示例
    CMP x1, x0 ; 将寄存器x1与寄存器x0的值相减,并根据结果设置CPSR的标志位
    ADD x1, #100 ; 将寄存器x1与立即数100的相减,并根据结果设置CPSR的标志位
  • b

    B{条件} 目标地址

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

    1
    2
    3
    4
    5
    ;指令示例
    B Label ; 程序无条件跳转到标号Label处执行

    CMP x1, #0 ; 比较x1和0的大小,将结果放入CPSR寄存器中
    BEQ Label ; B+条件(见下面条件域,中间无空格) 当CPSR寄存器中的z条件码置位时,程序跳转到标号Label处执行
  • cmp

    CMP {条件} 操作数1, 操作数2

    CMP指令用于把一个寄存器的内容和另一个寄存器的内容进行比较,同事更新CPSR中条件标志位的值。该指令进行一次减法运算,但不存储结果,只更改条件标志位。标志位标识的是操作数1余操作数2的关系(大、小、相等),例如,当操作数1大与操作数2,则此后的有GT后缀的指令将可以执行。关于后缀查看下面的条件域。

    1
    2
    3
    ;指令示例
    CMP x1, x0 ; 将寄存器x1与寄存器x0的值相减,并根据结果设置CPSR的标志位
    ADD x1, #100 ; 将寄存器x1与立即数100的相减,并根据结果设置CPSR的标志位
  • bl (带返回的跳转,类似函数调用)

    BL{条件} 目标地址

    BL指令是另一个跳转指令,但跳转之前,会在寄存器x14中保存PC的当前内容,因此,可以通过将x14的内容重新加载到PC中,来返回到跳转指令之后的那个指令处执行。改指令是实现之程序调用的一个基本当常用的手段。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    ;指令示例
    _test:
    ...
    bl somecode ; 程序无条件跳转到标号Label处执行时,同事将当前的PC值保存到x14中
    ...
    ret

    comecode:
    ...
    ret ; 程序返回bl之后的代码继续执行
  • orr 逻辑或运算

1
orr w8, wzr, #0x3   ; 将wzr 和#0x3或运算,结果#0x3赋值给w8

条件域

  • EQ: equal 相等
  • NE: not equal 不相等
  • GT: great than大于
  • GE: great equal 大于等于
  • LT: less than 小于
  • LE: less equal 小于等于

内存操作

  • load 从内存读取数据

    • ldr、ldur(ldr 通常是正偏移,ldur通常是负偏移)
    • ldp (p是pair,一对的简称) : 从内存读取数据,放到一对寄存器中
    1
    2
    ldr x0, [x1]              ; 将x1的值作为地址的内存中的数据传送到x0中
    ldp w0, w1, [x1, #0x10] ; 取x1+#0x10的地址对应的内存数据,前4个字节存储到w0,后4个自己存储到w1
  • store 往内存中写入数据 (只能从寄存器写入,不能直接写入立即数)

    • str、stur
    • stp
    1
    2
    str w0, [x1]     ; 将w0的数据,存入x1中地址对应的内存
    stp w0, w1 [x1] ; 将w0和w1的数据,存入x1中地址对应的内存
    • 零寄存器 里面存储的值是0
      • wzr (32bit)
      • xzr (64bit)

寻址方式

立即数寻址

立即寻址也叫立即数寻址,这是一种特殊的寻址方式,操作数本身就在指令中给出,只要取出指令也就取到了操作数。这个操作数被称为立即数,对应的寻址方式叫做立即寻址。例:

1
2
ADD x0, x0, #1    ; x0 <- x0 + 1
ADD x0, x0, #0x3f ; x0 <- x0 + 0x3f

寄存器寻址

寄存器寻址就是利用寄存器中的数值作为操作数,这种寻址方式是各类微处理器经常采用的一种方式,也是一种执行效率较高的寻址方式

1
ADD x0, x1, x2     ; x0 <- x1 + x2

寄存器间接寻址

寄存器间接寻址就是以寄存器中的值为操作数的地址,而操作数本身放在存储器中

1
2
3
ADD x0, x1, [x2]    ; x0 <- x1 + x2存储的地址指向的内存中的数据
LDR x0, [x1] ; 将x1的值作为地址的内存中的数据传送到x0中
str x0, [x1] ; [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 先取x1地址对应的内存数据到x0,然后x1地址+4
LDR x0, [x1, x2] ; x0 <- [x1+x2]

多寄存器寻址

采用多寄存器寻址,一条指令可以完成多个寄存器值的传送。这种寻址方式可以用一条指令完成传送最多16个通用寄存器的值。

1
2
3
4
5
LDMIA x0, {x1, x2, x3, x4} ; x1 <- [x0]
; x2 <- [x0+4]
; x3 <- [x0+8]
; x4 <- [x0+12]
; 将x0地址对应的连续的存储空间传送到x1-x4

相对寻址

与基址变址寻址方式相似,相对寻址以程序计数器PC的当前值为基地址,指令中的地址标号为偏移量,将两者相加之和得到操作数的有效地址。以下程序段完成子程序的调用和返回,跳转指令BL采用了相对寻址方式:

1
2
3
4
5
BL NEXT       ; 跳转到子程序NEXT处执行
.....
NEXT
.....
MOV PC, LR ; 从子程序返回

堆栈寻址

堆栈寻址堆栈是一种数据结构,按先进后出( First hi Last Out , FILO )的方式工作,使用一个称作堆栈指针的专用寄存器指示当前的操作位置,堆栈指针总是指向栈顶。当堆栈指针指向址后压入堆栈的数据时,称为满堆栈( Ful ! StaCk ) ,而当堆栈指针指向卜一个将要放入数据的空位置时,称为空堆栈( EmPty Stack )。同时,根据堆栈的生成方式,又可以分为递增堆栈( Ascending Stack )和递减堆栈( Decending Stack ) ,当堆栈由低地址向高地址生成 l 讨,称为递增堆栈,当堆栈由高地址向低地址生成 11 - 4 ,称为递减堆栈。这样就有四种类型的堆栈工作方式, ARM 微处理器支持这四种类型的堆栈工作方式,即:

满递增堆栈:堆栈指针指向最后压入的数据,且由低地为卜向高地为 L 生成。

满递减堆栈:堆栈指针指向最后压入的数据,且由高地址向低地址生成。

空递增堆栈:堆栈指针指向卜一个将要放入数据的空位置,且由低地址向高地址生成。

空递减堆栈:堆栈指针指向卜一个将要放入数据的空位置,且由高地址向低地址生成。

在Xcode中编写汇编代码

内嵌

1
2
3
4
5
- (void)someFunction() {
__asp{
...
}
}

外部建立

新建xxx.s汇编文件

1
2
3
4
5
6
7
8
9
10
11
               ; 注释
.text ; 表示放在代码段
.global _test, _add ; 将函数公开,否则为私有函数
_test: ; test() 调用的实际方法是 _test:
mov x0, #0x8 ; 将8赋值给x0寄存器,立即数前需加#,最好用16进制
mov x1, x0 ; 将x0赋值给x1
ret ; 返回

_add:

ret

新建xxx.h头文件

1
2
void test();
void add(int a, int b);

函数的堆栈

  • 函数的类型

    • 叶子函数 (内部不再调用其他函数)
    1
    2
    3
    4
    5
    ; 叶子函数汇编代码

    sub sp, sp, #16 ; 栈顶指针减16个字节,开辟16个字节的栈空间
    ...
    add sp, sp, #16 ; 栈顶指针加16个字节,释放空间,恢复栈平衡
    • 非叶子函数 (内部会调用其他函数)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    ; 非叶子函数汇编代码

    sub sp, sp #32 ; 栈顶指针-32个字节,开辟栈空间
    stp x29, x30, [sp, #16] ; 将x29,x30(栈底指针和堆栈指针)存储到sp栈顶指针+16的位置
    add x29, sp, #16 ; 栈底指针sp+16的位置(也就是除去存储栈底指针和堆栈指针剩余的位置)
    ...
    bl _func
    ...
    ldp x29, x30, [sp, #16] ; 从内存中恢复栈底和堆栈指针
    add sp, sp #32 ; 释放空间
    ret

C语言编译成汇编文件

1
xcrun -sdk iphoneos clang -arch arm64 -S CTest.c -o CTest.s