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个
语法
注释(两种方式)
1
2
3; 我是注释
/*…我是注释..*/声明一个代码段
1
2
3
4
5
6
7
8AREA test, CODE ;声明一个代码段,段的名称为test(名称可自定义),CODE关键字大小写都可,为了区分,一般大写
;=========在此编写汇编代码==========
END ;END表示编译结束标记
;段的声明需要以制表符开头,前面留出空数据表示
1
2
3
4
5
6
7
8
9mov R0,#13 ;将十进制13赋值给R0寄存器
mov R0,#0x13 ;将十六进制0x13赋值给R0寄存器
mov R0,#8_12 ;将8进制数12赋值给R0寄存器 N进制则为#N_xxx
mov R0,#'a' ;将字符a对应的ascii码值传送给R0寄存器函数声明和调用
1
2
3
4
5
6
7
8
9AREA test, CODE
bl print ;函数调用
print ;函数名称
mov R3,#5
bx lr
END ;END表示编译结束标记
声明一个数据段
1
2AREA da, DATA ;声明一个数据段 默认可读可写状态
;数据定义伪操作
数据定义
1
2
3
4
5
6
7
8
9
10
11# DCB
Str DCB "This is a test!" ;分配一片连续的字节存储单元并初始化。取名为Str 类似于8086中的db
Str = "This is a test!" ;DCB也可用等号代替
#DCW
Str DCW 1,2,3 ;定义字型数据 每个字符占用2个字节的空间
#DCD
Str DCD 1,2,3 ;定义半字型数据 每个字符占用4个字节的空间字符串必须使用DCB进行定义
分配一块连续的内存空间
1
2
3
4sp1 SPACE 100 ;分配一块连续100个字节的空间
;或者使用%代替SPACE简写
sp1 % 100 ;分配一块连续100个字节的空间
代码编写规范
- 所有指令和伪指令不允许顶格
- 所有变量和标签必须顶格
- 一般我们将伪指令大写,变量和标签小写
内存数据的读写
从内存中读取数据
使用中括号表示通过地址取值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21LDR 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. 向内存中写入数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14 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伪指令
这个指令和内存读取指令长的一模一样,如果我们在使用的时候加个等号,那么它就是另外一个指令
1 | ;如果不加等号 是内存读取功能 |
实际上,加了等号的LDR
指令,刚好可以弥补mov
指令的不足, mov
指令只能传送由八个二进制位右移而得的数据,而LDR
则没有这个限制
也就是说如果我们想将一个数值传入寄存器,可以有两种方式:
1 | ;第一种 |
LDR伪指令总结
作用:
- 弥补mov指令的不足
- 获取数据所对应的内存地址
ADR指令
那么除了通过LDR
伪指令来获取数据所在地址外还有一个指令也可以获取数据地址,那就是adr
指令,但这个指令只能获取当前段内数据的地址,段外数据无法获取,ldr
则没有这个限制
1 | AREA test2,CODE |
段的拓展
段属性拓展
- 段读写属性
READONLY
该段内存区域数据只能读取,不能写入,也就是如果使用内存读写指令,数据也写入不了
READWRITE
该段内存区域可读可写,不仅可以使用内存读写指令,还可以在调试的时候直接在memory窗口双击修改
示例:
1 | AREA code, CODE,READWRITE ;将代码段内存区域设置为可读可写状态,如果不写默认为只读 |
段对齐属性 ALIGN
1
AREA code, DATA,ALIGN=3 ;对齐数值范围为0~31
该属性会使得该段的基地址进行相应的偏移,
ALIGN=3
表示基地址会在上一个段数据的基础之上偏移2^3=8个字节的位置1
2
3
4
5
6
7
8AREA test1,DATA ;假设这个段的基地址为0x00000100
STR1 = "A"
AREA test2,DATA ;ALIGN默认为2 那么这个段的基地址为0x00000104
STR2 = "B"
AREA test3,DATA,ALIGN=8 ;这个段的基地址为0x0000010C
STR2 = "B"我们可以简单理解为,使用ALIGN这个属性可以让我们给上一个段预留除一部分缓冲区域,以ALIGN=2为例,当上一个段中的数据超过4个字节时,当前段基地器会向后再偏移4个字节,避免数据被覆盖,也就是说内存数据位置会进行重新分布,那么我们可以通过这个值来设置内存数据刷新频率,值越低,内存利用率越高,但是内存刷新频率也越高,负荷加重,反之,内存浪费越大,但是内存数据不需要频繁重新分布
另外:
除了在段属性中可以设置对齐之外,在指令中也可以插入ALGIN关键字:
1
2
3
4
5
6
7
8AREA code,CODE
mov R0,R1
ALIGN 4,3 ;下一条指令4字节对齐,并且偏移3个字节 为了补满4个字节,用0填充剩余1个未偏移位置
mov R2,R0
END
代码中使用AGLIN时用空格代替等号,同时单位为字节
多个代码段入口区分
当我们在同一个源文件中定义两个代码段时,程序从哪个段当做执行入口呢?
这个时候我们需要指定程序的入口,使用伪指令entry
1 | AREA test2,CODE |
栈的操作
- 入栈
1 | push {R0} ;将R0中的值存入栈内存中 相当于是STR R0,[R13,#-4] |
- 出栈
1 | pop {R0} ;将栈顶中的值取出存入R0寄存器 相当于是LDR R0,[R13],#0x0004 |
pop和push 本质上使用的是LDR和STR内存读写指令
对栈批量操作
如果想批量操作多个连续栈空间的话,直接使用逗号分隔开,连续标号的寄存器使用横杠分隔
1 | push {R0,R4-R12,LR} ;大括号中寄存器从右往左LR R12...R4 R0依次存入 |
除了使用pop
和push
之外,可以使用STM
(store much)和LDM
(load much)指令
格式:
1 | STM 起始地址/基地址寄存器,{寄存器名称,多个寄存器以逗号或者-分割} ;起始地址寄存器R0-R14任意选择 |
示例:
1 | STMFD R13!,{R0,R4-R12,LR} ;将寄存器列表中的寄存器(R0,R4到 R12,LR)存入栈,。 |
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架构四种方式均支持
内存批量读写示例:
1 | mov R1,#4 |
如果我想在上面的基础上再往后追加数据
1 | mov R1,#6 |
我们发现数据并没有追加,而是被覆盖了,因为R0
的值依然还是 0x00000008
,这个时候我们需要使用扩展指令,如下:
1 | mov R1,#4 |
事实上 STM指令如果不加后缀写法,默认使用的是STMIA指令,LDM指令默认使用LDMIA
多寄存器数据存放顺序
不管使用哪种扩展指令,皆为左低右高的形式,也就是左边的寄存器数据存放在低地址,右边的存放在高地址
1 | STMIA R0!,{R1,R2} ;左边R1的内容放低地址,右边R2的内容放高地址 |
宏
宏匹配
- 语法格式
1
2
3
4
5
6
7
8
9
10MACRO
$label macroname $param
;指令序列
MEND
;例如声明一个宏名为print的宏语句
MACRO
$label print $param ;带$的表示会被替换的内容
;这里写相关汇编代码
MEND- 使用
1
2
3
4
5
6
7
8
9MACRO
$label putR0 $param
mov R0,$param
MEND
; 使用
putR0 #10
- 延伸
第一个$label
是干嘛用的呢,由于宏的内部处理方式的替换,为了避免标签名称的冲突,增加一个标识
1 | ;假如我要在宏匹配中定义一个函数fun, 当我调用两次的时候,会出现函数名重复的问题 |
宏定义
- 全局宏的定义,可跨段访问
1
2
3
4
5
6
7
8
9
10
11
12;全局数字变量
GBLA number
number SETA 0Xaa
;全局逻辑变量
GBLL flag
flag SETL {TRUE}
;全局字符串变量
GBLS str
str SETS "hello world"- 局部宏的定义,只能在当前宏内被访问
1
2
3
4
5
6
7
8
9
10
11
12;局部数字变量
LCLA number
number SETA 0Xaa
;局部逻辑变量
LCLL flag
flag SETL {TRUE}
;局部字符串变量
LCLS str
str SETS "hello world"- 全局常量的定义
1
num EQU 10 ;关键字EQU 数据不允许修改
宏定义示例
1
2
3
4
5
6
7;定义一个全局整型数据宏
GBLA number
number SETA 0Xaa
;使用
mov R0,#number ;相当于mov R0,#0Xaa
LDR R0,=number ;相当于LDR R0,=0Xaa局部宏数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
MACRO ;声明一个宏
$label message $a ;取名为message 参数为$a
;定义宏内局部变量
LCLA number
number SETA $a
mov R0,#number
MEND ;宏结束,局部变量不再起作用
message 10 ;直接调用常量数据
1
2
3
4
5
6
7
8AREA data ,DATA
num EQU 10
AREA code ,CODE
mov R1,#5
ldr R1,=num
END
宏匹配和宏定义需要遵循先定义后使用的原则
如果需要从宏中跳出,可以使用伪指令MEXIT
宏替换
使用
include
或者get
伪指令1
2
3
4
5
6
7
8
9
10
11;使用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
1
mov R0,#4 ;将4传送至R0寄存器
取反传送指令
mvn
,也叫数据非传送指令1
mvn R0,#4 ;将4取反后传送至R0寄存器 0100取反为1011
转移指令
- B指令
直接跳转,仅更改PC寄存器的值
示例:
1 | B 0x00040000 ;直接跳转到物理地址0x00040000读取指令并执行 |
- BL指令
跳转并链接,除了更改PC寄存器的值之外,还会将下一条指令所对应的物理地址存放至lr
寄存器中
示例:
1 | BL 0x00040000 |
BX指令
跳转并切换状态
BX指令后面只能跟寄存器,弥补了B指令和BL指令的不足, 同时会根据寄存器中最低比特位值切换ARM/Thumb模式
示例:
1 | BL print |
除了通过指令来更改PC寄存器值之外,在ARM32中还可以直接使用传送指令对PC寄存器进行赋值:
1 | mov pc,#0x00000008 ;往pc寄存器中写入一个地址值 |
BLX指令
该指令将以下功能集于一身
- 更改PC寄存器的值
- 将下一条指令的地址存入lr寄存器
- 根据寄存器中最低比特位值切换ARM/Thumb模式
算术和逻辑运算指令
算术运算
1
2
3
4
5
6
7
8
9
10add R0,#5
add R0,R1 ;加法
sub R0,#5
sub R0,R1 ;减法
mul R0,R1,R2 ;乘法指令, 这里至少三个寄存器参与
mla R0,R1,R2,R3 ;先乘后加 R0 = R1 × R2 + R3逻辑运算
1
2
3
4
5
6
7
8and R0.#3
and R0,R0 ;逻辑与
orr R0.#3
orr R0,R0 ;逻辑或
eor R0.#3
eor R0,R0 ;逻辑异或
移位指令
1 | MOV R0, R1, LSL#2 ;将R1中的内容左移两位后传送到R0中。 |
比较指令
比较两个值是否相等
1
2
3
4
5
6
7
8
9
10cmp R0,R1
beq sub ;如果两个寄存器中的值相等则跳转到sub函数中,否则继续往下执行
sub
cmp R0,#5
bne sub ;如果两个值不相等,跳转到sub函数,否则继续往下执行
sub大于和小于(带符号)
1
2
3
4
5
6
7
8
9
10
11;大于
cmp R0,R1
bgt sub ;如果R0寄存器中的值大于R1,则跳转至sub
sub
;小于
cmp R0,R1
blt sub ;如果R0寄存器中的值大于R1,则跳转至sub
sub
标志寄存器
试想一下,我们的比较指令cmp
,它内部是如何进行数据大小判断的
在高级语言里,直接使用>
或者<
运算符,来判断两个值的大小,比较结束后返回True
或者Flase
,可是在汇编语言里面没有这么简便,那它又是如何对两个数据之间大小进行判断的呢?
别忘了, 计算机最擅长做二进制的算术和逻辑运算
1 | cmp R0,R1 |
要想判断两个数据是否相等,或者大于小于,直接做个减法运算不就完事了,也就是R0-R1
,如果结果为0,那么两个值相等,如果结果为正数,则R0>R1,结果为负数,则小于
但是问题来了,这个结果值放在哪里呢?放内存中还是寄存器呢? 答案是:寄存器
cpu设计者为了方便区分专门用了一个寄存器来存放数据运算后的结果,这个寄存器叫做状态寄存器,也叫标志寄存器
ARM32中一个寄存器有32二进制位的数据空间,那该怎么存放呢?
1 | 00000000000000000000000000000000 ;32个二进制位 |
为了方便程序员开发,设计者给这些二进制位进行了相应的命名:
当两个比较值相等,进行减法运算时,结果为0,那么Z标志位的值为1,也就是
1 | 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
等这些指令都是通过获取标志寄存器中的值来得知比较结果从而进行相应跳转,不同的指令需要满足不同的条件我们可以通过改变状态寄存器中的值来改变代码的走向
示例:
1 |
|
知识扩展:
状态寄存器的读取和写入
读取指令:MRS{mov to register from special register)
写入指令: MSR(Move to Special register from Register )
1
2
3
4
5
6
7
8
9
10
11# 读取状态寄存器中的值
mrs R0,cpsr ;将值读取到R0寄存器
#修改状态寄存器中的值
msr cpsr_c,#0x2d ;修改控制位区域
msr cpsr_x,#0x2d00 ;修改扩展位区域
msr cpsr_s,#0x2d0000 ;修改状态位区域
msr cpsr_f,#0x2d000000 ;修改标志位区域
#修改状态寄存器高位值时,低位必须补足0,虽然加了0但是不会影响其他区域的值状态寄存器中各个区域具体描述
一共分位四个大区域,从低到高分别为:控制位区域, 扩展位区域, 状态位区域, 标志位区域,每个区域各占8个二进制位的空间
以下是控制位区域细分详解图:
- 比较指令标志位条件参考表
指令 | 含义 | 需要满足的条件 |
---|---|---|
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 伪指令可以嵌套使用
1
2
3
4
5
6
7
8GBLL Test ;声明一个全局逻辑变量Test
Test SETL {TRUE}
IF Test = {TRUE}
程序段1
ELSE
程序段2
ENDIF
WHILE 和 WEND
根据条件的成立与否决定是否重复汇编一个程序段
若 WHILE 后面的逻辑表达式为真,则重复汇编该程序段,直到逻辑表达式为假
WHILE 和 WEND 伪指令可以嵌套使用
1
2
3
4
5
6GBLA Counter ;声明一个全局数字变量Counter
Counter SETA 3 ;赋值
...
WHILE Counter < 10
程序段
WEND
汇编语言和C语言交互
- 内嵌汇编
- 外链汇编
1.引入其他源文件函数
使用import
或者extern
伪指令
1 | ;使用import伪指令 |
两者区别:
import
:不管当前文件是否使用该引入的函数,该标签都会加入当前文件符号表,即为静态引用extern
:只有当前文件使用了该函数,才会将此标签加入符号表,即为动态引用
2.导出当前源文件中函数供其他文件访问
使用export
或者global
伪指令
1 | ;使用import伪指令 |
3.外链汇编之C语言调汇编函数
第一步,在汇编原文件中将函数暴露出来给供外部调用,使用export
或者global
伪指令:
1 | AREA code, CODE |
第二步,在C文件中引用汇编中的函数,C文件中只能使用extern
伪指令:
1 | extern arm_strcpy(char *src,char*des); |
4.外链汇编之汇编调c语言函数
第一步,在C文件中编写好函数
1 | int c_sum(int a,int b){ |
第二步, 在汇编文件中引入函数,使用import
或者extern
伪指令
1 | AREA code, CODE |
第三步, 使用BL指令调用函数
1 | AREA code, CODE |
在ARM中函数参数使用R0~R3这四个寄存器来进行传递,最多传递4个参数,超过4个参数使用栈进行处理,函数返回值通过R0进行传递
由于keil软件的特殊性,我们可以通过以下方式进行互调测试
C文件中代码:
1 |
|
汇编文件中代码:
1 | AREA code, CODE |
5.内嵌汇编
在C语言中嵌入汇编代码,格式如下:
1 | int main2(){ |
内嵌汇编的注意事项:
- 不能直接给PC寄存器赋值,如果想改变pc值需要借助转移指令
- 由于R0
R3用于存放函数参数和返回值,R12R15有特殊用途,因此我们能操作的寄存器只有R4~R11, 又因为编译器会优先将寄存器分配给函数中的局部变量,因此我们一般无法在内嵌汇编环境中准确地修改某个寄存器的值,比如我想修改R5寄存器的值,由于函数有个变量占用了R5这个寄存器,那么编译器会自动将你写的这个R5改成R6或者其他,所以,在内嵌汇编时我们需要把寄存器当作变量来看待,把局部变量也当成寄存器看待,就好理解了
1 | void c_strcopy(char *src,char *des){ |
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汇编指令
1 | AREA test, CODE |
附:指令集汇总
(一) 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 -空操作伪指令
本文为作者原创 转载时请注明出处 谢谢
乱码三千 – 点滴积累 ,欢迎来到乱码三千技术博客站