ARM汇编指令

文章目录

  • ARM汇编指令
    • ARM指令集
      • LDR和STR
      • LDM和STM
      • MOV
      • 运算操作
      • 比较指令
      • 分支跳转
      • SWI软中断指令
      • SVC系统服务调用
      • 饱和指令
      • 指令表
    • Thumb指令集
      • MRS 和 MSR
      • IT指令块
      • CBZ 和 CBNZ
      • TBB 和 TBH
      • REV, REVH, RBIT
      • BFC/BFI,UBFX/SBFX
      • UBFX/SBFX (位段提取指令)
    • 函数调用约定
      • 函数使用寄存器
      • 堆栈平衡
    • ARM汇编程序
      • 符号,标号
      • 伪操作
        • 伪操作——ARM编译器
        • 伪操作——GNU编译器
    • C/C++内嵌汇编
  • 附录A:16位指令表
  • 附录B:32位指令表
  • 附录C:Thumb指令表

ARM汇编指令

ARM指令集

  • 指令字长固定为32位
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:第二个操作数等。

  • 指令格式
    {}{S} , ,  (中括号表示可选)

指令助记符
:指令执行条件(看PSR寄存器)
S:决定指令的操作是否影响CPSR。
:目标寄存器
:第一个操作数的寄存器编码。
第二个操作数(可以写得很复杂) 如:# / / ,LSL # / ,LSL
指令后缀:.W 后缀表明为32位指令,B 后缀是一个字节,H 后缀是半个字,D 后缀是双字

  • 指令条件
    不同于x86汇编(JE,JNE…),ARM的指令条件(是以后缀形式出现BEQ…)
条件码

助记符 含义 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:内存 -> 寄存器
  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和STM

指令格式

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

MOV

立即数都会被移位拓展为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 压入堆栈。

SWI软中断指令

  软件中断(类似C语言中的signal()函数)由软件产生的中断。
指令格式:SWI{cond} imm_24(其中:imm_24 为24位立即数,值为从0~16777215)
它用来做用户程序和软中断处理程序之间的接头暗号。通过该立即数来区分不同软中断操作。
例如:

SWI 1          ; 产生 1 号软中断

在用户模式下调用 SWI 软中断指令,可使处理器跳转至 SWI 中断向量表所对应的地址中,继而跳入软中断处理程序中。在软中断处理程序中,可根据软中断号,实现不同的操作。

用户程序里可以通过写入 SWI 指令来切换到特权模式,当 CPU 执行到 SWI 指令时会从用户模式切换到管理模式下,执行软件中断处理。

SVC系统服务调用

同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。

指令表

ARM汇编指令_第1张图片

Thumb指令集

Thumb指令集中默认改变PSR标志位, 即默认使用MOVS, ADDS

MRS 和 MSR

这两条指令是访问特殊功能寄存器的“专用通道”,必须在特权级下使用。

MRS    ,          ;加载特殊功能寄存器的值到 Rn
MSR    ,          ;存储 Rn 的值到特殊功能寄存器

如图,是 MRS/MSR 可以使用的特殊功能寄存器
ARM汇编指令_第2张图片

IT指令块

IT块将影响下面的几条指令是否执行(if-else 快速分支)

ITTT          ; 其中是各种条件后缀  

根据 ‘I’ 后字母的个数影响下面的指令。
T表示符合条件则执行,E表示条件相反则执行
例如

ITTTEQ    
ITETNEQ

CBZ 和 CBNZ

比较并条件跳转指令,它只能做前向跳转(while 循环优化)

CBZ    ,    

它们的跳转范围较窄,只有 0-126。
与其它的比较指令不同,CBZ/CBNZ 不会更新标志位。

TBB 和 TBH

(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 时执行

ARM汇编指令_第3张图片

TBB 的跳转范围可达 514B;TBH 的跳转范围更可高达128KB。
偏移量是一个无符号整数。

REV, REVH, RBIT

REV 按字节反转。
REVH 按半字反转,且只反转低半字。
RBIT 按位反转的。

REV      Rd,     Rm
REVH     Rd,     Rm
RBIT.W   Rd,     Rn

BFC/BFI,UBFX/SBFX

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/SBFX (位段提取指令)

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               ; 返回到主调函数

ARM汇编指令_第4张图片

ARM汇编程序

  程序一般由不同的段(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编译器中标号必须以下划线开头

伪操作

  不同编译器,伪操作指令不同

伪操作——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 包含文件,但不编译,一般包含的是数据、配置文件等
伪操作——GNU编译器
伪操作 说明
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 设置指定符号的大小

C/C++内嵌汇编

在程序中的传参

__asm__ __volatile__(
    " ins1  op1,  op2...  \n"
    " ins2  op1,  op2...  \n"
    : 输出操作数
    : 输入操作数
    : 破坏描述部分
);
  • 最好不使用物理寄存器(R0 ~ R15)
  • 可使用在当前具有可见性的变量名和函数
  • 可使用高级语言中的运算符等
  • 无需push或pop编译器自动完成
  • 不可使用伪指令
    例如:
__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
    }
}

附录A:16位指令表

ARM汇编指令_第5张图片
ARM汇编指令_第6张图片
ARM汇编指令_第7张图片
ARM汇编指令_第8张图片

附录B:32位指令表

ARM汇编指令_第9张图片
ARM汇编指令_第10张图片
ARM汇编指令_第11张图片
ARM汇编指令_第12张图片
ARM汇编指令_第13张图片

附录C:Thumb指令表

ARM汇编指令_第14张图片

你可能感兴趣的:(#,ARM内核,单片机,stm32,arm开发,arm开发,汇编)