DATA TYPES 数据类型
这是ARM汇编系列基础教程的第二部分,它主要包含了数据类型和寄存器方面的知识。
和高级语言类似,ARM支持对不同数据类型进行操作。可以供我们载入(load)或者存储 (store)的数据类型可以分为有符号和无符号类型的字,半字,或字节。对这些数据类型的扩展是:半字为-h,-sh,字节为-b或者-sb,字没有扩展。有符号类型和无符号类型的区别是:
有符号类型可以包含正数和负数,因此它的取值范围较小 无符号类型可以包含正数(包括0),但是不能包含负数,因此他的取值范围更大
以下列举了几个例子,来说明指令集是如何存储和载入这些数据类型的
ENDIANNESS 字节顺序观察内存中的字节数据的两种基本的方式是:小字节顺序(LE)和大字节顺序(BE)。两者不同之处在于,存储区内存中的目标的每个字节以怎样的字节顺序来阅读。小字节序(
LE
)机制和英特尔X86指令集形式类似,最低有效字节存贮于地址的最低位(越靠近0的地址越低)。
ARM REGISTERS ARM寄存器寄存器的数量取决于ARM的版本。根据ARM参考手册可知,有30个32位的通用寄存器(除了
ARMv6-M和ARMv7-M的处理器)。在本基础系列课程中,我们学习的对象是在任意特权模式下都可以访问的寄存器:R0-R15。这16个寄存器可以被分成两组:通用寄存器和特殊功能寄存器。
(
从r0到r11都是通用寄存器
)
(特殊功能寄存器,R12是IP寄存器,内部程序调用寄存器。R13,SP,堆栈指针寄存器。R14,LR,连接寄存器。R15,PC,程序计数器。CPSR,当前程序状态寄存器)
下表是ARM寄存器和英特尔处理器的寄存器存在的相关联性的概览
R0-R12可以在通常的运算过程中用来存储临时的数据,指针(定位内存)等。以R0为例,当我们执行算数运算或者存储当前函数的返回值时,可以把R0视为累加器。系统调用发生时,R11开始生效,它存储了系统调用数值。R11作为栈指针帮助我们追踪栈的边界(稍后会讲到)。此外,ARM专用的函数调用规则规定了函数的前四个参数应该分别存贮与R0到R3中。
R13:SP(堆栈寄存器)堆栈寄存器指向栈的栈顶。栈是内存中一块用于存储特定数据的存储区,它用于回收函数的返回值。因此,栈指针别用来在栈空间中分配出区域,如果我们想获得 32位的数据空间,我们就让栈指针减去4。
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保留着这个定义的原因是为了保证和早期处理器的兼容性。
当前程序状态寄存器
当你用gdb调试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和空白的单元格),其大小都是一个位。这些位表示了针对程序当前状态的不同的属性。
我们假设,我们要使用CMP指令来比较数字1和数字2。结果会是“负的”因为1-2=-1。当我们比较两个相等的数字,比如2和2比,Z(0)标志位会被置位,因为2-2=0。记住,用于CMP指令的寄存器的指不会改变,只有CPSR会基于这些寄存器里的值的比较运算结果的改变而被编辑。
下面就是在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时会产生溢出。
|