2007年12月4日星期二

ARM中文指令--寄存器和处理器模式

ARM 处理器有二十七个寄存器,其中一些是在一定条件下使用的,所以一次只能使用十六个... 寄存器 0 到寄存器 7 是通用寄存器并可以用做任何目的。不象 80x86 处理器那样要求特定寄存器被用做栈访问,或者象 6502 那样把数学计算的结果放置到一个累加器中,ARM 处理器在寄存器使用上是高度灵活的。 寄存器 8 到 12 是通用寄存器,但是在切换到 FIQ 模式的时候,使用它们的影子(shadow)寄存器。 寄存器 13 典型的用做 OS 栈指针,但可被用做一个通用寄存器。这是一个操作系统问题,不是一个处理器问题,所以如果你不使用栈,只要你以后恢复它,你可以在你的代码中自由的占用 (corrupt)它。每个处理器模式都有这个寄存器的影子寄存器。 寄存器 14 专职持有返回点的地址以便于写子例程。当你执行带连接的分支的时候,把返回地址存储到 R14 中。同样在程序第一次运行的时候,把退出地址保存在 R14 中。R14 的所有实例必须被保存到其他寄存器中(不是实际上有效)或一个栈中。这个寄存器在各个处理器模式下都有影子寄存器。一旦已经保存了连接地址,这个寄存器就可以用做通用寄存器了。 寄存器 15 是程序计数器。它除了持有指示程序当前使用的地址的二十六位数之外,还持有处理器的状态。 为更清晰一些... 提供下列图表:

User 模式 SVC 模式 IRQ 模式 FIQ 模式 APCS R0 ------- R0 ------- R0 ------- R0 a1 R1 ------- R1 ------- R1 ------- R1 a2 R2 ------- R2 ------- R2 ------- R2 a3 R3 ------- R3 ------- R3 ------- R3 a4 R4 ------- R4 ------- R4 ------- R4 v1 R5 ------- R5 ------- R5 ------- R5 v2 R6 ------- R6 ------- R6 ------- R6 v3 R7 ------- R7 ------- R7 ------- R7 v4 R8 ------- R8 ------- R8 R8_fiq v5 R9 ------- R9 ------- R9 R9_fiq v6 R10 ------ R10 ------ R10 R10_fiq sl R11 ------ R11 ------ R11 R11_fiq fp R12 ------ R12 ------ R12 R12_fiq ip R13 R13_svc R13_irq R13_fiq sp R14 R14_svc R14_irq R14_fiq lr ------------- R15 / PC ------------- pc 最右侧的列是 APCS 代码使用的名字,关于 APCS 的详情参见这里。  程序计数器构造如下: 位 31 30 29 28 27 26 25------------2 1 0 N Z C V I F 程 序 计 数 器 S1 S0 对 R15 的详细解释,请参见 psr.html。下面是你想知道的"模式",比如上面提及的"FIQ"模式。 用户模式,运行应用程序的普通模式。限制你的内存访问并且你不能直接读取硬件设备。超级用户模式(SVC 模式),主要用于 SWI(软件中断)和 OS(操作系统)。这个模式有额外的特权,允许你进一步控制计算机。例如,你必须进入超级用户模式来读取一个插件(podule)。这不能在用户模式下完成。 中断模式(IRQ 模式),用来处理发起中断的外设。这个模式也是有特权的。导致 IRQ 的设备有键盘、 VSync (在发生屏幕刷新的时候)、IOC 定时器、串行口、硬盘、软盘、等等...
快速中断模式(FIQ 模式),用来处理发起快速中断的外设。这个模式是有特权的。导致 FIQ 的设备有处理数据的软盘,串行端口(比如在 82C71x 机器上的 A5000) 和 Econet。 IRQ 和 FIQ 之间的区别是对于 FIQ 你必须尽快处理你事情并离开这个模式。IRQ 可以被 FIQ 所中断但 IRQ 不能中断 FIQ。为了使 FIQ 更快,所以有更多的影子寄存器。FIQ 不能调用 SWI。FIQ 还必须禁用中断。如果一个 FIQ 例程必须重新启用中断,则它太慢了并应该是 IRQ 而不是 FIQ。 Phew! 关于如果变更处理器的模式的详情请参照 psr.html。


这里的许多信息取自 ARM 汇编器手册。我现在没有 32 位处理器,就只能信任文档了... 这个文档中表述的 UMUL 和 UMLA 只能在 32bit 模式下进行是错误的。如果你的处理器(比如: StrongARM)可以这么做,则它可以在 32bit 或 26bit 下工作... ARM2 和 ARM3 有一个 32 位数据总线和一个 26 位地址总线。在以后版本的 ARM 上,数据总线和地址总二者都是完全的 32 位宽。这解释了为什么一个“32 位处理器”被称为 26 位。数据宽度和指令/字大小是 32 位,并总是这样,但地址总线只是 24 位。因为 PC 总是字对齐的,一个地址中的低两位总是零,所以在 ARM2/ARM3 处理器上这些位持有处理器模式设置。尽管实际上只使用了 24 位,PC 的有效宽度仍是 26 位。 在老机器上这没有问题。4Mb 内存是基准的。一些人升级到 8Mb、和 16Mb 是理论上的限制。(Some people upgraded to 8Mb, and 16Mb was the theoretical limit.) 但是 RiscPC 使用一个 26 位程序计数器是不可能的,因为 RiscPC 允许安装 258Mb 内存,而 26 位只允许你寻址到 %11111111111111111111111100 (或 67108860 字节,或 64Mb)。这附带的解释了对应用任务的 28Mb 大小限制;就是希望系统与老的 RISC OS API 相容。 尽管这个汇编器站点的某些部分覆盖了 32 位模式(比如运行在 SVC32 下的一个简要的例子!),但多数部分是关于 26 位模式操作的,这是为了与 RISC OS 的当前可获得的版本相兼容(就是 RISC OS 2 到 RISC OS 4);我注意到部分例子不适用于 32 位。RiscPC、Mico、RiscStation、A7000 等都有能力运行完全的 32 位操作系统;实际上 ARMLinux 就是这样的一个操作系统。RISC OS 不是,因为 RISC OS 需要,至少一个时期,保持与现存版本的兼容。这是个古老的两分问题(dichotomy),有一个崭新的完全 32 位版本的 RISC OS 版本是美妙的,但当你发现许多你的现存软件不能继续运行(so much as load)就不那么美妙了! RISC OS 不是完全的 26 位。一些处理程序(handler)需要工作在 32 位模式下;限制它的是金钱(就是说,谁为完全转换 RISC OS 付钱;谁为用来重建它们的代码的开发工具付钱(PD 在 RISC OS 上是强壮的))和必要性(就是说,很多人使用 Impression 而 CC 不再与我们同在;Impression 好象不能在更新的 RISC OS 上工作,所以如果人们需要的软件将不能工作,那么他们不会认为有升级的必要.为什么这如此重要? 新的 ARM 处理器将不支持 26 位操作。尽管做了一些融合(ARM6、ARM7、StrongARM),但气数就要尽了。你可以增加一个 26/32 位系统的复杂性,或者只用 32 位而得到更简单、更小的处理器。我们要么随波逐流,要么被甩下... 所以我们别无选择。32 位体系ARM 体系在 ARM6 系列中进行了重大变更。下面我将描述 26 位 和 32 位操作行为的不同之处。 在 ARM 6 中,程序计数器被扩展到完整的 32 位。结果是: PSR 从 PC 中分离到自己的寄存器 CPSR(当前的程序状态寄存器)中。 在改变处理器模式的时候,不再与 PC 一起保存 PSR;现在是每个有特权的模式都有一个额外的寄存器 - SPSR (保存的程序状态寄存器) - 用来持有前面模式的 PSR。增加了使用这些新寄存器的指令。 除了允许 PC 使用完全的 32 位之外,还有进一步的变更,就是给 PSR 增加了额外的有特权的模式。这些模式用于处理未定义指令和异常终止例外: 未定义指令、异常终止、和超级用户不再共享同一个模式。去掉了在早期 ARM 上存在的对超级用户的那些限制。 在 ARM6 系列(和以后的其他兼容芯片)中通过设置片上某个控制寄存器来确定这些特征的可获得性。可以选择三个处理器配置中的一个: 26 位程序和数据空间,这个配置强制 ARM 在 26 位地址空间中进行操作。在这个配置中只能获得四个 26 位模式(参照处理器模式描述);不可能选择任何 32 位模式。在所有当前的 ARM6 和 7 系列上复位(reset)时被设置为这个模式。 26 位程序空间和 32 位数据空间。除了禁止地址例外来允许数据传送操作访问完整的 32 位地址空间之外,与 26 位程序和地址空间配置相同。 32 位程序和数据空间。这个配置把地址空间扩展成 32 位,并介入了对处理器模型的重大变更。在这个配置中你可以选择任何 26 位和 32 位处理器模式(参见下面的处理器模式)。在配置成 32 位程序和数据空间的时候,ARM6 和 ARM7 系列支持十个有所重叠的处理器操作模式: 用户模式: 正常的程序执行状态;或 User26 模式: 一个 26 位版本。 FIQ 模式: 设计来支持一个数据传送或通道处理;或 FIQ26 模式: 一个 26 位版本。 IRQ 模式: 用于通用中断处理;或 IRQ26 模式: 一个 26 位版本。 SVC 模式: 用于操作系统的保护模式或 SVC26模式: 一个 26 位模式。 异常终止模式(ABT 模式): 在一个数据或指令预取异常终止(abort)的时候进入的模式。 未定义模式(UND 模式): 在执行了一个未定义的指令的时候进入的模式。
当在一个 26 位处理器模式中的时候,编程模型倒退成早期的 26 位 ARM 处理器。除了下列变动之外,它的行为与 ARM2aS 宏单元(macrocell)相同: 只在 ARM 被配置为 26 位程序和数据空间的时候,它才生成地址例外。在其他配置下 OS 仍然可以通过使用外部逻辑模拟地址例外的行为,比如用一个内存管理单元在超出 64Mbyte 范围的时候生成一个异常终止,并把这个异常终止转换成给这个应用程序的一个‘地址例外陷入’。保持在通用寄存器和程序状态寄存器之间传送数据的新指令可操作。在调用了包含 26 位的 ARM 二进制代码的之后,操作系统可以使用这些新指令返回到一个 32 位模式。 当在一个 32 位程序和数据空间配置下的时候,所有例外(包括未定义指令和软件中断)把处理器返回到一个 32 位模式,所以必须修改操作系统来处理它们。如果处理器尝试写到在 &0 和 &1F 之间包括二者(就是例外向量)的一个位置,则硬件将禁止写操作并生成一个数据异常终止。这允许操作系统来截获对例外向量的变动并把向量重定向到一些伪装 (veneer)代码上。在调用 26 位例外处理程序之前,这些伪装代码应该把处理器置于一个 26 位模式中。
在所有其他方面,当在一个 26 位模式下进行操作的时候,ARM 表现的如同一个 26 位 ARM。CPSR 的相关的位将被组建(incorporated)回到 R15 中,来形成 I 和 F 位在位 27 和 26 的 PC/PSR。指令集表现的如同增加了 MRS 和 MSR 指令的 ARM2aS 宏单元。
在 ARM 6(和以后)的 32 位模式下可获得的寄存器有: User26 SVC26 IRQ26 FIQ26 User SVC IRQ ABT UND FIQ

R0 ----- R0 ----- R0 ----- R0 -- -- R0 ----- R0 ----- R0 ----- R0 ----- R0 ----- R1
R1 ----- R1 ----- R1 ----- R1 -- -- R1 ----- R1 ----- R1 ----- R1 ----- R1 ----- R2
R2 ----- R2 ----- R2 ----- R2 -- -- R2 ----- R2 ----- R2 ----- R2 ----- R2 ----- R2 R3 ----- R3 ----- R3 ----- R3 -- -- R3 ----- R3 ----- R3 ----- R3 ----- R3 ----- R3
R4 ----- R4 ----- R4 ----- R4 -- -- R4 ----- R4 ----- R4 ----- R4 ----- R4 ----- R4 R5 ----- R5 ----- R5 ----- R5 -- -- R5 ----- R5 ----- R5 ----- R5 ----- R5 ----- R5 R6 ----- R6 ----- R6 ----- R6 -- -- R6 ----- R6 ----- R6 ----- R6 ----- R6 ----- R6 R7 ----- R7 ----- R7 ----- R7 -- -- R7 ----- R7 ----- R7 ----- R7 ----- R7 ----- R7 R8 ----- R8 ----- R8 R8_fiq R8 ----- R8 ----- R8 ----- R8 ----- R8 R8_fiq R9 ----- R9 ----- R9 R9_fiq R9 ----- R9 ----- R9 ----- R9 ----- R9 R9_fiq R10 ---- R10 ---- R10 R10_fiq R10 ---- R10 ---- R10 ---- R10 ---- R10 R10_fiq R11 ---- R11 ---- R11 R11_fiq R11 ---- R11 ---- R11 ---- R11 ---- R11 R11_fiq R12 ---- R12 ---- R12 R12_fiq R12 ---- R12 ---- R12 ---- R12 ---- R12 R12_fiqR13 R13_svc R13_irq R13_fiq R13 R13_svc R13_irq R13_abt R13_und R13_fiq R14 R14_svc R14_irq R14_fiq R14 R14_svc R14_irq R14_abt R14_und R14_fiq --------- R15 (PC / PSR) --------- --------------------- R15 (PC) --------------------- ----------------------- CPSR ----------------------- SPSR_svc SPSR_irq SPSR_abt SPSR_und SPSR_fiq 简要的说,32 位的与 26 位的不同是: PC 是完全的 32 位宽,并只用做程序计数器。 PSR 包含在它自己的寄存器 CPSR 中。 每个有特权的模式都有一个专有的 SPSR 寄存器,用来保存 CPSR。 这里有两个新的特权模式,每个有特权的模式都有 R13 和 R14 的专有复件。 CPSR 和 SPSR 寄存器 CPSR 寄存器(和保存它的 SPSR 寄存器)中的位分配如下: 31 30 29 28 --- 7 6 - 4 3 2 1 0 N Z C V I F M4 M3 M2 M1 M0 0 0 0 0 0 User26 模式 0 0 0 0 1 FIQ26 模式 0 0 0 1 0 IRQ26 模式 0 0 0 1 1 SVC26 模式 1 0 0 0 0 User 模式 1 0 0 0 1 FIQ 模式 1 0 0 1 0 IRQ 模式 1 0 0 1 1 SVC 模式 1 0 1 1 1 ABT 模式 1 1 0 1 1 UND 模式 关于 N、Z、C、V 标志和 I、F 中断标志请参见(26 位) PSR。 这在实践中意味着什么? 多数 ARM 代码将正确的工作。唯一不能工作的是通过摆弄 R15 来设置处理器状态的那些操作。不幸的是,好象没有简便的方法修理这个问题。我检查了一个有潜在问题的 9K 程序(一个 MODE 7 teletext frame viewer,用 C 写的),基本上查找: 用 R15 作为目的寄存器的 MOVS 指令。以‘^’作为后缀并装载 R15 的 LDMFD 指令。 大约有 64 个指令被归入此类。 好象有没有什么方式来自动进行转换。基本上... 系统如何知道哪个是数据哪个是代码。实际上,一个灵巧的基于规则的程序能够可以做非常准确的猜测,但“非常准确的猜测”就足够了吗? 没有简单的指令替代。一个自动系统可以修补需要的指令并调整(jiggle)周围的代码,但这将导致不希望的副作用,比如一个 ADR 宏指令(directive)不在范围内(in range)。 需要难以置信的技巧(It is incredibly hacky)。当然,最好重新编译,或修改源代码。 这是很不容易的。这样小的变更,竟有如此严重(far-reaching)的后果。
寄存器装载和存储

LDM
LDR
STM
STR
SWP
它们可能是能获得的最有用的指令。其他指令都操纵寄存器,所以必须把数据从内存装载寄存器并把寄存器中的数据存储到内存中。

 


传送单一数据
使用单一数据传送指令(STR 和 LDR)来装载和存储单一字节或字的数据从/到内存。寻址是非常灵活的。

首先让我们查看指令格式:

LDR{条件} Rd, <地址>
STR{条件} Rd, <地址>
LDR{条件}B Rd, <地址>
STR{条件}B Rd, <地址>
指令格式
这些指令装载和存储 Rd 的值从/到指定的地址。如果象后面两个指令那样还指定了‘B’,则只装载或存储一个单一的字节;对于装载,寄存器中高端的三个字节被置零 (zeroed)。

地址可以是一个简单的值、或一个偏移量、或者是一个被移位的偏移量。可以还可以把合成的有效地址写回到基址寄存器(去除了对加/减操作的需要)。

各种寻址方式的示例: 

译注:下文中的 Rbase 是表示基址寄存器,Rindex 表示变址寄存器,index 表示偏移量,偏移量为 12 位的无符号数。用移位选项表示比例因子。标准寻址方式 - 用 AT&T 语法表示为 disp(base, index, scale),用 Intel 语法表示为 [base + index*scale + disp],中的变址(连带比例因子)与偏移量不可兼得。


STR Rd, [Rbase] 存储 Rd 到 Rbase 所包含的有效地址。

STR Rd, [Rbase, Rindex] 存储 Rd 到 Rbase + Rindex 所合成的有效地址。

STR Rd, [Rbase, #index] 存储 Rd 到 Rbase + index 所合成的有效地址。
index 是一个立即值。
例如,STR Rd, [R1, #16] 将把 Rd 存储到 R1+16。

STR Rd, [Rbase, Rindex]! 存储 Rd 到 Rbase + Rindex 所合成的有效地址,
并且把这个新地址写回到 Rbase。

STR Rd, [Rbase, #index]! 存储 Rd 到 Rbase + index 所合成的有效地址,
并且并且把这个新地址写回到 Rbase。

STR Rd, [Rbase], Rindex 存储 Rd 到 Rbase 所包含的有效地址。
把 Rbase + Rindex 所合成的有效地址写回 Rbase。

STR Rd, [Rbase, Rindex, LSL #2]
存储 Rd 到 Rbase + (Rindex * 4) 所合成的有效地址。

STR Rd, place 存储 Rd 到 PC + place 所合成的有效地址。
你当然可以在这些指令上使用条件执行。但要注意条件标志要先于字节标志,所以如果你希望在结果是等于的时候装载一个字节,要用的指令是 LDREQB Rx, <address> (不是 LDRBEQ...)。
如果你指定预先变址寻址(这里的基址和变址都在方括号中),用是否存在‘!’来控制写回操作。上面的第4和第5个例子中使用了这个标志。你可以使用它来在内存中自动正向或反向移动。一个字符串打印例程将变成:

.loop
LDRB R0, [R1, #1]!
SWI "OS_WriteC"
CMP R0, #0
BNE loop
而不是:
.loop
LDRB R0, [R1]
SWI "OS_WriteC"
ADD R1, R1, #1
CMP R0, #0
BNE loop
对于过后变址寻址‘!’是无效的(这里的变址在方括号外面,比如上面的例子6),因为写回是暗含的。

如同你见到的那样,变址可以被移位来实现比例缩放。除此之外,可以从基址上减去偏移量。在这种情况下,你可以使用如下代码:

LDRB R0, [R1, #-1]
尽管你可以存储或装载 PC,但你不可以用装载或存储指令来修改 PSR。要装载一个被存储的‘状态’并正确的恢复它,请使用:

LDR R0, [Rbase]
MOVS R15, R0
假如你在有特权的模式下,MOVS 将导致 PSR 的位被更改。
对 PC 使用 MOVS 不遵从 32-bit 体系,你需要使用 MRS 和 MSR 来处理 PSR。
依照 ARM 汇编手册:

译注:下文所叙述内容针对的是小端字节序配置,对大端字节序配置在手册中另有专门叙述。

如果提供的地址在一个字边界上,则字节装载(LDRB)使用在 0 至 7 位上的数据,如果在一个字地址加上一个字节上,则使用 8 至 15 位,以此类推。选择的字节被放入目标寄存器的低端 8 位中,并把寄存器中其余的位用零填充。
字节存储(STRB)在数据总线上重复源寄存器的的低端 8 位 4 次。由外部的内存系统来激活适当的字节子系统来存储数据。
字装载(LDR)或字存储(STR)将生成一个字对齐的地址。使用一个非字对齐的地址将有不明显和未规定的结果。实际上提示的是你不能使用 LDR 从一个非对齐的地址装载一个字。
 


传送多个数据
使用多数据传送指令(LDM 和 STM)来装载和存储多个字的数据从/到内存。
LDM/STM 的主要用途是把需要保存的寄存器复制到栈上。如我们以前见到过的 STMFD R13!, {R0-R12, R14}。

指令格式是:

xxM{条件}{类型} Rn{!}, <寄存器列表>{^}
‘xx’是 LD 表示装载,或 ST 表示存储。

再加 4 种‘类型’就变成了 8 个指令:

栈 其他
LDMED LDMIB 预先增加装载
LDMFD LDMIA 过后增加装载
LDMEA LDMDB 预先减少装载
LDMFA LDMDA 过后减少装载

STMFA STMIB 预先增加存储
STMEA STMIA 过后增加存储
STMFD STMDB 预先减少存储
STMED STMDA 过后减少存储
指令格式
汇编器关照如何映射这些助记符。注意 ED 不同于 IB;只对于预先减少装载是相同的。在存储的时候,ED 是过后减少的。

FD、ED、FA、和 EA 指定是满栈还是空栈,是升序栈还是降序栈。一个满栈的栈指针指向上次写的最后一个数据单元,而空栈的栈指针指向第一个空闲单元。一个降序栈是在内存中反向增长(就是说,从应用程序空间结束处开始反向增长)而升序栈在内存中正向增长。

其他形式简单的描述指令的行为,意思分别是过后增加(Increment After)、预先增加(Increment Before)、过后减少(Decrement After)、预先减少(Decrement Before)。

RISC OS 使用传统的满降序栈。在使用符合 APCS 规定的编译器的时候,它通常把你的栈指针设置在应用程序空间的结束处并接着使用一个 FD (满降序 - Full Descending)栈。如果你与一个高级语言(BASIC 或 C)一起工作,你将别无选择。栈指针(传统上是 R13)指向一个满降序栈。你必须继续这个格式,或则建立并管理你自己的栈(如果你是死硬派人士那么你可能喜欢这样做!)。

‘基址’是包含开始地址的寄存器。在传统的 RISC OS 下,它是栈指针 R13,但你可以使用除了 R15 之外的任何可获得的寄存器。

如果你想把复制操作后栈顶的当前的内存地址保存到栈指针中,可以寄存器按从最低到最高的编号次序与到从低端到高端的内存之间传送数据。并且因为用指令中的一个单一的位来表示是否保存一个寄存器,不可能指定某个寄存器两次。它的副作用是不能用下面这样的代码:

STMFD R13!, {R0, R1}
LDMFD R13!, {R1, R0}
来交换两个寄存器的内容。
提供了一个有用的简写。要包含一个范围的寄存器,可以简单的只写第一个和最后一个,并在其间加一个横杠。例如 R0-R3 等同与 R0, R1, R2, R3,只是更加整齐和理智而已...

在把 R15 存储到内存中的时候,还保存了 PSR 位。在重新装载 R15 的时候,除非你要求否则不恢复 PSR 位。要求的方法是在寄存器列表后跟随一个‘^’。 

STMFD R13!, {R0-R12, R14}
...
LDMFD R13!, {R0-R12, PC}
这保存所有的寄存器,做一些事情,接着重新装载所有的寄存器。从 R14 装载 PC,它由一个 BL 或此类指令所设置。不触及 PSR 标志。 
STMFD R13!, {R0-R12, R14}
...
LDMFD R13!, {R0-R12, PC}^
这保存所有的寄存器,做一些事情,接着重新装载所有的寄存器。从 R14 装载 PC,它由一个 BL 或此类指令所设置。变更 PSR 标志。
警告: 这些代码不遵从 32 bit 体系。你需要使用 MRS 和 MSR 来处理 PSR,你不能使用‘^’后缀。
注意在这两个例子中,R14 被直接装载到 PC 中。这节省了对 MOV(S) R14 到 R15 中的需要。
警告: 使用 MOVS PC,... 不遵从 32 bit 体系。你需要使用 MRS 和 MSR 来处理 PSR。

 


SWP : 单一数据交换
(Swap)

SWP{条件}{B} <dest>, <op 1>, [<op 2>]

指令格式
SWP 将:

从操作数 2 所指向的内存装载一个字并把这个字放置到目的寄存器中。
把寄存器操作数 1 的内容存储到同一个地址中。
如果目的和操作数 1 是同一个寄存器,则把寄存器的内容和给定内存位置的内容进行交换。
如果提供了 B 后缀,则将传送一个字节,否则传送一个字。

没有评论:

Sticky