转自http://www.cnblogs.com/huhu0013/p/4103024.html
ARM指令教程
ARM汇编程序特点:
l 所有运算处理都是发生通用寄存器(一般是R0~R14)的之中.所有存储器空间(如C语言变量的本质就是一个存储器空间上的几个BYTE).的值的处理,都是要传送到通用寄存器来完成.因此代码中大量看到LDR,STR指令来传送值.
l ARM汇编语句中.当前语句很多时候要隐含的使用上一句的执行结果.而且上一句的执行结果,是放在CPSR寄存器里,(比如说进位,为0,为负…)
CMP R0,R1
BNE NoMatch
比如上一句,BNE隐含的使用的上一句CMP执行结果.NE后缀表示使用Z标志位.两句合起来的意思就是,如果R0,R1的值不相等,就跳转到NoMatch处执行.
注意,PC=R15,CPSR=R16,
ARM伪指令不是必须的,但是一个完整没有伪指令几乎很难写出来.
n 比如一个程序至少包含READONLY AREA和ENTRY,否则CPU都无法知道从哪里开始运行
l ARM的属于RISC,指令并不多,但是可以带后缀表示扩展出不同用法,这里与X86汇编完全不同风格
n 如BNE实际上是B指令的变种,本质还同一类指令.只是多一个对CPSR的Z标志位的判断。
ARM常用指令,伪指令
ARM常用指令并不太多,因此使用阅读ARM汇编代码,并不太困难.以下是使用频率最高的指令和伪指令,并不是完整的指令集的教材。详细指令参见参考资料。
l B,BL
l MOV,MVN
l LDR,STR
l ADD,SUB,ADC,SBC,MUL
l AND,ORR,XOR,TST,BIC
l CMP
l LDM/STM
l nop
1. 跳转语句 B,BL
程序流程的跳转,在 ARM 程序中有两种方法可以实现程序流程的跳转指令用于实现
l 使用专门的跳转指令 B
l 直接向程序计数器PC 写入跳转地址值
n 这是几乎是任何一种CPU必备的机器,PC表示CPU当前执行语句位置,改变PC的值,相当于实现程序跳转
n 如实现类似C语言的Return 语句,就是用MOV PC,LR
n 这里可以在任意4G的空间进行跳转
B指令(Branch)表示无条件跳转.
B main ;跳转到标号为main地代码处
BL指令(Branch with Link)表示带返回值的跳转.
BL比B多做一步,在跳转前,BL会把当前位置保存在R14(即LR寄存器),当跳转代码结束后,用MOV PC,LR指令跳回来,这实际上就是C语言执行函数的用法,
汇编里调子程序都用BL,执行完子函数后,可以用MOV PC,LR跳回来.
BL delay ;执行子函数或代码段delay ,delay可以为C函数.
与MOV PC,XXX能在4G空间跳转不同,B语句只能32M空间跳转,(因为偏移量是一个有符号26bit的数值=32M)
2. 传输数据指令MOV,MVN
n MOV(MOVE)指令可完成从另一个寄存器、被移位的寄存器或将一个立即数加载到目的寄存器
MOV R0,R1 ; 把R1的值传到R0
MOV R3,#3 ;把常数3传给R3,MOV中用#表示常数,这个值不能超过
n MVN( MOVE Negative)取反后再传值,比MOV多了一步取反
MVN R0, #0 ;把0取反(即-1)传给R0
MVN R1,R2 ;把R2的值取反传给R1
3. 加载/存储指令,LDR,STR
n LDR,STR是用于寄存器和外部存储器交换数据指令,注意与MOV的区别,后面只在寄存器或常数交换.
u LDR/STR可以采用多种寻址方式,以下只举出使用频率最高几种用法
n LDR(load)用于把一个32Bit的WORD数据从外部存储空间装入到寄存器中
LDR R0,[R1]; R1的值当成地址,再从这个地址装入数据到R0 (R0=*R1)
LDR R1,=0x30008000 ; 把地址0x30008000的值装入到R1中,LDR中用常数要用=打头.(注意跟MOV的区别,MOV是#)
ldr r0, =(0<<13)|(0<<12)|(0<<10)|(0<<9)|(0<<8)|(1<<6)|(1<<5)|(1<<4)|(1<<1)|(1<<0)
用位与的方法赋值
n STR(Store) 用于把一个寄存器的值存入外部存储空间,是LDR的逆操作.
STR R0,[R1] ; 把R0的值,存入到R1对应地址空间上(*R1 = R0)
STR R0,=0x30008000 ;把R0中值存入到地址0x30008000
S2C2440的中CPU内核以外的模块的控制寄存器空间也是属于外部空间,所以也得用如下指令LDR R0,=GPFDAT
4. 算术运算指令,ADD/ADC,SUB/SBC ,MUL
n ADD加法指令
ADD R0,R1,R2; R0=R1+R2
ADD R0,R1,#3 ;R0=R1+3
n ADC带进位加法指令,即除了加两个数以外,还要把CPSR的C值也要带进来
u 通常用于大数(超过32Bit整数)相加,这时单用ADD不能处理,必须折成两步,其中一步用ADC.
u 以下是做64Bit的加法
ADDS R0,R1,R2; R0=R1+R2,ADDS中S表示把进位结果写入CPSR
ADC R5,R3,R4 ;R5=R3+R4+C
n SUB减法指令
SUB R0,R1,R2; R0=R1-R2
SUB R0,R1,#3 ;R0=R1-3
n SBC带进位减法指令,即除了加两个数以外,还要把CPSR的C值也要带进来,类似ADC
u 以下是做64Bit的减法
SUBS R0,R1,R2; R0=R1-R2,SUBS中S表示把进位结果写入CPSR
SBC R5,R3,R4 ;R5=R3-R4-C
n MUL 乘法指令
MUL R0,R1,R2; R0=R1*R2
MUL R0,R1,#3 ;R0=R1*3
5. 位操作指令 AND,ORR, TST,BIC
n AND位与指令
AND R0,R1,R2; R0=R1 & R2
AND R0,R1,#0xFF ;R0=R1 & 0xFF
n ORR位或指令
ORR R0,R1,R2; R0=R1 | R2
ORR R0,R1,#0xFF ;R0=R1 | 0xFF
n TST测试某一位是否为1,并把结果写入CPSR,供下一句使用
TST R1,#0xffe; 等同于if(R1 & 0xffe)
TST R1,#%1;测试最低位是否为1,%表示二进制
n BIC清位操作
BIC R0,R0,#0xF ; 等同于 R0 &=~(0xF)
BIC R0,R0,#%1011 ; 该指令清除 R0 中的位 0 1 3,其余的位保持; %表示是二进制,0x表示十六进制
6. 比较指令 CMP
n CMP比较两个操作数,并把结果存入CPSR供下一句语句使用
CMP R0,R1; 比较R0,R1
7. 多寄存器语句传输指令,LDM,STM
类似于一次传一个BUFFER到寄存器当中,或反过来.后面一般要接一个地址改变方法
n LDM 从BUFFER传数据多个寄存器传输数据到
LDMIA R0! ,{R3-R9} ;加R0指向的地址上连续空间的数据,保存到R3-R9当中,!表示R0值更新,IA后缀表示按WORD递增
LDMFD SP!,{R0-R7,PC}^;恢复现场,异常处理返回,^表示不允许在用户模式下使用。
n STM 从寄存器列表向存储空间传值。
STMIA R1!,{R3-R9} ;将R3-R9的数据存储到R1指向的地址上,R1值更新。
STMFD SP!,{R0-R7,LR}; 现场保存,将R0~R7,LR入栈
stmfd sp!,{r8-r9}
,把SP寄存器对庆的地址的值存到R8,R9当中.!表示最后的值写入SP中。Fd表示
8. ARM指令的变形
大部分指令后位可以接 <cond>与S两个特殊位来表示,对CPSR特殊的一些判断
S,表示当前指令执行后把结果改写CPSR
subs,Adds
<Cond>取决于具体条件,只有CPSR满足指定条件时才指这一指令
BEQ 实际上B+ EQ的条件执行.
addne 表示ADD +NE 才开始加.
9. ARM指令的寻址方式
寻址方式是根据指令中给出的地址码来定位真实的地址,ARM中有9种寻址方法
l 寄存器寻址
直接用寄存器编号来寻址,最为常用
MOV R1,R2 ;R2->R1
l 立即数寻址
即指令中的地址码是操作数本身,可以立即取出使用,立即数前带一个#表示,否则表示一个地址
SUBS R0,R0,#1 ;R0 -1 ->R0
注意与SUBS R0,R0,1区别
l 寄存器偏移寻址
这是ARM特有的寻址模式,当第2操作数是寄存器,在执行操作之前,可以做一次移位操作
MOV R0,R2,LSL #3 ;R2的逻辑左移3位,结果放入R0,即R0=R2*8
ANDS R1,R1,R2,LSL R3;RS的值左移R3位,然后和R1相与操作,结果放入R1
移位操作有LSL (逻辑左移),LSR(逻辑右移) ,ASR(算术右移),ROR(循环右移)RRX带扩展的循环右移
l 寄存器间接寻址
即寄存器中值是一个地址,用[]来取出定位到地址当中
LDR R2,[R0] ;把R0的值当成地址,取出相应值,赋给R2
l 基址寻址
把寄存器的地址值加上一个偏移量
LDR R2,[R3,#0x0F]; R3中的值加上0x0F,从这个地址取出值赋给R@
l 相对寻址
基址寻址的变形,由PC寄存器提供基准地址,指令中地址段作为偏移量.两者相加即是有效地址,以下是BL采用相对寻址
BL NEXT
…
NEXT
…
MOV PC,LR ;从子程序返回
10. ADS ARM的伪指令
类似于C语言的宏,由汇编程序预处理.
l 符号定义指令
全局变量定义 GBLA ,GBLL,GBLS
局域变量定义 LCLA,LCLL,LCLS
变量赋值SETA,SETL,SETS
其中上述伪指令中,最后面的A表示给一个算术变量赋值,L表示用于给一个逻辑变量赋值,s表示给一个字符串赋值
GBLL codedbg; 声明一个全局的逻辑变量
Codebg SETL {TRUE} ; 设置变量为{TRUE}
LCLA bitno; 声明一个算术变量
Bitno SETA 8 ;设变量值为8
l 数据定义伪指令
n SPACE 定义一个内存空间,并用0初始化
{label } SPACE expr
DataBuf SPACE 100 ;定义100字节长空间, unsigned char DataBuf[100];
n DCB 定义一个连续字节内存空间,用伪指令的表达式expr来初始化.一般可以用定义数据表格,或文字字符串.(这时等同于SETS),用于初始二进制BUFFER
{label} DCB expr{,expr …}
Dest DCB -120,20,36,55 ;等同于 unsigned char Dest[]={-120,20,36,55};
n DCU定义的一段字的内存空间(DCB是字节),并用后面表达式初始化
_RESET DCU Reset ; 等同于 DWORD _RESET[]={Reset};
n MAP定一个结构化内存,相当于定义一个C结构
n FILED 定义一个结构化内存的成员
MAP 0x00,R9 ; 定义内存表,地址为R9
Timer FIELD 4 ; 定义数据域Timer,长为4字
Attrib FIELD 4 ; 定义数据域Attrib,长为4字
String FILED 100 ; 定义数据域String ,长为100字
相当于C语言的定义:
struct {
DWORD Timer ;
DWORD Attrib ;
Char String[100];
} R9;
11. 杂项的伪指令
n 字节对齐 ALIGN
ALIGN; 声明4字节对齐
n 定义一个数字常量定义 EQU
NAME EQU expr {type}
PLLCON EQU 0xE01FC080;定义PLLCON,类似于C的宏或C++的常量
n 包含文件 GET和INCLUDE
INCLUDE lpc2106.inc
n NOP 空指令
在汇编时会被ARM的空操作代替,比如MOV R0,R0,一般用于延时与占位。
n 声明一个外部符符号 IMPORT,EXTERN
IMPORT,EXTERN 向外部导入一个符号,一般是外部程序全局变量
n 条件编译:[]。类似于C的#ifdef 之类定义。
格式 :[ 条件表达式
满足条件分支
|
不满足条件分支
]
示例1:
[ ENTRY_BUS_WIDTH=32 ;类似#if ENTRY_BUS_WIDTH=32
b ChangeBigEndian ;DCD 0xea000007
] ; 类似#endif
示例2: [ CLKDIV_VAL>1 ; 类似#if CLKDIV_VAL>1
bl MMU_SetAsyncBusMode
|;类似#else
bl MMU_SetFastBusMode ; default value.
]; 类似#endif
示例3 [ THUMBCODE 类似#ifdef THUMBCODE
bx lr
| ;类似#else
mov pc,lr
] ;类似#endif
n 段定义 AREA
n 指令集定义 CODE16和CODE32
指示是Thumb 指令集(压缩指令集,每个指令16位)。还是普通32位指令集
n 汇编结束:END
n 程序入口ENTRY
一个基本ARM程序结构
ARM汇编程序结构
源代码由文本文件组成.按照汇编的编译器不同,分为两大量,一类是ADS的汇编程序,一类是GNU汇编格式,两者在指令集是完成一样,但是在伪指令.程序结构等方法各不同相同.本节主要是讲解ADS汇编格式.
ADS汇编程序,主要包含如下几类程序
n 汇编源程序,后缀名是.S
n 汇编包含文件,后缀名是.inc
n 如果是与C混和编程..C,.h也能识别
ARM 汇编语句格式
[标号] <指令|条件|S> <操作数> [;注释]
l 所有标号顶格写,而指令和伪指令不能顶格写
l 标识符(标号,指令)大小写敏感,所以要在标号和指令时书写一致,一般伪指令,指令,寄存器名可以全部为大写
l 注释以;开头,可以顶格写
l 可以使用\来分行写太长语句
l 变量,常量的定义必须在一行顶格写
常量的书写
l 数字常量
在程序中直接写数字 ,十进制 12,256,十六进制 0x1228,
l 字符常量
类似于C的定义,用SETS来定义字符常量
HELLO SETS “hello,the world!”
l 逻辑常量
逻辑真为{TRUE},逻辑假为{FLASE}
Testno SETS {TURE}
汇编程序的段定义
任何一个程序都要分段,C语言一般由编译器自动分段,(分成.Text,.Data段之类),但在汇编程序这样的底层程序中,由开发者自行分段. 它包含如下段
l 至少一个代码段,并且代码段是只读的,对应(.Text)
l 数据段可以没有,也可以有多个.
l 每一个段用END结束
AREA 定义一个段
AREA 段名 属性1, 属性2,
例子:AREA Init,CODE,READONLY
l ENTRY 指明一个段的入口
l END结束一个段
ABC EQU 0x12
AREA Example,CODE,READONLY
ENTRY
START MOV R7,#10
MOV R6,#5
ADD R6,R6,R7
B
END
ADS ARM汇编程序格式要求
1. 所有标号要顶格写.
2. 所有指令不能顶格写,一般插入Tab键在行首
3. ADS ARM中,是大小写敏感的.建议标号,指令,伪指令,寄存器名全部为大写
4. 注释采用;打头
5. 每个程序至少有一个AREA在代码里(READONLY)
6. 每个段都要用END结束(不能顶格)
最常见几个伪指令 AREA,EQU,DCB,END ,ENTRY,EXPORT,GOBEL,IMPORT,
常见伪定义
l DCB 定义字符中
Str DCB “hello, world “
16位数据操作指令
名字 | 功能 |
---|---|
ADC | 带进位加法(ADD with Carry) |
ADD | 加法 |
AND | 按位与。这里的按位与和C的”&”功能相同 |
ASR | 算术右移(Arithmetic Shift Right) |
BIC | 按位清零(把一个数跟另一个无符号数的反码按位与) |
CMN | 负向比较(把一个数跟另一个数据的二进制补码相比较) |
CMP | 比较(Compare,比较两个数并且更新标志) |
CPY | 把一个寄存器的值拷贝(COPY)到另一个寄存器中 |
EOR | 近位异或 |
LSL | 逻辑左移(Logic Shift Left) |
LSR | 逻辑右移(Logic Shift Right) |
MOV | 寄存器加载数据,既能用于寄存器间的传输,也能用于加载立即数 |
MUL | 乘法(Multiplication) |
MVN | 加载一个数的 NOT值(取到逻辑反的值) |
NEG | 取二进制补码 |
ORR | 按位或 |
ROR | 循环右移 |
SBC | 带借位的减法 |
SUB | 减法(Subtraction) |
TST | 测试(Test,执行按位与操作,并且根据结果更新Z) |
REV | 在一个32位寄存器中反转(Reverse)字节序 |
REVH | 把一个32位寄存器分成两个(Half)16位数,在每个16位数中反转字节序 |
REVSH | 把一个32位寄存器的低16位半字进行字节反转,然后带符号扩展到32位 |
SXTB | 带符号(Signed)扩展一个字节(Byte)到 32位 |
SXTH | 带符号(Signed)扩展一个半字(Half)到 32位 |
UXTB | 无符号(Unsigned)扩展一个字节(Byte)到 32位 |
UXTH | 无符号(Unsigned)扩展一个半字(Half)到 32位 |
16位转移指令
名字 | 功能 |
---|---|
B | 无条件转移(Branch) |
B | 有条件(Condition)转移 |
BL | 转移并连接(Link)。用于呼叫一个子程序,返回地址被存储在LR中 |
CBZ | 比较(Compare),如果结果为零(Zero)就转移(只能跳到后面的指令) |
CBNZ | 比较,如果结果非零(Non Zero)就转移(只能跳到后面的指令) |
IT | If-Then |
16位存储器数据传送指令
名字 | 功能 |
---|---|
LDR | 从存储器中加载(Load)字到一个寄存器(Register)中 |
LDRH | 从存储器中加载半(Half)字到一个寄存器中 |
LDRB | 从存储器中加载字节(Byte)到一个寄存器中 |
LDRSH | 从存储器中加载半字,再经过带符号扩展后存储一个寄存器中 |
LDRSB | 从存储器中加载字节,再经过带符号扩展后存储一个寄存器中 |
STR | 把一个寄存器按字存储(Store)到存储器中 |
STRH | 把一个寄存器存器的低半字存储到存储器中 |
STRB | 把一个寄存器的低字节存储到存储器中 |
LDMIA | 加载多个字,并且在加载后自增基址寄存器 |
STMIA | 存储多个字,并且在存储后自增基址寄存器 |
PUSH | 压入多个寄存器到栈中 |
POP | 从栈中弹出多个值到寄存器中 |
其它16位指令
名字 | 功能 |
---|---|
SVC | 系统服务调用(Service Call) |
BKPT | 断点(Break Point)指令。如果调试被使能,则进入调试状态(停机)。 |
NOP 无操作(No Operation) | |
CPSIE | 使能 PRIMASK(CPSIE i)/FAULTMASK(CPSIE f)——清零相应的位 |
CPSID | 除能 PRIMASK(CPSID i)/FAULTMASK(CPSID f)——置位相应的位 |
32位数据操作指令
名字 | 功能 |
---|---|
ADC | 带进位加法 |
ADD | 加法 |
ADDW | 宽加法(可以加 12 位立即数) |
AND | 按位与(原文是逻辑与,有误——译注) |
ASR | 算术右移 |
BIC | 位清零(把一个数按位取反后,与另一个数逻辑与) |
BFC | 位段清零 |
BFI | 位段插入 |
CMN | 负向比较(把一个数和另一个数的二进制补码比较,并更新标志位) |
CMP | 比较两个数并更新标志位 |
CLZ | 计算前导零的数目 |
EOR | 按位异或 |
LSL | 逻辑左移 |
LSR | 逻辑右移 |
MLA | 乘加 |
MLS | 乘减 |
MOVW | 把 16 位立即数放到寄存器的底16位,高16位清0 |
MOV | 加载16位立即数到寄存器(其实汇编器会产生MOVW——译注) |
MOVT | 把 16 位立即数放到寄存器的高16位,低 16位不影响 |
MVN | 移动一个数的补码 |
MUL | 乘法 |
ORR | 按位或(原文为逻辑或,有误——译注) |
ORN | 把源操作数按位取反后,再执行按位或(原文为逻辑或,有误——译注) |
RBIT | 位反转(把一个 32 位整数先用2 进制表达,再旋转180度——译注) |
REV | 对一个32 位整数做按字节反转 |
REVH/REV16 | 对一个32 位整数的高低半字都执行字节反转 |
REVSH | 对一个32 位整数的低半字执行字节反转,再带符号扩展成32位数 |
ROR | 圆圈右移 |
RRX | 带进位的逻辑右移一格(最高位用C 填充,且不影响C的值——译注) |
SFBX | 从一个32 位整数中提取任意的位段,并且带符号扩展成 32 位整数 |
SDIV | 带符号除法 |
SMLAL | 带符号长乘加(两个带符号的 32 位整数相乘得到 64 位的带符号积,再把积加到另一个带符号 64位整数中) |
SMULL | 带符号长乘法(两个带符号的 32 位整数相乘得到 64位的带符号积) |
SSAT | 带符号的饱和运算 |
SBC | 带借位的减法 |
SUB | 减法 |
SUBW | 宽减法,可以减 12 位立即数 |
SXTB | 字节带符号扩展到32位数 |
TEQ | 测试是否相等(对两个数执行异或,更新标志但不存储结果) |
TST | 测试(对两个数执行按位与,更新Z 标志但不存储结果) |
UBFX | 无符号位段提取 |
UDIV | 无符号除法 |
UMLAL | 无符号长乘加(两个无符号的 32 位整数相乘得到 64 位的无符号积,再把积加到另一个无符号 64位整数中) |
UMULL | 无符号长乘法(两个无符号的 32 位整数相乘得到 64位的无符号积) |
USAT | 无符号饱和操作(但是源操作数是带符号的——译注) |
UXTB | 字节被无符号扩展到32 位(高24位清0——译注) |
UXTH | 半字被无符号扩展到32 位(高16位清0——译注) |
32位存储器数据传送指令
名字 | 功能 |
---|---|
LDR | 加载字到寄存器 |
LDRB | 加载字节到寄存器 |
LDRH | 加载半字到寄存器 |
LDRSH | 加载半字到寄存器,再带符号扩展到 32位 |
LDM | 从一片连续的地址空间中加载多个字到若干寄存器 |
LDRD | 从连续的地址空间加载双字(64 位整数)到2 个寄存器 |
STR | 存储寄存器中的字 |
STRB | 存储寄存器中的低字节 |
STRH | 存储寄存器中的低半字 |
STM | 存储若干寄存器中的字到一片连续的地址空间中 |
STRD | 存储2 个寄存器组成的双字到连续的地址空间中 |
PUSH | 把若干寄存器的值压入堆栈中 |
POP | 从堆栈中弹出若干的寄存器的值 |
32位转移指令
名字 | 功能 |
---|---|
B | 无条件转移 |
BL | 转移并连接(呼叫子程序) |
TBB | 以字节为单位的查表转移。从一个字节数组中选一个8位前向跳转地址并转移 |
TBH | 以半字为单位的查表转移。从一个半字数组中选一个16 位前向跳转的地址并转移 |
其它32位指令
名字 | 功能 |
---|---|
LDREX | 加载字到寄存器,并且在内核中标明一段地址进入了互斥访问状态 |
LDREXH | 加载半字到寄存器,并且在内核中标明一段地址进入了互斥访问状态 |
LDREXB | 加载字节到寄存器,并且在内核中标明一段地址进入了互斥访问状态 |
STREX | 检查将要写入的地址是否已进入了互斥访问状态,如果是则存储寄存器的字 |
STREXH | 检查将要写入的地址是否已进入了互斥访问状态,如果是则存储寄存器的半字 |
STREXB | 检查将要写入的地址是否已进入了互斥访问状态,如果是则存储寄存器的字节 |
CLREX | 在本地的处理上清除互斥访问状态的标记(先前由 LDREX/LDREXH/LDREXB做的标记) |
MRS | 加载特殊功能寄存器的值到通用寄存器 |
MSR | 存储通用寄存器的值到特殊功能寄存器 |
NOP | 无操作 |
SEV | 发送事件 |
WFE | 休眠并且在发生事件时被唤醒 |
WFI | 休眠并且在发生中断时被唤醒 |
ISB | 指令同步隔离(与流水线和 MPU等有关——译注) |
DSB | 数据同步隔离(与流水线、MPU 和cache等有关——译注) |
DMB | 数据存储隔离(与流水线、MPU 和cache等有关——译注) |