离线
TA的每日心情 | 拍拍 2022-6-27 11:09 |
---|
签到天数: 25 天 [LV.4]
|
有人预言,RISC-V或将是继Intel和Arm之后的第三大主流处理器体系。欢迎访问全球首家只专注于RISC-V单片机行业应用的中文网站
您需要 登录 才可以下载或查看,没有帐号?立即注册
x
本帖最后由 sky 于 2021-5-8 13:23 编辑
相对于抽象层次更高的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语言的注释语法//和/* */对单行或者大段程序进行注释。
二、汇编程序伪操作
在汇编语言中,有一些特殊的操作助记符通常被称为伪操作(Pseudo Ops)。
伪操作在汇编程序中的作用是指导汇编器处理汇编程序的行为,且仅在汇编过程中起作用,一旦汇编结束,伪操作的使命就此结束。
此处所用的RISC-V工具链是GCC工具链,一般的GNU汇编语法中定义的伪操作均可在RISC-V汇编语言中使用。经过不断地增加,目前GNU汇编中定义的伪操作数目众多,感兴趣的读者可以自行查阅完整的GNU汇编语法手册。这里将仅简单介绍一些常见的伪操作。
—————————————————————————————————————————————————————————.
.global symbol_name 或者 .globl symbol_name
.global和.globl伪操作用于定义一个全局的符号,使得链接器能够全局识别它,即一个程序文件中定义的符号能够被所有其他程序文件可见。
—————————————————————————————————————————————————————————
.weak symbol_name
在汇编程序中,符号的默认属性为强 (strong),.weak伪操作则用于设置符号的属性为 弱 (weak),如果此符号之前没有定义过,那么同时创建此符号并定义其属性为 weak。
如果符号的属性为 weak,那么它无须定义具体的内容。在链接的过程中,另外一个属性为 strong的同名符号可以将此weak符号的内容强制覆盖。利用此特性, .weak伪操作常用于预留一个空符号,使得其能够通过汇编器语法检查,但是在后续的程序中定义符号的真正实体,并且在链接阶段将空符号覆盖并链接。
—————————————————————————————————————————————————————————
.align integer
.align 伪操作用于将当前PC地址推进到 “2的integer次方字节” 对齐的位置。譬如 “.align 3” 表示将当前PC地址推进到8个字节对齐的位置处。
—————————————————————————————————————————————————————————
.section name [, subsection]
.section 伪操作指明将接下来的代码汇编链接到名为 name的段 (Section)中,还可以指定可选的子段 (Subsection),常见的段有 .text、.data、.rodata、.bss。例如 ,“.section .text”伪操作将接下来的代码汇编链接到 .text段。
三、汇编程序定义标签
标签名称通常在一个冒号(:)之前,常见的标签分为文本标签和数字标签。
文本标签在一个程序文件中是全局可见的,因此定义必须使用独一无二的命名,文本标签通常被作为分支或跳转指令的目标地址,示例如下:
- loop: //定义一个名为loop的标签,该标签代表了此处的PC地址
- ......
- j loop //跳转指令跳转到标签loop所在的位置
复制代码
数字标签为0~9之间的数字表示的标签,数字标签属于一种局部标签,需要时可以被重新定义。在被引用时,数字标签通常需要带上一个字母“f”或者“b”的后缀,“f”表示向前,“b”表示向后,示例如下:
- j 1f //跳转到“向前寻找第一个数字为1的标签”所在的位置,即下一行
- //(标签为1)所在的位置
- 1:
- j 1b //跳转到“向后寻找第一个数字为1的标签”所在的位置,即上一行
- //(标签为1)所在的位置
复制代码
五、汇编程序定义宏
宏(macro)是将汇编语言中具有一组独立功能的汇编语句组织在一起,然后可以以宏调用的方式进行调用。示例如下:
- 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
复制代码
六、完整实例
为了便于理解汇编程序,我们以 RV-STAR工程的非向量中断处理汇编代码为实例,讲解汇编程序。
在 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++程序中内嵌汇编程序之外,还可以在汇编程序中调用C/C++函数。这种情形在实际的工程中也很常见,C/C++语言构造的函数非常普遍,前面的中断处理函数中正是调用C/C++的函数。
在介绍C/C++函数调用之前,先介绍应用程序二进制接口(Abstract Binary Interface,ABI),ABI描述了应用程序和操作系统之间、应用和它的库之间,以及应用的组成部分之间的接口。
—————————————————————————————————————————————————————————
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;
- }
复制代码 此函数有两个参数,分别为mcause和sp,返回一个返回值。所以汇编中调用此函数前,分别向a0和a1寄存器中写入mcause和sp的值。
完
|
上一篇: 教你玩转[05_]RV STAR—Nuclei Studio+JLink篇下一篇: 教你玩转[06]_RVSTAR—QEMU篇
|