ARM语法 Part 2[数据类型]

  目录

与高级编程语言类似,ARM汇编也支持操作不同的数据类型。

我们载入(load)或存储(store)的数据类型可以是有符号或无符号的半字字节。这些数据类型的扩展符是:-h或-sh代表半字,-b和-sb代表字节,其中没有扩展符号。有符号和无符号的区别:

  • 有符号数据类型可以存储正数和负数,因此表示的值范围更小。
  • 无符号数据类型可以存储大的正数(包含0),不能存储符数因此可以表示更大的数。

载入和存储指令使用数据类型:

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

字节序列

查看内存中的字节有两种基本方式:小端模式(Little-Endian)和大端模式(Big-Endian)。它们的不同之处是对象存储在内存中时每个字节的排列顺序-字节顺序。在x86这种小端模式的机器上低位字节存储在低地址(更靠近零地址),而在大端模式的机器上高位字节存储在低地址。在第三版本之前ARM架构是小端模式,之后是两种模式都允许,可以进行设置来切换字节序列。例如,在 ARMv6 上,指令是固定的小端,数据访问可以是小端或大端,由程序状态寄存器 (CPSR) 的位 9(E 位)控制。

ARM寄存器

寄存器的数量取决于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(栈指针)。始终指向当前栈顶。

R14:LR(链接寄存器)。进行函数调用时,链接寄存器将更新为当前函数调用指令的下一个指令的地址,也就是函数调用返回后需要继续执行的指令。这么做是允许子函数调用完成后,在子函数中利用该寄存器保存的指令地址再返回到父函数中。

R15:PC(程序计数器)。程序计数器自动按执行的指令大小递增。此指令大小在ARM模式下始终为4个字节,在THUMB模式下为2个字节。执行分支指令时,PC保存目标地址。在执行过程中,在ARM模式下PC将当前指令的地址加上8(两个ARM指令),在Thumb(v1)状态下则指令加上4(两个Thumb指令)。这与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

输出:

$r0 0x00000000   $r1 0x00000000   $r2 0x00000000   $r3 0x00000000 
$r4 0x00000000   $r5 0x00000000   $r6 0x00000000   $r7 0x00000000 
$r8 0x00000000   $r9 0x00000000   $r10 0x00000000  $r11 0x00000000 
$r12 0x00000000  $sp 0xbefff7e0   $lr 0x00000000   $pc 0x00008054 
$cpsr 0x00000010 
​
0x8054 <_start> mov r0, pc     <- $pc
0x8058 <_start+4> mov r0, #2
0x805c <_start+8> add r1, r0, r0
0x8060 <_start+12> bkpt 0x0000
0x8064 andeq r1, r0, r1, asr #10
0x8068 cmnvs r5, r0, lsl #2
0x806c tsteq r0, r2, ror #18
0x8070 andeq r0, r0, r11
0x8074 tsteq r8, r6, lsl #6

我们可以看到PC持有将要执行的下一个指令(mov r0, pc) 的地址(0x8054)。现在,让我们执行这条指令,之后R0应该持有PC(0x8054) 的地址,对吗?

$r0 0x0000805c   $r1 0x00000000   $r2 0x00000000   $r3 0x00000000 
$r4 0x00000000   $r5 0x00000000   $r6 0x00000000   $r7 0x00000000 
$r8 0x00000000   $r9 0x00000000   $r10 0x00000000  $r11 0x00000000 
$r12 0x00000000  $sp 0xbefff7e0   $lr 0x00000000   $pc 0x00008058 
$cpsr 0x00000010
​
0x8058 <_start+4> mov r0, #2       <- $pc
0x805c <_start+8> add r1, r0, r0
0x8060 <_start+12> bkpt 0x0000
0x8064 andeq r1, r0, r1, asr #10
0x8068 cmnvs r5, r0, lsl #2
0x806c tsteq r0, r2, ror #18
0x8070 andeq r0, r0, r11
0x8074 tsteq r8, r6, lsl #6
0x8078 adfcssp f0, f0, #4.0

对吗?错!看一下R0中的地址。虽然我们期望R0包含以前读取的PC值(0x8054),但它保留的值比我们之前读取的PC 早两个指令(0x805c)。从这个示例中可以看到,当我们直接读取PC时,它遵循PC指向下一个指令的定义;但在调试时,PC会指向当前PC值之后的两个指令(0x8054 + 8 = 0x805C)。这是因为较旧的ARM处理器始终取当前执行的指令之后的两个指令。ARM保留此定义的原因是为了确保与早期处理器兼容。

状态寄存器

当你用gdb调试ARM程序时,你会看到一些状态标志:

寄存器$cpsr显示当前程序状态寄存器的值,在它下面你可以看到工作状态标志,用户模式,中断标志,溢出标志,进位标志,零标志位,符号标志。这些标志代表了CPSR寄存器中特定的位,并根据CPSR的值进行设置,如果标志位有效则会进行加粗。N、Z、C 和 V 位与x86上的EFLAG寄存器中的SF、ZF、CF和OF位相同。这些位用于支持条件分支中的条件执行,并在汇编层面支持循环语句。我们将在第6部分:条件执行和分支中进行介绍。

上图显示了32位寄存器(CPSR)的结构,左侧是高字节位,右侧是低字节位。每个单元(GE和M部分以及空白单元除外)的大小均为一个bit位。这些位定义了程序当前状态的各种属性。

假设我们可以使用CMP指令比较1和2,返回结果应该为负数(1 - 2 = -1)。当比较两个相等的数则会设置Z(zero)标志位(例如比较2和2, 2 - 2 = 0)。记住,CMP指令中使用的寄存器不会被修改,只有CPSR会根据这些寄存器相互比较的结果进行修改。

这是GDB(安装了GEF)中的模样:在此示例中,我们比较寄存器r1和r0,其中r1 = 4和r0 = 2。这是执行 cmp r1,r0 操作后标志的外观:

之所以设置Carry标志,是因为我们使用 cmp r1, r0 将 4 与 2(4 - 2)进行比较。相反,如果我们使用 cmp r0 r1、r1 将较小的数字(2)与较大的数字(4)进行比较,则设置负标志(N)。

CPSR 包含以下状态标志:

  • N – 当计算结果为负时被设置.
  • Z – 当计算结果为零时被设置.
  • C – 当计算结果有进位时被设置.
  • V – 当计算结果有溢出时被设置.

C:其设置分一下几种情况:

  • 加法运算(包括比较指令cmn):当运算结果产生了进位时(无符号数溢出),C=1,否则C=0.
  • 减法运算(包括比较指令cmp):当运算时发生了借位(无符号数下益出),C=0,否则C=1.
  • 对于包含移位操作的非加/减运算指令:C为移位操作中最后移出位的值.
  • 对于其他非加减运算指令:C的值通常保持不变.

V:如果加、减或比较的结果大于或等于2^31 或小于-2^31,则会发生溢出。


文章作者: 改变世界
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 改变世界 !
评论