ARM两种编译环境
两种常用的ARM的编译开发环境
- ARM原生编译环境:ARM官方提供的原生编译环境,相关集成开发软件有ADS,Keil等,常用于ARM单片机开发
- GNU编译环境:由GNU的汇编器as,交叉编译器gcc,和链接器ld等组成,一般适用于交叉编译环境需求
以上两种编译环境,使用的指令集都是一致的, 只是语法格式有不同,也就是宏指令,伪指令,伪操作不一样
ARM原生环境搭建
使用 Keil μVision5 这款软件进行ARM32的汇编学习
下载地址:http://www.mcuzone.com/down/Software.asp?ID=10000503
ARM32系列命名
ARM产品 | ARM架构 |
---|---|
ARM7 | ARM v4 |
ARM9 | ARM v5 |
ARM11 | ARM v6 |
Cortex-A | ARM v7-A |
Cortex-R | ARM v7-R |
Cortex-M | ARM v7-M |
寄存器
在ARM32中一共有37个寄存器,其中包含16个通用寄存器(R0~R15)和1个状态寄存器 ,15个通用影子寄存器,5个状态影子寄存器
影子寄存器
如上图所示,在ARM32中一共有7中不同的处理器模式,分别为:用户模式(User),快速中断模式(FIQ),普通中断模式(IRQ),管理模式(Svc),数据访问中止模式(Abort),未定义指令中止模式(Und),系统模式(Sys)
但是在不同的模式下,同样的一个寄存器名称指向不同的物理寄存器,这些不同的物理寄存器就被称之为影子寄存器
由于这些影子寄存器也属于通用寄存器的范畴, 因此很多人也将ARM32的通用寄存器归纳为31个
语法
注释(两种方式)
声明一个代码段
数据表示
函数声明和调用
声明一个数据段
数据定义
字符串必须使用DCB进行定义
分配一块连续的内存空间
代码编写规范
- 所有指令和伪指令不允许顶格
- 所有变量和标签必须顶格
- 一般我们将伪指令大写,变量和标签小写
内存数据的读写
从内存中读取数据
代码语言:javascript复制LDR R0,[R1] ;将内存地址为R1的字数据读入寄存器R0。
LDR R0,[R1,R2] ;将内存地址为R1 R2的字数据读入寄存器R0。
LDR R0,[R1,#8] ;将内存地址为R1 8的字数据读入寄存器R0。
LDR R0,[R1,R2]! ;将内存地址为R1 R2的字数据读入寄存器R0,并将新地 址R1+R2写入R1。
LDR R0,[R1,#8]! ;将内存地址为R1 8的字数据读入寄存器R0,并将新地址 R1+8写入R1。
LDR R0,[R1],R2 ;将内存地址为R1的字数据读入寄存器R0,并将新地址 R1+R2写入R1。
LDR R0,[R1,R2,LSL#2]! ;将内存地址为R1+R2×4的字数据读入寄存器R0,并将新地址R1+R2×4写入R1。
LDR R0,[R1],R2,LSL#2 ;将内存地址为R1的字数据读入 寄存器R0,并将新地址R1+R2×4写入R1。
;从标号即为地址
LDR R0,label ;将标号对应的内容赋值给R0
复杂格式如LDR R0,[R1],R2,LSL#2 其中 []运算优先 2. 向内存中写入数据
代码语言:javascript复制STR R0,[R1],#8 ;将R0中的字数据写入以R1为地址的内存中,并 将新地址R1+8写入R1。
STR R0,[R1,#8] ;将R0中的字数据写入以R1+8为地址的内存中。
STRB R0,[R1] ;将寄存器R0中的字节数据写入以R1为地 址的内存中。
STRB R0,[R1,#8] ;将寄存器R0中的字节数据写入以R1+8为地址的存 储器中。
STR R0,[R1],#8 ;将R0中的字数据写入以R1为地址的内存中,并 将新地址R1+8写入R1。
STR R0,[R1,#8] ;将R0中的字数据写入以R1+8为地址的内存中。
STRB R0,[R1] ;将寄存器R0中的字节数据写入以R1为地 址的内存中。
STRB R0,[R1,#8] ;将寄存器R0中的字节数据写入以R1+8为地址的存 储器中。
LDR伪指令
这个指令和内存读取指令长的一模一样,如果我们在使用的时候加个等号,那么它就是另外一个指令
代码语言:javascript复制;如果不加等号 是内存读取功能
LDR R0,label ;获取标签所对应的内存数据赋给R0
;一旦加了等号,则变成了传送指令
LDR R0,=label ;将标号对应的实际物理地址值赋值给R0 此时它的作用和mov无异
LDR R0,='a' ;直接将字符数据传送给R0
实际上,加了等号的LDR
指令,刚好可以弥补mov
指令的不足, mov
指令只能传送由八个二进制位右移而得的数据,而LDR
则没有这个限制
也就是说如果我们想将一个数值传入寄存器,可以有两种方式:
代码语言:javascript复制;第一种
mov R1,#0X100
;第二种
ldr R1,=0x100
;mov指令的限制:只能传送由八个二进制位右移而得的数据, 也就相当于是两个十六进制数据,由于可以不断移位那么数据的大小可以伸缩,比如以下数据都可使用mov指令
0x00000058 0x00000580 0x00005800 0x00058000 0x00580000 0x05800000 0x58000000
;我们发现一个规律:mov指令只能传送最大两个十六进制空间的数据,注意是空间,这两个数据随意你移动,一旦不满足这个条件则无法传送,比如
0x00000581
LDR伪指令总结
作用:
- 弥补mov指令的不足
- 获取数据所对应的内存地址
ADR指令
那么除了通过LDR
伪指令来获取数据所在地址外还有一个指令也可以获取数据地址,那就是adr
指令,但这个指令只能获取当前段内数据的地址,段外数据无法获取,ldr
则没有这个限制
AREA test2,CODE
mov R3,#8
aaaa dcb 1,2,3
adr R0,aaaa ;获取aaaa首地址
END
段的拓展
段属性拓展
- 段读写属性
- READONLY 该段内存区域数据只能读取,不能写入,也就是如果使用内存读写指令,数据也写入不了
- READWRITE 该段内存区域可读可写,不仅可以使用内存读写指令,还可以在调试的时候直接在memory窗口双击修改
示例:
代码语言:javascript复制AREA code, CODE,READWRITE ;将代码段内存区域设置为可读可写状态,如果不写默认为只读
AREA code, DATA,READONLY ;将数据段内存区域设置为只读状态,如果不写默认为可读可写
段对齐属性 ALIGN
该属性会使得该段的基地址进行相应的偏移,ALIGN=3
表示基地址会在上一个段数据的基础之上偏移2^3=8个字节的位置
我们可以简单理解为,使用ALIGN这个属性可以让我们给上一个段预留除一部分缓冲区域,以ALIGN=2为例,当上一个段中的数据超过4个字节时,当前段基地器会向后再偏移4个字节,避免数据被覆盖,也就是说内存数据位置会进行重新分布,那么我们可以通过这个值来设置内存数据刷新频率,值越低,内存利用率越高,但是内存刷新频率也越高,负荷加重,反之,内存浪费越大,但是内存数据不需要频繁重新分布
除了在段属性中可以设置对齐之外,在指令中也可以插入ALGIN关键字:
代码中使用AGLIN时用空格代替等号,同时单位为字节
多个代码段入口区分
当我们在同一个源文件中定义两个代码段时,程序从哪个段当做执行入口呢?
这个时候我们需要指定程序的入口,使用伪指令entry
AREA test2,CODE
mov R0,#7
AREA test3,CODE
entry ;程序入口
mov R0,#6
mov R1,#0x00000100
str R0,[R1]
END
栈的操作
- 入栈
push {R0} ;将R0中的值存入栈内存中 相当于是STR R0,[R13,#-4]
入栈过程:
* 第一步:将栈顶指针往低地址偏移四个字节
* 第二步:将数据存入指针指向的内存空间
- 出栈
pop {R0} ;将栈顶中的值取出存入R0寄存器 相当于是LDR R0,[R13],#0x0004
出栈过程:
* 第一步:将栈顶指针指向的内容取出存入寄存器
* 第二步:将指针往高地址恢复四个字节
pop和push 本质上使用的是LDR和STR内存读写指令
对栈批量操作
如果想批量操作多个连续栈空间的话,直接使用逗号分隔开,连续标号的寄存器使用横杠分隔
代码语言:javascript复制push {R0,R4-R12,LR} ;大括号中寄存器从右往左LR R12...R4 R0依次存入
pop {R0,R4-R12,PC} ;从左往右取出
除了使用pop
和push
之外,可以使用STM
(store much)和LDM
(load much)指令
格式:
代码语言:javascript复制STM 起始地址/基地址寄存器,{寄存器名称,多个寄存器以逗号或者-分割} ;起始地址寄存器R0-R14任意选择
示例:
代码语言:javascript复制STMFD R13!,{R0,R4-R12,LR} ;将寄存器列表中的寄存器(R0,R4到 R12,LR)存入栈,。
等价于
push {R0,R4-R12,LR} ;大括号中寄存器从右往左存入
LDMFD R13!,{R0,R4-R12,PC} ;将栈内容恢复到寄存器(R0,R4到R12,LR)。
等价于
pop {R0,R4-R12,PC} ;从左往右取出
pop
和push
,它们内部也是转成STM
和LDM
指令:
批量存取指令扩展
相关后缀含义:
- IA:(Increase After):数据操作后基地址增4
- IB:(Increase Before):数据操作前基地址增4
- DA:(Decrease After):数据操作后基地址减4
- DB:(Decrease Before):数据操作前基地址减4
- FD: 满递减堆栈 (相当于STMDB LDMIA)
- FA: 满递增堆栈 (相当于STMIB LDMDA)
- ED: 空递减堆栈(相当于STMDA LDMIB)
- EA: 空递增堆栈 (相当于STMIA LDMDB)
我们在使用的时候,要么使用结合的形式比如STMDB LDMIA,要么直接使用封装形式比如STMFD LDMFD
满栈和空栈
栈的生长方式可以有四种: 满增栈、满减栈、空增栈、空减栈
- 满栈(full stack):栈指针指向下一个将要取出数据的位置,数据入栈时,栈顶指针先偏移再入栈,数据出栈是,先取数据,后指针偏移。
- 空栈(empty stack):栈指针指向下一个将要放入数据的位置,数据入栈时,先存数据后指针偏移,数据出栈时,先指针偏移,后数据取出
- 递增堆栈(ascending stack):堆栈由低地址向高地址生长。
- 递减堆栈(secending stack):堆栈由高地址向低地址生长。
X86和mips架构都是采用满递减堆栈方式处理栈空间,ARM架构四种方式均支持
内存批量读写示例:
代码语言:javascript复制mov R1,#4
mov R2,#5
mov R0,#0x00000008
stm R0,{R1,R2}
;以上四行代码表示 从0x00000008这个内存地址开始 将R1和R2中的数据依次存入
如果我想在上面的基础上再往后追加数据
代码语言:javascript复制mov R1,#6
mov R2,#7
stm R0,{R1,R2}
我们发现数据并没有追加,而是被覆盖了,因为R0
的值依然还是 0x00000008
,这个时候我们需要使用扩展指令,如下:
mov R1,#4
mov R2,#5
mov R0,#0x00000008
STMIA R0!,{R1,R2} ;只有在寄存器后加上!才能修改寄存器中的值
mov R1,#6
mov R2,#7
stm R0,{R1,R2}
事实上 STM指令如果不加后缀写法,默认使用的是STMIA指令,LDM指令默认使用LDMIA
多寄存器数据存放顺序
不管使用哪种扩展指令,皆为左低右高的形式,也就是左边的寄存器数据存放在低地址,右边的存放在高地址
代码语言:javascript复制STMIA R0!,{R1,R2} ;左边R1的内容放低地址,右边R2的内容放高地址
LDMDB R0!,{R1,R2} ;高地址数据放入R2,低地址数据放入R1
宏
宏匹配
代码语言:javascript复制 MACRO
$label putR0 $param
mov R0,$param
MEND
; 使用
putR0 #10
- 使用
- 语法格式
- 延伸
第一个$label
是干嘛用的呢,由于宏的内部处理方式的替换,为了避免标签名称的冲突,增加一个标识
;假如我要在宏匹配中定义一个函数fun, 当我调用两次的时候,会出现函数名重复的问题
MACRO
$label putR0 $param
fun
mov R0,$param
MEND
putR0 #10
putR0 #10
;那么如果要解决这个问题的话,我可以利用第一个替换参数如下:
MACRO
$label putR0 $param
$label
mov R0,$param
MEND
fun1 putR0 #10 ;函数名为fun1
fun2 putR0 #10 ;函数名为fun2
宏定义
代码语言:javascript复制;局部数字变量
LCLA number
number SETA 0Xaa
;局部逻辑变量
LCLL flag
flag SETL {TRUE}
;局部字符串变量
LCLS str
str SETS "hello world"
- 全局常量的定义
- 局部宏的定义,只能在当前宏内被访问
- 全局宏的定义,可跨段访问
宏定义示例
局部宏数据
常量数据
宏匹配和宏定义需要遵循先定义后使用的原则 如果需要从宏中跳出,可以使用伪指令MEXIT
宏替换
代码语言:javascript复制 ;使用get伪指令
AREA code, CODE
GET pangshu.s ;通知编译器当前源文件包含源文件 softool.s
GET C:pp.s ;通知编译器当前源文件包含源文件 C:cn.s
END
;使用include伪指令
AREA code, CODE
include pangshu.s ;通知编译器当前源文件包含源文件 softool.s
include C:pp.s ;通知编译器当前源文件包含源文件 C:cn.s
END
指令学习
传送指令
正常传送指令mov
取反传送指令mvn
,也叫数据非传送指令
转移指令
- B指令
直接跳转,仅更改PC寄存器的值
示例:
代码语言:javascript复制B 0x00040000 ;直接跳转到物理地址0x00040000读取指令并执行
B 标号 ;直接跳转到标号处
- BL指令
跳转并链接,除了更改PC寄存器的值之外,还会将下一条指令所对应的物理地址存放至lr
寄存器中
示例:
代码语言:javascript复制BL 0x00040000
mov r1,3 ;假设这行指令对应物理地址为0x0040004, 那么BL一旦执行,会将该值存入lr寄存器
或者
BL 标号
- BX指令 跳转并切换状态 BX指令后面只能跟寄存器,弥补了B指令和BL指令的不足, 同时会根据寄存器中最低比特位值切换ARM/Thumb模式
示例:
代码语言:javascript复制BL print
print
mov r1,#1
BX lr ;函数返回 如果R0[0]=1,则进入Thumb状态 反之进入ARM模式
除了通过指令来更改PC寄存器值之外,在ARM32中还可以直接使用传送指令对PC寄存器进行赋值:
代码语言:javascript复制mov pc,#0x00000008 ;往pc寄存器中写入一个地址值
mov R0,pc ;获取pc寄存器中的值
- BLX指令
该指令将以下功能集于一身
- 更改PC寄存器的值
- 将下一条指令的地址存入lr寄存器
- 根据寄存器中最低比特位值切换ARM/Thumb模式
算术和逻辑运算指令
算术运算
逻辑运算
移位指令
代码语言:javascript复制MOV R0, R1, LSL#2 ;将R1中的内容左移两位后传送到R0中。
MOV R0, R1, LSR#2 ;将R1中的内容右移两位后传送到R0中,左端用零来填充。
比较指令
比较两个值是否相等
大于和小于(带符号)
标志寄存器
试想一下,我们的比较指令cmp
,它内部是如何进行数据大小判断的
在高级语言里,直接使用>
或者<
运算符,来判断两个值的大小,比较结束后返回True
或者Flase
,可是在汇编语言里面没有这么简便,那它又是如何对两个数据之间大小进行判断的呢?
别忘了, 计算机最擅长做二进制的算术和逻辑运算
代码语言:javascript复制cmp R0,R1
要想判断两个数据是否相等,或者大于小于,直接做个减法运算不就完事了,也就是R0-R1
,如果结果为0,那么两个值相等,如果结果为正数,则R0>R1,结果为负数,则小于
但是问题来了,这个结果值放在哪里呢?放内存中还是寄存器呢? 答案是:寄存器
cpu设计者为了方便区分专门用了一个寄存器来存放数据运算后的结果,这个寄存器叫做状态寄存器,也叫标志寄存器
ARM32中一个寄存器有32二进制位的数据空间,那该怎么存放呢?
代码语言:javascript复制00000000000000000000000000000000 ;32个二进制位
为了方便程序员开发,设计者给这些二进制位进行了相应的命名:
当两个比较值相等,进行减法运算时,结果为0,那么Z标志位的值为1,也就是
代码语言:javascript复制01000000000000000000000000000000 ;32个二进制位
如果不相等,结果不为0,那么Z标志位的值变成0
由于每个二进制位只能存0和1两个值,也就是最多只能表示两种状态,那大于和小于的状态表示就得放到另外一个二进制上了,由于二进制运算涉及到有符号和无符号两种情况,因此需要用到两个二进制分别进行处理,有符号的的结果存放在N标志位,无符号的结果存放在C标志位:
cmp
指令会同时对两个数据进行有符号和无符号运算
有符号运算,如果结果为正数,N标志位值为0,如果为负数,N标志位值为1
无符号运算,如果结果为正数,C标志位值为1,如果为负数,C标志位值为0
那么我们在使用cmp
指令的时候,到底是根据那个标志位的结果进行判断的呢?
如果我们使用bne
指令,那么取Z标志位的值进行参考
如果我们使用blt
,bgt
,那么取N标志位,Z标志位和V标志位三者的值进行参考
总结:
cmp
指令的功能相当于减法指令,只是对操作数之间运算比较,结果间接保存在标志寄存器高位中bne
,blt
,bgt
等这些指令都是通过获取标志寄存器中的值来得知比较结果从而进行相应跳转,不同的指令需要满足不同的条件- 我们可以通过改变状态寄存器中的值来改变代码的走向
示例:
代码语言:javascript复制
AREA test,CODE
mov R0,#5
mov R1,#6
cmp R0,R1
;在跳转之前改变状态寄存器的值 使得bgt必然跳转
MSR cpsr_f ,0x20000000
BGT fun
mov R1,#6
fun
mov R0,#4
bx lr
END
知识扩展:
状态寄存器的读取和写入
写入指令: MSR(Move to Special register from Register )
状态寄存器中各个区域具体描述
以下是控制位区域细分详解图:
- 比较指令标志位条件参考表
指令 | 含义 | 需要满足的条件 |
---|---|---|
beq | 相等 | Z标志位为1 |
bne | 不相等 | Z标志位为0 |
bgt | 带符号大于 | Z标志位为0,且N和V标志位值相等 |
blt | 带符号小于 | N不等于V |
bge | 带符号大于等于 | N等于V |
ble | 带符号小于等于 | Z标志位为1或者N不等于V |
bls | 无符号小于等于 | Z标志位为1且C标志位为0 |
bhi | 无符号大于 | Z标志位为0且C标志位为1 |
bcs | 无符号大于等于 | C标志位为1 |
bhs | 无符号大于等于 | C标志位为1 |
bcc | 无符号小于 | C标志位为0 |
blo | 无符号小于 | C标志位为0 |
bmi | 负数 | N标志位为1 |
bpl | 正数或零 | N标志位为0 |
bvs | 溢出 | V标志位为1 |
bvc | 未溢出 | V标志位为0 |
bnv | 无条件执行 | 忽略 |
bal | 无条件执行 | 忽略 |
条件和循环伪指令
IF、ELSE 和 ENDIF
根据条件的成立与否决定是否执行某个程序段
IF、ELSE、ENDIF 伪指令可以嵌套使用
代码语言:javascript复制GBLL Test ;声明一个全局逻辑变量Test
Test SETL {TRUE}
IF Test = {TRUE}
程序段1
ELSE
程序段2
ENDIF
WHILE 和 WEND
根据条件的成立与否决定是否重复汇编一个程序段
若 WHILE 后面的逻辑表达式为真,则重复汇编该程序段,直到逻辑表达式为假
WHILE 和 WEND 伪指令可以嵌套使用
代码语言:javascript复制GBLA Counter ;声明一个全局数字变量Counter
Counter SETA 3 ;赋值
...
WHILE Counter < 10
程序段
WEND
汇编语言和C语言交互
- 内嵌汇编
- 外链汇编
1.引入其他源文件函数
使用import
或者extern
伪指令
;使用import伪指令
AREA code, CODE
import fun1 ;导入其他源文件中名为fun1的函数
END
;使用extern伪指令
AREA code, CODE
extern fun1
END
两者区别:
import
:不管当前文件是否使用该引入的函数,该标签都会加入当前文件符号表,即为静态引用extern
:只有当前文件使用了该函数,才会将此标签加入符号表,即为动态引用
2.导出当前源文件中函数供其他文件访问
使用export
或者global
伪指令
;使用import伪指令
AREA code, CODE
export fun ;导出fun函数供其他源文件使用
fun
mov R0,#4
bx lr
END
3.外链汇编之C语言调汇编函数
第一步,在汇编原文件中将函数暴露出来给供外部调用,使用export
或者global
伪指令:
AREA code, CODE
export arm_strcpy ;或者使用global
arm_strcpy
loop
ldrb R4,[R0],#1 ;如果使用ldr 那么将偏移值改成4
cmp R4,#0
beq over
strb R4,[R1],#1
b loop
over
END
第二步,在C文件中引用汇编中的函数,C文件中只能使用extern
伪指令:
extern arm_strcpy(char *src,char*des);
int main2(){
char *a="hello pangshu";
char b[64];
arm_strcpy(a,b);
}
4.外链汇编之汇编调c语言函数
第一步,在C文件中编写好函数
代码语言:javascript复制int c_sum(int a,int b){
return a b;
}
第二步, 在汇编文件中引入函数,使用import
或者extern
伪指令
AREA code, CODE
import c_sum
mov R0,#1 ;第一个参数
mov R1,#2 ;第二个参数
END
第三步, 使用BL指令调用函数
代码语言:javascript复制AREA code, CODE
import c_sum
mov R0,#1 ;第一个参数
mov R1,#2 ;第二个参数
BL c_sum
END
在ARM中函数参数使用R0~R3这四个寄存器来进行传递,最多传递4个参数,超过4个参数使用栈进行处理,函数返回值通过R0进行传递
由于keil软件的特殊性,我们可以通过以下方式进行互调测试
C文件中代码:
代码语言:javascript复制#include<stdio.h>
extern arm_strcpy(char *src,char*des);
int main2(){
char *a="hello pangshu" ;
char b[64];
arm_strcpy(a,b); //调汇编中函数
return 0;
}
int c_sum(int a,int b){
return a b;
}
汇编文件中代码:
代码语言:javascript复制 AREA code, CODE
import c_sum
export arm_strcpy
arm_strcpy
mov R0,#1 ;第一个参数
mov R1,#2 ;第二个参数
BL c_sum ;结果存放至R0中
END
5.内嵌汇编
在C语言中嵌入汇编代码,格式如下:
代码语言:javascript复制int main2(){
int a=4;
int b=4;
int c=4;
__asm__{ //使用__asm或者__asm__
mov R5,#0x00000005 //在大括号内部直接写入汇编代码即可
mov R6,#0x00000005
}
return 0;
}
内嵌汇编的注意事项:
- 不能直接给PC寄存器赋值,如果想改变pc值需要借助转移指令
- 由于R0
R3用于存放函数参数和返回值,R12R15有特殊用途,因此我们能操作的寄存器只有R4~R11, 又因为编译器会优先将寄存器分配给函数中的局部变量,因此我们一般无法在内嵌汇编环境中准确地修改某个寄存器的值,比如我想修改R5寄存器的值,由于函数有个变量占用了R5这个寄存器,那么编译器会自动将你写的这个R5改成R6或者其他,所以,在内嵌汇编时我们需要把寄存器当作变量来看待,把局部变量也当成寄存器看待,就好理解了
void c_strcopy(char *src,char *des){
char ch
__asm__{
loop:
ldrb ch,[src],#1 //局部变量当成寄存器看待
strb ch,[des],#1
cmp,ch,#0
bne loop
}
}
ARM32中寄存器别名补充
寄存器 | 别名 | 用途 |
---|---|---|
r0 | a1 | 第一个函数参数和函数返回值 |
r1 | a2 | 第二个函数参数 |
r2 | a3 | 第三个函数参数 |
r3 | a4 | 第四个函数参数 |
r4 | v1 | 寄存器变量 |
r5 | v2 | 寄存器变量 |
r6 | v3 | 寄存器变量 |
r7 | v4 | 寄存器变量 |
r8 | v5 | 寄存器变量 |
r9 | v6 | 寄存器变量 实际的帧指针 |
r10 | sl | 栈接线 |
r11 | fp | 参数指针 |
r12 | ip | 临时 |
r13 | sp | 栈指针 |
r14 | lr | 连接寄存器 |
r15 | pc | 程序计数器 |
如何编译16位arm汇编指令
代码语言:javascript复制AREA test, CODE
code16 ;声明为16位arm指令 如果不写默认则为code32
END
附:指令集汇总
(一) ARM 指令集
1. 指令格式
2. 条件码
3. ARM 存储器访问指令
1) LDR/ STR -加载 /存储指令
2) LDM/ STM -多寄存器加载 /存储指令
3) SWP -寄存器和存储器交换指令
4. ARM 数据处理指令
1) 数据传送指令
a) MOV -数据传送指令
b) MVN -数据非传送指令
2) 算术逻辑运算指令
a) ADD -加法运算指令
b) SUB -减法运算指令
c) RSB- 逆向减法指令
d) ADC -带进位加法指令
e) SBC -带进位减法指令
f) RSC -带进位逆向减法指令
g) AND -逻辑“与”
h) ORR -逻辑“或”
i) EOR -逻辑“异或”
j) BIC -位清除指令
3) 比较指令
a) CMP -比较指令
b) CMN -负数比较指令
c) TST -位测试指令
d) TEQ -相等测试指令
4) 乘法指令
a) MUL - 32位乘法指令
b) MLA - 32位乘加指令
c) UMULL - 64位无符号乘法指令
d) UMLAL - 64位无符号乘加指令
e) SMULL - 64位有符号乘法指令
f) SMLAL - 64位有符号乘加指令
5. ARM 分支指令
1) B -分支指令
2) BL -带连接的分支指令
3) BX -带状态切换的分支指令
6. ARM 协处理器指令
1) CDP -协处理器数据操作指令
2) LDC -协处理器数据读取指令
3) STC -协处理器数据写入指令
4) MCR - ARM处理器到协处理器的数据传送指令
5) MRC -协处理器到 ARM处理器的数据传送指令
7. ARM 杂项指令
1) SWI -软中断指令
2) MRS -读状态寄存器指令
3) MSR -写状态寄存器指令
8. ARM 伪指令
1) ADR -小范围的地址读取伪指令
2) ADRL -中等范围的地址读取伪指令
3) LDR -大范围的地址读取伪指令
4) NOP -空操作伪指令
(二) Thumb 指令集
1. Thumb 指令集和 ARM指令集的区别
2. Thumb 存储器访问指令
1) LDR/ STR -加载 /存储指令
2) PUSH/ POP -寄存器入栈 /出栈指令
3) LDMIA/ STMIA -多寄存器加载 /存储指令
3. Thumb 数据处理指令
1) 数据传送指令
a) MOV -数据传送指令
b) MVN -数据非传送指令
c) NEG -数据取负指令
2) 算术逻辑运算指令
a) ADD -加法运算指令
b) SUB -减法运算指令
c) ADC -带进位加法指令
d) SBC -带进位减法指令
e) MUL -乘法运算指令
f) AND -逻辑“与”
g) ORR -逻辑“或”
h) EOR -逻辑“异或”
i) BIC -位清除指令
j) ASR -算术右移指令
k) LSL -逻辑左移指令
l) LSR -逻辑右移指令
m) ROR -循环右移指令
3) 比较指令
a) CMP -比较指令
b) CMN -负数比较指令
c) TST -位测试指令
4. Thumb 分支指令
1) B -分支指令
2) BL -带连接的分支指令
3) BX -带状态切换的分支指令
5. Thumb 杂项指令
1) SWI -软中断指令
6. Thumb 伪指令
1) ADR -小范围的地址读取伪指令
2) LDR -大范围的地址读取伪指令
3) NOP -空操作伪指令