查看: 1548|回复: 0
收起左侧

教你玩转[07]_RVSTAR—汇编程序篇

[复制链接]

  离线 

  • TA的每日心情
    拍拍
    2022-6-27 11:09
  • 签到天数: 25 天

    [LV.4]

    发表于 2021-5-8 13:12:30 | 显示全部楼层 |阅读模式

    有人预言,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部分组成,包含如下字段:
    1. [label:] opcode [operands] [;comment]
    2. [标签:]   操作码   [操作数]     [;注释]
    复制代码

    标签
    |
    表示当前指令的位置标记。
    |
    操作码
    |
    |  可以是如下任意一种:
    |  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段。


    三、汇编程序定义标签

    标签名称通常在一个冒号(:)之前,常见的标签分为文本标签和数字标签。

    文本标签在一个程序文件中是全局可见的,因此定义必须使用独一无二的命名,文本标签通常被作为分支或跳转指令的目标地址,示例如下:
    1. loop:     //定义一个名为loop的标签,该标签代表了此处的PC地址
    2.               ......
    3.               j loop   //跳转指令跳转到标签loop所在的位置
    复制代码

    数字标签为0~9之间的数字表示的标签,数字标签属于一种局部标签,需要时可以被重新定义。在被引用时,数字标签通常需要带上一个字母“f”或者“b”的后缀,“f”表示向前,“b”表示向后,示例如下:
    1.       j 1f    //跳转到“向前寻找第一个数字为1的标签”所在的位置,即下一行
    2.                //(标签为1)所在的位置
    3. 1:
    4.       j 1b   //跳转到“向后寻找第一个数字为1的标签”所在的位置,即上一行
    5.                //(标签为1)所在的位置
    复制代码


    五、汇编程序定义宏

    宏(macro)是将汇编语言中具有一组独立功能的汇编语句组织在一起,然后可以以宏调用的方式进行调用。示例如下:
    1. macro mac, a, b, c //定义一个名为mac的宏,参数为a、b、c
    2.   mul t0, b, c            // mul指令将b和c相乘得到乘积写入t0寄存器
    3.   add a, t0, a           // add指令将a与t0相加,将乘累加结果写入a
    4. .endm

    5. //调用mac宏
    6. 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功能。
    GD32VF 单片机芯片及应用-教你玩转[07]_RVSTAR—汇编程序篇risc-v单片机中文社区(1)

    在菜单栏中,选择“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”即可。
    GD32VF 单片机芯片及应用-教你玩转[07]_RVSTAR—汇编程序篇risc-v单片机中文社区(2)

    在选择模板工程页面修改“Project Example”选项为“baremetal_helloworld”,后续页面不需要修改,点击“Next”直到最后一页,点击“Finish”完成新建helloworld工程。

    打开 “nuclei_sdk->SoC->gd32vf103->Common->Source->GCC->intexc_g32vf103.S” 文件,翻到第144行开始到184行结束就是非向量中断处理汇编代码。这里列出具体代码,逐行讲解各个汇编代码的作用。
    1. .section .text.trap     //将接下来的代码汇编链接到text段的trap段中
    2. .align 6                     //2的6次方字节位置对齐
    3. .global exc_entry     //全局变量exc_entry
    4. .weak exc_entry      //定义属性为弱的变量exc_entry
    5. exc_entry:               //名为exc_entry的标签

    6.     SAVE_CONTEXT                     //调用宏SAVE_CONTEXT,作用是保存上下文
    7.     SAVE_CSR_CONTEXT            //调用宏SAVE_CSR_CONTEXT,作用是保存寄存器内容

    8.     csrr a0, mcause                       //将寄存器mcause的值传入通用寄存器a0
    9.     mv a1, sp                                 //将sp的值传入通用寄存器a1
    10.     call core_exception_handler  //调用中断处理函数,将前面传的a0和a1作为函数的参数

    11.     RESTORE_CSR_CONTEXT  //调用宏RESTORE_CSR_CONTEXT,作用是恢复保存的寄存器内容
    12.     RESTORE_CONTEXT          //调用宏RESTORE_CONTEXT,作用是恢复保存的上下文

    13.     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函数代码如下:
    1. uint32_t core_exception_handler(unsigned long mcause, unsigned long sp)
    2. {
    3.     uint32_t EXCn = (uint32_t)(mcause & 0X00000fff);
    4.     EXC_HANDLER exc_handler;

    5.     if ((EXCn < MAX_SYSTEM_EXCEPTION_NUM) && (EXCn >= 0)) {
    6.         exc_handler = (EXC_HANDLER)SystemExceptionHandlers[EXCn];
    7.     } else if (EXCn == NMI_EXCn) {
    8.         exc_handler = (EXC_HANDLER)SystemExceptionHandlers[MAX_SYSTEM_EXCEPTION_NUM];
    9.     } else {
    10.         exc_handler = (EXC_HANDLER)system_default_exception_handler;
    11.     }
    12.     if (exc_handler != NULL) {
    13.         exc_handler(mcause, sp);
    14.     }
    15.     return 0;
    16. }
    复制代码
    此函数有两个参数,分别为mcause和sp,返回一个返回值。所以汇编中调用此函数前,分别向a0和a1寄存器中写入mcause和sp的值。






    上一篇:教你玩转[05_]RV STAR—Nuclei Studio+JLink篇
    下一篇:教你玩转[06]_RVSTAR—QEMU篇
    RISCV作者优文
    全球首家只专注于RISC-V单片机行业应用的中文网站
    回复

    使用道具 举报

    高级模式
    B Color Image Link Quote Code Smilies

    本版积分规则

    关闭

    RISC-V单片机中文网上一条 /2 下一条



    版权及免责声明|RISC-V单片机中文网 |网站地图

    GMT+8, 2024-11-26 05:45 , Processed in 0.404660 second(s), 48 queries .

    快速回复 返回顶部 返回列表