寄存器的数量取决于ARM的版本。根据ARM参考手册可知,有30个32位的通用寄存器(除了ARMv6-M和ARMv7-M的处理器)。在本基础系列课程中,我们学习的对象是在任意特权模式下都可以访问的寄存器:R0-R15。这16个寄存器可以被分成两组:通用寄存器和特殊功能寄存器。
(从r0到r11都是通用寄存器)
下表是ARM寄存器和英特尔处理器的寄存器存在的相关联性的概览
R0-R12可以在通常的运算过程中用来存储临时的数据,指针(定位内存)等。以R0为例,当我们执行算数运算或者存储当前函数的返回值时,可以把R0视为累加器。系统调用发生时,R11开始生效,它存储了系统调用数值。R11作为栈指针帮助我们追踪栈的边界(稍后会讲到)。此外,ARM专用的函数调用规则规定了函数的前四个参数应该分别存贮与R0到R3中。
R14: LR(连接寄存器),当有一处函数调用时,连接寄存器会用一处内存地址来获取更新,该地址是函数初始化的地方的下一行代码的地址。这么做可以允许“子函数”执行完毕后让程序返回到起“父函数”的起始地址。
R15:PC(程序计数器)。程序计数器会根据指令的大小,在指令被执行时自动增加。一条指令的大小在ARM状态下总是4个字节,在THUMB模式下总是2个字节。当一条分支指令正在被执行时,PC保持存贮着目标地址。当执行该指令时,PC存贮了当前指令的地址加8字节(ARM状态下的两条ARM指令), 或当前指令的地址加4字节(Thumb(V1)模式下的两条Thumb指令的大小)。和x86不同的是,x86下PC永远会指向下一条要被执行的指令。
我们来看看PC在调试器中的表现形式。我们让接下来的程序,在R0存储PC的地址,并且包含了两条随机的指令。让我们看看究竟会发生什么。
在GDB调试器中我们在_start设置一个断点,并且运行这个程序
以下是我们首先看到的输出的截图
我们可以看到,PC里保持着下一条将被执行的指令(mov r0, pc)的地址0x8054,现在我们来执行下一条指令,并且在此之后R0应该还保持着0x8054的地址,对吗?
。。。真的对吗?显然错了。看看R0中的地址。当我们还预想着R0可以保持之前读取的PC值(0x8054)时,它反而储存了相对之前读取的0x8054之后的两条指令的地址。从这个地址我们可以看出,当我们直接读取PC时,它按照定义,PC指向下一条指令,但是当我们调试程序时,PC却指向当前PC值的下面两条指令的地址处(0x8054+8=0x805C)。这是因为,老款的ARM处理器总是获取当前已经执行的指令的后两条指令的地址。ARM保留着这个定义的原因是为了保证和早期处理器的兼容性。
$cpsr寄存器显示出CPSR寄存器的当前值,在他的下面一行,你可以看到标志位thumb, fast, interrupt, overflow, carry, zero, 以及 negative。这些标志位显示了CPSR寄存器中的某些特定的位,根据CPSR的值,当某个位被激活时,对应的字体会变成粗体。其中,N,Z,C和V标志位分别和x86寄存器的SF,ZF,CF和OF标志位表示的含义一一对应。这些标志位用来在汇编级别条件执行指令和循环指令中支配他们执行。我们会在第六节 条件执行及分支 中覆盖条件代码的知识 。
上图显示了32位寄存器CPSR的布局,左侧(<-)是最高有效位,右侧(->)是最低有效位。每一个单独的单元格(除了GE,M和空白的单元格),其大小都是一个位。这些位表示了针对程序当前状态的不同的属性。
下面就是在GDB里显示出来的样子(当GEF被安装时)。在这个例子里,我们比较寄存器r1和r2的值,r1=4,r0=2。下图显示了当执行完cmp r1, r0运算后这些标志位是怎样显示的
Carry 标志位被置位,因为我们使用cmp r1, r0 指令来比较4和2的大小(4-2)。相比之下,如果使用cmp r0, r1 来比较一个较小的数(2)和一个较大的数(4),那么负数标志位(N)被置位。
以下是从ARM消息中心的摘录下来的
APSR包含了以下ALU状态标志位:
N– 当操作结果为负时置位
Z– 当操作结果为零时置位
C– 当操作导致进位时置位
V– 当操作导致溢出时置位
以下为产生进位的情况:
1. 如果相加的结果大于或等于2^32时
2. 如果相减的结果是正数或零
3. 在赋值操作或者逻辑指令中,进行内联桶式移位操作的结果
当加法,减法或者比较指令的结果大于或者等于2^31或是小于或者等于-2^31时会产生溢出。联系客服