简介
汇编语言是一种“低级”语言。
汇编语言的缺点:
-难度
-难写
-难移植
汇编语言的优点:
-灵活
-强大
汇编语言的应用场景
-需要直接访问底层硬件的地方
-需要对性能执行极致优化的地方
汇编语言语法介绍(GNU版本)
基本组成
汇编文件一般后缀为.S或.s,.S包含了预处理的语句,.s就是纯粹的汇编语句。
一个完整的RISC-V汇编程序有多条语句(statement)组成。一条典型的RISC-V汇编语言由3部分组成:
[label:] [operation] [comment]
打方括号表示可选。
-label表示一个标号,必须以":"结尾。label相当于一个地址,给这个地址起了个名字。是这条指令存放在内存的地址。
-operation可以由以下多种类型:
-instruction(指令):直接对应二进制机器指令的字符串
-preudo-instruction(伪指令):为了提高编写代码的效率,可以用一条伪指令指示汇编器产生多条实际的指令(instruction)。(要在汇编器的手册里查看定义)
-directive(指示/伪操作):通过类似指令的形式(以“.”开头),通知汇编器如何控制代码的产生等,不对应具体的指令。属于汇编器自己定义的一些语法。(要在汇编器的手册里查看定义)
-macro:采用.macro/.endm自定义的宏
例子:
.macro do_nothing #directive
nop #preudo-instruction
nop #preudo-instruction
.endm
.text #directive 告诉大家生成的指令要放到text的section中
.global _start #directive _start是个全局变量,外部可见,有点像extern
_start: #label
li x6, 5 #preudo-instruction
li x7, 4 #preudo-instruction
add x5, x6, x7 #instruction
do_nothing #calling macro
.end #End of file
RISC-V汇编指令操作对象
寄存器:
-32个通用寄存器,x0~x31;
-在RISC-V中,Hart在执行算术逻辑运算时所操作的数据必须直接来自寄存器。
内存:
-Hart可以执行在寄存器和内存之间的数据读写操作;
-读写操作使用字节(Byte)为基本单位进行寻址;
-RV32可以访问最多2^32个字节的内存空间。
XLEN指的是寄存器的长度32/64。
x0寄存器是zero寄存器,里面读出来永远是0,只读不写。
pc寄存器是外界不可见的。
RISC-V汇编指令类型
参考riscv-spec-20191213.pdf文件中的第24章
-指令长度;ILEN1=32 bits(RV32I)
-指令对齐:IALIGN=32bits(RV32I),指的是在内存中对齐。地址对齐32byte。
-32个bit划分成不同的“域(field)”
-funct3/funct7和opcode一起决定最终的指令类型。
-指令在内存中按照小端序排列。
31-25 | 24-20 | 19-15 | 14-12 | 11-7 | 6-0 | |
funct7 | rs2 | rs1 | funct3 | rd | opcode | R-type |
imm[] | imm[] | rs1 | funct3 | rd | opcode | I-type |
imm[] | rs2 | rs1 | funct3 | imm[4:0] | opcode | S-type |
imm[] | rs2 | rs1 | funct3 | imm[4:1[11]] | opcode | B-type |
imm[] | imm[] | imm[] | imm[] | rd | opcode | U-type |
imm[] | imm[] | imm[] | imm[] | rd | opcode | J-type |
R-type:(register),每条指令中有三个fields,用于指定3个寄存器参数。
I-type:(Immediate),每条指令除了带有两个寄存器参数外,还带有一个立即数参数(宽度为12bits)。
S-type: (Store),每条指令除了带有两个寄存器参数外,还带有一个立即数参数(宽度为12bits,但fields的组织方式不同于I-type)。(用来访问内存的指令)
B-type: (Branch),每条指令除了带有两个寄存器参数外,还带有一个立即数参数(宽度为12bits,但取值为2的倍数)。(跟分支跳转有关)
U-type:(Upper),每条指令含有一个寄存器参数再加上一个立即数参数(宽度为20bits,用于表示一个立即数的高20位)。
J-type:(Jump),每条指令含有一个寄存器参数再加上一个立即数参数(宽度为20bits)。
小端序的概念
-主机字节序(HBO - Host Byte Order),默认小端序。
-一个多字节整数在计算机内存中存储的字节顺序称为主机字节序(HBO-Host Byte Order,或者叫本地字节序);
-不同类型CPU的HBO不同,这与CPU的设计有关。分为大端序和小端序。
riscv是小端序编指令。
算数指令
基于算术运算指令实现的其他伪指令
伪指令 | 语法 | 等价指令 | 指令描述 | 例子 |
NEG | NEG RD, RS | SUB RD, X0, RS | 对RS中的值取反并将结果存放在RD中 | neg x5, x6 |
MV | MV RD, RS | ADDI RD, RS, 0 | 将RS中的值拷贝到RD中 | mv x5, x6 |
NOP | NOP | ADDI x0, x0, 0 | 什么也不做 | nop |
LUI(Load Upper Immediate)
语法 | LUI RD, IMM | |
例子 | lui x5, 0x12345 | x5 = 0x12345 << 12 |
LUI指令会构造一个32bits的立即数,这个立即数的高20位对应指令中的imm,低12位清零。这个立即数作为结果存放在RD中。
例子
-利用LUI+ADDI来为寄存器加载一个大数0x12345678
lui x1, 0x12345 # x1 = 0x12345000
addi x1, x1, 0x678 # x1 = 0x12345678
-利用LUI+ADDI来为寄存器加载一个大数0x12345FFF
由于addi里的立即数会被符号扩展,所以不能直接加上FFF。
lui x1, 0x12346 # x1 = 0x12346000
addi x1, x1, -1 # x1 = 0x12345FFF
LI(Load Immediate)
AUIPC
语法 | AUIPC RD, IMM | |
例子 | auipc x5, 0x12345 | x5 = 0x12345 << 12 + PC |
auipc指令采用U-type
和LUI指令类似,AUIPC指令也会构造一个32bits的立即数,这个立即数的高20位对应指令中的imm,低12位清零。但和LUI不同的是,AUIPC会先将这个立即数和PC值相加,将相加的结果放在RD中。
应用场景:动态库地址的加载。
LA(Load Address)
语法 | LA RD, LABEL | |
例子 | la x5, foo |
LA是一个伪指令
具体编程时给出需要加载的label,编译器会根据实际情况利用auipc和其他指令自动生成正确的指令序列。
常用语加载一个函数或者变量的地址。
例子
_start:
la x5, _start # x5 = _start
jr x5
反汇编出来很可能就是一条auipc指令。
逻辑运算指令
移位运算指令
内存读写指令
条件分支指令
x1寄存器用来保存返回地址。