アーキテクチャシミュレータGem5を使ってみる

# はじめに

アーキテクチャシミュレータ[Gem5](http://gem5.org/Main_Page)について、インストールから簡単な使い方まで説明する。なお、インストール環境はMac、エミュレート対象はLinuxとする。

# Gem5の概要

Gem5はアーキテクチャシミュレータで、CPUその他様々なハードウェアをシミュレートできる。Gem5にはFull System (FS)モードと、System Call Emulation (SE) モードの二種類のモードが存在する。FSモードは、その名の通り「全部シミュレーションする」モードで、とりあえずQEMUみたいなもんだと思えば良いと思う。SEモードは、その名の通りシステムコールをエミュレーションすることで、OS不要でユーザプログラムを実行できる。FSモードの方がより「リアル」なシミュレーションができるが、OSが介在するため、例えばOSによるスケジューリングの影響を受けたりする。SEモードはOS不要であるため、ユーザプログラムの解析に集中できる。また、デバイスドライバが足りないとかそういうことを考えなくても良い。

本稿では、とりあえずMac上で、SEモードで、x86Linux向けコードを実行し、解析するところまでやる。

# Gem5のインストールとビルド

まず、Gem5をダウンロードする。Gitリポジトリがあるので、適当なディレクトリでクローンすれば良い。

```sh
git clone --depth 1 https://gem5.googlesource.com/public/gem5
cd gem5
```

ビルドにはSConsを使うので、もし入っていなければ入れる。

```sh
brew install scons
```

また、ビルドの実行にはPython 2系列が必要なようだ。僕はpyenvで環境管理していたので2系列に切り替えた。

```sh
pyenv local anaconda2-4.4.0
```

ビルドは、Gem5リポジトリの中でsconsコマンドを使う。

```sh
scons build/X86/gem5.opt
```

sconsは、まず`build`というディレクトリを探すが、存在しないので`build_opts`を探しに行く。するとそこに`X86`があるので、それを参照しつつ、`build`を作って、そこにgem5のバイナリを作る。ものすごく時間がかかるので、コアがたくさんある人は`-j 8`とかやって並列ビルドしたほうが良いかも。

ビルドが終わったら実行バイナリができたか確認する。

```sh
$ ./build/X86/gem5.opt --version
gem5.opt 2.0
```

できたっぽいですね。

テストもしてみよう。リポジトリにx86-linuxのサンプルバイナリが入っているので、実行してみる。

```sh
$ ./build/X86/gem5.opt ./configs/example/se.py -c ./tests/test-progs/hello/bin/x86/linux/hello
gem5 Simulator System.  http://gem5.org
gem5 is copyrighted software; use the --copyright option for details.

gem5 compiled Aug 27 2019 12:23:47
gem5 started Aug 27 2019 12:27:22
gem5 executing on watanabe-iMac.local, pid 65602
command line: ./build/X86/gem5.opt ./configs/example/se.py -c ./tests/test-progs/hello/bin/x86/linux/hello

Global frequency set at 1000000000000 ticks per second
warn: DRAM device capacity (8192 Mbytes) does not match the address range assigned (512 Mbytes)
0: system.remote_gdb: listening for remote gdb on port 7000
**** REAL SIMULATION ****
info: Entering event queue @ 0.  Starting simulation...
Hello world!
Exiting @ tick 5984000 because exiting with last active thread context
```

「Hello world!」を実行するプログラムをSEモードで実行し、無事に「Hello world!」が表示された。

# 自分のプログラムの実行

さて、自分のコードをコンパイルして、gem5で動かしてみよう。Mac上でクロスコンパイルとかするのは面倒なので、適当なLinuxマシンでコンパイルしたバイナリを持ってきて、それを動かすことにする。なお、SEモードで実行するプログラムを用意する時、全てのライブラリを静的にリンクしてやらなければならない。SEモードはシステムコールをエミュレーションしてくれるため、OSイメージが不要となるが、逆にOSがよしなにやってくれていたダイナミックリンクができないためだ。

まず、適当なプログラムを書こう。

```c:test.c
#include <stdio.h>

int main(){
  printf("My first gem5\n");
  return 0;
}
```

これを`-static`をつけてビルドする。もし、

```sh
$ gcc test.c -static
/usr/bin/ld: -lc が見つかりません
collect2: エラー: ld はステータス 1 で終了しました
```

などと言われてビルドに失敗したら、静的リンク用のライブラリがインストールされていない。CentOSなら、

```sh
sudo yum install glibc-static
```

で入るはず。

あらためて、`gcc test.c -static`でビルドしたバイナリを持ってきて、以下を実行する。

```sh
gem5 Simulator System.  http://gem5.org
gem5 is copyrighted software; use the --copyright option for details.

gem5 compiled Aug 27 2019 12:23:47
gem5 started Aug 27 2019 14:35:34
gem5 executing on watanabe-iMac.local, pid 70256
command line: ./build/X86/gem5.opt ./configs/example/se.py -c a.out

Global frequency set at 1000000000000 ticks per second
warn: DRAM device capacity (8192 Mbytes) does not match the address range assigned (512 Mbytes)
0: system.remote_gdb: listening for remote gdb on port 7000
**** REAL SIMULATION ****
info: Entering event queue @ 0.  Starting simulation...
My first gem5
Exiting @ tick 6672500 because exiting with last active thread context
```

無事に実行できた。なお、実行するとカレントディレクトリに`m5out`というディレクトリができて、そこに情報が入る。例えば`stats.txt`にはこんな感じの統計情報が入る。

```txt
---------- Begin Simulation Statistics ----------
final_tick                                    6672500                       # Number of ticks from beginning of simulation (restored from checkpoints and never reset)
host_inst_rate                                 460199                       # Simulator instruction rate (inst/s)
host_mem_usage                                 691024                       # Number of bytes of host memory used
host_op_rate                                   918524                       # Simulator op (including micro ops) rate (op/s)
host_seconds                                     0.01                       # Real time elapsed on the host
host_tick_rate                              530785140                       # Simulator tick rate (ticks/s)
sim_freq                                 1000000000000                       # Frequency of simulated ticks
sim_insts                                        5735                       # Number of instructions simulated
```

どの命令が何回実行されたか、キャッシュミスはどのくらいおきたかなど、有用な情報が入っているので参考にされたい。

# 実行パイプラインの可視化

Gem5は、命令ごとに「フェッチ、デコード、ディスパッチ、リタイア」といったパイプライン情報を取ることができる。それを可視化してみよう。

こんなコードを実行することにしよう。

```test2.c
#include <stdio.h>
  
int main(){
  __asm__("CLC");
  printf("My first gem5\n");
  return 0;
}
```

プログラムを実行した際のトレース情報が膨大なので、とりあえず無害であまり使われない命令`clc`を目印代わりに突っ込んでいる。これを`gcc -static test2.c`でコンパイルしたバイナリを持ってきて、以下を実行する。

```sh
$ ./build/X86/gem5.opt --debug-flags=O3PipeView --debug-file=trace.out configs/example/se.py --cpu-type=DerivO3CPU --caches -c ./a.out
gem5 Simulator System.  http://gem5.org
gem5 is copyrighted software; use the --copyright option for details.

gem5 compiled Aug 27 2019 12:23:47
gem5 started Aug 27 2019 14:41:23
gem5 executing on watanabe-iMac.local, pid 70375
command line: ./build/X86/gem5.opt --debug-flags=O3PipeView --debug-file=trace.out configs/example/se.py --cpu-type=DerivO3CPU --caches -c ./a.out

Global frequency set at 1000000000000 ticks per second
warn: DRAM device capacity (8192 Mbytes) does not match the address range assigned (512 Mbytes)
0: system.remote_gdb: listening for remote gdb on port 7000
**** REAL SIMULATION ****
info: Entering event queue @ 0.  Starting simulation...
My first gem5
Exiting @ tick 22164000 because exiting with last active thread context
```

これは、デバッグフラグとしてパイプライン情報を表示する(O3PipeView)を指定し、トレース情報を`trace.out`として出力せよ、という意味だ。`trace.out`は`m5out`というディレクトリの下に保存される。

注意点として、[Gem5の公式ドキュメント](http://www.m5sim.org/Visualization)ではCPUタイプの指定で`--cpu-type=detailed`とせよ、と書いてあるのだが、うちの環境では`--cpu-type=DerivO3CPU`としないとトレース情報が出力されなかった。

何も指定しないとすべての情報が出力されるので注意。`printf`一つだけのバイナリで`trace.out'は7.8MBになった。必要があれば`--debug-start`で開始tickを、`-m`で終了tickを指定することで調べる範囲を狭めることができる。

`m5out/trace.out`ができたらそれを可視化しよう。

```sh
$ ./util/o3-pipeview.py -o pipeview.out --color m5out/trace.out
Processing trace...  done!
```

およそ3万2千行(7.5MB)の`pipeview.out`が出力される。

これを`less -r`するとパイプラインの様子を見ることができる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79744/333c20c1-f57d-c621-acab-99b8e98bc71d.png)

対応するアセンブリと比較してみよう。

```nasm:test2.s
.LFB0:
  pushq %rbp
  movq  %rsp, %rbp
#APP
# 4 "test2.c" 1
  CLC
# 0 "" 2
#NO_APP
  movl  $.LC0, %edi
  call  puts
  movl  $0, %eax
  popq  %rbp
  ret
```

最初にPUSHして、MOV_R_R(レジスタからレジスタへのMOV)して、CLC呼んで、MOV_R_I(即値をレジスタへMOV)して、CALL_NEAR_Iして、MOV_R_Iして、POP_Rして、RET_NEARでおしまい、というわけで、ちゃんとそのまま実行していることがわかる。

# まとめ

MacにGem5をインストールし、Linuxでビルドしたバイナリを実行し、そのパイプラインを可視化してみた。本稿が少しでもこれからこういうのでいろいろ調べたりするガチ勢の助けになれば幸いである。