相对于抽象层次更高的C/C++语言,汇编语言是一门抽象层次比较低的语言,面向的是最底层的硬件,直接使用处理器的基本指令。虽然现在大多数的程序设计已经不再使用汇编语言,但是在一些特殊的场合,譬如底层驱动、引导程序、高性能算法库等领域,汇编语言还经常扮演着重要的角色。
系统环境
Windows 10-64bit
软件平台
Nuclei Studio IDE 202102版
汇编语句组成
汇编程序的最基本元素是指令,指令集是处理器架构的最基本要素,因此RISC-V汇编语言的最基本元素自然是一条条的RISC-V指令。除了指令之外,由于此处所用RISC-V工具链是GCC工具链,因此一般的GNU汇编语法也能被GCC的汇编器识别,GNU汇编语法中定义的伪操作、操作符、标签等语法规则均可以在RISC-V汇编语言中使用。
一条典型的RISC-V汇编语句由4部分组成,包含如下字段:
[label:] opcode [operands] [;comment]
[标签:] 操作码 [操作数] [;注释]
标签
表示当前指令的位置标记。
操作码
可以是如下任意一种:
RISC-V指令的指令名称,譬如addi指令、lw指令等。
汇编语言的伪操作。
用户自定义的宏。
操作数
操作码所需的参数,与操作码之间以空格分开,可以是符号、常量,或者由符号和常量组成的表达式。
注释
为了程序代码便于理解而添加的信息,注释并不发挥实际功能,仅起到注解作用。注释是可选的,如果添加注释,需要注意以下规则:
以“;”或者“#”作为分隔号,以分隔号开始的本行之后部分到本行结束都会被当作注释。
或者使用类似C语言的注释语法//和/* */对单行或者大段程序进行注释。
汇编程序伪操作
伪操作在汇编程序中的作用是指导汇编器处理汇编程序的行为,且仅在汇编过程中起作用,一旦汇编结束,伪操作的使命就此结束。
.weak symbol_name
.align integer
.section name [, subsection]
汇编程序定义标签
文本标签在一个程序文件中是全局可见的,因此定义必须使用独一无二的命名,文本标签通常被作为分支或跳转指令的目标地址,示例如下:
loop: //定义一个名为loop的标签,该标签代表了此处的PC地址
......
j loop //跳转指令跳转到标签loop所在的位置
j 1f //跳转到“向前寻找第一个数字为1的标签”所在的位置,即下一行
//(标签为1)所在的位置
1:
j 1b //跳转到“向后寻找第一个数字为1的标签”所在的位置,即上一行
//(标签为1)所在的位置
汇编程序定义宏
.macro mac, a, b, c //定义一个名为mac的宏,参数为a、b、c
mul t0, b, c // mul指令将b和c相乘得到乘积写入t0寄存器
add a, t0, a // add指令将a与t0相加,将乘累加结果写入a
.endm
//调用mac宏
mac x1, x2, x3
完整实例
在 Nuclei Studio中新建一个 helloworld工程。芯来科技官网的文档与工具页面可以下载Nuclei Studio,下载后解压缩,在 Nuclei Studio解压缩的目录下双击 NucleiStudio.exe即可启动 IDE。
第一次启动 Nuclei Studio将会弹出对话框要求设置 Workspace目录路径,该目录将用于存放后续创建的项目工程文件。设置好 Workspace路径,再单击 “Launch”启动 Nuclei Studio。
启动后推荐打开Launch Bar功能,方便快速编译和调试。打开菜单栏“Window -> Preferences”,搜索“bar”,勾选第一个选项“Enable the Launch Bar”即可启用Launch Bar功能。
在菜单栏中,选择“File-> New -> C/C++ Project”开始新建工程,在弹窗中双击选择“C Managed Build”。
新的页面中“Project name”填写“iasm”,“Project type” 选择“Nuclei SDK Project For GD32VF103 SoC”和“RISC-V Cross GCC”,如下图,点击“Next”。新的页面不用修改,直接点击“Next”即可。
在选择模板工程页面修改“Project Example”选项为“baremetal_helloworld”,后续页面不需要修改,点击“Next”直到最后一页,点击“Finish”完成新建helloworld工程。
打开 “nuclei_sdk->SoC->gd32vf103->Common->Source->GCC->intexc_g32vf103.S” 文件,翻到第144行开始到184行结束就是非向量中断处理汇编代码。这里列出具体代码,逐行讲解各个汇编代码的作用。
.section .text.trap //将接下来的代码汇编链接到text段的trap段中
.align 6 //2的6次方字节位置对齐
.global exc_entry //全局变量exc_entry
.weak exc_entry //定义属性为弱的变量exc_entry
exc_entry: //名为exc_entry的标签
SAVE_CONTEXT //调用宏SAVE_CONTEXT,作用是保存上下文
SAVE_CSR_CONTEXT //调用宏SAVE_CSR_CONTEXT,作用是保存寄存器内容
csrr a0, mcause //将寄存器mcause的值传入通用寄存器a0
mv a1, sp //将sp的值传入通用寄存器a1
call core_exception_handler //调用中断处理函数,将前面传的a0和a1作为函数的参数
RESTORE_CSR_CONTEXT //调用宏RESTORE_CSR_CONTEXT,作用是恢复保存的寄存器内容
RESTORE_CONTEXT //调用宏RESTORE_CONTEXT,作用是恢复保存的上下文
mret //退出中断处理函数
在汇编中调用C/C++函数
在介绍C/C++函数调用之前,先介绍应用程序二进制接口(Abstract Binary Interface,ABI),ABI描述了应用程序和操作系统之间、应用和它的库之间,以及应用的组成部分之间的接口。
其中,函数调用约定决定了函数调用时参数传递和函数返回结果的规则,有关RISC-V架构ABI的函数调用约定,可以查看RISC-V的架构手册。
对于RISC-V汇编程序而言,在汇编程序中调用C/C++语言函数,必须遵照ABI所定义的函数调用规则,即函数参数由寄存器a0~a7传递,函数返回由寄存器a0~a1指定。上面汇编实例中调用的c函数代码如下:
uint32_t core_exception_handler(unsigned long mcause, unsigned long sp)
{
uint32_t EXCn = (uint32_t)(mcause & 0X00000fff);
EXC_HANDLER exc_handler;
if ((EXCn < MAX_SYSTEM_EXCEPTION_NUM) && (EXCn >= 0)) {
exc_handler = (EXC_HANDLER)SystemExceptionHandlers[EXCn];
} else if (EXCn == NMI_EXCn) {
exc_handler = (EXC_HANDLER)SystemExceptionHandlers[MAX_SYSTEM_EXCEPTION_NUM];
} else {
exc_handler = (EXC_HANDLER)system_default_exception_handler;
}
if (exc_handler != NULL) {
exc_handler(mcause, sp);
}
return 0;
}
https://www.rvmcu.com/quickstart.html