简介
汇编语言是一种“低级”语言。
汇编语言的缺点:
-难度
-难写
-难移植
汇编语言的优点:
-灵活
-强大
汇编语言的应用场景
-需要直接访问底层硬件的地方
-需要对性能执行极致优化的地方
汇编语言语法介绍(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 fileRISC-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寄存器用来保存返回地址。





















