搭建NEMU与QEMU的DiffTest环境(动态库方式)
- 1 DiffTest原理简述
- 2 编译NEMU
- 3 编译qemu-dl-difftest
- 3.1 修改NEMU/scripts/isa.mk
- 3.2 修改NEMU/tools/qemu-dl-diff/src/diff-test.c
- 3.3 修改NEMU/scripts/build.mk
- 3.4 让qemu-dl-difftest带调试信息
- 3.5 编译生成riscv64-qemu-so
- 4 NEMU、动态库、QEMU之间的关系
- 5 以difftest方式,运行NEMU
- 5.1 坑1:dlopen失败
- 5.2 坑2:xxx.debug文件
本文属于 《RISC-V指令集差分测试(DiffTest)系列教程》之一,欢迎查看其它文章。
环境:Ubuntu 18.04.5 LTS
1 DiffTest原理简述
DiffTest核心思想: 对于根据同一规范的两种实现, 给定相同的有定义的输入, 它们的行为应当一致。
如果,我们正在开发一款处理器或者模拟器,那么我们的指令集实现,可能存在无数错误,此时我们需要找一个标准实现,也就是参考对象(REF)。我们以该参考对象,进行指令级的一对一验证,以确保我们的处理器或模拟器,实现是正确的。
本文中,我们选择:
- QEMU作为参考对象(REF)
- NEMU作为测试对象(DUT)
当然,如果需要测试处理器硬件,那它也可以作为DUT。
2 编译NEMU
执行以下命令:
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:选中,表示使能调试信息
配置以下内容(开启动态库方式的DiffTest功能):
Testing and Debugging -> [*] Enable differential testing -> Reference design (QEMU, communicate with dynamic linking)
在NEMU/scripts/build.mk中,删除所有-Werror选项(否则编译报错),如下所示:
CFLAGS := -O2 -MMD -Wall -Werror $(INCLUDES) $(CFLAGS)
CXXFLAGS := -O2 -MMD -Wall -Werror --std=c++17 $(XINCLUDES) $(CFLAGS)
编译
make -j`nproc`
编译成功,生成NEMU/build/riscv64-nemu-interpreter可执行文件。
3 编译qemu-dl-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/tools/qemu-dl-diff/src/diff-test.c
在NEMU/tools/qemu-dl-diff/src/diff-test.c中,将isa_raise_intr(NO);
注释掉。
void difftest_raise_intr(uint64_t NO) {
// isa_raise_intr(NO);
}
这个函数在NEMU/tools/qemu-dl-diff/src/isa/x86/intr.c中定义(riscv怎么会用x86的代码?这里没深究,直接注释掉,继续)。
3.3 修改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.4 让qemu-dl-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.5 编译生成riscv64-qemu-so
进入NEMU/tools/qemu-dl-difftest目录
cd tools/qemu-dl-difftest/
编译qemu-dl-difftest
make
在NEMU/tools/qemu-dl-diff/build目录下,生成riscv64-qemu-so动态库。
4 NEMU、动态库、QEMU之间的关系
首先,需要大致解释一下,NEMU、动态库、QEMU之间的关系和原理,如上图所示。
基本关系:
- riscv64-qemu-so动态库,导出了difftest_xx系列函数。
- qemu-system-riscv64可执行文件中,有cpu_xx、gdb_xx以及qemu_xx系列函数,会被动态库调用。
- 基本关系就是,NEMU调用动态库,动态库再调用qemu-system-riscv64。
基本流程:
- NEMU可执行程序riscv64-nemu-interpreter,通过调用dlopen函数加载riscv64-qemu-so,并将difftest_xx系列函数符号解析出来,以备后续调用。
- 然后,NEMU调用difftest_init函数,进行初始化。
- 在difftest_init函数中,会再次调用dlopen函数,去加载qemu-system-riscv64可执行文件,并解析cpu_xx、gdb_xx以及qemu_xx系列函数符号,这些函数符号,其实就是difftest_xx系列函数的底层实现代码。
- 从qemu-system-riscv64文件中,解析的函数,就有main函数,随后difftest_init函数就会调用它,以启动qemu程序。
- 随后,如果一切正常的话,就可以调用qemu函数,以实现指令结果比对了。
5 以difftest方式,运行NEMU
动态库启动qemu时,使用的启动参数(NEMU/tools/qemu-dl-diff/src/isa/riscv64/init.c),如下:
char *isa_qemu_argv[] = {
"/usr/bin/qemu-system-riscv64",
"-nographic", "-S", "-serial", "none", "-monitor", "none",
NULL
};
因此,qemu路径,必须为/usr/bin/qemu-system-riscv64。
此外,还需要将benos_payload.bin、riscv64-qemu-so,均拷贝至NEMU/build目录下。
以difftest方式,运行NEMU:
./riscv64-nemu-interpreter -b benos_payload.bin -d ./riscv64-qemu-so
见证奇迹的时刻!!!
5.1 坑1:dlopen失败
哦豁,riscv64-qemu-so里面,dlopen打开/usr/bin/qemu-system-riscv64失败,报错信息:
cannot dynamically load executable
报错原因:dlopen是glibc库函数。我在老版机器上glibc 2.27,发现可以打开;在新版机器上glibc 2.31,发现打开失败;猜测是glibc版本问题,dlopen官方定义用途为打开so,这里用来打开可执行文件,新版glibc更严格,所以不允许这么用了?
解决办法:期间尝试dlopen劫持,以及移植重写dlopen函数,都挺麻烦的;暂时没什么好办法。
5.2 坑2:xxx.debug文件
因此,我回到老版系统上,这里dlopen打开成功,再次运行又报assert错误。
使用gdb --args ./riscv64-nemu-interpreter -b benos_payload.bin -d ./riscv64-qemu-so
命令调试跟踪,发现报错原因,是需要一个debug文件,路径为:/usr/lib/debug/.build-id/97/612b9c612a179a100ded49c146a82904b8c5d8.debug
经过了解,需要从该文件中,解析调试symbol信息,我的机器上没有这个文件。
这个文件是apt-get命令,使用包管理器,安装qemu时,自动创建的。
但是我这个老系统,最多只能升级到qemu 2.11.1,这个版本居然不带qemu-system-riscv64,自然也就没有这个debug文件。
QEMU emulator version 2.11.1(Debian 1:2.11+dfsg-1ubuntu7.42)
走到这里,已经没法继续了,要么只有一个版本一个版本去试:
- 首先,要能装qemu-system-riscv64的系统;
- 并且,系统上的glibc还要老的,比如:ubuntu glibc 2.27以前。(我在redhat上glibc 2.12,dlopen也失败,醉了,可能redhat上一直都很严格)
算了,麻烦得很,放弃。
后面,使用socket方式来试试。