(Addition with Carry)html
ADC{条件}{S} <dest>, <op 1>, <op 2> dest = op_1 + op_2 + carry
ADC
将把两个操做数加起来,并把结果放置到目的寄存器中。它使用一个进位标志位,这样就能够作比 32 位大的加法。下列例子将加两个 128 位的数。
128 位结果: 寄存器 0、一、二、和 3
第一个 128 位数: 寄存器 四、五、六、和 7
第二个 128 位数: 寄存器 八、九、十、和 11。ide
ADDS R0, R4, R8 ; 加低端的字 ADCS R1, R5, R9 ; 加下一个字,带进位 ADCS R2, R6, R10 ; 加第三个字,带进位 ADCS R3, R7, R11 ; 加高端的字,带进位
若是若是要作这样的加法,不要忘记设置 S 后缀来更改进位标志。oop
(Addition)测试
ADD{条件}{S} <dest>, <op 1>, <op 2> dest = op_1 + op_2
ADD
将把两个操做数加起来,把结果放置到目的寄存器中。操做数 1 是一个寄存器,操做数 2 能够是一个寄存器,被移位的寄存器,或一个当即值:ui
ADD R0, R1, R2 ; R0 = R1 + R2 ADD R0, R1, #256 ; R0 = R1 + 256 ADD R0, R2, R3,LSL#1 ; R0 = R2 + (R3 << 1)
加法能够在有符号和无符号数上进行。url
(logical AND)spa
AND{条件}{S} <dest>, <op 1>, <op 2> dest = op_1 AND op_2
AND
将在两个操做数上进行逻辑与,把结果放置到目的寄存器中;对屏蔽你要在上面工做的位颇有用。 操做数 1 是一个寄存器,操做数 2 能够是一个寄存器,被移位的寄存器,或一个当即值:操作系统
AND R0, R0, #3 ; R0 = 保持 R0 的位 0 和 1,丢弃其他的位。
AND 的真值表(两者都是 1 则结果为 1):3d
Op_1 Op_2 结果 0 0 0 0 1 0 1 0 0 1 1 1
(Bit Clear)code
BIC{条件}{S} <dest>, <op 1>, <op 2> dest = op_1 AND (!op_2)
BIC
是在一个字中清除位的一种方法,与 OR 位设置是相反的操做。操做数 2 是一个 32 位位掩码(mask)。若是若是在掩码中设置了某一位,则清除这一位。未设置的掩码位指示此位保持不变。
BIC R0, R0, #%1011 ; 清除 R0 中的位 0、一、和 3。保持其他的不变。
BIC 真值表 :
Op_1 Op_2 结果 0 0 0 0 1 0 1 0 1 1 1 0
译注:逻辑表达式为 Op_1 AND NOT Op_2
(logical Exclusive OR)
EOR{条件}{S} <dest>, <op 1>, <op 2> dest = op_1 EOR op_2
EOR
将在两个操做数上进行逻辑异或,把结果放置到目的寄存器中;对反转特定的位有用。操做数 1 是一个寄存器,操做数 2 能够是一个寄存器,被移位的寄存器,或一个当即值:
EOR R0, R0, #3 ; 反转 R0 中的位 0 和 1
EOR 真值表(两者不一样则结果为 1):
Op_1 Op_2 结果 0 0 0 0 1 1 1 0 1 1 1 0
(Move)
MOV{条件}{S} <dest>, <op 1> dest = op_1
MOV
从另外一个寄存器、被移位的寄存器、或一个当即值装载一个值到目的寄存器。你能够指定相同的寄存器来实现 NOP 指令的效果,你还能够专门移位一个寄存器:
MOV R0, R0 ; R0 = R0... NOP 指令 MOV R0, R0, LSL#3 ; R0 = R0 * 8
若是 R15 是目的寄存器,将修改程序计数器或标志。这用于返回到调用代码,方法是把链接寄存器的内容传送到 R15:
MOV PC, R14 ; 退出到调用者
MOVS PC, R14 ; 退出到调用者并恢复标志位
(不听从 32-bit 体系)
(MoveNegative)
MVN{条件}{S} <dest>, <op 1> dest = !op_1
MVN
从另外一个寄存器、被移位的寄存器、或一个当即值装载一个值到目的寄存器。不一样之处是在传送以前位被反转了,因此把一个被取反的值传送到一个寄存器中。这是逻辑非操做而不是算术操做,这个取反的值加 1 才是它的取负的值:
MVN R0, #4 ; R0 = -5 MVN R0, #0 ; R0 = -1
(logical OR)
ORR{条件}{S} <dest>, <op 1>, <op 2> dest = op_1 OR op_2
OR
将在两个操做数上进行逻辑或,把结果放置到目的寄存器中;对设置特定的位有用。操做数 1 是一个寄存器,操做数 2 能够是一个寄存器,被移位的寄存器,或一个当即值:
ORR R0, R0, #3 ; 设置 R0 中位 0 和 1
OR 真值表(两者中存在 1 则结果为 1):
Op_1 Op_2 结果 0 0 0 0 1 1 1 0 1 1 1 1
(Reverse Subtraction)
RSB{条件}{S} <dest>, <op 1>, <op 2> dest = op_2 - op_1
SUB
用操做数 two 减去操做数 one,把结果放置到目的寄存器中。操做数 1 是一个寄存器,操做数 2 能够是一个寄存器,被移位的寄存器,或一个当即值:
RSB R0, R1, R2 ; R0 = R2 - R1 RSB R0, R1, #256 ; R0 = 256 - R1 RSB R0, R2, R3,LSL#1 ; R0 = (R3 << 1) - R2
反向减法能够在有符号或无符号数上进行。
(Reverse Subtraction with Carry)
RSC{条件}{S} <dest>, <op 1>, <op 2> dest = op_2 - op_1 - !carry
同于 SBC
,但倒换了两个操做数的先后位置。
(Subtraction with Carry)
SBC{条件}{S} <dest>, <op 1>, <op 2> dest = op_1 - op_2 - !carry
SBC
作两个操做数的减法,把结果放置到目的寄存器中。它使用进位标志来表示借位,这样就能够作大于 32 位的减法。SUB
和 SBC
生成进位标志的方式不一样于常规,若是须要借位则清除进位标志。因此,指令要对进位标志进行一个非操做 - 在指令执行期间自动的反转此位。
(Subtraction)
SUB{条件}{S} <dest>, <op 1>, <op 2> dest = op_1 - op_2
SUB
用操做数 one 减去操做数 two,把结果放置到目的寄存器中。操做数 1 是一个寄存器,操做数 2 能够是一个寄存器,被移位的寄存器,或一个当即值:
SUB R0, R1, R2 ; R0 = R1 - R2 SUB R0, R1, #256 ; R0 = R1 - 256 SUB R0, R2, R3,LSL#1 ; R0 = R2 - (R3 << 1)
减法能够在有符号和无符号数上进行。
ARM 处理器组建了能够与数据处理指令(ADC、ADD、AND、BIC、CMN、CMP、EOR、MOV、MVN、ORR、RSB、SBC、SUB、TEQ、TST)一块儿使用的桶式移位器(barrel shifter)。你还可使用桶式移位器影响在 LDR/STR 操做中的变址值。
译注:移位操做在 ARM 指令集中不做为单独的指令使用,它是指令格式中是一个字段,在汇编语言中表示为指令中的选项。若是数据处理指令的第二个操做数或者单一数据传送指令中的变址是寄存器,则能够对它进行各类移位操做。若是数据处理指令的第二个操做数是当即值,在指令中用 8 位当即值和 4 位循环移位来表示它,因此对大于 255 的当即值,汇编器尝试经过在指令中设置循环移位数量来表示它,若是不能表示则生成一个错误。在逻辑类指令中,逻辑运算指令由指令中 S 位的设置或清除来肯定是否影响进位标志,而比较指令的 S 位老是设置的。在单一数据传送指令中指定移位的数量只能用当即值而不能用寄存器。
下面是给不一样的移位类型的六个助记符:
LSL 逻辑左移 ASL 算术左移 LSR 逻辑右移 ASR 算术右移 ROR 循环右移 RRX 带扩展的循环右移
ASL
和 LSL
是等同的,能够自由互换。
你能够用一个当即值(从 0 到 31)指定移位数量,或用包含在 0 和 31 之间的一个值的寄存器指定移位数量。
(Logical or Arithmetic Shift Left)
Rx, LSL #n or Rx, ASL #n or Rx, LSL Rn or Rx, ASL Rn
接受 Rx 的内容并按用‘n’或在寄存器 Rn 中指定的数量向高有效位方向移位。最低有效位用零来填充。除了概念上的第 33 位(就是被移出的最小的那位)以外丢弃移出最左端的高位,若是逻辑类指令中 S 位被设置了,则此位将成为从桶式移位器退出时进位标志的值。
考虑下列:
MOV R1, #12 MOV R0, R1, LSL#2
在退出时,R0 是 48。 这些指令造成的总和是 R0 = #12, LSL#2
等同于 BASIC 的 R0 = 12 << 2
(Logical Shift Right)
Rx, LSR #n or Rx, LSR Rn
它在概念上与左移相对。把全部位向更低有效位方向移动。若是逻辑类指令中 S 位被设置了,则把最后被移出最右端的那位放置到进位标志中。它同于 BASIC 的 register = value >>> shift
。
(Arithmetic Shift Right)
Rx, ASR #n or Rx, ASR Rn
相似于 LSR,但使用要被移位的寄存器(Rx)的第 31 位的值来填充高位,用来保护补码表示中的符号。若是逻辑类指令中 S 位被设置了,则把最后被移出最右端的那位放置到进位标志中。它同于 BASIC 的 register = value >> shift
。
(Rotate Right)
Rx, ROR #n or Rx, ROR Rn
循环右移相似于逻辑右移,可是把从右侧移出去的位放置到左侧,若是逻辑类指令中 S 位被设置了,则同时放置到进位标志中,这就是位的‘循环’。一个移位量为 32 的操做将致使输出与输入彻底一致,由于全部位都被移位了 32 个位置,又回到了开始时的位置!
(Rotate Right with extend)
Rx, RRX
这是一个 ROR#0 操做,它向右移动一个位置 - 不一样之处是,它使用处理器的进位标志来提供一个要被移位的 33 位的数量。
乘法指令 |
这两个指令与普通算术指令在对操做数的限制上有所不一样:
(Multiplication with Accumulate)
MLA{条件}{S} <dest>, <op 1>, <op 2>, <op 3> dest = (op_1 * op_2) + op_3
MLA
的行为同于 MUL
,但它把操做数 3 的值加到结果上。这在求总和时有用。
(Multiplication)
MUL{条件}{S} <dest>, <op 1>, <op 2> dest = op_1 * op_2
MUL
提供 32 位整数乘法。若是操做数是有符号的,能够假定结果也是有符号的。
比较指令 |
译注:CMP 和 CMP 是算术指令,TEQ 和 TST 是逻辑指令。把它们纳入一类的缘由是它们的 S 位老是设置的,就是说,它们老是影响标志位。
(Compare Negative)
CMN{条件}{P} <op 1>, <op 2> status = op_1 - (- op_2)
CMN
同于 CMP
,但它容许你与小负值(操做数 2 的取负的值)进行比较,好比难于用其余方法实现的用于结束列表的 -1。这样与 -1 比较将使用:
CMN R0, #1 ; 把 R0 与 -1 进行比较
详情参照 CMP
指令。
(Compare)
CMP{条件}{P} <op 1>, <op 2> status = op_1 - op_2
CMP
容许把一个寄存器的内容如另外一个寄存器的内容或当即值进行比较,更改状态标志来容许进行条件执行。它进行一次减法,但不存储结果,而是正确的更改标志。标志表示的是操做数 1 比操做数 2 如何(大小等)。若是操做数 1 大于操做操做数 2,则此后的有 GT 后缀的指令将能够执行。
明显的,你不须要显式的指定 S
后缀来更改状态标志... 若是你指定了它则被忽略。
(Test Equivalence)
TEQ{条件}{P} <op 1>, <op 2> Status = op_1 EOR op_2
TEQ
相似于 TST
。区别是这里的概念上的计算是 EOR 而不是 AND。这提供了一种查看两个操做数是否相同而又不影响进位标志(不象 CMP
那样)的方法。加上 P
后缀的 TEQ
还可用于改变 R15 中的标志(在 26-bit 模式中)。详情请参照 psr.html,在 32-bit 模式下如何作请参见这里。
(Test bits)
TST{条件}{P} <op 1>, <op 2> Status = op_1 AND op_2
TST
相似于 CMP
,不产生放置到目的寄存器中的结果。而是在给出的两个操做数上进行操做并把结果反映到状态标志上。使用 TST
来检查是否设置了特定的位。操做数 1 是要测试的数据字而操做数 2 是一个位掩码。通过测试后,若是匹配则设置 Zero 标志,不然清除它。象 CMP
那样,你不须要指定 S
后缀。
TST R0, #%1 ; 测试在 R0 中是否设置了位 0。
(Branch)
B{条件} <地址>
是最简单的分支。一旦遇到一个 指令,ARM 处理器将当即跳转到给定的地址,从那里继续执行。BB
注意存储在分支指令中的实际的值是相对当前的 R15 的值的一个偏移量;而不是一个绝对地址。
它的值由汇编器来计算,它是 24 位有符号数,左移两位后有符号扩展为 32 位,表示的有效偏移为 26 位(+/- 32 M)。
在其余处理器上,你可能常常见到这样的指令:
OPT 1 LDA &70 CMP #0 BEQ Zero STA &72 .Zero RTS
(取自 Acorn Electron User Guide issue 1 page 213)
在 ARM 处理器上,它们将变成下面这些东西:
OPT 1 ADR R1, #&70 LDR R0, [R1] CMP #0 BEQ Zero STR R0, [R1, #2] .Zero MOV PC, R14
这不是一个很好的例子,但你能够构想如何更好的去条件执行而不是分支。另外一方面,若是你有大段的代码或者你的代码使用状态标志,那么你可使用条件执行来实现各种分支: 这样一个单一的简单条件执行指令能够替代在其余处理器中存在的全部这些分支和跳转指令。
OPT 1 ADR R1, #&70 LDR R0, [R1] CMP R0, #0 STRNE R0, [R1, #2] MOV PC, R14
(Branch with Link)
BL{条件} <地址>
是另外一个分支指令。就在分支以前,在寄存器 14 中装载上 R15 的内容。你能够从新装载 R14 到 R15 中来返回到在这个分支以后的那个指令,BL
它是子例程的一个基本但强力的实现。它的做用在屏幕装载器 2 (例子 4)中得以很好的展示...
.load_new_format BL switch_screen_mode BL get_screen_info BL load_palette .new_loop MOV R1, R5 BL read_byte CMP R0, #255 BLEQ read_loop STRB R0, [R2, #1]!
...在这里咱们见到在装载器循环以前调用了三个子例程。接着,一旦知足了条件执行就在循环中调用了 read_byte 子例程。
条件执行 |
XXX
- 这里的 XXX 是任何东西。为了举例,下面是 Intel 8086 处理器分支指令的一个列表:
JA Jump if Above JAE Jump if Above or Equal JB Jump if Below JBE Jump if Below or Equal JC Jump if Carry JCXZ Jump if CX Zero (CX is a register that can be used for loop counts) JE Jump if Equal JG Jump if Greater than JGE Jump if Greater than or Equal JL Jump if Less than JLE Jump if Less Than or Equal JMP JuMP JNA Jump if Not Above JNAE Jump if Not Above or Equal JNB Jump if Not Below JNBE Jump if Not Below or Equal JNC Jump if No Carry JNE Jump if Not Equal JNG Jump if Not Greater than JNGE Jump if Not Greater than or Equal JNL Jump if Not Less than JNLE Jump if Not Less than or Equal JNO Jump if Not Overflow JNP Jump if Not Parity JNS Jump if Not Sign JNZ Jump if Not Zero JO Jump if Overflow JP Jump if Parity JPE Jump if Parity Even JPO Jump if Parity Odd JS Jump if Sign JZ Jump if Zero 80386 添加了: JECXZ Jump if ECX Zero
B 分支 BL 带链接的分支
BEQ Branch if EQual BNE Branch if Not Equal BVS Branch if oVerflow Set BVC Branch if oVerflow Clear BHI Branch if HIgher BLS Branch if Lower or the Same BPL Branch if PLus BMI Branch if MInus BCS Branch if Carry Set BCC Branch if Carry Clear BGE Branch if Greater than or Equal BGT Branch if Greater Than BLE Branch if Less than or Equal BLT Branch if Less Than BLEQ Branch with Link if EQual .... BLLT Branch with Link if Less Than
AL
- ALways,缺省条件因此不须指定NV
- NeVer,不是很是有用。你不管如何不要使用这个代码...接着你会想,若是你能够在一个分支指令上加上全部这些条件,那么对一个寄存器装载指令可否加上它们? 答案是能够。
下面是可得到的条件代码的列表:
S,
它以相反的方式工做。当用于一个指令的时候,致使更改状态标志。这不是自动发生的 - 除非这些指令的目的是设置状态。例如:ADD R0, R0, R1 ADDS R0, R0, R1 ADDEQS R0, R0, R1
第二个例子是同一个加法,只不过它致使更改状态寄存器。
最后一个例子是同一个加法,更改状态寄存器。不一样在于它是一个有条件的指令。只有前一个操做的结果是 EQ (若是设置了 Z 标志)的时候它才执行。
下面是条件执行的一个工做中的例子。你把寄存器 0 与存储在寄存器 10 中内容相比较。
若是不等于 R10,则调用一个软件中断,增长它并分支回来再次作这些。不然清除 R10 并返回到调用它的那部分代码(它的地址存储在 R14)。
\ 条件执行的一个例子 .loop ; 标记循环开始位置 CMP R0, R10 ; 把 R0 与 R10 相比较 SWINE &40017 ; 不等于: 调用 SWI &40017 ADDNE R0, R0, #1 ; 向 R0 加 1 BNE loop ; 分支到 'loop' MOV R10, #0 ; 等于 : 设置 R10 为零 LDMFD R13!, {R0-R12,PC} ; 返回到调用者
LDMFD,
它从栈中装载多个寄存器。在这个例子中,咱们从一个彻底正式的栈中装载 R0 至 R12 和 R14。关于寄存器装载和存储的更多信息请参阅 str.html。LDMFD R13!, {R0-R12,R14}
MOV PC, R14
MOV
语句。 SWI 指令 |
SWI : 软件中断
(Software Interrupt)
SWI{条件} <24 位编号>
这是一个简单的设施,但多是最经常使用的。多数操做系统设施是用 SWI 提供的。没有 SWI 的 RISC OS 是不可想象的。
Nava Whiteford 解释了 SWI 是如何工做的(最初在 Frobnicate issue 12?)...
SWI 表示 Software Interrupt。在 RISC OS 中使用 SWI 来访问操做系统例程或第三方生产的模块。许多应用使用模块来给其余应用提供低层外部访问。
SWI 的例子有:
在以这种方式使用的时候,SWI 容许操做系统拥有一个模块结构,这意味着用来创建完整的操做系统的所需的代码能够被分割成许多小的部分(模块)和一个模块处理程序(handler)。
当 SWI 处理程序获得对特定的例程编号的一个请求的时候,它找到这个例程的位置并执行它,并传递(有关的)任何数据。
首先查看一下如何使用它。一个 SWI 指令(汇编语言)看起来以下:
SWI &02
或
SWI "OS_Write0"
这些指令其实是相同的,将被汇编成相同的指令。惟一的不一样是第二个指令使用一个字符串来表示 SWI 编号 &02。
在使用采用了字符串编号的程序的时候,在执行以前首先查找这个字符串。
在这里咱们不想处理字符串,由于它不能给出它要进行什么的一个真实表示。它们一般用于增进一个程序的清晰程度,但不是实际执行的指令。
让咱们再次看一下第一个指令:
SWI &02
这是什么意思? 字面的意思是进入 SWI 处理程序并传递值 &02。在 RISC OS 中这意味着执行编号是 &02 的例程。
它是如何这么做的? 它如何传递 SWI 编号和进入 SWI 处理程序?
若是你查看内存的开始 32 字节(位于 0-&1C)并反汇编它们(查开实际的 ARM 指令)你将见到以下:
地址 内容 反汇编 00000000 : 0..? : E5000030 : STR R0,[R0,#-48] 00000004 : .ó?? : E59FF31C : LDR PC,&00000328 00000008 : .ó?? : E59FF31C : LDR PC,&0000032C 0000000C : .ó?? : E59FF31C : LDR PC,&00000330 00000010 : .ó?? : E59FF31C : LDR PC,&00000334 00000014 : .ó?? : E59FF31C : LDR PC,&00000338 00000018 : .ó?? : E59FF31C : LDR PC,&0000033C 0000001C : 2?? : E3A0A632 : MOV R10,#&3200000
让咱们仔细看一下。
除了第一个和最后一个指令以外(它们是特殊状况)你见到的都是把一个新值装载到 PC (程序计数器)的指令,它们告诉计算机到哪里去执行下一个指令。
还展现了这个值是从内存中的一个地址接受来的。(你能够在 !Zap 主菜单上使用“Read Memory”选项去本身查看一下。)
这看起来好象与 SWI 没多少关系,下面作进一步的说明。
一个 SWI 所作的一切就是把模式改变成超级用户并设置 PC 来执行在地址 &08 处的下一个指令!
把处理器转换到超级用户模式会切换掉两个寄存器 r13 和 r14 并用 r13_svc 和 r14_svc 替换它们。
在进入超级用户模式的时候,还把 r14_svc 设置为在这个 SWI 指令以后的地址。
这个实际上就象一个链接到地址 &08 的分支指令(BL &08),但带有用于一些数据(SWI 编号)的空间。
象我说过的那样,地址 &08 包含跳转到另外一个地址的一个指令,就是实际的 SWI 程序的地址!
此时你可能会想“稍等一会! 还有 SWI 编号呢?”。实际上处理器忽略这个值自己。SWI 处理程序使用传递来的 r14_svc 的值来获取它。
下面是完成它的步骤(在存储寄存器 r0-r12 以后):
容易吧! ;)
下面是一个例子,来自 ARM610 datasheet:
0x08 B Supervisor EntryTable DCD ZeroRtn DCD ReadCRtn DCD WriteIRtn ... Zero EQU 0 ReadC EQU 256 WriteI EQU 512 ; SWI 包含须要的例程在位 8-23 中和数据(若是有的话)在位 0-7 中。 ; 假定 R13_svc 指向了一个合适的栈 STMFD R13, {r0-r2 , R14} ; 保存工做寄存器和返回地址。 LDR R0,[R14,#-4] ; 获得 SWI 指令。 BIC R0,R0, #0xFF000000 ; 清除高端的 8 位。 MOV R1, R0, LSR #8 ; 获得例程偏移量。 ADR R2, EntryTable ; 获得入口表(EntryTable)的开始地址。 LDR R15,[R2,R1,LSL #2] ; 分支到正确的例程 WriteIRtn ; 写 R0 中的位 0 - 7 中的字符。 ............. LDMFD R13, {r0-r2 , R15}^ ; 恢复工做空间,并返回、恢复处理器模式和标志。
这就是 SWI 指令的基本处理步骤。