Part3.2ARM汇编指令学习

先看作用,再看格式,最后看例子自己实现一下

1、算术和逻辑指令

  1、1MOV

dest是目的数,必须是通用寄存器;op_1是操作数,可以是立即数和通用寄存器

注释用@符号

 


.text
.global _start
_start:
@mov范例
mov r1,#1
mov r2,r1
mov r3,#3

  1、2MVN

会进行取反操作(二进制)再赋值给目的寄存器

.text
.global _start
_start:
@mvn 范例
mvn r1,#0b10
mvn r2,#5

结果r1为0xfffffffd(处理器32位)

r2为0xfffffffa

  1、3SUB

其中被减数和差值(dest,op1)都不能是立即数

  1、4ADD

其中总和与第一个值都不能是立即数

  1、5AND

就是与操作

.text
.global _start
_start:
@and 范例
mov r1,#5
and r2,r1,#0

mov
r1,#5
and r2,r1,#1

结果第一个r2=0,第二个r2=1

  1、6BIC

位清除,BIC 是在一个字中清除位的一种方法,与 OR 位设置是相反的操作。操作数 2 是一个 32 位位掩码(mask)。如果如果在掩码中设置了某一位,则清除这一位。未设置的掩码位指示此位保持不变。

.text
.global _start
_start:
mov r0,0b11111
bic r0,r0,#%1011

清除 R0 中的位 0、1、和 3。保持其余的不变。   (%在标准汇编中代表二进制,在我们GNU汇编中不代表什么   标准汇编中bic这种命令是大写的)

2、比较指令

  2、1CMP

CMP{条件}{P}  <op 1>, <op 2>

                status = op_1 - op_2

CMP 允许把一个寄存器的内容如另一个寄存器的内容或立即值进行比较,更改状态标志来允许进行条件执行。它进行一次减法,但不存储结果,而是正确的更改标志。标志表示的是操作数 1 比操作数 2 如何(大小等)。如果操作数 1 大于操作操作数 2,则此后的有 GT 后缀的指令将可以执行。
明显的,你不需要显式的指定 S 后缀来更改状态标志... 如果你指定了它则被忽略。 

会影响CPSR、SPSR中N\Z寄存器位

.text
.global _start
_start:
@cmp 范例
mov r1,#2
cmp r1,#1

mov r1,#2
cmp r1,#3

mov r1,#2
cmp r1,#2

  2、2TST

两个操作数进行按位与的操作,影响寄存器但不影响原来的数

mov r1,#0b101
tst r1,#0b001

mov r1,#0b101
tst r1,#0b010

3、跳转指令

  3、1B

if(a>b)
    a-b;
else
    a+b;


//汇编:
mov r1,#6
mov r2,#5
cmp r1,r2
bgt branch1
add r3,r1,r2//如果不满足条件,会直接执行下面的操作,所以不需要另一个分支;再接下去执行还需要跳过第一个分支
b end
branch1:
sub r3,r1,r2
end:
nop

   3、2BL

 

mov r1,#2
cmp r1,#1
bl func1

mov r1,#2
cmp r1,#3
func1:
mov r1,#2
mov r2,#3
mov pc,lr

这串代码bl处如果换成b,不能很好的保存lr地址,会导致程序跑飞,但bl就能保存回来的地址。

在汇编中看到,lr的地址就是mov r1,#2的地址(是在bl语句下面那个),回到了函数调用的下一条语句并且运行

4、移位指令

  4、1LSL

逻辑或者算术左移

MOV    R0, R1, LSL#2

在退出时,R0 是 48。 这些指令形成的总和是 R0 = #12, LSL#2 等同于 BASIC 的 R0 = 12 << 2

mov r1,#0b1
mov r1,r1,lsl#2

  

  4、2ROR

循环右移

mov r1,#0b11001
mov r1,r1,ror#2

 

5、程序状态字访问指令

两个格式一致,状态也一致

两个指令对应搬出和搬回

  5、1MRS

  5、2MSR

第一个是S到R      程序状态字寄存器到通用寄存器

第二个是R到S      通用寄存器到程序状态字寄存器

 

mrs r1,cpsr
orr r1,#0b100//对第三位赋值1
msr cpsr,r1

 

6、存储器访问指令

前面都是CPU核里面的寄存器访问,而不是对网卡芯片,内存芯片这些进行访问

 

  6、1LDR

同下

  6、2STR

例:

mov r0,#0xff
str r0,[r1]
ldr r2,[r1]

预先设定r1的值,然后在memory窗口中查看相应地址的数据变化。可以右击format来更改显示的配置

 

 

6、2、1传送单一数据

使用单一数据传送指令(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 从一个非对齐的地址装载一个字。

  

6、2、2传送多个数据

使用多数据传送指令(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。

 

 

 

7、标号拓展(命令后面可以加,添加相应命令)

  • AL - ALways,缺省条件所以不须指定
  • NV - NeVer,不是非常有用。你无论如何不要使用这个代码...

当你发现所有 Bxx 指令实际上是同一个指令的时候,紧要关头就到了。接着你会想,如果你可以在一个分支指令上加上所有这些条件,那么对一个寄存器装载指令能否加上它们? 答案是可以。

下面是可获得的条件代码的列表:

EQ : 等于
如果一次比较之后设置了 Z 标志。
 
NE : 不等于
如果一次比较之后清除了 Z 标志。
 
VS : 溢出设置
如果在一次算术操作之后设置了 V 标志,计算的结果不适合放入一个 32bit 目标寄存器中。
 
VC : 溢出清除
如果清除了 V 标志,与 VS 相反。
 
HI : 高于(无符号)
如果一次比较之后设置了 C 标志清除了 Z 标志。
 
LS : 低于或同于(无符号)
如果一次比较操作之后清除了 C 标志设置了 Z 标志。
 
PL : 正号
如果一次算术操作之后清除了 N。出于定义‘正号’的目的,零是正数的原因是它不是负数...
 
MI : 负号
如果一次算术操作之后设置了 N 标志。
 
CS : 进位设置
如果一次算术操作或移位操作之后设置了 C 标志,操作的结果不能表示为 32bit。你可以把 C 标志当作结果的第 33 位。
 
CC : 进位清除
与 CS 相反。
 
GE : 大于或等于(有符号)
如果一次比较之后...
设置了 N 标志设置了 V 标志
或者...
清除了 N 标志清除了 V 标志。
 
GT : 大于(有符号)
如果一次比较之后...
设置了 N 标志设置了 V 标志
或者...
清除了 N 标志清除了 V 标志
并且...
清除了 Z 标志。
 
LE : 小于或等于(有符号)
如果一次比较之后...
设置了 N 标志清除了 V 标志
或者...
清除了 N 标志设置了 V 标志
并且...
设置了 Z 标志。
 
LT : 小于(有符号)
如果一次比较之后...
设置了 N 标志清除了 V 标志。
或者...
清除了 N 标志设置了 V 标志。
 
AL : 总是
缺省条件,所以不用明显声明。
 
NV : 从不
不是特别有用,它表示应当永远不执行这个指令。是穷人的 NOP。
包含 NV 是为了完整性(与 AL 相对),你不应该在你的代码中使用它。

有一个在最后的条件代码 S,它以相反的方式工作。当用于一个指令的时候,导致更改状态标志。这不是自动发生的 - 除非这些指令的目的是设置状态。例如:

  ADD     R0, R0, R1

  ADDS    R0, R0, R1

  ADDEQS  R0, R0, R1

第一个例子是一个基本的加法(把 R1 的值增加到 R0),它不影响状态寄存器。

第二个例子是同一个加法,只不过它导致更改状态寄存器。 

最后一个例子是同一个加法,更改状态寄存器。不同在于它是一个有条件的指令。只有前一个操作的结果是 EQ (如果设置了 Z 标志)的时候它才执行。 

posted @ 2017-05-25 10:27  郁兴力  阅读(546)  评论(0)    收藏  举报