一、寄存器
对于日常使用高级语言的程序员来说,其看到的是连续的主存(内存)空间,使用的也是主存储器(简称内存)的空间。但是对于使用汇编的程序原来说,其面对的主要是寄存器。事实上,CPU内部也是分为运算器、控制器、寄存器、多级缓存。对于基于汇编语言的程序开发来说,寄存器是必须了解的一个概念。

二、通用寄存器
通用原来寄存器命名是r[0]、r[1]、r[2]、r[3]......,但是随着armv8,也就是64位的处理器推出,寄存器命名修改为x[n],但是为了保持兼容,x[n]的低32位被命名为w[n]
通用寄存器从名字上看就是做一些通用得工作的寄存器,通用工作有哪些呢?一般就是存储内存的地址、各种计算所需的临时数据、堆栈地址等。
2.0 栈概述
由于寄存器与栈这种数据结构有密切的关系,并且在后面也会详细讲到函数调用堆栈,为使后面顺利讲述,在这里先概要介绍一下栈。
栈是一种先进后出的数据结构。在日常生活中能够见到的典型栈类型的东西就是——箱子,先装进箱子的东西只能后拿出来,而后装进去的东西则可以先拿出来。

注意
在ARM64汇编中,栈是从高地址向低地址生长的、先开劈栈空间然后再使用。这句话可能暂时不理解,但是一定请先记住。
_addwwj: ; @addwwj
Lfunc_begin0:
sub sp, sp, #16 ; 开辟栈空间
str w0, [sp, #12]
str w1, [sp, #8]
Ltmp1:
ldr w8, [sp, #12]
ldr w9, [sp, #8]
add w0, w8, w9
add sp, sp, #16 ; 回收栈空间
ret
Ltmp2:
Lfunc_end0:
在上面汇编代码中(sub sp, sp, #16),sub是减的意思,由于栈空间是向低地址增长的,那么所以在当前栈底的基础之上再扩展16个字节的空间。
在上面汇编代码中(add sp, sp, #16),把栈底又加16个字节,所以栈又恢复到初始调用此函数的状态。
2.1 传参寄存器
有人说xo-x7,这8个寄存器一般用来存储函数的入参,多于8个的参数会存放在内存的之中。但是经过我的测试,发现其与优化等级相关,如果不优化(o0)的情况下,多达18个参数的情况下依然会通过寄存器来传参,用于测试的电脑是:MacBook Pro (13-inch, M1, 2020)。如果选择(o3)级别的优化,在参数太多的情况下会通过内存来传递。
总的来说,前几个寄存器一般用于传递函数的参数。



2.2 x0寄存器
对于x0(w0)寄存器来说,其还有一个特殊用途,就是用来存储函数的返回值,如下图所示。

图中左边是一个返回10的函数,右边是对应的汇编代码,可见直接把16进制的10复制到w0寄存器来实现传递返回值。
2.3 fp/x29
fp(frame pointer)寄存器是x29寄存器的简称,用于存储函数调用堆栈的栈底信息。

2.4 lr/x30
lr寄存器用来保存函数返回的路。
int aTestFun(int para1) {
return para1 + 3;
}
int theOtherTestFun(int para1) {
return para1 + aTestFun(para1);
}
;theOtherTestFun 对应的汇编代码如下
0x100003f20 <+0>: sub sp, sp, #0x20 ; =0x20
0x100003f24 <+4>: stp x29, x30, [sp, #0x10]
0x100003f28 <+8>: add x29, sp, #0x10 ; =0x10
0x100003f2c <+12>: stur w0, [x29, #-0x4]
-> 0x100003f30 <+16>: ldur w8, [x29, #-0x4]
0x100003f34 <+20>: ldur w0, [x29, #-0x4]
0x100003f38 <+24>: str w8, [sp, #0x8]
0x100003f3c <+28>: bl 0x100003f08 ; aTestFun at CImp.c:10 这里是调用 aTestFun函数,进入此函数之后,lr的地址是0x100003f40
0x100003f40 <+32>: ldr w8, [sp, #0x8] ; 就是这个地址
0x100003f44 <+36>: add w0, w8, w0
0x100003f48 <+40>: ldp x29, x30, [sp, #0x10]
0x100003f4c <+44>: add sp, sp, #0x20 ; =0x20
0x100003f50 <+48>: ret

在上面的截图中,函数theOtherTestFun调用aTestFun函数,当从aTestFun函数返回是需要执行theOtherTestFun中调用aTestFun函数的下一行。
0x100003f3c <+28>: bl 0x100003f08 ; aTestFun at CImp.c:10 实现调用aTestFun函数
0x100003f40 <+32>: ldr w8, [sp, #0x8] ; aTestFun返回后,需要执行的下语句汇编代码
;从上面的截图来看,当前lr地址就是上面汇编指令的地址
2.5 sp/x31
sp(stack pointer)寄存器是寄存器x31的简称,用于存储函数调用堆栈的栈顶信息。
int need1Parameter(int para1) {
return 10;
}
; 这里就是上述 need1Parameter 函数的汇编代码
0x100003dac <+0>: sub sp, sp, #0x10 ; =0x10 此时移动栈顶空间,为need1Parameter函数开辟堆栈空间
0x100003db0 <+4>: str w0, [sp, #0xc]
0x100003db4 <+8>: mov w0, #0xa
-> 0x100003db8 <+12>: add sp, sp, #0x10 ; =0x10 need1Parameter函数执行完毕,回收刚才开辟的堆栈空间
0x100003dbc <+16>: ret
2.6 pc/x32
所有的数据、指令、常量等,在内存中,都是01001。那么,cpu怎么知道哪段01序列是命令,需要CPU来执行呢?其实很简单,pc寄存器所指的内容就会被当做指令。

2.7 cpsr/x33
目前,上面介绍的寄存器都是作为一个整体来使用的,但是cpsr寄存器是每个位都单独来使用的。其标识当前程序所处的状态,目前暂未涉及,先不做深入介绍,如果想要深入理解的话,可以自行百度。

三、浮点数寄存器
为了加速浮点数的处理,现在arm提供专用的浮点数寄存器。64位的浮点数寄存器是V0-V31、spsr、spcr。

四、向量寄存器
为了支持游戏等用到向量计算比较多的情况,CPU针对向量计算,提供了向量寄存器。

五、寄存器相关lldb命令
- register read 读取所有通用寄存器
- register read x0 读取某个寄存器
- register write x0 0x0001 向寄存器写入值
注意
例如,pc寄存器是不能直接写入值得。
网友评论