搭建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 = appifdef 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方式來試試。