# 01.寄存器

## 寄存器是什么

> CPU除了有控制器、运算器还有寄存器。其中寄存器的作用是进行数据的临时存储
>
> CPU的运算速度非常快，为了性能CPU在内部开辟一小块零食存储区域，并在进行运算时先将数据从内存复制到这一小块临时区域中，运算时就在这一小块临时存储区域内进行。

**高速缓存**

> CPU每执行一条指令前都要从内存中将指令读取到CPU内执行。而寄存器的运行速度相比内存读写要快很多，为了性能，CPU还集成了一个**高速缓存存储区域**当程序在运行时，先将需要执行的指令代码以及数据复制到高速缓存中去（由操作系统完成），CPU直接从高速缓存中依次读取指令来执行。

ARM64一个寄存器64位，8个字节

## 一：几种寄存器

![各种寄存器](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2F93ca08509aa66b796ed582368c1f602876ac04ef.png?generation=1617113133953456\&alt=media)

* 浮点寄存器
  * 因为浮点数的存储以及运算的特殊性，CPU中专门提供浮点寄存器来处理浮点数
    * 浮点寄存器64位：D0-D31 32位：S0-S31
* 向量寄存器
  * 先在很多CPU支持向量运算（图形处理用的很多），为了支持向量计算而提供
    * 向量寄存器：128位 V0-V31
* 异常状态寄存器
* 通用寄存器(数据地址寄存器)
  * ARM64拥有32个64位的通用寄存器X0-X30以及XZR（零寄存器）
  * 为了兼容32位，所以ARM64拥有W0-W30和WZR30个32位寄存器
  * 32位寄存器并不是独立存在的。如：W0是X0的低32位
* PC寄存器：指令指针寄存器
  * PC寄存器里面的值就是保存的CPU接下来需要执行的指令地址
  * 改变PC寄存器的值可以改变程序执行的流程

### 1.1 通用寄存器

* x0-x7: 用于子程序调用时的参数传递，X0还用于返回值传递
* `x0 - x30`是31个通用整形寄存器。每个寄存器可以存取一个64位大小的数。 当使用 r0 - r30访问时，它就是一个64位的数。当使用 w0 - w30访问时，访问的是这些寄存器的低32位，如图：

![通用寄存器](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2Fac7afd7c690aa066d87ea7f9d0b7467331ae8f10.png?generation=1617113134528934\&alt=media)

### 1.2 CPSR 状态寄存器

> Current Program State Register
>
> 状态寄存器是32位的

![CPSR](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2F4be717550d024906022f02a43a66f0f3f4211976.png?generation=1617113136402571\&alt=media)

* CPSR的低8位（包括I、F、T和M\[4：0]）称为控制位，程序无法修改，除非CPU运行于`特权模式`下，程序才能修改控制位
* `N、Z、C、V`均为`条件码标志位`。它们的内容可被`算术或逻辑运算`的结果所改变，并且可以决定某条指令是否被执行，意义重大
* NZCV是状态寄存器的条件标志位，分别代表运算过程中产生的状态，其中：
  * N, `negative condition flag`，一般代表运算结果是负数
    * 1 代表结果为负数
    * 0 代表结果为非负数
  * Z, `zero condition flag`, 指令结果为0时Z=1，否则Z=0
  * C, `carry condition flag`, 一般情况下进行无符号数的运算
    * 加法运算
      * 当运算结果产生了进位（无符号溢出）C=1否则C=0
    * 减法运算（包括CMP）
      * 当运算是产生了借位时（无符号溢出）则C=0，否则C=1
      * 对于位数为N的无符号数来说其对应的二进制的最高位，即第N-1位就是他的最高有效位，而假想存在的第N位就是相对于最高有效位的更高位。
    * 进位
      * 我们知道，当两个数相加的时候，有可能从最高有效位向更高位进位。比如两个32位数据：`0xaaaaaaaa` + `0xaaaaaaaa`，将会产生进位。但由于进位值在32位中无法保存，我们就只是简单的说这个进位值丢失了。其实CPU在运算的时候并不会丢弃这个进位值，而是记录在一个特殊的寄存器的某一位上。ARM上就用C为来记录这个进位值。
    * 借位
      * 当两个数据做减法的时候，有可能向更高位。比如：`0x00000000` - `0x000000ff`，将产生借位，借位后相当于计算`0x100000000` - `0x000000ff` = `0xffffff01`，由于借了一位，所以C位用来标记借位。

![Carry](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2F158efe9639c97752f8c02d2bc9931e56fd55f0dc.jpg?generation=1617113135466044\&alt=media)

* V, `oVerflow condition flag` 有符号运算有溢出时，V=1
  * 正数 + 正数 = 负数 则 溢出
  * 负数 + 负数 = 正数 则 溢出
  * 正数 + 负数 不可能溢出

## 二：栈

后进先出 ARM64栈开口向底地址，即开辟的地址是朝底地址方向的

### 2.01 SP 和 FP寄存器

* SP寄存器在任意时刻会保存栈顶的地址
* FP寄存器也称为X29寄存器属于通用寄存器，但是在某些时候我们利用它保存栈底的地址

> 注意：ARM64开始，取消了32位的LDM、STM、PUSH、POP指令，取而代之的是：ldr、ldp、str、stp。在ARM64中对栈的操作是16字节对齐的

### 2.02 编译器会根据编译情况开辟固定的栈空间

### 2.03 函数调用栈

常见函数调用开辟和恢复栈空间

* `sub sp, sp, #0x40`
  * 拉伸0x40（64字节）空间&#x20;
  * 即sp寄存器地址减0x40
  * 开辟了64字节空间
* `stp x29, x30, [sp, #0x30]`
  * x29、x30寄存器入栈保护
* `add x29, sp, #0x30`
  * x29指向栈帧的底部
* `ldp x29, x30, [sp, #0x30]`
  * 恢复x29、x30寄存器的值
* `add sp, sp, #0x40`
  * 栈平衡，用完了要归位
* `ret`

### 2.03 内存读写指令

> 读写：数据都是往高地址读写的 即：一段数据一个字节无法完整保存的话，连续的多个地址进行保存，地址递增

* str 指令 store register
  * 将数据从寄存器中读出来，存到内存中
* ldr 指令 load register
  * 将数据冲内存中读出来，存到寄存器中
* ldp stp 可以一次性操作两个寄存器

### 2.04 bl 和 ret 指令

* `bl`
  * 将下一条指令的地址放入`lr(x30)`寄存器
  * 转到标号处执行指令
* `ret`
  * 默认使用`lr(x30)`寄存器的值，通过底层指令提示CPU此处作为下条指令地址
* `lr(x30)`寄存器
  * 存放的是函数的返回地址，当`ret`指令执行时，回去寻找`lr(x30)`寄存器保存的地址
  * `在嵌套调用函数的时候需要将 x30 入栈`

### 2.05 函数的参数和返回值

* `ARM64`下，函数的参数是放在`x0 ~ x7`（`w0 ~ w7`）这8个寄存器中。如果超过八个参数就会入栈。
* 函数的返回值会放在`X0`寄存器中的。
* 函数的局部变量是放在栈里面的
