31:28 | 27:25 | 24:21 | 21:20 | 19:16 | 15:12 | 11:0 |
---|---|---|---|---|---|---|
cond | 001 | opcode | S | Rn | Rd | shifter_operand |
opcode:指令操作符编码。
cond:指令执行的条件码。
S:决定指令的操作是否影响CPSR。
Rn:第一个操作数的寄存器编码。
Rd:目标寄存器编码。
shifter_operand:第二个操作数等。
:指令助记符
:指令执行条件(看PSR寄存器)
S:决定指令的操作是否影响CPSR。
:目标寄存器
:第一个操作数的寄存器编码。
:第二个操作数(可以写得很复杂) 如:# / / ,LSL # / ,LSL
指令后缀:.W 后缀表明为32位指令,B 后缀是一个字节,H 后缀是半个字,D 后缀是双字
条件码 |
助记符 | 含义 | PSR标志位 |
---|---|---|---|
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 未定义 AL |
从不执行 不可预知 无条件执行 |
ARMv3之前 ARMv3/v4 ARMv5及以上 |
注意:当出现BLS等类似指令时,不要理解为BL+S(跳转后置位),应该是B+LS(根据条件跳转)!!!
LDR{条件} 源寄存器, <存储器地址> 读出内存中数据
STR{条件} 源寄存器, <存储器地址> 向内存写入数据
LDR:内存 -> 寄存器
STR:寄存器 -> 内存
== 在[ ]里, 取偏移后的地址, 但值不变==
== 在[ ]外,则先取址后变==
== [ ]加!,则先变后取址==
ldr R0, [R1] ; 地址R1的数据写入寄存器R0
str R0, [R1] ; R0中的数据写入地址R1
; 在[]里, 取偏移后的地址, 但R1值不变
ldr R0, [R1,R2] ; 地址R1+R2的数据写入寄存器R0
ldr R0, [R1,#imm] ; 地址R1+#imm的数据写入寄存器R0
; 在[]外,则先取址后变
ldr R0, [R1],R2 ; 地址R1的数据写入寄存器R0,并将新值R1+R2写入R1
ldr R0, [R1],R2,LSL #imm ; 地址R1的数据写入寄存器R0,并将新值(R1+R2 × #imm)写入R1
; []加!,则先变后取址
ldr R0, [R1,R2]! ; 地址R1+R2的数据写入寄存器R0,并将新值R1+R2写入R1
ldr R0, [R1,#imm]! ; 地址R1+#imm的数据写入寄存器R0,并将新值R1+#imm写入R1
ldr R0, [R1,R2,LSL #imm]!; 地址R1+R2×4的数据写入寄存器R0,并将新值(R1+R2 × #imm)写入R1
; str同ldr
str R0, [R1], #imm ; 将R0中的数据写入地址R1,并将新值R1+#imm写入R1
str R0, [R1, #imm] ; 将R0中的数据写入地址R1+#imm
伪指令ADR用于相对寻址 ```asm adr R0, label ; 通常是小范围寻址, 通过计算偏移量实现 ```
LDM{cond} mode Rn{!}, reglist_group{^}
STM{cond} mode Rn{!}, reglist_group{^}
! : 最后的地址写回到Rn中, 即Rn会变;
reglist_group : 寄存器组用 ‘,’ / ‘-’ 隔开,如{R1,R2,R6-R9},由小到大排列;
低地址 -> R小
高地址 -> R大
指令后缀{cond}
- IA : (Increase After) 传送后地址+4
- IB : (Increase Before) 传送前地址+4
- DA : (Decrease After) 传送后地址-4
- DB : (Decrease Before) 传送前地址-4
- FD : 满递减堆栈 (传送前地址-4)
- FA : 满递增堆栈 (传送后地址-4)
- ED : 空递减堆栈 (传送前地址+4)
- EA : 空递增堆栈 (传送后地址+4)
LDMFD == LDMIA == pop
STMFD == STMDB == push
ldmib R1!, {R0,R4-R6}
; R1 = 0x04, 传送前地址加+4,
; 所以 R0 = 0X04地址里的内容
; 地址加4, R4 = 0X08地址里的内容
; 地址加4, R5 = 0X0C地址里的内容
; 地址加4, R6 = 0X10地址里的内容
; 最后更新R1, R1 = 0X10
立即数都会被移位拓展为32位
mov Rx, #imm ; Rx <- #imm 立即数会被移位拓展为32位
mov Rx, Rm ; Rx <- Rm
mov Rx, Rm, LSL #imm ; Rx <- (Rm << #imm) Rx值不会变
; 写入32位数据
mov Rx, #imm; ; Rx的低16位会被写入
movt Rx, #imm; ; Rx的高16位会被写入
;当然也可以使用伪指令
LDR R0, =0x12345678
当这些指令加上{S}后缀时,则会影响PSR寄存器
所有的立即数都在寄存器的后面(rsb指令)
add Rx, Rm, Rn ; Rn = Rm + Rn
add Rx, Rm, Rn, lsl #imm ; Rx = Rm + (Rn << #imm)
adc ; 带进位加法
sub Rx, Rn, #imm ; Rx = Rn - #imm
rsb Rx, Rn, #imm ; Rx = #imm - Rn
and Rx, Rn, #imm ; 逻辑与 Rx = Rn & #imm
bic Rx, Rx, #%1011 ; 逻辑位清除 (清除Rx的0,1,3位)
orr ; 逻辑或
eor ; 逻辑异或
lsl ; 逻辑左移
lsr ; 逻辑右移
asr ; 算数右移
; 伪指令:
adr R0, _start ; 小范围地址读取伪指令,基于PC的相对偏移地址
; 值读到目标寄存器中。
adrl R0, _start ; 这是一条中等范围的地址读取伪指令,它将基于
; PC的相对偏移的地址值读到目标寄存器中。
; 这两条伪指令在运行时都会编译为add Rx, PC, #imm以实现地址偏移
均不保存操作的结果,只是影响状态寄存器 CPSR 的值。
cmp 就是不写回结果的减操作,teq 就是不写回结果的异或操作,tst 就是不写回结果的与操作。
cmn Rn, Rm ; status = Rn - (-Rm) 相加操作 (含负数的比较)
cmp Rn, Rm ; status = Rn - (Rm) 相减操作
teq Rn, Rm ; status = Rn EOR Rm 异或操作 (测试等价)
tst Rn, Rm ; status = Rn AND Rm 与操作 (测试某一位)
B{cond}
{cond}为执行条件如:EQ, NE . . .
有L : 将下一条指令写入LR寄存器
有X : 记录目标指令模式是ARM还是Thumb(但有特例blx)
通过给地址最低位加上PSR的T位,并在跳转后判断
b #imm ; 跳到label但不保证目标地址的模式是ARM还是Thumb
bl #imm ; 写LR寄存器
bx Rx ; 记录目标指令模式
blx #imm ; 写LR寄存器, 并记录目标指令模式
blx Rx ; 写LR寄存器, 注意此指令没有模式检测
; e.g. ff ff ff fe ??
; 其他跳转指令(不常用)
mov PC, Rx ; 不同于bx, 此指令没有模式检测
ldr PC,= #address ;
注意:ARM 的 BL 虽然省去了耗时的访内操作(不将返回地址压入栈,存入 LR 寄存器),却只能支持一级子程序调用。如果子程序再呼叫 “孙程序”,则返回地址( LR 寄存器)会被覆盖。因此当函数嵌套多于一级时,必须在调用“孙程序”之前先把 LR 压入堆栈。
软件中断(类似C语言中的signal()函数)由软件产生的中断。
指令格式:SWI{cond} imm_24(其中:imm_24 为24位立即数,值为从0~16777215)
它用来做用户程序和软中断处理程序之间的接头暗号。通过该立即数来区分不同软中断操作。
例如:
SWI 1 ; 产生 1 号软中断
在用户模式下调用 SWI 软中断指令,可使处理器跳转至 SWI 中断向量表所对应的地址中,继而跳入软中断处理程序中。在软中断处理程序中,可根据软中断号,实现不同的操作。
用户程序里可以通过写入 SWI 指令来切换到特权模式,当 CPU 执行到 SWI 指令时会从用户模式切换到管理模式下,执行软件中断处理。
同SWI,只不过是stm32版本。
饱和指令可以控制输出数据的范围(类似饱和失真)
饱和运算的结果可以拿去更新 APSR 中的 Q 标志。
SSAT{.W} Rd, #imm, Rn {,shift} ; 饱和到带符号数边界(-2048 - 2047)
USAT{.W} Rd, #imm, Rn {,shift} ; 饱和到无符号数边界(0 - 4095)
Rn 存储“放大后待做饱和运算的信号”,(Rn 总是 32 位带符号整数——译者注)。同很多其它数据
操作指令类似,Rn 也可以使用移位来“预加工”。
Rd 存储饱和运算的结果。
#imm 用于指定饱和边界(多少位的带符号整数),取值范围是 1-32。
Thumb指令集中默认改变PSR标志位, 即默认使用MOVS, ADDS
这两条指令是访问特殊功能寄存器的“专用通道”,必须在特权级下使用。
MRS , ;加载特殊功能寄存器的值到 Rn
MSR , ;存储 Rn 的值到特殊功能寄存器
IT块将影响下面的几条指令是否执行(if-else 快速分支)
ITTT ; 其中是各种条件后缀
根据 ‘I’ 后字母的个数影响下面的指令。
T表示符合条件则执行,E表示条件相反则执行
例如
ITTTEQ
ITETNEQ
比较并条件跳转指令,它只能做前向跳转(while 循环优化)
CBZ ,
它们的跳转范围较窄,只有 0-126。
与其它的比较指令不同,CBZ/CBNZ 不会更新标志位。
(switch 分支优化)
TBB(查表跳转字节范围的偏移量)指令,从一个字节数组表中查找转移地址
和 TBH(查表跳转半字范围的偏移量)指令,从半字数组表中查找转移地址
TBB.W [Rn, Rm] ; PC += Rn[Rm] * 2
Rn 指向跳转表的基址
Rm 则给出表中元素的下标。
TBB.W [pc, r0] ; 执行此指令时,PC 的值正好等于 branchtable
branchtable
DCB ((dest0 – branchtable)/2) ; 注意:因为数值是 8 位的,故使用 DCB 指示字
DCB ((dest1 – branchtable)/2)
DCB ((dest2 – branchtable)/2)
DCB ((dest3 – branchtable)/2)
dest0
... ; r0 = 0 时执行
dest1
... ; r0 = 1 时执行
dest2
... ; r0 = 2 时执行
dest3
... ; r0 = 3 时执行
TBB 的跳转范围可达 514B;TBH 的跳转范围更可高达128KB。
偏移量是一个无符号整数。
REV 按字节反转。
REVH 按半字反转,且只反转低半字。
RBIT 按位反转的。
REV Rd, Rm
REVH Rd, Rm
RBIT.W Rd, Rn
BFC(位段清零)指令把 32 位整数中任意一段连续的 2 进制位清 0,语法格式为:
BFC.W Rd, #lsb, #width
lsb 为位段的末尾
width 为 lsb 和它的左边(更高有效位),共有多少个位参与操作。
LDR R0, =0x1234FFFF
BFC R0, #4, #10
; 执行后,R0 = 0x1234C00F
BFI(位段插入指令),把某个寄存器按 LSB 对齐的数值,拷贝到另一个寄存器的某个位段中,
BFI.W Rd, Rn, #lsb, #width; (从 Rn 的最低位提取,#lsb 只对 Rd 起作用)
LDR R0, =0x12345678
LDR R1, =0xAABBCCDD
BFI.W R1, R0, #8, #16
; 执行后,R1 = 0xAA5678DD
UBFX.W Rd, Rn, #lsb, #width
SBFX.W Rd, Rn, #lsb, #width
UBFX 从 Rn 中取出任一个位段,执行零扩展后放到 Rd 中
SBFX 则以带符号的方式进行扩展。
LDR R0, =0x5678ABCD
UBFX.W R1, R0, #12, #16
; 则 R0 = 0x0000678A
LDR R0, =0x5678ABCD
SBFX.W R1, R0, #8, #4
; 则 R0 = 0xFFFFFFFB
前四个参数,存入R0 ~ R3,其他则依次入栈
R12:导入表寻址(不同编译器实现不同)
R4~R11:非易变寄存器,默认调用前后值不变
SP:栈底指针
FP:栈顶指针(不同编译器实现不同)
LR:保存PC跳转值
传参小于32位靠R0,大于32位则使用R1和R0,
函数堆栈由被调方平衡
LDMIA == pop
STMDB == push
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
ubroutine_1 ;
PUSH {R0, R12, R14} ; 保存寄存器列表
...... ; 执行处理
POP {R0, R12, R14} ; 恢复寄存器列表
BX R14 ; 返回到主调函数
程序一般由不同的段(section)组成,分为代码段,数据段 . . .
例如:
AREA COPY, CODE, READONLY ; 代码段,段名:COPY
ENTRY ; ENTRY为程序开始
START ;
LDR R0, =SRC ;
LDR R1, =DST ;
MOV R2, #10 ;
LOOP ; 循环结构
LDR R3, [R0], #4 ;
STR R3, [R1], #4 ;
SUBS R2, R2, #1 ;
BNE LOOP ;
;----------------------------------- next section
AREA COPYDATA, DATA, READWRITE ; 数据段,段名:COPYDATA
SRC DCD 1,2,3,4,5,6,7,8,9,0 ;
DST DCD 0,0,0,0,0,0,0,0,0,0 ;
END ; END为程序结束
符号可用来标识地址、变量、数字常量(标识地址使又叫做标号)
引用局部标号:%{F|B|A|T} N{routename}
%:引用标号
F:向前搜索标号(程序未执行到的地方)
B:向后搜索标号(程序已执行过的地方)
A:搜索宏的所有宏命令层
T:搜索宏的当前层
N:局部标号的名字
routename:作用范围
例如:
0 ;<-------------------+
LDR R3, [R0], #4 |
STR R3, [R1], #4 |jump to here
SUBS R2, R2, #1 |
BNE %B0 ;---------+
GUN ARM编译器中标号必须以下划线开头
不同编译器,伪操作指令不同
数据相关 | |
---|---|
DCD | 分配一段连续的字内存空间并初始化(每个操作数占四字节) |
DCB | 分配一段连续的字节内存空间并初始化(每个操作数占一字节) |
SPACE | 分配n个字节的空间,并初始化为0 |
GBLA | 全局算术变量 |
GBLL | 全局逻辑变量b |
GBLS | 全局字符串变量 |
LCLA | 局部算术变量 |
LCLL | 局部逻辑变量 |
LCLS | 局部字符串变量 |
EQU | 赋值伪指令,类似宏,给常量定义一个符号名 |
DATA | 内部RAM地址赋给指定符号??? |
GBLA a ; 定义一个全局算术变量a, 并初始化为 0
a SETA 10 ; 给算术变量a赋值为10
GBLL b ; 定义一个全局逻辑变量b, 并初始化为{false}
b SETL 20 ; 给逻辑变量b赋值为:20
GBLS STR ; 定义一个全局字符串变量STR, 并初始化为( C
STR SETS "zhaixue. cc" ; 给变量 STR赋值为"zhaixue. cc"
LCLA a ; 定义一个局部算术变量a, 并初始化为
LCLL b ; 定义一个局部逻辑变量b, 并初始化为{false}
LCLS name ; 定义一个局部字符串变量name, 并初始化为0
name SETS "wanglitao" ; 给局部字符串变量赋值
DATA1 DCB 10,20,30,40 ; 分配一段连续的字节内存空间并初始化数字
STR DCB "ABCD" ; 分配一段连续的字节内存空间并初始化为字符串
DATA2 DCD 10,20,30,40 ; 分配一段连续的字空间并初始化
BUF SPACE 100 ; 给BUF分配100个字节的空间, 并初始化为0
地址/属性相关 | |
---|---|
ALIGN | 地址对齐 |
AREA | 用来定义一个代码段或数据段,常用的段属性为CODE/DATA |
CODE16/CODE32 | 指示编译器后面的指令为THUMB/ARM 指令 |
ENTRY | 指定汇编程序的执行入口 |
END | 源程序已到了结尾 |
EXPORT/GLOBAL | 声明一个全局符号,可以被其他文件引用 |
IMPORT/EXTURN | 引用其他文件的全局符号前,要先IMPORT |
GET/INCLUDE | 包含文件,并将该文件当前位置进行编译,一般包含的是程序文件 |
INCBIN | 包含文件,但不编译,一般包含的是数据、配置文件等 |
伪操作 | 说明 |
---|---|
ENTRY(_start) | 定义汇编程序的执行入口 |
@、# | 代码中的注释、整行注释符号 |
.section.text,“x” | 定义一个段,a:只读;w:读写;x:执行 |
.align、.balign | 地址对齐方式,按照指定字节数对齐 |
label: | 标号,以冒号结尾 |
byte | 把字节插入目标文件 |
.quad、.long、.word、.byte、.short | 分配不同大小的存储空间,插入目标文件 |
.string、.ascii、.asciz | 定义字符串、字符、以NULL结束的字符串 |
.rept、.endr | 重复定义 |
float | 浮点数定义 |
.space 10 FF | 分配一片连续的10字节空间,填充为FF |
.equ、.set | 赋值语句 |
.type func,@function | 指定符号类型为函数 |
.type num,@object | 指定符号类型为对象 |
.include、.incbin | 展开头文件、二进制文件 |
tmp.reg、.unregr12 | 为寄存器取别名 |
.pool、.ltorg | 声明一个文字池,一般用来存放32位地址 |
.comm buff,20 | 申请一段buf |
OUTPUT_ARCH(arm) | 指定可执行文件运行平台 |
OUTPUT_FORMAT(“elf32-littlearm”) | 指定输出可执行文件格式 |
#、$ | 直接操作数前缀 |
arch | 指定指令集版本 |
file | 汇编对应的C源文件 |
.fpu | 浮点类型 |
.reg | 寄存器重新命名:lr_svc、.req、r14 |
.size | 设置指定符号的大小 |
在程序中的传参
__asm__ __volatile__(
" ins1 op1, op2... \n"
" ins2 op1, op2... \n"
: 输出操作数
: 输入操作数
: 破坏描述部分
);
__asm {
ADD y, R0, x / y; // 使用R0但未改变其值。
}
int example(int a) {
__asm
{
ADD a, a, #1; // 不可使用R0来替代a。
}
return a;
}
void strcpy(char* src, char* dst) {
int ch; // 复制字符串
__asm
{
loop:
LDRB ch, [src], #1
STRB ch, [dst], #1
CMP ch, #0
BNE loop
}
}