cisen / blog

Time waits for no one.
132 stars 20 forks source link

QEMU/nemu 相关 #1052

Open cisen opened 3 years ago

cisen commented 3 years ago

总结

下载安装

ninja安装
```sh
python3 ./configure.py --bootstrap
sudo cp ./ninja /usr/bin
ninja --version
wget https://download.qemu.org/qemu-6.1.1tar.xz
tar xvJf qemu-6.1.1.tar.xz
cd qemu-6.1.1/
./configure --target-list=riscv64-softmmu,riscv64-linux-user --prefix=/opt/qemu
make -j $(nproc)
sudo make install
export PATH=$PATH:/opt/riscv64/bin:/opt/qemu/bin
qemu-system-riscv64 --version
# 查看开发板列表
qemu-system-riscv64 -machine help

制作开发板

cisen commented 2 years ago

第五章 RISC-V汇编语言程序的调试 https://lab.cs.tsinghua.edu.cn/cod-lab-docs/labs/5-riscv-assem/

第五章 RISC-V汇编语言程序的调试 本章的内容是使用GDB和QEMU来调试RISC-V程序。在QEMU的命令行上,使用-s(小写)来启动一个内置的gdb server服务器,这样外部的gdb客户端连接这个端口(1234)来进行程序调试。也可以使用-gdb tcp::9000来更改这个服务程序的端口。-S的选项是在QEMU启动的时候,先不启动客户虚拟机的CPU,先等待一个gdb客户端连接进来。

样例代码 下面看一个程序如何编译以及在QEMU环境中运行,从1加到10。

.section .text .globl _start _start: addi t0, x0, 0x1 addi t1, x0, 0x0 addi t3, x0, 0xa loop: add t1, t1, t0 add t0, t0, 0x1 add t3, t3, -1 bne t3, x0, loop nop jr ra nop 代码编译 用下面的命令进行编译,生成二进制代码:

riscv64-unknown-elf-c++ -c -nostdlib -nostdinc -static -g -Ttext 0x80000000 -o sum.elf sum.s

或者生成32位结构的代码:

riscv64-unknown-elf-c++ -nostdlib -nostdinc -static -g -Ttext 0x80000000 sum.s -o sum.elf -march=rv32i -mabi=ilp32

各个编译参数的解释:

-nostdlib -nostdinc 不需要标准库

-c 只进行编译和汇编,不进行链接

-static 静态编译,不做动态的特殊处理

-g 加入调试信息(符号表等),便于调试

-o 输出文件

-Ttext 0x80000000把代码段放到这个位置(模拟器刚刚启动的时候,会从0x1000的位置执行,这个位置的代码会把PC值指向0x8000 0000位置。这样就需要把用户的代码放到0x8000 0000位置。)(见下面的执行截图)

-march=rv32i 指定使用的RISC-V指令集

-mabi=ilp32 指定使用的abi结构(指令集是32位,不指定这个的话,会使用64位的abi,会不兼容。注意,这里使用的是默认64位的编译器。)

执行完上述5条指令后的寄存器的内容。

t0就是目标地址,即用户地址,通过jr t0跳入。

模拟器执行与调试 下面通过编译为32位的代码来看一下程序的执行过程(由于程序不需要任何操作系统的支持,会容易跑偏,所以要用远程gdb进行单条指令执行,观察寄存器的执行情况。)

上述代码保存为sum.s,调用:

riscv64-unknown-elf-c++ -nostdlib -nostdinc -static -g -Ttext 0x80000000 sum.s -o sum.elf -march=rv32i -mabi=ilp32

生成可执行文件:

生成的可执行文件能在RISC-V的Linux操作系统下执行,在Windows下显然无法执行。

可以使用objectdump来反汇编,看看可执行程序里面的内容:

使用QEMU模拟器开始装入可执行程序:

QEMU-system-riscv32 -m 2G -nographic -machine virt -kernel sum.elf -s -S

注意一定要带入-s -S参数。-s的意思是在QEMU中启动gdb server,端口号为1234,-S的意思是,完成装载之后,不要启动模拟的处理器,等待调试器接入。

另外-bios non代表的意思就是不需要装载qemu默认的bios,在模拟的平台上不需要这部分的信息。

启动gdb的调试器客户端:

riscv64-unknown-elf-gdb sum.elf

后面要跟上sum.elf,主要目的是两个,一个是gdb本身是64位的,需要告诉它调试的对象是32位的,另外一个是sum.elf里面有调试的符号表,在进入调试的时候能够看到被调试的代码。

输入调试目标:

target remote localhost:1234

之后可以开始调试,先把断点设置到0x8000 0000的位置,之后可以控制程序单步执行。

可以通过下面的一些指令来观察程序的执行行为。

gdb的一些命令:

break 设置断点,比如break *0x80000000

disas $pc, $pc+20 在一个范围内反编译

info reg 列出所有的寄存器

x /24i $pc 在一个地址之后反编译24条指令

x /24d $pc 在一个地址之后列出内存中24字节的内容

p $x1 打印某一个寄存器的内容

容易产生错误的地方:如果QEMU和gdb互相不能匹配的最大问题就是32位和64位不匹配。要保证32位的QEMU模拟器执行32位的程序,64位模拟器去执行64位的程序。在进行调试的时候也指定正确的位数。

下面是综合起来看一下的效果:

上面的那个是启动qemu和用户程序的,用命令行启动qemu等待连接,注意-s 和-S。下面是启动gdb,连接。先target remote localhost:1234。然后break *0x80000000。然后c,这个时候就会启动上面的qemu。然后碰到第一个断点,停下来。然后,只能si了,因为没有断点了。如果再continue的话会跑飞。

当前目录的.gdbinit文件可能会带来困扰,找一个没有.gdbinit的目录下面执行上面的目录,避开使用和kernel一样的目录。

上述执行的过程中都使用"打开命令行.cmd"打开的命令行窗口,然后可以用cd命令切换到别的目录。这个命令已经设置好一系列的环境了。

启动和调试监控程序 下面过程说明如何使用gdb和QEMU来调试监控程序:

使用命令行:

QEMU-system-riscv32 -M virt -m 32M -kernel .\kernel.elf -nographic -monitor none -serial tcp::6666,server -s -S

其中几个参数意义是:

-M virt:该参数决定了模拟的RISC-V处理器的平台型号,通常不用更改

-m 32M:处理器可访问的内存容量是32M字节

-nographic:不启用图形功能支持

-monitor none:不启用控制窗口

-s -S:两个参数联合使用,表示启用调试功能,并等待调试器连接(默认的端口是1234)

-kernel fib.elf:要运行的程序,必须为elf格式文件

在调试监控程序的时候,QEMU会开两个端口,一个端口用于串口通信(端口号:6666),一个端口用以gdb server程序的端口(端口号:1234)。实际QEMU在开始执行的时候,会首先打开串口的通信,然后再打开gdb server程序的端口。所以,如果没有一个终端程序来连到串口的话,QEMU是不会打开调试端口的。因此,为了能够对监控程序进行调试,需要把终端程序也启动起来。

第一步:使用上述命令来启动QEMU模拟器。

第二步:使用term终端程序来连接6666号端口。

QEMU模拟器的状态也发生了变化。

可以看到,tcp已经连接上了。但是,这个时候监控程序没有起来。因为QEMU还在等待gdb程序的连接。

第三步:下一步,使用gdb连接到QEMU的gdb sever。

注意,这里的命令是riscv64-unknown-elf-gdb kernel.elf,后面跟着对应的二进制代码。这样可以通知gdb的客户端程序是在服务器端调试那个程序。而且,riscv64-unknown-elf-gdb默认任务服务器端的程序是64位的,但是实际上监控程序是32位的。这里通过kernel.elf的参数明确告诉gdb客户端,需要调试的程序是32位的,这样双方才能够正确通信。

下图是gdb启动之后的情况。

这个时候就可以输入调试的目标了

target remote localhost:1234

可以看到,gdb使用localhost:1234开始调试。

这时,其它的两个窗口(QEMU窗口和term窗口)没有发生变化,因为这个时候QEMU并没有启动模拟的处理器(或者说虚拟机内部的处理器)。在gdb窗口中键入continue,启动QEMU中的模拟的处理器。

终端程序已经可以进行交互了:

关于-M的解释

可以看到,这一部分是制定对应的模拟机器的型号,在实验中其实不重要,因为并不需要外设。