无论是高级程序语言还是汇编语言,程序能够访问和操作的只有Register和Memory,本文对ARM汇编中Register的用法进行介绍
ARM中所有寄存器都是32位的(不包括aarch64),有13个GPRs(General Purpose Registers,通用寄存器),和几个特殊用途寄存器,如下图
特殊寄存器主要是堆栈寄存器SP、链接寄存器LR、程序计数器PC,还有一个程序状态寄存器CPSR未在图中体现
通用寄存器GPRs
通用寄存器和其他处理器中的寄存器用法类似,都可以用于算数和逻辑指令中
MOV
首先介绍第一条ARM汇编指令MOV
1 | MOV Rn,Op2 |
该指令将操作数”Op2”的值拷贝到寄存器Rn中。”Op2”可以是以类似#k
的形式的立即数,也可以是一个寄存器Rm
,Rm
和Rn
可以是R0到R15之间的任何一个寄存器。例如将立即数0x10拷贝到寄存器R0中
1 | MOV R0,#0x10 |
该指令执行后,R0的值为0x10。也可以在寄存器之间进行拷贝,例如
1 | MOV R0,#0x20 |
第一条指令将R0赋值为立即数0x20,第二条指令将R0的值拷贝到R1,因此R1的值也是0x20
ADD
ADD指令格式为
1 | ADD Rd,Rn,Op2 |
该指令将寄存器Rn与操作数Op2相加,结果放入Rd中,Op2可以是立即数或者寄存器。例如
1 | MOV R1,#0x18 |
SUB
SUB指令的格式为
1 | SUB Rd,Rn,Op2 |
该指令将Rn减去Op2的结果放入Rd中,例如
1 | MOV R0,#0x55 |
立即数的表示和范围
注意以上指令中,对立即数的使用,都是以”#”开头,表示操作数是一个立即数。ARM汇编支持多种进制数
十六进制:#0x25
十进制:#10
二进制:#0b0001
ASCII:#’A’
上文中介绍的指令中,MOV/ADD/SUB的立即数操作数都是比较小的数,既然ARM寄存器都是32位的,能否操作较大的数呢,例如以下代码
1 | .global _start |
使用arm-none-eabi-gcc编译该汇编代码会报以下错误
这是为什么呢?这就要介绍一下ARM汇编代码与二进制机器码的关系,ARM使用RISC指令集架构,所有指令的机器码都是32位的, 而在MOV/ADD/SUB等机器码中,32位里只有8bit是留给操作数使用的,因此立即数的范围是0~255,超过255的数据编译器认为非法
编译以下汇编代码
1 | .global _start |
编译后用hexdump查看二进制文件内容,可以看到总共用了12字节,3个word的大小,可以看到确实是每个指令对应32位
程序计数器PC
PC是程序计数器,类似x86中的ip寄存器,都是用来指向下一条将要被执行的指令的地址。当CPU读取并执行完一条指令后,PC会自动增加以指向下一条指令地址。PC寄存器位宽决定了程序能够访问的地址空间范围,32位地址能够访问的地址范围为0x00000000到0xFFFFFFFF共4GB空间。可以想象,64位CPU的PC寄存器一定是64位的
PC既然指向了下一条将要执行的指令地址,那么当CPU刚启动时,第一条指令指向哪里呢?不同的处理器有不同的启动地址,ARM一般以0x00000000为上电复位后的指令起始地址,但是具体芯片的启动地址可能由生产厂商来确定,因为厂商会在片上ROM中存储一些启动代码,而片上ROM的内存映射地址不是确定的
程序状态寄存器CPSR
程序状态寄存器CPSR是一个非常特殊的寄存器,它不可被汇编程序修改,只有CPU可以维护CPSR的数值。CPSR用于暂存程序运行过程中的各种标志
其中N、Z、C、V是条件标志,用来指示一些条件标志,某些汇编指令可以与这些标志进行关联使用,完成一些条件执行
C:进位标志,当bit31发生进位时被置1
Z:零标志,算数或者逻辑运算的结果会影响该标志,当结果为0时Z=1。该标志被广泛应用在比较或循环操作中,后续文章会介绍
N:负数标志,当有符号数的bit31为负号时,该标志为1
V:溢出标志,当有符号数运算结果导致高位数据覆盖了符号位时,该标志为1
T:用于指示ARM处于Thumb状态
I/F:指示中断使能或者关闭
CPSR另外一个特殊的原因在于很多指令可以增加一个”S”后缀,表示该指令的结果会更新到CPSR中,也就是说,如果指令不带”S”后缀,表示结果不会更新CPSR,例如
1 | ADD R0,R1,R2 ;不更新CPSR |
堆栈寄存器SP
SP用于暂存数据或寄存器的值到堆栈中,当发生子程序调用或者中断时,常常会用到,这里先不做介绍
链接寄存器LR
LR用于子程序返回时的返回地址记录,主要用于子程序调用过程,先不做介绍