RISC-Vを使用したアセンブリ言語入門 〜1. 環境構築編〜


はじめに

この記事はアセンブリ言語を学びたいと思っている友人のために,書き記していくメモのようなものである.
どうせなら前から気になっていたQiitaを使用して公開する形で記述すれば良いのではと考えたので,他にも興味がある人たちにも参考になればと思う.
Qiitaにはこれが初投稿となるので,探り探りでやっていく.

アセンブリ言語を学ぶと何が嬉しいか

アセンブリ言語を学ぶメリットとして一番大きなものは,「ハードウェアを意識してプログラミングができるようになる」ということだと思う.
アセンブリ言語は最も機械語に近い言語であるので,学ぶことでプログラムが具体的にハードウェアでどのように実行されるのかを理解することができる.
特にプログラム内でどのようにメモリ領域を利用しているかを知ることで,C言語やその他の言語を記述する際にも役に立つのではないかと思う.

またアセンブリ言語が理解できるとそれらの命令が実行されているプロセッサなどのハードウェアを学ぶ上での入り口となり得るので,アーキテクチャ屋さんとしては学んでもらえると嬉しい.

なぜRISC-Vを使用するか

アセンブリ言語を学ぶ上で検討するべきことは,どの命令セットアーキテクチャ (ISA) を使用するかということだ.

ISAには様々な種類があり,その設計の方向性としてCISC (Complex Instruction Set Computer) とRISC (Reduced Instruction Set Computer) の大きく2つがある.
現在どちらも様々な製品や教育用途に使用されており,代表的なものは以下の通り.

  • CISC : x86, System/360
  • RISC : ARM, SPARC, MIPS, RISC-V

学習用途ではMIPSなどがよく使用されている気がするが,今回は以下のような理由からRISC-Vを使用することにした.

  • 最近注目されているオープンISA
  • RISC-Vを使用する成果物もオープンで公開されており使いやすい
  • ISAが整理されており全容を把握しやすいのでは (主観)
  • RISCの方が命令長が固定でメモリ領域を把握しやすい
  • (著者が主に使用しているISAなので)

RISC-Vのプログラムを実行できる環境の構築

とりあえずRISC-Vのプログラムを汎用OS上で実行するためには,以下の3つが必要である.

  • RISC-Vのクロスコンパイラ
  • RISC-Vのプロセッサシミュレータ
  • I/Oなどの本来OSが行う仕事を肩代わりするRISC-V Proxy Kernel

環境として"Ubuntu 18.04"を使用して作業を行う.
(正確にはmacOS上の"Vagrant + VirtualBox"上の"Ubuntu 18.04"だが特に変わらないと思う)
(Windows10 2004のWSL2を使用した"Ubuntu 18.04"の環境でも動作することを確認した)

RISC-Vのクロスコンパイラのインストール

RISC-Vのクロスコンパイラにもいろいろ種類があるが,今回は自分もよく使っているRISC-V GNU Compiler Toolchainを使用する.
GitHubのページこのページを参考にした.

まず前準備として依存関係のあるパッケージをインストールする.

$ sudo apt install autoconf automake autotools-dev curl python3 libmpc-dev libmpfr-dev libgmp-dev gawk build-essential bison flex texinfo gperf libtool patchutils bc zlib1g-dev libexpat-dev

次にGitHubからソースコードをcloneしてくる.
このリポジトリはサブモジュールを使用しているので,--recursiveオプションが必要.
またこのクロスコンパイラは現在も更新されているため,Releases Tagから"v20180629: June 29th, 2018 Toolchain Release"を選択して使用するようにする.

$ git clone https://github.com/riscv/riscv-gnu-toolchain
$ cd riscv-gnu-toolchain
$ git checkout cb6b34b8581bfc72197aa24bd4f367d54db81b51
$ git submodule update --init --recursive

ここまで来れば準備完了.
後はインストールするディレクトリとアーキテクチャやABIの種別を指定する.
インストールするディレクトリについて特に指定はないが,今回は/opt/riscv/elfにインストールする.

アーキテクチャやABIの種別については様々な構成から選択可能だが,今回は簡潔なアセンブリプログラムが出力できるように以下のように指定する.

  • 32ビットの Newlib クロスコンパイラ (riscv32-unknown-elf-XXX)
    • Linuxをビルド可能な方のクロスコンパイラではない
  • アーキテクチャ種別 : --with-arch=rv32im
    • iは整数基本命令,mは整数乗除算命令をサポートする
    • アトミック演算や浮動小数点演算はサポートしない
  • ABI種別 : --with-abi=ilp32

詳細についてはこのページなどが分かりやすいと思う.

よって以下のコマンドを実行する.

$ ./configure --prefix=/opt/riscv/elf --with-arch=rv32im --with-abi=ilp32
$ sudo make

ここまで実行すると/opt/riscv/elf/binにクロスコンパイラがインストールされる.

後はパスを通せば良い.
パスの通し方についてはいろいろなサイトに記載があるが例えば以下のコマンドのような感じ.

~/.bash_aliases
export PATH="$PATH:/opt/riscv/elf/bin"

このコマンドを毎回打つのは面倒くさいので,~/.bashrc~/.bash_profileに記述したりする.
パスが通せたら以下のコマンドを実行して,指定したディレクトリにクロスコンパイラがインストールされているかを確認する.

$ which riscv32-unknown-elf-gcc
/opt/riscv/elf/bin/riscv32-unknown-elf-gcc

$ riscv32-unknown-elf-gcc -v
Using built-in specs.
COLLECT_GCC=riscv32-unknown-elf-gcc
COLLECT_LTO_WRAPPER=/opt/riscv/elf/libexec/gcc/riscv32-unknown-elf/8.1.0/lto-wrapper
Target: riscv32-unknown-elf
Configured with: ...
Thread model: single
gcc version 8.1.0 (GCC)

RISC-Vのプロセッサシミュレータのインストール

クロスコンパイラでビルドしたRISC-Vのプログラムが動作するプロセッサシミュレータにもいろいろ種類がある.
今回はRISC-V公式サイトAvailable Softwareの"Simulators"からSpikeを選択する.
RISC-Vを開発したカリフォルニア大学バークレー校の人たちが開発したC++で記述されたシミュレータなのでほぼ公式のものって感じ.

まず前準備として依存関係のあるパッケージをインストールする.

$ sudo apt install device-tree-compiler

次にGitHubからソースコードをcloneしてくる.
このシミュレータは現在も更新されているため,Releases Tagから"Version 1.0.0"を選択して使用するようにする.
シミュレータのビルドを行うディレクトリbuild/を作成する.

$ git clone https://github.com/riscv/riscv-isa-sim
$ cd riscv-isa-sim
$ git checkout 2710fe575e7e6a4e2418224f8d254d5ca31f6c0e
$ mkdir build
$ cd build

ここまで来れば準備完了.
後はビルド時のオプションを指定する.
--prefixオプションでインストールするディレクトリを指定する.
インストールするディレクトリについて特に指定はないが,今回は/opt/riscv/spikeにインストールする.
またPCヒストグラムの生成を有効にする--enable-histogramを指定する.
最後にアーキテクチャ種別を--with-isa=RV32IMAFDCとして指定する.
クロスコンパイラの方では整数乗除算命令までをサポートするRV32IMを指定したので,それを包含するようにRV32IMAFDCを指定した.
ちなみにAはアトミック命令,FDは単精度&倍精度浮動小数点命令,Cは2バイト圧縮命令である.

よって以下のコマンドを実行する.

$ ../configure --prefix=/opt/riscv/spike --enable-histogram --with-isa=RV32IMAFDC
$ make
$ sudo make install

ここまで実行すると/opt/riscv/spike/binにSpikeシミュレータがインストールされる.

後はパスを通せば良いので,例えば以下のコマンドを~/.bashrc~/.bash_profileに記述する.

~/.bash_aliases
export PATH="$PATH:/opt/riscv/spike/bin"

パスが通せたら以下のコマンドを実行して,指定したディレクトリにSpikeシミュレータがインストールされているかを確認する.

$ which spike
/opt/riscv/spike/bin/spike

RISC-VのProxy Kernelのインストール

SpikeシミュレータでI/Oなどの入出力関係を簡単にシミュレートするために,I/Oなどの本来OSが行う仕事を肩代わりする"RISC-V Proxy Kernel"をインストールする.
Proxy KernelについてはGitHubのページこのページが詳しい.

GitHubからソースコードをcloneしてくる.
このシミュレータは現在も更新されているため,Releases Tagから"v1.0.0: Release riscv-pk-1.0.0"を選択して使用する.
シミュレータのビルドを行うディレクトリbuild/を作成する.

$ git clone https://github.com/riscv/riscv-pk
$ cd riscv-pk
$ git checkout fafaedd2825054222ce2874bf4a90164b5b071d4
$ mkdir build
$ cd build

後はビルド時のオプションを指定する.
--prefixオプションでインストールするディレクトリを指定する.
インストールするディレクトリについて特に指定はないが,今回は/opt/riscv/pkにインストールする.
使用するクロスコンパイラを--host=riscv32-unknown-elfとして指定する.
またアーキテクチャ種別をクロスコンパイラと同様に--with-arch=rv32imとして指定する.

よって以下のコマンドを実行する.

$ ../configure --prefix=/opt/riscv/pk --host=riscv32-unknown-elf --enable-print-device-tree --enable-logo --with-arch=rv32im
$ make
$ sudo make install

ここまで実行すると/opt/riscv/pk/riscv32-unknown-elf/binにRISC-V Proxy Kernelがインストールされる.
実際のProxy Kernelのバイナリは/opt/riscv/pk/riscv32-unknown-elf/bin/pkである.
コンパイルしたRISC-VのプログラムをSpikeシミュレータで動作させるためには,このバイナリpkがプログラムのバイナリと同じディレクトリにある必要がある.
そこでプログラムの実行を簡単にするために以下のaliasを~/.bashrc~/.bash_profileに記述しておく.

~/.bash_aliases
alias pk="ln -s /opt/riscv/pk/riscv32-unknown-elf/bin/pk ./pk"

これでコマンドpkを実行すると先ほどインストールしたRISC-V Proxy Kernelのバイナリpkのシンボリックリンクpkがカレントディレクトリに作成されるようになる.

インストールしたツールの動作確認

インストールしたツールの動作確認をするために"Hello, World!!"を表示するプログラムの作成から実行を試してみる.

まず普通の"Hello, World!!"のプログラムをhello.cとして作成する.

hello.c
#include <stdio.h>

int main(void)
{
    printf("Hello, World!!\n");
    return 0;
}

以下のコマンドを実行してhello.cをコンパイルする.

$ riscv32-unknown-elf-gcc -o hello hello.c

RISC-Vのバイナリhelloが生成される.

ちなみにこのバイナリhelloをそのまま実行しようとするともちろんエラーが発生する.
(RISC-Vのプロセッサ上で実行していないので)

$ ./hello 
-bash: ./hello: cannot execute binary file: Exec format error

次にコマンドpk実行してRISC-V Proxy Kernelのバイナリpkのシンボリックリンクをカレントディレクトリに作成する.

$ pk

最後に以下のコマンドを実行してSpikeでProxy Kernelであるpkとバイナリhelloを実行すると"Hello, World!!"が実行される.

$ spike pk hello
bbl loader
Hello, World!!

おわりに

今回はC言語で記述したプログラムをRISC-VでコンパイルしSpikeシミュレータで実行するまでの環境構築について説明した.
動作確認まで実行できれば環境構築ができていると思うので,まずはいろいろなC言語のプログラムをRISC-Vでコンパイルして実行してみてほしい.

次回は実際のプログラムがどのようなアセンブリ言語で構成されていて実行されているかを確認してみたいと思う.