与高级语言类似,ARM支持对不同数据类型的操作。我们可以加载或存储的数据类型可以是有符号和无符号字、半字或字节。这些数据类型的扩展名是:-h或-sh表示半字,-b或-sb表示字节,不表示字的扩展名。有符号数据类型或无符号数据类型之间的区别在于:
以下是这些数据类型如何与Load和Store指令的使用示例:
ldr = Load Word
ldrh = Load unsigned Half Word
ldrsh = Load signed Half Word
ldrb = Load unsigned Byte
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这样的小端机器上,最低有效字节存储在最低地址(最接近零的地址)。在big-endian机器上,最高有效字节存储在最低地址。ARM体系结构在版本3之前是小端序,从那时起就是双端序,这意味着它具有允许切换端序的设置。例如,在ARMv6上,指令是固定的小端序,数据访问可以是小端序或大端序,由程序状态寄存器(CPSR)的位9(E位)控制
寄存器的数量取决于ARM版本。根据ARM参考手册,除基于ARMv6-M和ARMv7-M的处理器外,共有30个通用32位寄存器。前16个寄存器可在用户级模式下访问,其他寄存器可在特权软件执行中使用(ARMv6-M和ARMv7-M除外)。在本系列教程中,我们将使用在任何特权模式下都可以访问的寄存器:r0-15。这16个寄存器可以分为两组:通用寄存器和专用寄存器。
下表只是ARM寄存器如何与英特尔处理器中的寄存器相关的一个对比。
R0-R12:可在常见操作期间用于存储临时值、指针(指向内存的位置)等。例如,R0可在算术操作期间被称为累加器,或用于存储先前调用的函数的结果。R7在处理系统调用时变得很有用,因为它存储系统调用编号,R11帮助我们跟踪用作帧指针的堆栈上的边界(稍后将介绍)。此外,ARM上的函数调用约定指定函数的前四个自变量存储在寄存器r0-r3中。
R13:SP(堆栈指针)。堆栈指针指向堆栈的顶部。堆栈是用于函数特定存储的内存区域,当函数返回时会回收。因此,堆栈指针用于通过从堆栈指针中减去我们想要分配的值(以字节为单位)来分配堆栈上的空间。换句话说,如果我们想分配一个32位的值,我们从堆栈指针中减去4。
R14:LR(链路寄存器)。当进行函数调用时,链接寄存器会更新为一个内存地址,该地址引用函数启动的下一条指令。这样做允许程序在“子”函数完成后返回到启动“子”功能调用的“父”函数。
R15:PC(程序计数器)。程序计数器会根据执行的指令的大小自动递增。%1字节此大小在ARM状态下始终为4字节,在THUMB模式下始终为2字节。当正在执行分支指令时,PC保持目的地地址。在执行过程中,PC将当前指令的地址加8(两条ARM指令)存储在ARM状态,将当前指令加4(两条Thumb指令)的地址存储在Thumb(v1)状态。这与x86不同,在x86中,PC总是指向要执行的下一条指令。
让我们看看PC在调试器中的行为。我们使用以下程序将pc的地址存储到r0中,并包含两条随机指令。让我们看看会发生什么。
.section .text
.global _start
_start:
mov r0, pc
mov r1, #2
add r2, r1, r1
bkpt
在GDB中,我们在_start处设置了一个断点并运行它:
gef> br _start
Breakpoint 1 at 0x8054
gef> run
下面是我们首先看到的输出的屏幕截图:
我们可以看到,PC保存着将要执行的下一条指令(mov r0,PC)的地址(0x8054)。现在让我们执行下一条指令,在这之后R0应该保存PC的地址(0x8054),对吗?
错误的,看看R0中的地址。虽然我们期望R0包含先前读取的PC值(0x8054),但它保留的值比我们先前读取的电脑(0x805c)提前两条指令。从这个例子中,你可以看到,当我们直接阅读PC时,它遵循PC指向下一条指令的定义但在调试时,PC在当前PC值(0x8054+8=0x805C)之前指向两条指令。这是因为较旧的ARM处理器总是在当前执行的指令之前获取两条指令。ARM保留此定义的原因是为了确保与早期处理器的兼容性。
当您使用gdb调试ARM二进制文件时,您会看到一个名为Flags的东西:
寄存器$cpsr显示当前程序状态寄存器(cpsr)的值,在该寄存器下,您可以看到Flags thumb、fast、interrupt、overflow、carry、zero和negative。这些标志表示CPSR寄存器中的某些位,并根据CPSR的值进行设置,并在激活时变为粗体。N、Z、C和V位与x86上EFLAG寄存器中的SF、ZF、CF和OF位相同。这些位用于支持汇编级条件和循环中的条件执行。我们将介绍第6部分:条件执行和分支中使用的条件代码。
原文链接:ARM Data Types and Registers (Part 2) | Azeria Labs (azeria-labs.com)