Moiz's journal

プログラミングやFPGAなどの技術系の趣味に関するブログです

RISCVエミュレータ-ELFファイルの実行

はじめに

前回前々回のエントリーの続きです。

ゆっくりとRISCVエミュレータを作っています。

命令の追加

RV32Iの命令の殆ど(SRETとWFI以外)を処理できるようになりました。 ただし、システムレジスタ系の命令(CSRR*)は、読み書きはできるものの内部状態は実装されていません。 同じ理由でMRETも、権限の変更は行いません。

システムコールエミュレーション

ECALL命令ではシステムコールのエミュレーションを実行します。今実行できるのは以下の2つです。

システムコール 番号 処理
exit 93 終了
write 64 ファイルへの書き出し

fstat等の実装がまだなので、writeが出力するのは標準出力だけです。

ELFファイルの読み込み

簡単なELFファイルを読みこんで実行できるようになりました。 これで普通のC言語でRISCVプログラムを書いて、GCCでクロスコンパイルして、RISCVエミュレータで実行する事ができます。

RISCV用のクロスコンパイル環境はriscv-gnu-toolchainを使用しました。

github.com

実行できる命令が限られているので、このエミュレータ用のコンフィギュレーションにするには、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という名前の標準テストがあります。

github.com

このテストのうち、rv32ui(RV32ユーザーレベル、整数命令のみ)を実行してみたところ、LH命令にサイン拡張のバグが見つかりました。

テスト重要ですね。

他にも何個か見つかったバグを修正し、すべてのテストがパスするようになりました。

まとめ

RISCVのエミュレータC++で書いて、簡単なプログラムのELF形式実行ファイルを処理できるようになりました。

また、RISCV-TESTSのユーザーレベル整数命令テストをパスすることを確認しました。

このエミュレータのコードは私のgitリポジトリで公開しています。

github.com