Ubuntu Weekly Recipe

第505回 オープン規格の新しい命令セットアーキテクチャRISC-V入門 ツールチェインを用意する

この記事を読むのに必要な時間:およそ 9 分

ツールチェインのテスト

インストールしたツールチェインを使って,RISC-V用の実行バイナリを作ってみましょう。

$ mkdir ~/test && cd $_
$ export PATH=$PATH:$RISCV/bin
$ echo -e '#include <stdio.h>\n int main(void) { printf("Hello world!\\n"); return 0; }' > hello.c
$ riscv64-unknown-elf-gcc -o hello hello.c
$ file hello
hello: ELF 64-bit LSB executable, UCB RISC-V, version 1 (SYSV), statically linked, not stripped

riscv-toolsは特に指定せずにビルドした場合,標準Cライブラリの実装として一般的なLinuxディストリビューションで使われているglibcではなく,組み込みでよく使われているNewlibを静的にリンクします。

実行したバイナリはRISC-V ISAのシミュレーターであるspikeから立ち上げたpk(Proxy Kernel)越しに実行できます。

$ which spike
/home/ubuntu/riscv//bin/spike
$ find $RISCV -name pk
/home/ubuntu/riscv/riscv64-unknown-elf/bin/pk
$ spike pk hello
Hello world!

ちなみにspikeコマンドはデバッグ機能も備えていますので,たとえば-dオプションを付けることでステップ実行もできますし,-lオプションを付けることで1命令ごとのログを残すことも可能です。

64bit向けNewlib用のツールチェインはriscv64-unknown-elf-のプレフィックスが付きます。

$ riscv64-unknown-elf-
riscv64-unknown-elf-addr2line   riscv64-unknown-elf-gcc         riscv64-unknown-elf-gdb         riscv64-unknown-elf-readelf
riscv64-unknown-elf-ar          riscv64-unknown-elf-gcc-7.2.0   riscv64-unknown-elf-gprof       riscv64-unknown-elf-run
riscv64-unknown-elf-as          riscv64-unknown-elf-gcc-ar      riscv64-unknown-elf-ld          riscv64-unknown-elf-size
riscv64-unknown-elf-c++         riscv64-unknown-elf-gcc-nm      riscv64-unknown-elf-ld.bfd      riscv64-unknown-elf-strings
riscv64-unknown-elf-c++filt     riscv64-unknown-elf-gcc-ranlib  riscv64-unknown-elf-nm          riscv64-unknown-elf-strip
riscv64-unknown-elf-cpp         riscv64-unknown-elf-gcov        riscv64-unknown-elf-objcopy
riscv64-unknown-elf-elfedit     riscv64-unknown-elf-gcov-dump   riscv64-unknown-elf-objdump
riscv64-unknown-elf-g++         riscv64-unknown-elf-gcov-tool   riscv64-unknown-elf-ranlib

$RISCV/riscv64-unknown-elf/share/riscv-tests/以下にはいくつかのベンチマークプログラムや各命令のテストコマンドが存在します。試しに手元のマシンでDhrystoneを実行した結果は,以下のとおりです。

$ spike /home/ubuntu/riscv/riscv64-unknown-elf/share/riscv-tests/benchmarks/dhrystone.riscv
Microseconds for one run through Dhrystone: 393
Dhrystones per Second:                      2544
mcycle = 196524
minstret = 196530

RISC-Vバイナリの中身

たとえばgdbを使って,helloプログラムのmain関数をdisassembleしてみましょう※4⁠。

※4
読みやすいようにhexとmnemonicの間の空白は手動で調整しています。
$ riscv64-unknown-elf-gdb hello -batch -ex 'disassemble /r main'
Dump of assembler code for function main:
   0x000000000001019a <+0>:     41 11           addi    sp,sp,-16
   0x000000000001019c <+2>:     06 e4           sd      ra,8(sp)
   0x000000000001019e <+4>:     22 e0           sd      s0,0(sp)
   0x00000000000101a0 <+6>:     00 08           addi    s0,sp,16
   0x00000000000101a2 <+8>:     c9 67           lui     a5,0x12
   0x00000000000101a4 <+10>:    13 85 87 fd     addi    a0,a5,-40 # 0x11fd8
   0x00000000000101a8 <+14>:    ef 00 a0 20     jal     ra,0x103b2 <puts>
   0x00000000000101ac <+18>:    81 47           li      a5,0
   0x00000000000101ae <+20>:    3e 85           mv      a0,a5
   0x00000000000101b0 <+22>:    a2 60           ld      ra,8(sp)
   0x00000000000101b2 <+24>:    02 64           ld      s0,0(sp)
   0x00000000000101b4 <+26>:    41 01           addi    sp,sp,16
   0x00000000000101b6 <+28>:    82 80           ret
End of assembler dump.

各命令の意味はRISC-VのUser-Level ISA Specification(PDF)の以下の表などが参考になるでしょう。

  • Table 20.1: Assembler mnemonics for RISC-V integer and floating-point registers.
  • Table 20.2: RISC-V pseudoinstructions.

やっていることは至極単純です。

addi    sp,sp,-16           スタックポインタを16バイト分減らす
sd      ra,8(sp)            スタック領域に呼び出し元のアドレスを保存
sd      s0,0(sp)            スタック領域に呼び出し元のフレームポインタを保存
addi    s0,sp,16            フレームポインタに呼び出し時のスタックアドレスを保存
lui     a5,0x12             a5レジスタに「0x12 << 12 = 0x12000」を保存
addi    a0,a5,-40 # 0x11fd8 a0レジスタに「a5 - 40 = 0x11fd8」
                            つまり"Hello wolrd!"が保存されたアドレス」を保存
jal     ra,0x103b2 <puts>   puts("Hello world!")を呼び出し
li      a5,0                a5レジスタに0を保存
mv      a0,a5               a0レジスタ(mainの戻り値)にa5レジスタの内容をコピー
ld      ra,8(sp)            スタック領域から呼び出し元のアドレスを復帰
ld      s0,0(sp)            スタック領域から呼び出し元のフレームポインタを復帰
addi    sp,sp,16            スタックポインタを元に戻す
ret                         呼び出し元に戻る(raレジスタのアドレスに戻る)

最適化されていないため,少しばかり無駄な処理が含まれています。⁠Hello world!」の文字列はrodataセクションを確認するとアドレスがわかります。

$ riscv64-unknown-elf-objdump -sj .rodata hello

hello:     ファイル形式 elf64-littleriscv

セクション .rodata の内容:
 11fd8 48656c6c 6f20776f 726c6421 00000000  Hello world!....
 11fe8 0a000000 00000000                    ........

RISC-Vの命令長さは32bit固定ですが,⁠標準拡張」機能のひとつとしてARMのThumbのような16bit幅の命令もサポートしています。そのためdisassembleしたときに,同じ加算命令であるにも関わらず,複数の長さが現れていました。

16bit幅の加算命令(s0 = sp + 16):
   0x00000000000101a0 <+6>:     00 08           addi    s0,sp,16
32bit幅の加算命令(a0 = a5 - 40):
   0x00000000000101a4 <+10>:    13 85 87 fd     addi    a0,a5,-40 # 0x11fd8

著者プロフィール

柴田充也(しばたみつや)

Ubuntu Japanese Team Member株式会社 創夢所属。数年前にLaunchpad上でStellariumの翻訳をしたことがきっかけで,Ubuntuの翻訳にも関わるようになりました。