环境搭建

risc-v32入门

https://github.com/plctlab/riscv-operating-system-mooc/blob/main/README_zh.md

按照上面的进行搭建,工具链和qemu都不用编译,直接解压设置环境变量后可使用,需要注意的是ubuntu使用20.04以上版本。

对于asm下面的code编译方式,需要修改一下Makefile

--- a/asm/add/Makefile
+++ b/asm/add/Makefile
@@ -1 +1 @@
-../build.mk
+include ../build.mk

编译

riscv64-unknown-elf-gcc -nostdlib -fno-builtin -march=rv32g -mabi=ilp32 -g -Wall xxx.c -Ttext=0x80000000 -o xxx.elf

-nostdlib:不链接标准C库
-fno-builtin:关闭编译器对内置函数的使用。
-march=rv32g:指定目标架构为 RISC-V RV32G,其中 'rv32' 表示 32 位指令集宽度,'g' 表示该架构扩展包括通用整数指令集(I)、浮点数指令集(F)以及乘法累加指令集(D)。
-mabi=ilp32:设置应用程序二进制接口 (ABI) 为 ilp32,即在 32 位架构上使用 32 位长度的 int、long 和指针类型。
-g:生成调试信息,使得编译后的程序可以用 gdb 等调试器进行源代码级别的调试。
-Wall:开启编译器的所有警告信息,提高代码质量。
-Ttext:指定程序的起始地址或者入口点地址为 0x80000000。这对于裸机程序或嵌入式系统特别重要,因为它们通常需要在特定地址处加载并开始执行。

objcopy

riscv64-unknown-elf-objcopy -O binary xx.elf xxx.bin

objcopy -O binary 这个命令的作用是用来将目标文件(如ELF格式的可执行文件或对象文件)转换成纯粹的二进制格式文件。具体来说:

1.去除符号信息:当执行 -O binary 时,objcopy 工具会丢弃原始目标文件中的所有符号表信息、重定位信息以及其他非执行相关的数据。

2.生成纯二进制映像:输出的二进制文件仅包含原始文件中的可执行代码和初始化的数据,这些数据按照它们在内存中运行时的布局排列。这样的二进制文件可以直接用于固件烧录、设备内存加载等场景,因为它不再需要操作系统提供的加载器来解析额外的元数据。

3.简化部署:对于嵌入式系统或其他资源受限的环境,这种转换有助于创建一个单一的、最小化的二进制镜像,可以直接写入闪存或其他存储介质,并从特定地址启动执行。

举例来说,命令 arm-linux-objcopy -O binary -S source.elf destination.bin 将会把 source.elf 文件转换成名为 destination.bin 的纯二进制文件,适合于在没有操作系统的微处理器上直接运行或用于硬件编程。

qemu运行

  • qume:是一个支持跨平台虚拟化的虚拟机,有 user mode 和 system mode 两种配置方式。其中qemu 在system mode配置下模拟出整个计算机,可以在qemu之上运行一个操作系统,如linux系统。qemu 的system mode与常见的VMware和Virtualbox等虚拟机比较相似,但是qemu 的优势是可以跨指令集。例如,VMware和Virtualbox之类的工具通常只能在x86计算机上虚拟出一个x86计算机,而qemu 支持在x86上虚拟出一个ARM计算机。qemu在user mode配置下,可以运行跟当前平台指令集不同的平台可执行程序。例如可以用qemu在x86上运行ARM的可执行程序,但是两个平台必须是同一种操作系统,比如Linux。
qemu-system-riscv32 -nographic -smp 1 -machine virt -bios none -kernel ./xxx.elf

-nographic:这个选项指示 QEMU 在没有图形界面的情况下运行,也就是说,不打开 VGA 显示设备,所有的输出将会直接在控制台显示。对于嵌入式开发或者远程服务器部署非常有用。
-smp 1:定义虚拟机中的 CPU 核心数量为 1。这意味着模拟的 RISC-V 系统将只有一个核心。
-machine virt:指定使用的虚拟机模型为 virt。这是 QEMU 提供的一个通用的、基于硬件的 RISC-V 虚拟机模型,适合于开发和测试目的。
-bios none:表示不使用任何固件(如 UEFI 或 OpenSBI)。在某些情况下,尤其是对于裸机环境或者自定义引导过程,不需要 BIOS 或者其他固件来启动系统。
-kernel ./xxx.elf:参数后面跟的是要加载作为虚拟机启动镜像的 ELF 文件的路径,这里是指向名为 xxx.elf 的文件。此 ELF 文件通常包含了已经编译好的操作系统内核或者是可以直接执行的裸机程序。会解析elf的入口地址,将其加载到对应的dram地址上。后续由bios执行结束后进行跳转,所以kernel的elf入口地址要与bios跳转地址一致,否则启动不了。

gdb调试

  • gdb-multiarch:是一个经过交叉编译后的、支持多架构版本的 gdb。

qemu-system-riscv32 -kernel xxx.elf -s -S &

-s: “-gdb tcp::1234” 的缩写,启动 gdbserver 并在 1234 端口号上监听客户端
-S: 在启动时停止CPU (只有到在客户端键入'c' 才会开 始执行)
gdb-multiarch xxx.elf -q -x gdbinit

-q: 这是一个静默模式选项,表示在启动 GDB 时不打印欢迎信息和其他一些非关键信息,使得启动过程更简洁。

-x gdbinit: 使用 -x 选项可以让 GDB 加载并执行指定文件中的命令序列。这里是指定了 gdbinit 文件。在 gdbinit 文件中,你可以预先编写一系列的 GDB 自动化命令,比如设置断点、加载符号表、配置工作目录等,在 GDB 启动时自动执行,从而提升调试效率和一致性。

gdbinit 内容如下:

display/z x5
display/zx6
display/z $x7

set disassemble-next-line on
b _start
target remote : 1234
c
1.启动与退出

gdb [program]:启动GDB并调试指定的程序。
quit 或 q:退出GDB调试器。

2.运行与控制

run [args] 或 r [args]:运行程序,可以传递命令行参数。
start:从程序的第一行开始单步执行。
continue 或 c:继续执行至下一个断点或程序结束。
next 或 n:单步执行,不进入函数内部。
nexti或ni:以机器指令为单位进行单步执行,不进入函数内部

step 或 s:单步执行,进入函数内部。
stepi或si:以机器指令为单位进行单步执行,遇到函数调用会进入函数内部

finish:执行到当前函数返回为止。


3.查看源代码

list 或 l [line number/function name]:显示源代码列表,默认显示当前行及其周围的代码。

4.断点管理

break 或 b [location]:在指定位置设置断点。如b a==2或b a.c:22或b func
delete [breakpoint number] 或 d [number]:删除指定编号的断点。
clear [location]:清除在特定位置的断点。
info breakpoints 或 i b:列出所有已设置的断点信息。


5.查看
layout asm 可以查看所有的汇编代码
layout src 查看源代码,使用ctrl+x,再a,会到传统模式
display [expression]:动态监视表达式,在每次停止时自动显示其值。
info reg:查看所有寄存器
print 或 p [expression]:打印变量或表达式的值。可以查看具体代码变量的值,如p pxCurrentTCB->name。另外p还支持打印的格式/x来表示格式,p/x reg:查看某个寄存器,如p/xa5


6.栈回溯

backtrace 或 bt:显示当前调用栈的所有帧。
7.线程调试

thread 或 t:切换到指定线程。
info threads 或 i t:查看当前进程中所有线程的信息。
8.附加进程

attach [pid]:附加到一个正在运行的进程进行调试。
9.信号处理

handle signal:设置当GDB接收到特定信号时的行为。
10.读取内存

x/<count>/<format> <address>:查看内存内容
  <count>:你要查看的内存单元的数量。
  <format>:内存单元的显示格式,可以是:
    b(byte,字节),
    h(halfword,半字,对于RISC-V通常是16位),
    w(word,字,对于RISC-V通常是32位),
    g(giant,巨字,对于RISC-V通常是64位)等。
 <address>:你要查看的内存地址,可以是具体的内存地址数值,也可以是变量的地址,如&myVar。
 示例:x/6xw 0x地址;表示查看 6 个单元,每个单元是w(4字节),

11.其他

set args:设置下次运行时传递给程序的命令行参数。
show args:查看当前设置的程序运行参数。
file [executable]:加载新的可执行文件进行调试。
help [command] 或 h [command]:查看GDB中某个命令的帮助信息。




示例

display/z x5: 此命令设置了一个动态观察点,每当程序停止时,GDB都会显示寄存器x5 的内容,且以十六进制(z)格式显示。在RISC-V架构中,x5 是其中一个通用寄存器。

display/zx6: 类似于上面的命令,但它关注的是 x6 寄存器的值。

display/zx7: 同样,设置了一个动态观察点来显示 x7 寄存器的内容。

set disassemble-next-line on: 当执行 next 或 step 命令时,开启反汇编下一行代码的功能。这样在单步执行时,除了看到源代码,还会显示对应的汇编指令。

b _start: 在 _start 函数处设置一个断点,这是许多嵌入式或操作系统内核启动时的第一个入口点。

target remote : 1234: 这个命令告诉 GDB 与远程目标通信,并连接到本地主机的1234端口。在这种情况下,通常是通过QEMU模拟器运行的RISC-V系统,QEMU开启GDB服务器监听这个端口。

c 或 continue: 继续执行程序直至遇到断点(在这里是 _start 函数),或者如果没有断点,则一直执行到程序结束。如果之前已经建立了与远程目标的连接,那么GDB将控制远程系统的执行。

总结起来,这一系列命令首先设置了对几个寄存器的监控,然后开启了单步执行时显示下一行汇编代码的功能,接着在 _start 函数处设断点,最后连接到远程目标并继续执行程序。在整个过程中,GDB将根据设置实时展示相关寄存器的值和程序执行状态。


总结常用命令:
layout src/asm, ctrl+x a退出
i b:查看断点,可以b 函数名补全打断点
n(i):不进入函数内部单步执行
s(i):进入函数内部单步执行
p:查看变量信息,可以配合/....使用
display/zX1:动态监控寄存器
set disassemble-next-line on

risc-v64 Xuantie

这里使用的是玄铁编译好的工具链,也是下载直接解压,设置环境变量即可。

下载工具链

https://www.xrvm.cn/community/download?id=4267734522939904000

下载的版本:Xuantie-900-gcc-linux-5.10.4-glibc-x86_64-V2.8.1-20240115.tar.gz

下载qemu

https://www.xrvm.cn/community/download?id=4238019891233361920

下载的版本:XuanTie-DebugServer-linux-x86_64-V5.18.0-20230926.sh.tar.gz

执行qemu可能会少一些库,缺少就网上搜索一下,使用apt-get install xxx安装。

不带系统的测试例子:https://github.com/rvboards/test_c920

Linux运行

下载镜像(这里下载的版本920v2_images.tar.gz):https://github.com/c-sky/buildroot/releases

执行下面命令启动

qemu-system-riscv64 \\
-M virt \\
-cpu c910v \\
-bios ./images/fw_jump.bin \\
-kernel ./images/Image \\
-append "rootwait root=/dev/vda ro" \\
-drive file=./images/rootfs.ext2,format=raw,id=hd0 \\
-device virtio-blk-device,drive=hd0 \\
-nographic -smp 1

  • -M virt: 指定虚拟机使用 virt 平台。
  • -cpu c910v: 指定虚拟机使用 c910v CPU 模型。
  • -bios ./images/fw_jump.bin: 系统引导程序,opensbi。
  • -kernel ./images/Image: 指定使用指定路径下的 Image 文件作为内核镜像。
  • -append \"rootwait root=/dev/vda ro\": 设置内核启动参数,包括等待根文件系统、根文件系统路径为 /dev/vda、以只读模式挂载根文件系统。
  • -drive file=./images/rootfs.ext2,format=raw,id=hd0: 挂载名为 hd0 的硬盘设备,使用指定路径下的 rootfs.ext2 文件作为硬盘镜像,格式为 raw。
  • -device virtio-blk-device,drive=hd0: 添加一个 virtio 块设备,并将其连接到 hd0 硬盘。
  • -nographic: 在无图形界面的模式下运行虚拟机。
  • -smp 1: 设置虚拟机的 CPU 为单核。