分析C代码编译成RISC-V汇编后如何执行

参照x86和ARM64的做法,以ISA的寄存器、寻址方式、常用指令、堆栈操作等为基础,理解C代码编译成RISC-V汇编代码后是如何一步步执行的

分析C代码编译成RISC-V汇编后如何执行

环境搭建

  1. 环境Ubuntu 22.04LTS

  2. 新建文件夹RISCV并进入

  3. 使用git下载工具链git clone --recursive https://github.com/riscv/riscv-gnu-toolchain

  4. 安装依赖sudo apt-get install autoconf automake autotools-dev curl python3 libmpc-dev libmpfr-dev libgmp-dev gawk build-essential bison flex texinfo gperf libtool patchutils bc zlib1g-dev libexpat-dev

  5. 配置编译选项./configure --prefix=/opt/riscv --enable-multilib

  6. 编译make -j4,根据自己设备的核心数设置多线程

  7. 修改/etc/profile/opt/riscv/bin添加到PATH,并使用source /etc/profile重新加载

  8. 在终端中输入riscv64并使用tab补全,若能出现补全则为配置完成。

  9. 安装GCCsudo apt install gcc-risc64-unknown-elf

    image-20220516194535705

编译

  1. 编写测试代码

    image-20220516195307395

  2. 编译成汇编代码riscv64-unknown-elf-gcc -S riscv-test.c -o riscv-test.s

    image-20220516195637286

    -S:生成汇编代码*.s文件

    -o:把输出文件输出到指定文件里

分析

  1. 打开汇编代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    	.file	"riscv-test.c"
    .option nopic # 与位置有关代码段
    .attribute arch, "rv64i2p0_m2p0_a2p0_f2p0_d2p0_c2p0"
    .attribute unaligned_access, 0 #我猜是禁止非对齐访问
    .attribute stack_align, 16 # 堆栈16字节对齐
    .text # 代码段
    .align 1 # 按2^1字节对齐
    .globl g # 声明g为全局符号
    .type g, @function # 将g定义为一个函数
    g:
    addi sp,sp,-32 # 修改栈指针sp=sp-32,push
    sd s0,24(sp) # 把s0保存到sp+24,s0表示帧指针fp
    addi s0,sp,32 # 栈底保存到s0,s0=sp+32
    mv a5,a0 # 将a0存放的参数值存入a5
    sw a5,-20(s0) # 将a5中的字保存到s0-20
    lw a5,-20(s0) # 将s0-20的字存入a5
    addiw a5,a5,3 # a5=a5+3,按word加
    sext.w a5,a5 # 将a5标记为扩展字(Sign extend word)
    mv a0,a5 # 将a5的值返回a0
    ld s0,24(sp) # 恢复s0
    addi sp,sp,32 # 恢复sp
    jr ra # 跳转到ra地址返回
    .size g, .-g # 为了源代码兼容性
    .align 1 # 按2^1字节对齐
    .globl f # 声明f为全局符号
    .type f, @function # 将f定义为一个函数
    f:
    addi sp,sp,-32 # 修改栈指针sp=sp-32,push
    sd ra,24(sp) # 将ra值保存到sp+24
    sd s0,16(sp) # 将s0值保存到sp+16
    addi s0,sp,32 # 栈底保存到s0,s0=sp+32
    mv a5,a0 # 将a0存放的参数值存入a5
    sw a5,-20(s0) # 将a5中的字保存到s0-20
    lw a5,-20(s0) # 将s0-20的字存入a5
    mv a0,a5 # 将a5的值返回a0
    call g # 调用函数g
    mv a5,a0 # 将返回来的值a0移入a5中
    mv a0,a5 # 将a5中的值移入返回值a0中
    ld ra,24(sp) # 恢复ra
    ld s0,16(sp) # 恢复s0
    addi sp,sp,32 # 恢复sp
    jr ra # 跳转到ra地址返回
    .size f, .-f # 为了源代码兼容性
    .align 1 # 按2^1字节对齐
    .globl main # 声明main为全局符号
    .type main, @function # 将main定义为一个函数
    main:
    addi sp,sp,-16 # 修改栈指针sp=sp-16,push
    sd ra,8(sp) # 将ra值保存到sp+8
    sd s0,0(sp) # 将s0值保存到sp+0
    addi s0,sp,16 # 栈底保存到s0,s0=sp+16
    li a0,8 # 立即数赋值a0=8
    call f # 调用函数f
    mv a5,a0 # 将返回来的值a0移入a5中
    addiw a5,a5,1 # a5=a5+1,按word加
    sext.w a5,a5 # 将a5标记为扩展字(Sign extend word)
    mv a0,a5 # 将a5的值返回a0
    ld ra,8(sp) # 恢复ra
    ld s0,0(sp) # 恢复s0
    addi sp,sp,16 # 恢复sp
    jr ra # 跳转到ra地址返回
    .size main, .-main # 为了源代码兼容性
    .ident "GCC: () 10.2.0" # 为了源代码兼容性(accepted for source compatibility)