搭建NEMU与QEMU的DiffTest环境(Socket方式)
本文属于 《RISC-V指令集差分测试(DiffTest)系列教程》之一,欢迎查看其它文章。
环境:Ubuntu 18.04.5 LTS
1 简述
本文中,我们选择:
- QEMU作为参考对象(REF)
- NEMU作为测试对象(DUT)
2 编译NEMU
2.1 配置
执行以下命令:
apt install build-essential man gcc gdb git libreadline-dev libsdl2-dev zstd libzstd-dev
git clone https://github.com/OpenXiangShan/NEMU.git
cd NEMU/
export NEMU_HOME=/home/test/NEMU # /home/test/NEMU换成自己NEMU源码目录
make riscv64-benos_defconfig # riscv64-benos_defconfig需放入NEMU/configs/
make menuconfig
配置以下内容(让NEMU附带调试信息):
Build Options -> Optimization Level:选择O0,表示编译器不优化
Build Options -> Enable link-time optimization:取消选中,表示禁用链接时优化
Build Options -> Enable debug information:选中,表示使能调试信息
配置以下内容(开启socket方式的DiffTest功能):
Testing and Debugging -> [*] Enable differential testing -> Reference design (QEMU, communicate with socket)
配置以下内容(打开指令跟踪日志):
Testing and Debugging -> [*] Enable debug features: instruction tracing and watchpoint
Testing and Debugging -> [*] Enable rich Log for tracing instructions, pc, inst and dasm
2.2 修改NEMU/scripts/build.mk
在NEMU/scripts/build.mk中,删除所有-Werror选项(否则编译报错),如下所示:
CFLAGS := -O2 -MMD -Wall -Werror $(INCLUDES) $(CFLAGS)
CXXFLAGS := -O2 -MMD -Wall -Werror --std=c++17 $(XINCLUDES) $(CFLAGS)
2.3 修改isa_difftest_checkregs函数
在NEMU/src/isa/riscv64/difftest/dut.c中,将isa_difftest_checkregs函数,修改为只检查gpr[32]和PC寄存器,其他不检查。
如下所示:
bool isa_difftest_checkregs(CPU_state *ref_r, vaddr_t pc) {
csr_prepare();
#ifdef CONFIG_DIFFTEST_REF_SPIKE
cpu.mip &= 0xffffff4f; // ignore difftest for mip
#endif
if(cpu.mip != ref_r->mip) ref_r->mip = cpu.mip; // ignore difftest for mip
if (memcmp(&cpu.gpr[1], &ref_r->gpr[1], ARRLEN(cpu.gpr) - sizeof(cpu.gpr[0])) || (cpu.pc != ref_r->pc)) {
int i;
// do not check $0
for (i = 1; i < ARRLEN(cpu.gpr); i ++) { // check gpr[20]
difftest_check_reg(reg_name(i, 4), pc, ref_r->gpr[i]._64, cpu.gpr[i]._64);
}
difftest_check_reg("pc", pc, ref_r->pc, cpu.pc); // check pc
return false;
}
#ifdef CONFIG_DIFFTEST_STORE_COMMIT
return difftest_check_store(pc);
#else
return true;
#endif
}
备注:
isa_difftest_checkregs函数,会将DUT和REF的寄存器进行比较。
原来代码中,编码预期:
- 如果配置了CONFIG_RVV的话,那么就会进行v0~31寄存器的比较。
- 如果配置了CONFIG_FPU_SOFT的话,那么就会进行f0~31寄存器的比较。
但是,目前NEMU调用gdb_getregs函数,只能从QEMU获取到gpr[20]和pc寄存器值,因此我们需要这样修改。
2.4 修改isa_pmp_check_permission函数
在NEMU/src/isa/riscv64/system/mmu.c中,修改isa_pmp_check_permission函数,为如下内容:
bool isa_pmp_check_permission(paddr_t addr, int len, int type, int out_mode) {
return true;
}
备注:
在原来代码中,此函数会返回false,报错。
在配置选项中,未开启DiffTest功能时,运行固件,此函数是直接返回true,所以这里也这么修改。
2.5 编译
编译
make -j`nproc`
编译成功,生成NEMU/build/riscv64-nemu-interpreter可执行文件。
3 编译qemu-socket-difftest
3.1 修改NEMU/scripts/isa.mk
在NEMU/scripts/isa.mk中,将ISA ?= x86
修改为ISA ?= riscv64
,如下所示:
ISA ?= riscv64
ISAS = $(shell ls $(NEMU_HOME)/src/isa/)
ifeq ($(filter $(ISAS), $(ISA)), ) # ISA must be valid
$(error Invalid ISA=$(ISA). Supported: $(ISAS))
endif
NAME := $(ISA)-$(NAME)
CFLAGS += -D__ISA_$(ISA)__=1
3.2 修改NEMU/scripts/build.mk
在NEMU/scripts/build.mk中,在LDFLAGS末尾,添加-ldl
。
.DEFAULT_GOAL = app
ifdef SHARE
SO = -so
CFLAGS += -fPIC -D_SHARE=1
LDFLAGS += -rdynamic -shared -fPIC -Wl,--no-undefined -lz -ldl
endif
注意:build.mk中有多处LDFLAGS,-ldl
只能加在上文所示位置。
3.3 让qemu-socket-difftest带调试信息
在NEMU/scripts/build.mk中,将CFLAGS、CXXFLAGS、LDFLAGS中:
- 所有
-O2
改为-O0
- 并且在
-O0
后,增加-g
如下所示:
CFLAGS := -O0 -g -MMD -Wall $(INCLUDES) $(CFLAGS)
CXXFLAGS := -O0 -g -MMD -Wall --std=c++17 $(XINCLUDES) $(CFLAGS)
LDFLAGS := -O0 -g $(LDFLAGS)
3.4 解决DIFFTEST_REG_SIZE未定义
在NEMU/tools/qemu-socket-difftest/include/common.h中,添加#include <difftest.h>
在NEMU/tools/qemu-socket-difftest/src/diff-test.c中,删除#include <difftest.h>
3.5 修改gdb_getregs函数
在NEMU/tools/qemu-socket-difftest/include/isa/riscv64.h中,删除uint64_t fpr[32];
,如下所示:
union isa_gdb_regs {
struct {
uint64_t gpr[32];
uint64_t pc;
};
struct {
uint32_t array[DIFFTEST_REG_SIZE/sizeof(uint32_t)];
};
};
在NEMU/tools/qemu-socket-difftest/src/gdb-host.c中,将gdb_getregs函数,修改为如下内容:
bool gdb_getregs(union isa_gdb_regs *r) {
gdb_send(conn, (const uint8_t *)"g", 1);
size_t size;
uint8_t *reply = gdb_recv(conn, &size);
int i;
uint8_t *p = reply;
uint8_t c;
assert(size % (sizeof(uint64_t) * 2) == 0);
assert((size / 2) <= sizeof(union isa_gdb_regs));
for (i = 0; i < size / (sizeof(uint64_t) * 2); i ++) {
c = p[16];
p[16] = '\0';
((uint64_t*)(r->array))[i] = gdb_decode_hex_str(p);
p[16] = c;
p += 16;
}
free(reply);
return true;
}
备注:
NEMU可以调用gdb_getregs函数,从QEMU获取到寄存器数据,一共528字节,每个字节为"0" ~ “9"或"a” ~ “f"之间的任一字符,每16个字节表示一个寄存器值,共表示33个寄存器。
比如:
数据包中"1234567823456789…”,那么"1234567823456789",低位在前,高位在后,即表示寄存器值为0x8967452378563412,相当于数据包中用2字节表示1字节的实际数据。
528字节的数据包中,从前往后依次,表示32个gpr与pc寄存器的值。
3.6 修改difftest_regcpy函数
在NEMU/tools/qemu-socket-diff/src/diff-test.c中,将difftest_regcpy函数,修改为如下内容:
void difftest_regcpy(void *dut, bool direction) {
union isa_gdb_regs qemu_r;
gdb_getregs(&qemu_r);
int dut_pc_off = 32 * sizeof(uint64_t) * 2 + 18 * sizeof(uint64_t);
if (direction == DIFFTEST_TO_REF) {
memcpy(qemu_r.gpr, dut/*dut.gpr*/, (32 * sizeof(uint64_t)));
memcpy(&(qemu_r.pc), (dut + dut_pc_off)/*dut.pc*/, sizeof(uint64_t));
gdb_setregs(&qemu_r);
} else {
memcpy(dut/*dut.gpr*/, qemu_r.gpr, (32 * sizeof(uint64_t)));
memcpy((dut + dut_pc_off)/*dut.pc*/, &(qemu_r.pc), sizeof(uint64_t));
}
}
注意:
在difftest_regcpy函数中,dut_pc_off表示pc变量在riscv64_CPU_state结构体中的偏移量,如果该结构体定义发生改变,则该偏移量可能发生变化,导致拷贝pc寄存器值出错。
3.7 让QEMU输出指令日志
在NEMU/tools/qemu-socket-diff/src/diff-test.c文件,将difftest_init函数中的execlp函数调用,修改为如下内容:
void difftest_init(int port) {
...
// qemu-system-riscv64 -S -gdb tcp::1234 -nographic -d in_asm -D ./qemu.log
execlp(ISA_QEMU_BIN, ISA_QEMU_BIN, ISA_QEMU_ARGS "-S", "-gdb", buf, "-nographic", "-d", "in_asm", "-D", "./qemu.log", NULL);
...
}
备注:
只修改execlp所在那一行,其他不变。
主要是为了,在做DiffTest时,QEMU可以将运行过的指令,打印到日志中,以便QEMU与NEMU不同步时,进行对比分析。
3.8 编译生成riscv64-qemu-so
进入NEMU/tools/qemu-socket-difftest目录
cd tools/qemu-socket-difftest/
编译qemu-socket-difftest
make -j`nproc`
在NEMU/tools/qemu-socket-diff/build目录下,生成riscv64-qemu-so动态库。
4 以DiffTest方式,运行NEMU
我们还需要,将以下文件,拷贝到NEMU/build目录下。
- riscv64-qemu-so:上面生成DiffTest的socket动态库。
- benos_payload.bin:MySBI+BenOS二进制文件,生成方法可参考《NEMU模拟器源码编译与使用》第2节内容。
- qemu-system-riscv64:QEMU可执行程序,生成方法可参考《在QEMU上运行OpenSBI+Linux+Rootfs》第1节内容。
- opensbi-riscv64-generic-fw_dynamic.bin:QEMU RISCV64默认的OpenSBI二进制文件,可从QEMU源码路径:
qemu-7.1.0/pc-bios/opensbi-riscv64-generic-fw_dynamic.bin
获得;QEMU启动时,默认会加载该文件,没有该文件的话,启动时会报错。
以DiffTest方式,运行NEMU:
./riscv64-nemu-interpreter -b benos_payload.bin -d ./riscv64-qemu-so -l nemu.log
NEMU会fork一个子进程,去启动QEMU。
QEMU启动成功后,NEMU会将benos_payload.bin二进制代码,发送到QEMU中,随后两边就可以一起运行,并逐条指令进行结果对比,一旦两边结果不一致,就会触发异常停下来,期间执行过程,会写入nemu.log文件。
4.1 DiffTest检测到结果相同
运行效果,如下:
我们的BenOS,通过串口打印出了信息:Welcome RISC-V!
我们再看nemu.log,如下:
证明NEMU与QEMU,运行MySBI+BenOS二进制文件,每一条指令的运行结果,都是一样的,因此DiffTest未报错。
4.2 DiffTest检测到结果不同
假设DiffTest检测到,运行结果不一致时,其运行效果,如下:
我们的BenOS,没有打印出Welcome RISC-V!,却打印出了CPU当前寄存器值。
我们再看nemu.log,如下:
在difftest.h文件第49行,difftest_check_reg函数报错:
pc is different after executing instruction at pc = 0x00000000800000a8, right = 0x0000000000000000, wrong = 0x0000000080200000
也就是说,在执行mret
指令后,QEMU与NEMU中两边寄存器值不相等:
- pc is different after…:表示是PC寄存器的值不一样,如果是ra寄存器,这里就会显示为ra。
- right:表示QEMU中值,为0。
- wrong:表示NEMU中值,为0x80200000。
两边不一致,因此报错,我们就需要去分析,看看是哪里有问题。
标签:Socket,NEMU,QEMU,pc,DiffTest,difftest,isa,qemu From: https://blog.csdn.net/u011832525/article/details/140376947