与高等措辞类似,ARM支持对不同数据类型的操作。我们可以加载(或存储)的数据类型可以是有符号和无符号字、半字或字节。这些数据类型的扩展是。-h或-sh用于半字,-b或-sb用于字节,而字则没有扩展。有符号和无符号数据类型之间的差异是。
有符号的数据类型可以容纳正值和负值,因此范围较小。
无符号数据类型可以保存大的正值(包括 "零"),但不能保存负值,因此范围更广。
下面是一些例子,解释这些数据类型如何与指令Load和Store一起利用。
大小端
在内存中,有两种查看字节的基本方法。小端(LE)或大端(BE)。差异在于一个工具的每个字节在内存中的存储顺序。在像英特尔x86这样的小端机器上,最不主要的字节被存储在最低地址(最靠近零的地址)。在big-endian机器上,最主要的字节被存储在最低地址。ARM架构在第3版之前是小-endian,从那时起,它是双-endian,这意味着它有一个许可可切换endianness的设置。例如,在ARMv6中,指令是固定的小字节,数据访问可以是小字节或大字节,由程序状态寄存器(CPSR)的第9位(E位)掌握。
ARM寄存器
寄存器的数量取决于ARM的版本。根据ARM参考手册,除了基于ARMv6-M和ARMv7-M的处理器外,有30个通用的32位寄存器。前16个寄存器可在用户级模式下访问,其他寄存器可在特权软件实行中利用(ARMv6-M和ARMv7-M例外)。在本系列教程中,我们将处理在任何特权模式下都可以访问的寄存器:r0-15。这16个寄存器可以分成两组:通用寄存器和分外用场寄存器。
下表展示了ARM寄存器与Intel处理器中的寄存器之间的关系。
R0-R12:在普通操作中可用于存储临时值、指针(存储器的位置)等。例如,R0在进行算术运算时可作为累加器,或用于存储先前调用的函数的结果。R7在处理系统调用时变得非常有用,由于它存储了系统调用的编号,R11帮助我们跟踪堆栈上的边界,作为框架指针(将在后面先容)。此外,ARM的函数调用老例规定,函数的前四个参数存储在寄存器r0-r3中。
R13:SP(堆栈指针)。堆栈指针指向堆栈的顶部。堆栈是一个用于特定函数存储的内存区域,在函数返回时被回收。因此,堆栈指针用于分配堆栈的空间,方法是用堆栈指针减去我们要分配的值(以字节为单位)。换句话说,如果我们想分配一个32位的值,我们从堆栈指针中减去4。
R14:LR(链接寄存器)。当一个函数被调用时,链接寄存器被更新为内存地址,引用函数启动的下一条指令。这样做许可程序在 "子 "函数完成后返回到启动 "子 "函数的 "父 "函数。
R15:PC(程序计数器)。程序计数器根据所实行的指令的大小自动递增。这个大小在ARM状态下总是4字节,在THUMB模式下是2字节。当一个分支指令被实行时,PC保存目标地址。在实行过程中,PC在ARM状态下存储当前指令的地址加8(两条ARM指令),在Thumb(v1)状态下存储当前指令加4(两条Thumb指令)。这与x86不同,x86的PC总是指向要实行的下一条指令。
让我们看看PC在调试器中是如何表现的。我们用下面的程序将PC的地址存入r0,并包括两条随机指令。让我们看看会发生什么。
在gdb中我们在_start处设定一个断点
如下是运行的结果:
我们可以看到,PC持有将被实行的下一条指令(mov r0, pc)的地址(0x8054)。现在让我们实行下一条指令,之后R0该当持有PC的地址(0x8054),对吗?
...对吗?错了。看看R0中的地址。当我们期望R0包含先前读取的PC值(0x8054)时,它却包含了比我们先前读取的PC值(0x805c)提前两条指令的值。从这个例子中你可以看到,当我们直接读取PC时,它遵照PC指向下一条指令的定义;但在调试时,PC指向当前PC值前面的两条指令(0x8054 + 8 = 0x805C)。这是由于较早的ARM处理器总是在当前实行的指令之前获取两条指令。ARM保留这一定义的缘故原由是为了确保与早期处理器的兼容性。
当出路序状态寄存器当你用gdb调试一个ARM二进制文件时,你会看到一个叫做Flags的东西。
寄存器$cpsr显示了当出路序状态寄存器(CPSR)的值,在它下面可以看到Flagsthumb, fast, interrupt, overflow, carry, zero, and negative。这些标志代表了CPSR寄存器中的某些位,并根据CPSR的值来设置,激活时变成粗体。N、Z、C和V位与x86上EFLAG寄存器中的SF、ZF、CF和OF位相同。这些位被用来支持汇编级的条件和循环的条件实行。我们将在第6部分 "条件实行和分支 "中先容利用的条件代码。
上图显示了一个32位寄存器(CPSR)的布局,左边(<-)是最主要的位,右边(->)是最小的位。每一个单元(除了GE和M部分以及空缺部分)都是一个比特的大小。这些一比特的部分定义了程序当前状态的各种属性。
让我们假设我们利用CMP指令来比较数字1和2。结果是 "负",由于1-2=-1。当我们比较两个相等的数字时,比如2对2,Z(零)标志被设置,由于2-2=0。请记住,CMP指令利用的寄存器不会被修正,只有CPSR会根据这些寄存器相互比较的结果被修正。
这是GDB中的情形(安装了GEF)。在这个例子中,我们比较寄存器r1和r0,个中r1=4,r0=2。这是实行了cmp r1, r0操作后的标志的情形。
进位标志被设置,由于我们用cmp r1, r0来比较4和2(4-2)。相反,如果我们利用cmp r0, r1来比较一个较小的数字(2)和一个较大的数字(4),则负标志(N)被设置。
下面是ARM信息中央的一段摘录:
APSR包含以下ALU状态标志。
N - 当操作的结果为负数时设置。
Z - 当操作的结果为零时设置。
C - 当操作的结果是Carry时设置。
V--当操作引起溢出时设置。
carry在以下情形被设置:
如果加法的结果大于或即是2^32
如果减法的结果是正数或零
作为移动或逻辑指令中的内联移位操作的结果。
如果加法、减法或比较的结果大于或即是2^31,或小于2^31,则发生溢出。
本文是ARM汇编系列教程的第2部分, 先容arm的基本数据类型, 和基本寄存器干系知识, 后续内容会尽快更新, 敬请持续关注!