RISC-V汇编学习(二)—— 汇编语法
在具体汇编指令和汇编实战之前,还是有必要对RISC-V汇编进行下介绍,我一般称之为RISC-V汇编的“语法”,可能“语法”较少,也相对比较简单的原因,大部分的博主都是一笔带过,但本着循序渐进的原则,还是简单概述下,以便加深认识。
1 汇编“语法”概述
汇编文件一般后缀为.S或.s,.S包含了预处理的语句,.s就是纯粹的汇编语句。
1.1 汇编语句格式
和其他指令架构一样, 一个完整的RISC-V汇编程序有多条语句(statement)组成。一条典型的RISC-V汇编语言格式包括:RISC-V指令、操作符、标签以及GNU汇编语法定义。具体即:
[label:] opcode [operands] [;comment] # []内的部分可以省略
-
label(标签):GNU汇编中,任何以冒号结尾的标识符都被认为是一个标签,而不一定非要在一行的开始。
-
opcode (操作码):操作码部分有以下多种类型
- instruction(指令):直接对应二进制机器指令的字符串
- pseudo-instruction(伪指令):一条伪指令在汇编时会生成多条实际的汇编指令,使用伪指令可提高代码的编写效率
- directive(指示/ 伪操作/宏):通过类似指令的形式(以“.”开头),通知汇编器如何控制代码的产生等,不对应具体的指令.
- macro:采用.macro/.endm自定义的宏
-
operands(操作数)操作码所需要的的参数,可以是符号、常量或者二者组成的表达式等
-
comment(注释):代码注释,一般以"#"或“;”开始到当前行结束;
1.2 简单示例:
# ====== 伪操作(Directive) ======
.macro delay_loop cycles # 宏定义(Macro)开始
li t0, \cycles # 伪指令(Pseudo-instruction)
1: addi t0, t0, -1 # 基础指令(Instruction),"1:"为局部标签(Label)
bnez t0, 1b # 基础指令(引用局部标签)
.endm # 宏定义结束
# ====== 数据段定义 ======
.data # 伪操作(声明数据段)
.align 2 # 伪操作(对齐控制)
array_data: # 全局标签(Label,数据段地址标记)
.word 0x1234, 0x5678 # 伪操作(定义32位数据)
# ====== 代码段 ======
.text # 伪操作(声明代码段)
.globl _start # 伪操作(符号全局化声明)
_start: # 全局标签(Label,程序入口)
la a0, array_data # 伪指令(实际展开为auipc+addi)
lw a1, 0(a0) # 基础指令(内存加载)
delay_loop 100 # 宏调用(Macro Expansion)
li a7, 93 # 伪指令(加载立即数)
ecall # 基础指令(系统调用触发)
说明:
- 标签(Label)
- 以冒号:结尾的符号(如array_data:)
- 用于标记代码/数据位置,不生成机器码
- 伪指令(Pseudo-instruction)
- 用户友好的指令别名(如la=Load Address)
- 汇编阶段会展开为实际指令序列
- 基础指令(Instruction)
- 直接映射到RISC-V ISA的机器码(如addi对应OP-IMM格式)
- 伪操作(Directive)
- 以.开头的控制命令(如.text)
- 指导汇编器行为,不生成机器码
- 宏(Macro)
- 通过.macro定义的代码模板
- 调用时展开为预定义的指令序列
2 汇编“语法”说明
前面已经简单示例,基本上已经可以算是入门了解了,下面稍微详细介绍下各部分
2.1 label标签
标签的本质是代表它所在的地址,因此标签可以当作变量或函数来使用。常见的标签分为文本标签和数字标签。
标签只能由a~z,A~Z,0~9,“.”,“_”这些点、字母、数字、下划线等字符组成,除数字标签外,不能以数字开头。
- (1)文件标签在程序文件中是全局可见的,所以在定义是不可重复。文本标签通常被作为分支或跳转指令的目标地址,如:
loop: # 定义一个loop标签
...
j loop # 跳转到loop标签处
- (2)数字标签属于一种局部标签,可重复定义,通常用0~9之间的数字定义。在被引用时,数字标签通常需要带上字母“f”或“b”字母后缀,
- f:指示编译器向前搜索,即代码行数增加的方向
- b:指示编译器向后搜索,即代码行数减少的方向
如:
j 1f # 向前寻找并跳转至第一个数字为1的标签处
...
1: #数字标签1
...
j 1b # 向后寻找并跳转至第一个数字为1的标签处
2.2 汇编指令和伪指令
汇编程序的最基本元素是指令,指令集是处理器架构的最基本要素。因此RISC-V汇编语言的最基本元素便是一条条的RISC-V指令;除了普通的指令,RISC-V还定义了伪指令以便于用户编写汇编程序。但是这一部分呢,是RISC-V的核心,所以后续将另开一篇介绍。
2.3 伪操作
伪操作通常以“.”开头,在汇编程序中的作用是指导汇编器处理汇编程序的行为,仅在汇编过程中起作用。
-
(1).file filename
.file 伪操作,指示汇编器该汇编程序的逻辑文件名 -
(2).global symbol_name或.globl symbol_name
.global和.globl伪操作,用于定义一个全局的符号,使得其他文件也可调用该symbol_name -
(3).local symbol_name
.local伪操作,用于定义局部符号,使得该symbol_name对其他文件不可见 -
(4).weak symbol_name
在汇编程序中,符号的默认属性为强(strong),.weak伪操作用于设置符号的属性为弱(weak) -
(5).type name,type description
.type伪操作用于定义符号的类型。如“.type symbol,@function”表示将名为symbol的符号定义为一个函数(function) -
(6).align integer
.align伪操作,用于定义接下来的地址按照2的integer次方对齐。如“.align 2”表示按照4字节对齐。 -
(7).byte expression [,expression]*
.byte伪操作将从当前PC地址处开始分配若干个字节(byte)的空间,每个字节填充的值由分号分隔开的expression指定。 -
(8) .float 或者 .double expression [, expression]*
- (1).float伪操作将从当前PC地址处开始分配若干个单精度浮点数(32位)的空间,每个单精度浮点数填充的值由分号分隔开的expression指定。空间分配的地址一定与32位对齐。
- (2).double伪操作将从当前PC地址处开始分配若干个双精度浮点数(64位)的空间,每个双精度浮点数填充的值由分号分隔开的expression指定。空间分配的地址一定与64位对齐。
-
(9).section name [,subsection]
.section伪操作指示将接下来的代码汇编链接到名为name的段中(Section),还可以指定可选的子段(Subsection),常见的段有.text、.data、.bss.- “.section .text” 将接下来的代码汇编链接到.text段中
- “.section .data” 将接下来的代码汇编链接到.data段中
- “.section .bss” 将接下来的代码汇编链接到.bss段中
-
(10).macro和.endm宏定义
.macro和.endm用于定义宏,可以将一段汇编代码定义为一个宏(可以类比于C的宏函数)使用方式如下:
.macro mutil_add a,b,c #定义一个名为mutil_add的宏,参数为a、b、c
add t0,a,b #将a和b相加,值写入t0寄存器
add c,c,t0 #加t0和c相加,值写入c
.endm #宏定义结束
mutil_add x1,x2,x3 #调用mutil_add
还有更多比如:.half .word .option等等,不再详细一一列举了,使用到或者感兴趣,可以自行搜索学习。
关于RISC-V的基本语法格式介绍完毕,后续在汇编实战中使用到时再做学习。
参考
RISC-V (二)汇编语言编程
RISC-V嵌入式开发入门篇2:RISC-V汇编语言程序设计(上)
RISC-V嵌入式开发入门篇2:RISC-V汇编语言程序设计(中)
RISC-V嵌入式开发入门篇2:RISC-V汇编语言程序设计(下)