程序项目代做,有需求私信(小程序、网站、爬虫、电路板设计、驱动、应用程序开发、毕设疑难问题处理等)

嵌入式Linux之常用ARM汇编

在嵌入式开发中,汇编程序常常用于非常关键的地方,比如系统启动时的初始化,中断上下文的保存和恢复,对性能要求非常苛刻的函数等。

在S3C2440的数据手册中,对各种汇编指令的作用及使用方法都有详细说明,这里只对一些常用的汇编指令进行介绍。

一、ARM寄存器介绍

1.1 32位体系

ARM 处理器在32位体系下主要由以下寄存器组成:

  • 寄存器 0 到寄存器 7 是通用寄存器并可以用做任何目的。不像 80x86 处理器那样要求特定寄存器被用做段地址访问,或者像 6502 那样把数学计算的结果放置到一个累加器中,ARM 处理器在寄存器使用上是高度灵活的。
  • 寄存器 8 到 12 是通用寄存器,但是在切换到 FIQ 模式的时候,使用它们的影子(shadow)寄存器。
  • 寄存器 13 典型的用做 OS 栈指针,但可被用做一个通用寄存器。这是一个操作系统问题,不是一个处理器问题,所以如果你不使用栈,只要你以后恢复它,你可以在你的代码中自由的占用(corrupt)它。每个处理器模式都有这个寄存器的影子寄存器。
  • 寄存器 14 专职持有返回点的地址以便于写子例程。当你执行带连接的分支的时候,把返回地址存储到 R14 中。同样在程序第一次运行的时候,把退出地址保存在 R14 中。R14 的所有实例必须被保存到其他寄存器中(不是实际上有效)或一个栈中。这个寄存器在各个处理器模式下都有影子寄存器。一旦已经保存了连接地址,这个寄存器就可以用做通用寄存器了。
  • 寄存器 15 是程序计数器。PC 是完全的 32 位宽,并只用做程序计数器。
  • 两个 PSR - CPSR 是当前的程序状态寄存器(Current Program Status Register),而 SPSR 是保存的程序状态寄存器(Saved Program Status Register)。每个有特权的模式都有自己的 SPSR,可获得的 PSR 有:

CPSR_all - 当前的 
SPSR_svc - 保存的,SVC(32) 模式 
SPSR_irq - 保存的,IRQ(32) 模式 
SPSR_abt - 保存的,ABT(32) 模式 
SPSR_und - 保存的,UND(32) 模式 
SPSR_fiq - 保存的,FIQ(32) 模式 

你不能显式的指定把 CPSR 保存到哪个SPSR 中,比如 SPSR_fiq。而是必须变更到 FIQ 模式并接着保存到 SPSR。换句话说,你只能在你所在的模式中改变这个模式的 SPSR。

为更清晰一些... 提供下列图表(其中User26,...26是为了兼容26位体系): 

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_fiq
R13      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

下面是你想知道的"模式",比如上面提及的"FIQ"模式。

  • 用户模式,运行应用程序的普通模式。限制你的内存访问并且你不能直接读取硬件设备。
  • 超级用户模式(SVC 模式),主要用于 SWI(软件中断)和 OS(操作系统)。这个模式有额外的特权,允许你进一步控制计算机。例如,你必须进入超级用户模式来读取一个插件(podule)。这不能在用户模式下完成。
  • 外部中断模式(IRQ 模式),用来处理发起中断的外设。这个模式也是有特权的。导致 IRQ 的设备有键盘、 VSync (在发生屏幕刷新的时候)、IOC 定时器、串行口、硬盘、软盘、等等...
  • 快速中断模式(FIQ 模式),用来处理发起快速中断的外设。这个模式是有特权的。导致 FIQ 的设备有处理数据的软盘,串行端口(比如在 82C71x 机器上的 A5000) 和 Econet。
  • 异常终止模式(ABT 模式): 在一个数据或指令预取异常终止(abort)的时候进入的模式。

  • 未定义模式(UND 模式): 在执行了一个未定义的指令的时候进入的模式。

ARM处理器的工作模式可以通过软件改变,也可以通过外部中断或异常处理来改变处理器的工作模式。大多数的应用程序运行在用户模式下,当处理器运行在用户模式下时,某些被保护的系统资源是不能被访问的。

除了软件切换工作模式外,还有没有其他方法呢?答案当然是有!通过异常中断的方式也可以进入相应的工作模式。例如,当IRQ中断发生时,处理器就进入外部中断模式(IRQ模式)。

IRQ 和 FIQ 之间的区别是对于 FIQ 你必须尽快处理你事情并离开这个模式。IRQ 可以被 FIQ 所中断但 IRQ 不能中断 FIQ。FIQ 不能调用 SWI。FIQ 还必须禁用中断。

当进行工作模式切换时,哪些寄存器的值需要保护呢?为什么快速中断模式比外部中断模式中断响应的速度要快?

假设ARM处理器处于用户模式执行程序,在程序执行过程中,发生了外部中断,则处理器进入外部中断模式(IRQ模式),从上图可以看出,用户模式和IRQ模式的寄存器R0-R12是公用的,也就是说,在进入IRQ模式之前,在用户模式下可能使用到了寄存器R0-R12。如果在IRQ模式中也需要使用,那么寄存器R0~R12中的值将被更改,在IRQ模式执行完中断处理程序,返回到用户模式后,寄存器R0-R12中的值被破坏了。因此,在IRQ模式中,使用寄存器R0-R12之前需要将其中的值保存,当从IRQ模式返回到用户模式时将原来的值恢复就可以避免上述问题。

从上图还可以看出,用户模式下的寄存器R13-R14和IRQ模式的寄存器R13_irq、R14_irq不是同一个寄存器,也就是说在每种工作模式中,这两个寄存器是独立的。因此,当从用户模式切换到IRQ模式时,不需要保存这两个寄存器的值。

此外,从上图还可以看到,用户模式和快速中断模式公用的寄存器是R0-R7,在FIQ模式中,R8_fiq~R14_fiq是独立的。因此,当从用户模式切换到快速中断模式(FIQ)时不需要保存这几个寄存器的值,只需要保存R0-R7的值即可。因此发生快速中断只需要保存R0-R7,共8个寄存器。但是,当发生外部中断时需要保存R0~R12共13个寄存器,这里讲的保存寄存器的值是通过将其值入栈实现的,入栈是需要时间的,因此快速中断的响应时间要快一些。

1.2 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

标志的意义:

  • N   Negative   如果结果是负数则置位
  • Z   Zero          如果结果是零则置位
  • C  Carry         如果发生进位则置位
  • V  Overflow    如果发生溢出则置位
  • I    IRQ            中断禁用
  • F   FIQ             快速中断禁用
  • M4.. 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 模式

二、寄存器装载和存储

  • LDM
  • LDR
  • STM
  • STR
  • SWP

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

2.1 传送单一数据(STR 和 LDR)

ldr命令:把数据从内存加载到寄存器。

ldr{条件} r1, [r0]        ; r1 = *r0
ldr{条件} r1, [r0, #4]    ; r1 = *(r0+4)
ldr{条件} r1, [r0, #4] !    ; r1 = *(r0+4);r0=r0+4;
ldr{条件} r1, [r0], #4    ; r1 = *(r0);r0=r0+4;

str命令:把数据从寄存器保存到内存。

str{条件} r1, [r0]        ; *r0 = r1 
str{条件} r1, [r0, #4]    ; *(r0+4) = r1 
str{条件} r1, [r0, #4] !    ; *(r0+4) = r1;r0=r0+4;
str{条件} r1, [r0], #4    ; *r0 = r1;r0=r0+4;

LDR(伪指令):

伪指令(并不存在的指令,最终被解析成真正的汇编指令)

例1:LDR伪指令可以在立即数前加上=,以表示把一个地址写到某寄存器中。

COUNT EQU       0x56000054
LDR       R1,=COUNT

COUNT是我们定义的一个变量,地址为0x56000054。该指令会在内存中开辟一个地址,该地址保存0x56000054。而LDR指令会被转换为 LDR,R1,[PC,偏移量]。

例2:下面的例子是获取代码的绝对位置,这是位置有关码,该地址的值是和程序的链接地址有关的:

    LDR R1,=label
label:
    ...

LDR会去读取标号的地址,然后赋值给R1,由于标号的地址和链接地址有关,因此这是一个位置有关指令。

例3:LDR伪指令把数据从内存中某处读取到寄存器中。

LDR R0, 0x12345678 ;就是把0x12345678这个地址中的值存放到r0中。

该指令会读取0x12345678地址处的值,然后赋值给R0。

LDR伪指令和MOV是比较相似的。只不过MOV指令限制了立即数的长度为8位,也就是不能超过512。而LDR伪指令没有这个限制。如果使用LDR伪指令时,后面跟的立即数没有超过8位,那么在实际汇编的时候该LDR伪指令是被转换为MOV指令的。

2.2 传送多个数据(LDM和STM)

LDM:(load  much)多数据加载,将地址上的值加载到寄存器上;

STM:(store much)多数据存储,将寄存器的值存到地址上;

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     过后减少存储

LDM例子:

Ldr R1,=0x10000000          #传送数据的起始地址0x10000000     

LDMIB R1!,{R0,R4-R6}        #从左到右加载,相当于 LDR R0,10000004  LDR R4,10000008... ...

IB:(Increase Before)的含义每次传送前地址加4, 传送前地址加+4;

所以地址加4,R0=0x1000004地址里的内容;

地址加4,R4=0x10000008地址里的内容;

地址加4,R5=0x1000000C地址里的内容;

地址加4,R6=0x10000010 地址里的内容;

由于!, 最后的地址写回到R1中,R1=0x10000010 ;

指令中寄存器列表和内存单元的对应关系为:编号低的寄存器对应内存中的低地址单元。编号搞得寄存器对应内存中的高地址单元。

2.3 单一数据交换(SWP)

 指令格式:

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

寄存器和存储器交换指令。使用SWP 可实现信号量操作。

实列代码如下: 

SWP R1,R1,[R0] ; 取出r0地址中的数据,放在r1中,并把r1中的数据放在r0中。  
SWP R1,R2,[R0] ; 将R0 指向的存储单元内容读取数据到R1 中 ; 并将R2 的内容写入到该内存单元中 使用SWP 指令可以方便地进行信号量的操作: 

三、分支指令

3.1 B分支

B跳转指令,语法格式:

B{条件}  <地址>

B 是最简单的分支。一旦遇到一个 B 指令,ARM 处理器将立即跳转到给定的地址,从那里继续执行。注意存储在分支指令中的实际的值是相对当前的 R15 的值的一个偏移量;而不是一个绝对地址。它的值由汇编器来计算,它是 24 位有符号数,左移两位后有符号扩展为 32 位,表示的有效偏移为 26 位(+/- 32 M)。

3.2 BL带连接的分支

BL语法格式:

 BL{条件}  <地址>

带链接程序跳转,也就是要带返回地址。在发生跳转前,将当前PC-4保存到R14中。 也就是返回地址存在R14中,所以可以在子程序返回时只要MOV PC, LR即可。

四、算数和逻辑指令

  • ADC
  • ADD
  • AND
  • BIC
  • EOR
  • MOV
  • MVN
  • ORR
  • RSB
  • RSC
  • SBC
  • SUB

4.1 ADC带进位的加法

 ADC{条件}{S}  <dest>, <op 1>, <op 2>
 dest = op_1 + op_2 + carry

ADC 将把两个操作数加起来,并把结果放置到目的寄存器中。它使用一个进位标志位,这样就可以做比 32 位大的加法。

下列例子将两个128位的数进行相加:

128 位结果: 寄存器 0、1、2、和 3;

第一个 128 位数: 寄存器 4、5、6、和 7;

第二个 128 位数: 寄存器 8、9、10、和 11;

 ADDS    R0, R4, R8              ; 加低端的字
 ADCS    R1, R5, R9              ; 加下一个字,带进位
 ADCS    R2, R6, R10             ; 加第三个字,带进位
 ADCS    R3, R7, R11             ; 加高端的字,带进位

如果如果要做这样的加法,不要忘记设置 S 后缀来更改进位标志。

4.2 ADD加法

 ADD{条件}{S}  <dest>, <op 1>, <op 2>
 dest = op_1 + op_2

ADD 将把两个操作数加起来,把结果放置到目的寄存器中。操作数 1 是一个寄存器,操作数 2 可以是一个寄存器,被移位的寄存器,或一个立即值: 

ADD     R0, R1, R2              ; R0 = R1 + R2
ADD     R0, R1, #256            ; R0 = R1 + 256
ADD     R0, R2, R3,LSL#1        ; R0 = R2 + (R3 << 1)

加法可以在有符号和无符号数上进行。

4.3 AND : 逻辑与

AND{条件}{S}  <dest>, <op 1>, <op 2>
dest = op_1 AND op_2

AND将在两个操作数上进行逻辑与,把结果放置到目的寄存器中;对设置特定的位有用。操作数 1 是一个寄存器,操作数 2 可以是一个寄存器,被移位的寄存器,或一个立即值: 

AND     R0, R0, #0xFFFFFFE              ; 清除R0 中位 0 

AND真值表(二者中均为 1 则结果为 1): 

Op_1   Op_2   结果

  0      0      0
  0      0      0
  0      0      0
  1      1      1

4.4 BIC位清除

 BIC{条件}{S}  <dest>, <op 1>, <op 2>
 dest = op_1 AND (!op_2)

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

BIC     R0, R0, #%1011          ; 清除 R0 中的位 0、1、和 3。保持其余的不变。

BIC 真值表: 

 Op_1   Op_2   结果

  0      0      0
  0      1      0
  1      0      1
  1      1      0

译注:逻辑表达式为 Op_1 AND NOT Op_2。

4.5 EOR逻辑异或

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

4.6 MOV传送

 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 体系)

4.7 MVN传送取反的值

(Move Negative)
 MVN{条件}{S}  <dest>, <op 1>
 dest = !op_1

MVN 从另一个寄存器、被移位的寄存器、或一个立即值装载一个值到目的寄存器。不同之处是在传送之前位被反转了,所以把一个被取反的值传送到一个寄存器中。这是逻辑非操作而不是算术操作,这个取反的值加 1 才是它的取负的值: 

 MVN     R0, #4                  ; R0 = -5
 MVN     R0, #0                  ; R0 = -1

4.8 ORR : 逻辑或

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

4.9 RSB : 反向减法

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

反向减法可以在有符号或无符号数上进行。

4.10 RSC : 带借位的反向减法

RSC{条件}{S}  <dest>, <op 1>, <op 2>
dest = op_2 - op_1 - !carry

同于 SBC,但倒换了两个操作数的前后位置。

4.11 SBC : 带借位的减法

SBC{条件}{S}  <dest>, <op 1>, <op 2>
dest = op_1 - op_2 - !carry

SBC 做两个操作数的减法,把结果放置到目的寄存器中。它使用进位标志来表示借位,这样就可以做大于 32 位的减法。SUB 和 SBC 生成进位标志的方式不同于常规,如果需要借位则清除进位标志。所以,指令要对进位标志进行一个非操作 - 在指令执行期间自动的反转此位。 

4.12 SUB : 减法

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)

减法可以在有符号和无符号数上进行。 

五、程序状态指令

5.1  MRS

MRS指令用于将程序状态寄存器的内容传送到通用寄存器中。

MRS{条件}   通用寄存器,程序状态寄存器(CPSR或SPSR)

例子:

  MRS     R0, CPSR                ; 复制 CPSR 到 R0 中
  MRS     R0, SPSR                ; 复制 SPSR 到 R0 中

注意事项: MRS指令用于将程序状态寄存器的内容传送到通用寄存器中。该指令一般用在以下两种情况: 

  • 当需要改变程序状态寄存器的内容时,可用MRS将程序状态寄存器的内容读入通用寄存器,修改后再写回程序状态寄存器;
  • 当在异常处理或进程切换时,需要保存程序状态寄存器的值,可先用该指令读出程序状态寄存器的值,然后保存。

5.2 MSR

MSR指令用亍将操作数的内容传送到程序状态寄存器的特定域中。其中,操作数可以为通用寄存器或立即数。

MSR{条件}   程序状态寄存器(CPSR或SPSR)_<域>,操作数

例子:

  MSR     CPSR, R0                ; 复制 R0 到 CPSR 中
  MSR     SPSR, R0                ; 复制 R0 到 SPSR 中
  MSR     CPSR_flg, R0            ; 复制 R0 的标志位到 CPSR 中
  MSR     CPSR_flg, #1<<28        ; 复制(立即值)标志位到 CPSR 中

注意事项:

<域>用于设置程序状态寄存器中需要操作的位,32位的程序状态寄存器可分为4个域:
位[31:24]为条件标志位域,用f表示;
位[23:16]为状态位域,用s表示;
位[15:8]为扩展位域,用x表示;
位[7:0]为控制位域,用c表示;
该指令通常用于恢复或改变程序状态寄存器的内容,在使用时,一般要在MSR指令中指明将要操作的域。

5.3 改变模式示例

使用 _flg 后缀允许你改变标志位而不影响控制位。 要设置 V 标志: 

MSR     CPSR_flg, #&10000000

要改变模式: 

MRS     R0, CPSR_all            ; 复制 PSR
BIC     R0, R0, #&1F            ; 清除模式位
ORR     R0, R0, #new_mode       ; 把模式位设置为新模式
MSR     CPSR_all, R0            ; 写回 PSR,变更模式

六、比较指令

  • CMN
  • CMP
  • TEQ
  • TST

6.1 CMN:比较取负的值

CMN{条件}{P}  <op 1>, <op 2>
status = op_1 - (- op_2)

CMN 同于 CMP,但它允许你与负值(操作数 2 的取负的值)进行比较,比如难于用其他方法实现的用于结束列表的 -1。这样与 -1 比较将使用: 

CMN     R0, #1                  ; 把 R0 与 -1 进行比较

6.2 CMP : 比较

 CMP{条件}{P}  <op 1>, <op 2>
 status = op_1 - op_2

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

6.3 TEQ : 测试等价

  TEQ{条件}{P}  <op 1>, <op 2>
  Status = op_1 EOR op_2

TEQ 类似于 TST。区别是这里的概念上的计算是 EOR 而不是 AND。这提供了一种查看两个操作数是否相同而又不影响进位标志(不象 CMP 那样)的方法。加上 P 后缀的 TEQ 还可用于改变 R15 中的标志(在 26-bit 模式中)。

6.4 TST : 测试位

TST{条件}{P}  <op 1>, <op 2>
Status = op_1 AND op_2

TST 类似于 CMP,不产生放置到目的寄存器中的结果。而是在给出的两个操作数上进行操作并把结果反映到状态标志上。使用 TST 来检查是否设置了特定的位。操作数 1 是要测试的数据字而操作数 2 是一个位掩码。经过测试后,如果匹配则设置 Zero 标志,否则清除它。

 TST     R0, #%1                 ; 测试在 R0 中是否设置了位 0。 

七、移位指令

  • LSL    逻辑左移
  • ASL    算术左移
  • LSR    逻辑右移
  • ASR    算术右移
  • ROR   循环右移
  • RRX    带扩展的循环右移  

移位操作在 ARM 指令集中不作为单独的指令使用,它是指令格式中是一个字段,在汇编语言中表示为指令中的选项。

如果数据处理指令的第二个操作数或者单一数据传送指令中的变址是寄存器,则可以对它进行各种移位操作。如果数据处理指令的第二个操作数是立即值,在指令中用 8 位立即值和 4 位循环移位来表示它,所以对大于 255 的立即值,汇编器尝试通过在指令中设置循环移位数量来表示它,如果不能表示则生成一个错误。在逻辑类指令中,逻辑运算指令由指令中 S 位的设置或清除来确定是否影响进位标志,而比较指令的 S 位总是设置的。在单一数据传送指令中指定移位的数量只能用立即值而不能用寄存器。

7.1 逻辑或算术左移

  Rx, LSL #n  
  Rx, ASL #n 
  Rx, LSL Rn 
  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 

7.2 逻辑右移

  Rx, LSR #n
  Rx, LSR Rn

它在概念上与左移相对。把所有位向更低有效位方向移动。如果逻辑类指令中 S 位被设置了,则把最后被移出最右端的那位放置到进位标志中。它同于 BASIC 的 register = value >>> shift。 

7.3 算术右移

  Rx, ASR #n 
  Rx, ASR Rn

类似于 LSR,但使用要被移位的寄存器(Rx)的第 31 位的值来填充高位,用来保护补码表示中的符号。如果逻辑类指令中 S 位被设置了,则把最后被移出最右端的那位放置到进位标志中。它同于 BASIC 的 register = value >> shift。 

7.4 循环右移

  Rx, ROR #n 
  Rx, ROR Rn

循环右移类似于逻辑右移,但是把从右侧移出去的位放置到左侧,如果逻辑类指令中 S 位被设置了,则同时放置到进位标志中,这就是位的‘循环’。一个移位量为 32 的操作将导致输出与输入完全一致,因为所有位都被移位了 32 个位置,又回到了开始时的位置! 

7.5 带扩展的循环右移

 Rx, RRX

这是一个 ROR#0 操作,它向右移动一个位置 - 不同之处是,它使用处理器的进位标志来提供一个要被移位的 33 位的数量。

八、伪指令

ARM 汇编伪指令主要包括ADR、ADRL、ALIGN、EQUx、.extern、.text等等,这里只介绍一部分。下面展示一部分代码:

.extern main
.text
.global _start
_start:

8.1 .extern

.extern定义一个外部符号(可以是变量也可以是函数),上面的代码表示本文件中引用的main是一个外部函数。调用的时候可以遍访所有文件找到main函数并且使用它。

8.2 .text

.text表示下面的语句都属于代码段。

8.3 .global

.global将程序中某个标号定义为全局的。.global _start 让_start符号成为全局可见的标识符,这个符号可以被当前源文件以外的其他文件使用也可以被链接脚本(链接器)使用,_start仅仅是一个标号对应一个地址并不是C中的一个变量。

使用.global之后,这样链接器就知道跳转到程序中的什么地方并开始执行。一般我们在链接文件中会这样使用_start:

/*  s3c2440链接脚本   */
OUTPUT_ARCH(arm)
ENTRY(_start)       
SECTIONS
{
   ...
}

此外,如果start.S汇编中想引入引入lowleverl_init.S文件中的函数,只需要在lowleverl_init.S中使用.global声明即可;

.global lowlevel_init
lowlevel_init:
    /* memory control configuration */
    /* make r0 relative the current location so that it */
    /* reads SMRDATA out of FLASH rather than memory ! */
    ldr     r0, =SMRDATA
    ldr    r1, =CONFIG_SYS_TEXT_BASE
    sub    r0, r0, r1
    ldr    r1, =BWSCON    /* Bus Width Status Controller */
    add     r2, r0, #13*4
0:
    ldr     r3, [r0], #4
    str     r3, [r1], #4
    cmp     r2, r0
    bne     0b

    /* everything is fine now */
    mov    pc, lr

start.s中直接调用:

    bl    lowlevel_init

8.4 .DCx初始化数据存储

 DCx <值>

没有 DCx 指令。‘x’表示一个可能的范围。它们是: 

   DCB    预备一个字节(8 位值)
   DCW    预备一个半字(16 位值)
   DCD    预备一个字(32 位值)
   DCS    按给出的字符串的要求预备直到 255 个的字符

例如:

   .start_counter
    DCB     1

   .pointer
     DCD     0

   .error_block
     DCD     17
     DCS     "Uh-oh! It all went wrong!" + CHR$0
     ALIGN

8.5 EQUx : 初始化数据存储

 EQUx <值>

没有 EQUx 指令,‘x’表示一个可能的范围。它们是: 

   EQUB   预备一个字节(8 位值)
   EQUW   预备一个半字(16 位值)
   EQUD   预备一个字(32 位值)
   EQUS   按给出的字符串的要求预备直到 255 个的字符

简单的理解,除了名字不同之外与DCx 完全一样。

九、汇编指令的执行条件

大多数ARM指令都可以条件执行,即根据CPSR寄存器中的条件标志位决定是否执行该指令;如果条件不满足,该指令相当于一条nop指令。

每天ARM指令包含4位的条件码域,这表明可以定义16个执行条件。可以将这些执行条件的助记符附加在汇编指令后,比如MOVEQ、MOVGT。这16个条件码和它们的助记符如下表所示:

条件码 助记符 含义 CPSR中条件标志位
0000 EQ 相等 Z=1
0001 NE 不相等 Z=0
0010 CS/HS 无符号数大于/等于 C=1
0011 CC/LO 无符号数小于 C=0
0100 MI 负数 N=1
0101 PL 非负数 N=0
0110 VS 上溢出 V=1
0111 VC 没有上溢出 V=0
1000 HI 无符号数大于 C=1且Z=0
1001 LS 无符号数小于等于 C=0或Z=1
1010 GE 带符号数大于等于 N=1、V=1或N=0、V=0
1011 LT 带符号数小于 N=1、V=0或N=0、V=1
1100 GT 带符号数大于 Z=0且N=V
1101 LE 带符号数小于/等于 Z=1或N!=V
1110 AL 无条件执行 -
1111 NV 从不执行 -

标志的意义:

  • N   Negative   如果结果是负数则置位
  • Z   Zero          如果结果是零则置位
  • C  Carry         如果发生进位则置位
  • V  Overflow    如果发生溢出则置位

十、注释

在GNU ARM嵌入式汇编源程序中的注释方式有:

  • /**/ 块注释,跟C语言的一样;
  • // 行注释,跟C语言的一样;
  • @ 行注释;
  • # 行注释;

另外,汇编指令大小写也是可以混合的。

十一、C与汇编混合编程

11.1 汇编中调用C语言

ldr pc,=C语言函数名

11.2 C语言中调用汇编

在汇编语言中声明为全局标识符:

.global asm

这样在C语言中就可以直接调用:

asm();

11.3 C语言中嵌入汇编

__asm__(
     汇编部分语句
    :输出部分
    :输入部分
    :破坏描述部分       
);

注:C内嵌汇编以关键字”__asm__”或”asm”开始,下辖四个部分,各部分之间使用":"分开, 第一部分是必须写的,后面三部分是可以省略,但是分号:不能省略;

  • 汇编语句部分:汇编语句的集合,可以包含多条汇编语句,每条语句之间需要使用换行符 “\n”隔开或使用分号“ ; ”隔开;
  • 输出部分:在汇编中被修改的C变量列表;
  • 输入部分作为参数输入到汇编中的变量列表;
  • 破坏描述部分执行汇编指令会破坏的寄存器描述;

示例1,向cp15 c1寄存器写入数值:

void write_p15_c1 (unsigned long value)
{
__asm__(
"mcr p15, 0, %0, c1, c0, 0\n"   @%0是一个占位符,被输入部分的寄存器替代
:
: "r" (value)             @编译器选择一个R*寄存器(通用寄存器)
: "memory");
}

示例2:向cp15 c1寄存器读出数值:

unsigned long read_p15_c1 (void)
{
unsigned long value;
__asm__(
"mrc p15, 0, %0, c1, c0, 0\n"  @%0是一个占位符,被输出部分的寄存器替换
: "=r" (value)           @ ’=‘ 表示只写操作数,用于输出部分
:
: "memory");
return value;
}

对于硬件寄存器操作一般加上volatile关键字,使用volatile来告诉编译器,不要对接下来的这部分代码优化:

__asm__ volatile(
    ...   
)

示例3:

#define GPKCON 0x7f008800
#define GPKDAT 0x7f008808 
int gboot_main()
{
     __asm__(
            "ldr r1, =0x11110000\n"
            "str r1, [%0]\n"
          
            "ldr r1, =0xa0\n"
            "str r1, [%1]\n"
            :
            :"r"(GPKCON),"r"(GPKDAT)
             :"r1"
             );
     return 0;
}

参考文章

[1] ARM汇编手册

posted @ 2021-05-30 16:06  大奥特曼打小怪兽  阅读(1409)  评论(0编辑  收藏  举报
如果有任何技术小问题,欢迎大家交流沟通,共同进步