数据类型和寄存器
数据类型
与高级语言类似,ARM支持对不同数据类型操作。
可以加载或存储的数据类型可以是有符号或无符号的一个字、半字、或字节。这三种数据类型的扩展名为:-h 或 -sh 表示半字,-b 或 -sb 表示字节,字没有扩展名。
将数据类型与“加载和存储”指令一起使用:
ldr = Load Word # ldr为指令
ldrh = Load unsigned Half Word #ldr指令后跟一个h
ldrsh = Load signed Half Word #ldr指令后跟一个sh
ldrb = Load unsigned Byte #ldr指令后跟一个b
ldrsb = Load signed Bytes
str = Store Word
strh = Store unsigned Half Word
strsh = Store signed Half Word
strb = Store unsigned Byte
strsb = Store signed Byte
字节序
内存中数据存储的两种方式:小端序(LE)和大端序(BE)。Intel x86这种小端序机器上,最低有效字节存储在内存的最低地址上;在大端序机器上,最高有效字节存储在最低地址。ARM架构的计算机在版本3之前是固定使用小端序的。但从ARMv6版本开始,它变得灵活了,可以根据需要在小端序和大端序之间切换。这种切换是通过程序状态寄存器(CPSR)中的一个特殊位(E位)来控制的。这意味着ARM计算机可以根据程序的需要来决定数据的存储方式,无论是小端序还是大端序。
寄存器
寄存器的数量取决于ARM版本,根据ARM参考手册,除了基于ARMv6-M 和ARMv7-M的处理器外,其他处理器有30个通用32位寄存器。前16个寄存器可在用户级模式下访问,其他寄存器可在特权软件(内核)执行中使用,接下来的介绍,只使用前16个用户级模式下可访问的寄存器:r0-15
。这 16 个寄存器可分为两组:通用寄存器和特殊用途寄存器。
寄存器 | 别名 alias | 用途 | X86对应寄存器 |
---|---|---|---|
R0 | – | 一般用途 | EAX |
R1 | – | 一般用途 | EBX, ECX, EDX, ESI, EDIEBX、ECX、EDX、ESI、EDI |
R2 | – | 一般用途 | EBX, ECX, EDX, ESI, EDIEBX、ECX、EDX、ESI、EDI |
R3 | – | 一般用途 | EBX, ECX, EDX, ESI, EDIEBX、ECX、EDX、ESI、EDI |
R4 | – | 一般用途 | EBX, ECX, EDX, ESI, EDIEBX、ECX、EDX、ESI、EDI |
R5 | – | 一般用途 | EBX, ECX, EDX, ESI, EDIEBX、ECX、EDX、ESI、EDI |
R6 | – | 一般用途 | - |
R7 | – | 保存系统呼叫号码 | - |
R8 | – | 一般用途 | - |
R9 | – | 一般用途 | - |
R10 | – | 一般用途 | - |
R11 | FP | 帧指针 | EBP |
R12 | IP | 程序内调用 | - |
R13 | SP | 堆栈指针 | ESP |
R14 | LR | 链接寄存器 | - |
R15 | PC | 程序计数器 | EIP |
R16 | - | 当前计划状态寄存器 | EFLAGS |
R0-R12: 在常用操作中用于存储临时值、指针等。例如,R0既可以在算术运算期间称为累加器,也可以用于存储先前调用的函数的结果。R7 存储了系统调用编号,因此在处理系统调用时变得很有用。而 R11作为帧指针,可以跟踪堆栈上的边界。此外,当调用一个函数时,它的前四个参数会先被放入r0、r1、r2和r3这四个寄存器中,然后函数通过读取这些寄存器来获取传入的参数值
R13: SP (堆栈指针):堆栈是一种特殊的内存区域,主要用于存储函数运行时需要的临时数据。当你调用一个函数时,计算机会在这个区域为这个函数分配一些空间来存储数据,比如函数的参数、局部变量和返回地址。当你的函数执行完毕并返回时,之前为这个函数分配的堆栈空间就会被释放,也就是“回收”。这样做是为了重新利用这些空间,为其他函数调用提供存储空间。堆栈指针
是一个特殊的寄存器,它总是指向堆栈的顶部。当需要在堆栈上分配空间时,计算机会通过修改堆栈指针来实现。当你需要在堆栈上存储数据时,比如一个32位的值,计算机会从堆栈指针的当前位置减去相应的字节数(在这个例子中是4字节)。这样做会更新堆栈指针的位置,从而为这个值分配空间。这个过程是自动的,不需要程序员手动管理。
R14:LR(链路寄存器):当你调用一个函数时,链路寄存器会被更新为一个特殊的内存地址。这个地址实际上指向了调用函数的下一条指令,也就是你调用函数后应该继续执行的地方。假设你有一个主函数,它调用了一个子函数。在子函数执行的过程中,链路寄存器保存了主函数中紧随函数调用之后的那条指令的地址。当子函数完成它的任务后,程序会通过读取链路寄存器中的地址,来知道接下来应该执行哪条指令。这样,程序就可以顺利地从子函数返回到主函数,继续之前的工作。
R15:PC(程序计数器)。程序计数器(PC)是计算机中的一个特殊寄存器,它的作用是跟踪当前正在执行的指令的位置。你可以把它想象成阅读时的手指,它总是指向你接下来要阅读的单词。在ARM架构的处理器中,程序计数器的行为会根据处理器的工作模式而有所不同。ARM架构有两种主要的工作模式:ARM状态和THUMB模式。ARM状态是处理器的标准工作模式。在这个状态下,每条指令都是4个字节大小。因此,每当处理器执行一条指令后,程序计数器会自动增加4,指向下一条指令的位置。THUMB模式是一种更紧凑的指令集,每条指令只有2个字节大小。在这种模式下,程序计数器在执行完一条指令后会增加2,指向下一条指令。当处理器执行一个分支指令,也就是告诉它跳转到程序的另一个部分时,程序计数器会直接更新为这个新位置的地址。
这里有一个特别的地方需要注意:在ARM状态中,程序计数器在执行当前指令后,会预先增加8(也就是两条ARM指令的大小),这样做是为了准备下下条指令的地址。而在THUMB模式(对于v1版本)中,程序计数器在执行当前指令后,会预先增加4(也就是两条THUMB指令的大小)。这与x86架构的处理器不同,在x86中,程序计数器始终只是简单地指向下一条要执行的指令,而不会预先增加额外的量。
当前程序状态寄存器
使用gdb调试ARM二进制文件时,会看到一个名为 Flags 的东西:
寄存器$cpsr
显示是ARM处理器中的一个特殊部件,它的全名是“当前程序状态寄存器”。它记录了处理器在任何时刻的状态和一些重要的信息。
上图是32 位寄存器 (CPSR) 的布局,其中左侧保存最高有效位,右侧保存最低有效位。每个单元格(除了 GE 和 M 部分以及空白单元格)的大小都是一位。这些一位部分定义了程序当前状态的各种属性。
Flag | 说明 |
---|---|
N | 如果指令结果产生负数,则启用 |
Z | 如果指令结果产生零值则启用 |
C | 如果指令结果产生需要完全表示第 33 位的值,则启用 |
V | 如果指令结果产生无法用 32 位二进制补码表示的值,则启用 |
E | ARM 可以以小端或大端运行。对于小端模式,该位设置为 0,对于大端模式,该位设置为 1 |
T | 如果处于 Thumb 状态,则该位被设置;当处于 ARM 状态时,该位被禁用 |
M | 这些位指定当前特权模式(USR、SVC 等) |
J | 第三种执行状态,允许某些 ARM 处理器在硬件中执行 Java 字节码 |
假设现在比较寄存器r1和r0,其中 r1 = 4 且 r0 = 2, 执行cmp r1, r0
操作后标志的样子: