はじめに
ゆっくりとRISCVエミュレータを作っています。
命令の追加
RV32Iの命令の殆ど(SRETとWFI以外)を処理できるようになりました。 ただし、システムレジスタ系の命令(CSRR*)は、読み書きはできるものの内部状態は実装されていません。 同じ理由でMRETも、権限の変更は行いません。
システムコールエミュレーション
ECALL命令ではシステムコールのエミュレーションを実行します。今実行できるのは以下の2つです。
システムコール | 番号 | 処理 |
---|---|---|
exit | 93 | 終了 |
write | 64 | ファイルへの書き出し |
fstat等の実装がまだなので、writeが出力するのは標準出力だけです。
ELFファイルの読み込み
簡単なELFファイルを読みこんで実行できるようになりました。 これで普通のC言語でRISCVプログラムを書いて、GCCでクロスコンパイルして、RISCVエミュレータで実行する事ができます。
RISCV用のクロスコンパイル環境はriscv-gnu-toolchainを使用しました。
実行できる命令が限られているので、このエミュレータ用のコンフィギュレーションにするには、toolのビルドのときに、
./configure --prefix=/opt/riscv --with-arch=rv32i --with-abi=ilp32
を指定して、I命令とILP32のABIを指定する必要があります。
これで、例えばこんなCプログラムが実行できます。
#include <unistd.h> int main() { write(1, "Hello, RISCV\n", 13); return 0; }
コンパイルするときはriscv用コンパイラを使って、RV32IとILP32を指定して、ライブラリを静的にリンクする必要があります。 例えばこうなります。(gccにパスが通っている前提です。)
$ riscv32-unknown-elf-gcc hello.c -o hello -Wall -march=rv32i -mabi=ilp32 -static
これでhelloという名前のELF形式実行ファイルが作られます。
ちなみに-Sオプションをつけてコンパイルするとこんなアセンブラコードになります。
.file "hello.c" .option nopic .attribute arch, "rv32i2p0" .attribute unaligned_access, 0 .attribute stack_align, 16 .text .section .rodata .align 2 .LC0: .string "Hello, RISCV\n" .text .align 2 .globl main .type main, @function main: addi sp,sp,-16 sw ra,12(sp) sw s0,8(sp) addi s0,sp,16 li a2,13 lui a5,%hi(.LC0) addi a1,a5,%lo(.LC0) li a0,1 call write li a5,0 mv a0,a5 lw ra,12(sp) lw s0,8(sp) addi sp,sp,16 jr ra .size main, .-main .ident "GCC: (GNU) 9.2.0"
こちらが実行結果です。
$ cmake-build-debug/RISCV_Emulator hello Elf file name: hello This is an Elf file Program Header 0:Type: LOAD. Copy to 0x10000 from 0x0, size 5270. Memory size extended to 100000 Loaded Program Header 1:Type: LOAD. Copy to 0x12498 from 0x1498, size 2132. Loaded Section .bss found at 0x01cec. Secure BSS. Entry point is 0x10090 Section .symtab(2) found at 0x01d1c. Number of symbols = 92, (1472 bytes) Section .strtab found at 0x022dc. Symbol "__global_pointer$" found at index 34. Global Pointer Value = 0x12ca8. Memory size extended to 800000 Execution start Hello, RISCV Return value: 0.
いろいろエミュレータが生成したメッセージに紛れていますが、最後に"Hello, RISCV"と表示されています。成功です。
この例ではwrite以外のシステムコールを使わずに済むように、write()を使って出力させましたが、printf()でも動作するようです。
RISCV-TESTSの実行。
前回のエントリでも触れましたが、RISCVにはRISCV-TESTSという名前の標準テストがあります。
このテストのうち、rv32ui(RV32ユーザーレベル、整数命令のみ)を実行してみたところ、LH命令にサイン拡張のバグが見つかりました。
テスト重要ですね。
他にも何個か見つかったバグを修正し、すべてのテストがパスするようになりました。
まとめ
RISCVのエミュレータをC++で書いて、簡単なプログラムのELF形式実行ファイルを処理できるようになりました。
また、RISCV-TESTSのユーザーレベル整数命令テストをパスすることを確認しました。