【嵌入式Linux+ARM】ARM体系结构与编程(ARM汇编指令)

时间:2021-10-22 18:47:55

自己的一些简单的总结,也是最常用的ARM汇编指令,之后也会不断的补充完善。

1. 汇编系统预定义的段名

.text    @代码段
.data   @初始化数据段
.bss    @未初始化数据段
需要注意的是,源程序中.bss段应该在.text之前。

2.定义入口点
汇编程序的缺省入口是 start标号,用户也可以在连接脚本文件中用ENTRY标志指明其它入口点。

.text

.global _start

_start:


3 .word用法

word expression就是在当前位置放一个word型的值,这个值就是expression 
举例来说,
 

_rWTCON: 

     .word 0x15300000 
就是在当前地址,即_rWTCON处放一个值
0x15300000 


4 .equ赋值操作,相当于c语言的宏定义

.equ MEM_CTRL_BASE, 0x48000000  //注意要加,号


5. 逻辑指令

AND―――――逻辑"与"操作指令

指令格式:
AND{cond}{S} Rd,Rn,operand2
AND指令将操作数operand2 与Rn 的值按位逻辑"与",结果存放到目的寄存器Rd 中。若设置S,则根据运算结果影响N、Z位,在计算第二操作数时,更新C位,不影响V位(指令ORR、EOR、BIC 对标志位的影响同AND 指令)。
指令示例:
ANDS R1,R1,R2  ;R1=R1&R2,并根据运算的结果更新标志位
AND R0,R0,#0x0F ;R0=R0&0x0F,取出R0最低4位数据。

ORR―――――逻辑"或"操作指令
指令格式:ORR{cond}{S} Rd,Rn,operand2 ORR指令将操作数operand2 与Rn 的值按位逻辑"或",结果存放到目的寄存器Rd 中。指令示例: 
ORRS R1,R1,R2 ;R1=R1|R2,并根据运算的结果更新标志位
ORR R0,R0,#0x0F ;R0=R0|0x0F,将R0最低4位置1,其余位不变。
 
BIC―――――位清除指令
指令格式:
BIC{cond}{S} Rd,Rn,operand2 
BIC指令将Rn 的值与操作数operand2 的反码按位逻辑"与",结果存放到目的寄存器Rd 中。
指令示例:BIC R0,R0,#0x0F ;将R0最低4位清零,其余位不变。

CMP―――――比较指令
指令格式:
CMP{cond} Rn,operand2
CMP指令用Rn的值减去操作数operand2 ,并将结果的状态(Rn 与operand2比较是大、小、相等)反映在CPSR中,以便后面的指令根据条件标志决定程序的走向。CMP指令与SUBS指令完成的操作一样,只是CMP指令只减,不存结果。
指令示例: 
cmp R0,R1 ;比较R0,R1 
beq stop ;R0=R1跳到stop
blt less ;R0<R1跳到Less 
Less:...
Stop:...

参考:

http://blog.csdn.net/denlee/article/details/2501182


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

1、相对跳转指令: b  bl 
不同之处在于: bl 指令除了跳转之外,还将返回地址( bl 的下一条指令的地址)保存在lr 寄存器中。
跳转范围:当前指令的前后32M
它们是与位置无关的指令。
示例:
        b    fun1
......
fun1:
        bl    fun2
......
fun2:
......


2、数据传送指令: mov ,地址读取伪指令: ldr
mov 指令可以把一个寄存器的值赋给另一个寄存器,或者把一个常数赋给寄存器。
例:
mov    r1,  r2
mov    r1,  #4096
mov 指令传送的常数必须能用立即数来表示。
当不知道一个数能否用立即数来表示时,可以使用 ldr 命令来赋值。 ldr 是伪指令,它不是真实存在的指令,编译器会把它扩展成真正的指令:如果该常数能用立即数来表示,则使用 mov 指令;否则编译时将该常数保存在某个位置,使用内存读取指令把它读出来。
例:
ldr    r1,  =4097
ldr 本意为“大范围的地址读取伪指令”,以下是获得代码的绝对地址:
例:
        ldr    r1,  =label
label:
......


3、内存访问指令: ldr  str  ldm  stm
ldr 指令既可能是大范围的地址读取伪指令,也可能是内存访问指令。当它的第二个参数前面有 “ = ” 时,表示伪指令,否则表示内存访问指令。
ldr 指令是从内存中读取数据到寄存器,str 指令把寄存器的值存储到内存中,它们操作的数据都是32位的。
例:
ldr    r1,  [r2,  #4]         // 将地址为r2+4的内存单元数据读取到r1中
ldr    r1,  [r2]                // 将地址为r2的内存单元数据读取到r1中
ldr    r1,  [r2],  #4         // 将地址为r2的内存单元数据读取到r1中,然后r2=r2+4
str    r1,  [r2,  #4]         // 将r1的数据保存到地址为r2+4的内存单元中
str    r1,  [r2]                // 将r1的数据保存到地址为r2的内存单元中
str    r1,  [r2],  #4         // 将r1的数据保存到地址为r2的内存单元中,然后r2=r2+4
ldm 和 stm 属于批量内存访问指令,只用一条指令就可以读写多个数据。格式为:
ldm  {cond}<addressing_mode>  <rn>{ ! }  <register  list>{ ^ }
stm  {cond}<addressing_mode>  <rn>{ ! }  <register  list>{ ^ }
其中,{cond} 表示指令的执行条件有:

条件码(cond)

助记符

含义

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

从不执行

-

大多数ARM指令都可以条件执行,即根据cpsr寄存器中的条件标志位决定是否执行该指令:如果条件不满足,该指令相当于一条nop指令。
每条ARM指令包含4位的条件码域,这表明可以定义16个执行条件。
cpsr条件标志位N、Z、C、V分别表示Negative、Zero、Carry、oVerflow。

<addressing_mode>
 表示地址变化模式,有4种方式:
ia (Increment After)        :事后递增方式。
ib (Increment Before)     :事先递增方式。
da (Decrement After)     :事后递减方式。
db (Decrement Before)  :事先递减方式。
<rn> 中保存内存的地址,如果后面加上感叹号,指令执行后,rn 的值会更新,等于下一个内存单元的地址。
<register  list> 表示寄存器列表,对于 ldm 指令,从 <rn> 所对应的内存块中取出数据,写入这些寄存器;对于 stm 指令,把这些寄存器的值写入 <rn> 所对应的内存块中。
{^} 有两种含义:
如果 <register  list> 中有 pc寄存器 ,它表示指令执行后,spsr寄存器的值将自动复制到cpsr寄存器中——这常用于从中断处理函数中返回
如果 <register  list> 中没有 pc寄存器 , {^} 表示操作的是用户模式下的寄存器,而不是当前特权模式的寄存器。
例:
HandleIRQ:                                             @中断入口函数
        sub    lr,  lr,  #4                                @计算返回地址
        stmdb    sp!,  { r0 - r12,  lr }             @保存使用的寄存器
                                                                @r0 - r12,  lr被保存在sp表示的内存中
                                                                @“!”使得指令执行后sp = sp - 14 * 4

        ldr    lr,  =int_return                          @设置调用IRQ_Handle函数后的返回地址
        ldr    pc,  =IRQ_Handle                    @调用中断分发函数
int_return:
        ldmia    sp!,  { r0 - r12,  pc }^            @中断返回,“^”表示将spsr的值复制到cpsr
                                                                 @于是从irq模式返回被中断的工作模式
                                                                 @“!”使得指令执行后sp = sp + 14 * 4


4、加减指令: add 、 sub
例:
add    r1,  r2,  #1       // r1 = r2 + 1
sub    r1,  r2,  #1       // r1 = r2  - 1


5、程序状态寄存器的访问指令: msr  mrs
ARM处理器有一个程序状态寄存器(cpsr),它用来控制处理器的工作模式、设置中断的总开关。
例:
msr    cpsr,  r0              // 复制r0到cpsr中
mrs    r0,  cpsr              // 复制cpsr到r0中

6、其他伪指令
.extern    :    定义一个外部符号(可以是变量也可以是函数)
.text        :    表示现在的语句都属于代码段
.global    :    将本文件中的某个程序标号定义为全局的


ARM-THUMB子程序调用规则:ATPCS
为了使C语言程序和汇编程序之间能够互相调用,必须为子程序间的调用制定规则,在ARM处理器中,这个规则被称为 ATPCS :ARM程序和THUMB程序中子程序调用的规则。基本的ATPCS规则包括寄存器使用规则数据栈使用规则参数传递规则

1、寄存器使用规则
子程序间通过寄存器 r0 ~ r3 来传递参数,这时可以使用它们的别名 a1 ~ a4 。被调用的子程序返回前无需恢复 r0 ~ r3 的内容。 
在子程序中,使用 r4 ~ r11 来保存局部变量,这时可以使用它们的别名 v1 ~ v8 。如果在子程序中使用了它们的某些寄存器,子程序进入时要保存这些寄存器的值,在返回前恢复它们;对于子程序中没有使用到的寄存器,则不必进行这些操作。在THUMB程序中,通常只能使用寄存器 r4 ~ r7 来保存局部变量。
寄存器 r12 用作子程序间scratch寄存器,别名为ip。
寄存器 r13 用作数据栈指针,别名为 sp 。在子程序中寄存器 r13 不能用作其他用途。它的值在进入、退出子程序时必须相等。
寄存器 r14 称为连接寄存器,别名为 lr 。它用于保存子程序的返回地址。如果在子程序中保存了返回地址(比如将 lr 值保存到数据栈中), r14 可以用作其他用途。
寄存器 r15 是程序计数器,别名为 pc 。它不能用作其他用途。

寄存器

别名

使用规则

r15

pc

程序计数器

r14

lr

连接寄存器

r13

sp

数据栈指针

r12

ip

子 程序内部调用的scratch寄存器

r11

v8

ARM状态局部变量寄存器8

r10

v7、s1

ARM状态局部变量寄存器7、在支持数据栈检查的ATPCS中为数据栈限制指针

r9

v6、sb

ARM状态局部变量寄存器6、在支持RWPI的ATPCS中为静态基址寄存器

r8

v5

ARM状态局部变量寄存器5

r7

v4、wr

ARM状态局部变量寄存器4、THUMB状态工作寄存器

r6

v3

ARM状态局部变量寄存器3

r5

v2

ARM状态局部变量寄存器2

r4

v1

ARM状态局部变量寄存器1

r3

a4

参数/结果/scratch寄存器4

r2

a3

参数/结果/scratch寄存器3

r1

a2

参数/结果/scratch寄存器2

r0

a1

参数/结果/scratch寄存器1


2、数据栈使用规则
数据栈有两个增长方向:向内存地址减小的方向增长时,称为 DESCENDING栈 ;向内存地址增加的方向增长时,称为 ASCENDING栈 。
所谓数据栈的增长就是移动栈指针。当栈指针指向栈顶元素(最后一个入栈的数据)时,称为 FULL栈 ;当栈指针指向栈顶元素(最后一个入栈的数据)相邻的一个空的数据单元时,称为 EMPTY栈 。
则数据栈可以分为4种:
FD:Full  Descending         满递减
ED:Empty  Descending    空递减
FA :Full  Ascending            满递增
EA:Empty  Ascending       空递增
ATPCS规定数据栈为FD类型,并且对数据栈的操作是8字节对齐的。使用 stmdb / ldmia 批量内存访问指令来操作FD数据栈。
使用stmdb命令往数据栈中保存内容时,先递减sp指针,再保存数据,使用ldmia命令从数据栈中恢复数据时,先获得数据,再递增sp指针,sp指针总是指向栈顶元素,这刚好是FD栈的定义。

3、参数传递规则
一般地,当参数个数不超过 4 个时,使用 r0 ~ r3 这4个寄存器来传递参数;如果参数个数超过 个,剩余的参数通过数据栈来传递。
对于一般的返回结果,通常使用 r0 ~ r3 来传递。
例:
假设CopyCode2SDRAM函数是用C语言实现的,它的数据原型如下:
int    CopyCode2SDRAM( unsigned  char  *buf,  unsigned  long  start_addr,  int  size )
在汇编代码中,使用下面的代码调用它,并判断返回值:
ldr    r0,  =0x30000000                 @1. 目标地址 = 0x30000000,这是SDRAM的起始地址
mov    r1,  #0                                 @2. 源地址 = 0
mov    r2,  #16*1024                     @3. 复制长度 = 16K
bl    CopyCode2SDRAM               @调用C函数CopyCode2SDRAM
cmp    a0,  #0                               @判断函数返回值