FPGA開発日記

FPGAというより、コンピュータアーキテクチャかもね! カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages

RISC-V for LLVMのビルド試行 (2. RISC-Vターゲットでのコンパイルとテスト)

f:id:msyksphinz:20181111134229p:plain

RISC-V on LLVMについて少し調べている。 LLVMソースコードを眺めていると、TargetとしてRISC-Vが追加されているのを発見した。 なんだ、追加されてるじゃないか。じゃあ動くんじゃないか。

という訳で最新版のLLVM(ver 8.0?)とClangを落としてきて、パッチを当てずにターゲットをRISC-Vにしてビルドを試行した。

mkdir build-llvm
cd build-llvm
cmake -G Ninja -DCMAKE_BUILD_TYPE="Release" -DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD="RISCV" ../llvm
cmake --build .

一応ビルドは成功したようだ。 テストプログラムを流してみる。まずは、オブジェクトを生成するだけ。

$ cat hello.c
int test_int(int a, int b) {
  return a + b;
}

float test_float(float a, float b) {
  return a + b;
}
$ ./bin/clang --target=riscv64-unknown-elf -O3 hello.c -c -o hello.o
fatal error: error in backend: Cannot select: 0x7fffd2df4fb8: i64 = sign_extend_inreg 0x7fffd2df4f50, ValueType:ch:i32
  0x7fffd2df4f50: i64 = add 0x7fffd2df4c78, 0x7fffd2df4ba8
    0x7fffd2df4c78: i64 = AssertSext 0x7fffd2df4ad8, ValueType:ch:i32
      0x7fffd2df4ad8: i64,ch = CopyFromReg 0x7fffd2d9fc78, Register:i64 %1
        0x7fffd2df4a70: i64 = Register %1
    0x7fffd2df4ba8: i64 = AssertSext 0x7fffd2df4a08, ValueType:ch:i32
      0x7fffd2df4a08: i64,ch = CopyFromReg 0x7fffd2d9fc78, Register:i64 %0
        0x7fffd2df49a0: i64 = Register %0
In function: test_int
clang-8: error: clang frontend command failed with exit code 70 (use -v to see invocation)
clang version 8.0.0 (https://git.llvm.org/git/clang.git/ 3919b8d9833d23d98e8be71fe608627866d5c40c) (https://git.llvm.org/git/llvm.git 20a1e3c6acad46a947e7633f39c83eb00de22ea2)
Target: riscv64-unknown-unknown-elf
Thread model: posix
InstalledDir: /home/msyksphinz/work/riscv/llvm-build/./bin
clang-8: note: diagnostic msg: PLEASE submit a bug report to https://bugs.llvm.org/ and include the crash backtrace, preprocessed source, and associated run script.
clang-8: note: diagnostic msg:
********************

PLEASE ATTACH THE FOLLOWING FILES TO THE BUG REPORT:
Preprocessed source(s) and associated run script(s) are located at:
clang-8: note: diagnostic msg: /tmp/hello-3a45a2.c
clang-8: note: diagnostic msg: /tmp/hello-3a45a2.sh
clang-8: note: diagnostic msg:

********************

うお、いきなり落ちた!まだきちんと対応していないのか?

と思ったら、よく考えたらまだriscv32の対応に限定されているのか。riscv32-unknown-elfでリコンパイルしてみる。

$ ./bin/clang --target=riscv32-unknown-elf -O3 hello.c -c -o hello.o
$ riscv64-unknown-elf-objdump -d hello.o

hello.o:     file format elf32-littleriscv


Disassembly of section .text:

00000000 <test_int>:
   0:   00a58533                add     a0,a1,a0
   4:   00008067                ret

00000008 <test_float>:
   8:   ff010113                addi    sp,sp,-16
   c:   00112623                sw      ra,12(sp)
  10:   00000097                auipc   ra,0x0
  14:   000080e7                jalr    ra
  18:   00c12083                lw      ra,12(sp)
  1c:   01010113                addi    sp,sp,16
  20:   00008067                ret

ちゃんと生成できている。と思ったらtest_floatの方は様子が変だぞ。こんなコードで浮動小数点の加算ができるはずがない。

ちゃんと-marchを指定しなきゃだめだ。

$ ./bin/clang --target=riscv32-unknown-elf -march=rv32gc -O3 hello.c -c -o hello.o
$ riscv64-unknown-elf-objdump -d hello.o

hello.o:     file format elf32-littleriscv


Disassembly of section .text:

00000000 <test_int>:
   0:   952e                    add     a0,a0,a1
   2:   8082                    ret

00000004 <test_float>:
   4:   f0058053                fmv.w.x ft0,a1
   8:   f00500d3                fmv.w.x ft1,a0
   c:   0000f053                fadd.s  ft0,ft1,ft0
  10:   e0000553                fmv.x.w a0,ft0
  14:   8082                    ret

よく考えたらこれもおかしい。なんで整数レジスタ経由で渡しているんだ?ABIを変えてみよう。

$ ./bin/clang --target=riscv32-unknown-elf -march=rv32gc -mabi=ilp32 -O3 hello.c -c -o hello.o
$ riscv64-unknown-elf-objdump -d hello.o

hello.o:     file format elf32-littleriscv


Disassembly of section .text:

00000000 <test_int>:
   0:   952e                    add     a0,a0,a1
   2:   8082                    ret

00000004 <test_float>:
   4:   f0058053                fmv.w.x ft0,a1
   8:   f00500d3                fmv.w.x ft1,a0
   c:   0000f053                fadd.s  ft0,ft1,ft0
  10:   e0000553                fmv.x.w a0,ft0
  14:   8082                    ret

それでも直らない。float経由で引数を渡すABIはあるはずだけど...

2018年のRISC-V関係の(私の)講演資料をすべて公開しました

f:id:msyksphinz:20181115000946p:plain

ずいぶんとアップロードするのを忘れていましたが、今年の予定しているRISC-V関係の講演はすべてやり切ったので、発表資料をすべて公開したいと思います。

今年は4件講演しました。秋頃から集中したのでずいぶんと息切れしました。 特にDesign Solution Forumは40分、ET & IoT Techonolgyのテクニカルセッションは90分なのでかなりきつい。

あとは、RISC-V Day WorkshopでMICRO51の会場で発表できたのは個人的にうれしかった。

RISC-V Day Workshopの資料はriscv.orgに載るはずなんですが、未だにアップロードされていないので私の資料は勝手に公開します。 RISC-V FoundationのAsia支部、どうなってんの?

  • 2018年09月12日 Design Solution Forum 2018
  • RISC-V Day Tokyo 2018, Thursday, 18 October 2018
  • IEEE Micro51 RISC-V Day 2018 Fukuoka Saturday, October 20th 2018
  • ET & IoT Techonolgy 2018 テクニカルセッションTS-3 2018/11/14

特にETのテクニカルセッションは大作。90ページ近くもあります。講演台に水を持ってくるのを忘れて声が枯れかけました。

以下発表資料。

speakerdeck.com

  • RISC-Vエコシステムを構成する技術と立役者たち
    • RISC-V Day Tokyo 2018, Thursday, 18 October 2018

speakerdeck.com

  • Introduction of Technologies and People Supporting RISC-V Ecosystem
    • IEEE Micro51 RISC-V Day 2018 Fukuoka Saturday, October 20th 2018

speakerdeck.com

  • ますます注目されるオープンCPUアーキテクチャRISC-Vの最新動向
    • ET & IoT Techonolgy 2018 テクニカルセッションTS-3 2018/11/14

speakerdeck.com

Ubuntu on Windows Subsystem for LinuxでRISC-V環境のビルド試行

f:id:msyksphinz:20181113014541p:plain

Ubuntu on Windows Subsystem for Linux上でRISC-Vのツールチェインとデザインが使えるのかどうか試行した。

なんでこんなことをしているのかというと、自宅で使っているUbuntu on Virtual Boxがクラッシュして自宅のRISC-V環境を全部失ったから。 折角なので、Windows Subsystem for Linux上でRISC-Vのツールが動作するか確かめておきたい。

時間がかかるので後で追記する。

riscv-tools

64-bit版は正常にビルド完了した。 32-bit版も正常にビルド完了した。

Rocket-Chip (とchisel)

Chiselは通常のUbuntuと同様にインストールできた。

Rocket-Chipのビルドも完了し、RTLシミュレーションは実行できた。

$ make output/dhrystone.riscv.out
mkdir -p ./output
ln -fs /home/msyksphinz/riscv64//riscv64-unknown-elf/share/riscv-tests/benchmarks/dhrystone.riscv output/dhrystone.riscv
./emulator-freechips.rocketchip.system-DefaultConfig +max-cycles=100000000 +verbose output/dhrystone.riscv 3>&1 1>&2 2>&3 | /home/msyksphinz/riscv64/bin/spike-dasm  > output/dhrystone.riscv.out && [ $PIPESTATUS -eq 0 ]
Microseconds for one run through Dhrystone: 496
Dhrystones per Second:                      2013
mcycle = 248426
minstret = 198768

Freedom-u-sdk

UbuntuのPATHが悪さをしているようだ。デフォルトだとLinuxのPATHにはWindowsの$PATH$が入り込むが、レジストリエディタを使ってこの設定を無効化する。

qiita.com

以下のエラーメッセージで落ちた。このエラーメッセージで検索すると、WSLではサポートされていないようだ。残念。

PATH="/home/msyksphinz/work/riscv/freedom-u-sdk/work/buildroot_initramfs/host/bin:/home/msyksphinz/work/riscv/freedom-u-sdk/work/buildroot_initramfs/host/sbin:/home/msyksphinz/work/riscv/freedom-u-sdk/work/buildroot_initramfs/host/usr/bin:/home/msyksphinz/work/riscv/freedom-u-sdk/work/buildroot_initramfs/host/usr/sbin:/home/msyksphinz/work/riscv/freedom-u-sdk/toolchain/bin:/home/msyksphinz/.cargo/bin:/home/msyksphinz/.cargo/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:/usr/lib/jvm/java-8-oracle/bin:/usr/lib/jvm/java-8-oracle/db/bin:/usr/lib/jvm/java-8-oracle/jre/bin:/home/msyksphinz/software/sbt/bin/:/usr/lib/jvm/java-8-oracle/bin:/usr/lib/jvm/java-8-oracle/db/bin:/usr/lib/jvm/java-8-oracle/jre/bin:/home/msyksphinz/software/sbt/bin/" /home/msyksphinz/work/riscv/freedom-u-sdk/work/buildroot_initramfs/host/usr/bin/fakeroot -- /home/msyksphinz/work/riscv/freedom-u-sdk/work/buildroot_initramfs/build/_fakeroot.fs
fakeroot, while creating message channels: Function not implemented
This may be due to a lack of SYSV IPC support.
fakeroot: error while starting the `faked' daemon.
kill: usage: kill [-s sigspec | -n signum | -sigspec] pid | jobspec ... or kill -l [sigspec]
fs/tar/tar.mk:13: recipe for target '/home/msyksphinz/work/riscv/freedom-u-sdk/work/buildroot_initramfs/images/rootfs.tar' failed
make[2]: *** [/home/msyksphinz/work/riscv/freedom-u-sdk/work/buildroot_initramfs/images/rootfs.tar] Error 1
Makefile:36: recipe for target '_all' failed
make[1]: *** [_all] Error 2
make[1]: Leaving directory '/home/msyksphinz/work/riscv/freedom-u-sdk/buildroot'
Makefile:90: recipe for target '/home/msyksphinz/work/riscv/freedom-u-sdk/work/buildroot_initramfs/images/rootfs.tar' failed
make: *** [/home/msyksphinz/work/riscv/freedom-u-sdk/work/buildroot_initramfs/images/rootfs.tar] Error 2

Freedom-e-sdk

ダウンロードとビルド自体はできる。しかしサンプルコードをアップロードしようとすると失敗した。 デバイスドライバがまだWSLでサポートされていないらしい。 SiFiveのアカウントでも未サポートとのことらしい。

forums.sifive.com

RISC-V for LLVMのビルド試行

f:id:msyksphinz:20181111134229p:plain

コンパイラ勉強会に参加してコンパイラ熱が高まっているので、LLVM for RISC-Vについて久しぶりに調べた。

これまで何度かRISC-V for LLVMについて調べてきたが、Mailing Listなどを見ていてもあまりアップデートがない。

msyksphinz.hatenablog.com

msyksphinz.hatenablog.com

LLVM for RISC-Vについては、以下が最新の情報だと思っている。

github.com

このあたりでも、やり取りが行われているのを見つけた。

forums.sifive.com

forums.sifive.com

なんか状況的にはぱっとしないなあ。。。

RISC-V向けのパッチが格納されており、これはまだUpstreamされていないのか? しかもRV32向けのパッチしか存在していないように思える。 RV64は未対応なのだろうか。 LLVMのリリースノートを見ても、サポートされているアーキテクチャが示されていないので、現状ではパッチを充てるしか方法が無いように思える。

とりあえず、LLVMをダウンロードして、パッチを当ててビルドをしてみる。上記のリポジトリの通りに作業を行うが、その前に32ビット版のRISC-Vコンパイラを用意する必要があるらしい。

git clone https://github.com/riscv/riscv-tools.git --recurse-submodules
cd riscv-tools
export RISCV=${HOME}/riscv32
MAKEFLAGS="-j4" ./build-rv32ima.sh

次に、LLVMをダウンロードしてパッチをあて、ビルドする。

export REV=326957 # Check the most recent commit on this repo to ensure this is correct
svn co http://llvm.org/svn/llvm-project/llvm/trunk@$REV llvm
cd llvm/tools
svn co http://llvm.org/svn/llvm-project/cfe/trunk@$REV clang
cd ..
for P in /path/to/riscv-llvm/*.patch; do patch -p1 < $P; done
for P in /patch/to/riscv-llvm/clang/*.patch; do patch -d tools/clang -p1 < $P; done
mkdir build
cd build
cmake -G Ninja -DCMAKE_BUILD_TYPE="Debug" \
  -DBUILD_SHARED_LIBS=True -DLLVM_USE_SPLIT_DWARF=True \
  -DLLVM_OPTIMIZED_TABLEGEN=True \
  -DLLVM_BUILD_TESTS=True \
  -DDEFAULT_SYSROOT="${RISCV}/riscv32-unknown-elf" \
  -DGCC_INSTALL_PREFIX="${RISCV}" \
  -DLLVM_DEFAULT_TARGET_TRIPLE="riscv32-unknown-elf" \
  -DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD="RISCV" ../
cmake --build .

一応ビルドできたようだ。テストを流してみると、結構コケている。余り状況は変わっていないのかな?

msyksphinz@msyksphinz-VirtualBox:~/work/riscv/llvm/build$ ./bin/llvm-lit -s -i -v test/MC/RISCV
Testing Time: 1.82s
  Expected Passes    : 51
msyksphinz@msyksphinz-VirtualBox:~/work/riscv/llvm/build$ ./bin/llvm-lit -s -i -v test/CodeGen/RISCV
Testing Time: 3.84s
  Expected Passes    : 53
  Expected Failures  : 2
...

Chiselで記述された教育用RISC-VプロセッサSodor (1. ビルドとセットアップ)

f:id:msyksphinz:20181105012126p:plain

Chiselのイントロダクションを一通りやり終えたので、次はより実践的なデザインを見ていくために、RISC-V Sodorを見ていくことにした。

Sodorは教育用に作られたRISC-Vのプロセッサで、Rocketよりも単純な構成を取っている。 デザインも1ステージのものから5ステージのものまで用意されており、徐々にスケールアップしていくには良いデザインではなかろうか。

まずはデザインをコンパイルしてからシミュレーションをするところまで進めてみる。

github.com

まずはリポジトリをダウンロードする。RISC-V Fesvrも一緒に落ちてくるので、--recurse-submodulesを付けておくとよいかもしれない。

git clone https://github.com/ucb-bar/riscv-sodor.git --recurse-submodules

fesvrのビルドをする。これはシミュレーションを行うときに必要だ。

cd riscv-sodor/riscv-fesvr
mkdir build
cd build
../configure --prefix=${PWD}/../local
make
make install

次に、Sodorの本体に戻ってビルドを実行する。

cd ${riscv-sodor repository}
./configure --with-riscv=${PWD}/riscv-fesvr/local
make

シミュレーションを実行するためには、run-emulatorを実行すればよい。

make run-emulator
...
./emulator +max-cycles=10000000 +verbose +loadmem=/home/msyksphinz/work/riscv/riscv-sodor/install/riscv-bmarks/rsort.riscv none 3>&1 1>&2 2>&3 | /home/msyksphinz/riscv64/bin/spike-dasm  > output/rsort.riscv.out
mcycle =
minstret =
mkdir -p output
./emulator +max-cycles=10000000 +verbose +loadmem=/home/msyksphinz/work/riscv/riscv-sodor/install/riscv-bmarks/towers.riscv none 3>&1 1>&2 2>&3 | /home/msyksphinz/riscv64/bin/spike-dasm  > output/towers.riscv.out
mcycle =
minstret =
mkdir -p output
./emulator +max-cycles=10000000 +verbose +loadmem=/home/msyksphinz/work/riscv/riscv-sodor/install/riscv-bmarks/vvadd.riscv none 3>&1 1>&2 2>&3 | /home/msyksphinz/riscv64/bin/spike-dasm  > output/vvadd.riscv.out
mcycle =
minstret =

  [ PASSED ] output/median.riscv.out
  [ PASSED ] output/multiply.riscv.out
  [ PASSED ] output/qsort.riscv.out
  [ PASSED ] output/rsort.riscv.out
  [ PASSED ] output/towers.riscv.out
  [ PASSED ] output/vvadd.riscv.out

make[1]: Leaving directory '/home/msyksphinz/work/riscv/riscv-sodor/emulator/rv32_ucode'
install -d emulator/rv32_ucode/generated-src/
date > emulator/rv32_ucode/generated-src/timestamp

とりあえず動作することは確認した。

PortSmashで学ぶ高性能プロセッサの同時マルチスレッディング

CPUの脆弱性として新たに報告された "PortSmash" 、どういう脆弱性なのかを調べるために論文を探すと公開されていた。

  • Port Contention for Fun and Profit

https://eprint.iacr.org/2018/1060.pdf

"Port Contention for Fun and Profit" という論文として公開されている。 どうでもいいけれども、"Fun and Profit" (趣味と実益)っていう名前がついているのは謎である。

読み進めていくと、結構理屈としては分かりやすい攻撃手法だということが分かった。 ちなみにこのPortという言葉は、SSHなどのポートのことではない。 現代のアウトオブオーダのプロセッサには、命令の種類に応じて実行されるユニットが分けられている。 例えば算術演算命令ならばALUユニット、メモリアクセス命令だったらLSUユニットが使われる、といった具合だ。 これらのユニットは1プロセッサに同一の機能のものが複数入っており、命令発行ユニットからどのポート(どのユニット)に発行されるかは動的に決められる。

この「ポート」をうまく活用するので、"PortSmash"という名前がついている、という訳だ。

PortSmashが利用するハイパースレッディングとSMT(Simultaneous Multi Threading)とは

現代のハイパフォーマンスプロセッサには、物理コアと論理コアという考え方があり、物理的には1つのCPUでも、仮想的(論理的)には2つのコアに見せるという機能がある。

例えば、汎用レジスタなどは物理的に別々のものを用意しないと2つのプロセッサとして機能しないが、ALUやメモリアクセスユニットなどは同じものを時分割共有で使うことで、プロセッサのパイプライン重点率を上げつつ、チップサイズを削減することが可能だ。

これがハイパースレッディングの基本的な考え方だ。Intelのプロセッサではハイパースレッディングと呼び、一般的にはSMT(Simultaneous Multi Threading)と呼ぶ。

f:id:msyksphinz:20181109000634p:plain

ハイパースレッディングでは同じタイミングでCPU内に複数の命令系列が流れる。論理コアが2つの場合、プロセッサ内では2つの命令系列が流れている。 これらは明確に分離され、互いのスレッドの情報が読み取れてはならない。 これが読み取れてしまうというのがこのCPUの脆弱性であり、Meltdown, Spectreの脆弱性も類似するものである。

同時に複数のスレッドが混在されており、さらにプロセッサの内部には複数の機能ユニットが内蔵されているため、どの命令をどのユニットに発行するかというものが制御される。

例えばALUの算術演算命令は実行頻度が高いので、ALUユニットはCPU内に複数個用意されている。 命令発行ユニットは、空いているユニットに算術演算を発行し、たとえ異なるスレッドであってもALUは共有されている。

現代のIntelの高性能プロセッサ(SkyLake / Kaby Lake)では、実行ユニットとして8つのユニットが用意されている。

f:id:msyksphinz:20181108225632p:plain
図2. Intel SkyLake, Kaby Lakeのマイクロアーキテクチャ (本論文より抜粋)
  • Port 0 : 整数ALU、整数除算、ベクトルALU、ベクトル乗算、AES、ベクトル文字列処理、浮動小数点除算、分岐命令
  • Port 1 : 整数ALU、整数乗算、ベクトルALU、ベクトル乗算、ビットスキャン
  • Port 2 : アドレス生成(AGU)、ロード命令
  • Port 3 : アドレス生成(AGU)、ロード命令
  • Port 4 : ストア命令
  • Port 5 : 整数ALU、ベクトルシャッフル、ベクトルALU、アドレス計算(LEA)
  • Port 6 : 整数ALU、分岐
  • Port 7 : アドレス生成(AGU)

これらをどのように各スレッドが使っていくか、このポートの使い方の特徴を見抜いて、隣で走っているスレッドの情報を読み取ろうというのが、PortSmashの基本的な考え方だ。

ポート割り当て方のモデル化

物理コア1つの中に論理コアが2つ入っているとして、各スレッドの命令がどのようにしてポート割り当てを行っているのかというのをモデルかしてみる。 論文中には結構小難しいことが書いてある。

 i はクロックサイクル番号を示し、 j = i \mod 2を示し、Pはポートのセットを示す。

  1.  C _ {j} is allotted P _ {j} ⊆ P such that  \left|P \backslash P _ {j} \right| is minimal.
  2.  C _ {1−j} is allotted P _ {1−j} = P \backslash P _ {j}.

小難しそうだが、大したことはない。1サイクル毎にスレッドの優先度を交換し、最大優先度のスレッドが所望のポートを取ることができる。 優先度の低いスレッドは残ったポートから割り当てが行われる。

では、同一のスレッドが同じ命令ばかりを実行してスレッドを占有したらどうなるだろうか? もう一つのスレッドは、ある命令を流したくてそのポートを使いたいのだけれども、他のスレッドに邪魔をされて毎サイクルその命令を流すことができない。 したがって、スレッド的には命令の実行レイテンシに影響が出てくる。 アリスとボブの例で、これが説明されている。これを、「Secret Timing Channel」と呼ばれている。

2つのレイテンシ1命令が存在する。ポート0でのみ実行できるNOP0と、ポート1で実行されるNOP1である。 アリスは、以下のように1ビットの情報をBobに送信する。

  1. アリスがゼロを送信したい場合、彼女はNOP0の実行を継続する。それ以外の場合は、NOP1が代わりに実行される。
  2. 同時に、ボブは固定数のNOP0命令を実行し、実行時間 t0を測定する。
  3. その後、ボブは同じ固定数のNOP1命令を実行し、実行時間 t1を測定する。
  4.  t1> t0 の場合、ボブは1ビットを受信する。 そうでなければ、 t0> t1 およびゼロビットである。

これにより、本来は互いに独立したはずのスレッドで情報が読み取れてしまっている。これがPortSmashの基本的な考え方である。

f:id:msyksphinz:20181109001251p:plain:w1000
図. VictimとSpyで実行している命令の使用するポートが異なる場合、Spyコードのレイテンシは変わらない。
f:id:msyksphinz:20181109001454p:plain:w1000
図. VictimとSpyで実行している命令の使用するポートが同じ場合、Spyコードのレイテンシは伸びる。

PortSmashのSpyプログラム

PortSmashのSpyプログラムの例を以下に示す。P1P5P0156の3つのdefineで分けられていることに注意。

 mov  $COUNT, %rcx 
1: 
    lfence
    rdtsc
    lfence
    mov  %rax, %rsi
#ifdef P1 
.rept 48 
    crc32    %r8, %r8
    crc32    %r9, %r9 
    crc32    %r10, %r10 
.endr shl $32, %rax
#elif defined(P5) 
.rept 48 
    vpermd   %ymm0, %ymm1, %ymm0 
    vpermd   %ymm2, %ymm3, %ymm2 
    vpermd   %ymm4, %ymm5, %ymm4 
.endr
#elif defined(P0156)
.rept 64
    add  %r8, %r8
    add  %r9, %r9
    add  %r10, %r10
    add  %r11, %r11
.endr
#else
#error No ports defined
#endif
    lfence
    rdtsc
    or       %rsi, %rax
    mov  %rax, (%rdi)
    add  $8, %rdi
    dec  %rcx
    jnz  1b

P0156が有効の時、add命令が連続して発行され(しかも依存関係がない)るため、4つのポート0,1,5,6を占有してしまう。 これを64回繰り返すのだが、ポート競合が無い場合、これは128サイクルで実行されるはずである。

一方で、P1P5が有効の場合、それぞれcrc32命令と、vpermd命令が実行される。それぞれポート1とポート5を占有するのだが、それぞれレイテンシは3なので、48回実行することで最小で 3\times 48 + 2 = 146クロックサイクルで実行される。ポートの競合が発生すると、レイテンシはその2倍に増加する。

他の脆弱性攻撃との違い

PortSmashの最大の特徴は、それ以外のプロセッサの脆弱性攻撃(Meltdown, Spectre, TLBleed)などに比べて非常に粒度の小さいことだ。

基本的にキャッシュアクセスの脆弱性を突くタイプの攻撃は、キャッシュラインのサイズに依存して粒度が変動する。したがってあまり細かな粒度の情報を抽出することができないといわれている。 一方で、PortSmashは命令発行のポートを使用するため、その粒度は非常に小さい。 このため、取得できる情報も細かく制御できる。これがPortSmashの最大の特徴だ。

アプリケーション

PortSmashは、命令のカテゴリが違うだけで別のスレッドの情報を読み取れてしまうため、さまざまアプリケーションでその情報を抽出することができる。

  • ECC and P-384
  • ECDSA and P-384 in OpenSSL
  • Procurement Phase: TLS
  • Signal Processing Phase

などといろいろ書いてあるのだが、これまでの問題と大きく異なるのは、これまでIntelのライブラリなどはレイテンシを同一にすることで暗号化の計算結果を読み取ることができないような手法を用いていたが、そもそも発行される命令ポートが異なるだけで命令の情報が読み取れてしまうということは、この手法は使えなくなるということを意味する。

また、SGXでの保護を実装したとしても、これは意味をなさない。命令ポートの制御にはSGXは監視を行わないからだ。

回避する手段はあるのか?

PortSmashを回避する手段としては、2つ提案されている。

  • そもそもSMTを有効化しない(OpenBSDの考え方)。
  • ポートに依存しないコードを書く。プログラムの構成が非常に難しそうだが。。。

もう一つ、論文を読んで思ったことは、そもそも1サイクル毎に割り当てらるスレッドが規則正しく切り替わるというのが根本的な原因などだが、それをランダムにスレッドが切り替わるようにしてみてはどうだろう。

細粒度に同じ優先度でスレッドが切り替わらないと本来のマルチスレッドの性能が出せないのだが、例えば秘密データを取り扱うときだけ、スレッドの切り替わりのタイミングをランダムにしてみると、レイテンシを測定することによるデータの抽出が不可能になるかもしれない。

Chisel2とChisel3でのVerilog/C++コード生成フローの違い

f:id:msyksphinz:20181105012126p:plain

Chiselにはバージョンがあり、Chisel2とChisel3が公開されている。 微妙に文法が異なっているので要注意だ。

Chisel2ではVerilogC++を生成する方法が公開されているが、Chisel3ではどうだろう。

Chisel2でのVerilogC++の生成方法については、以下のサイトにまとめられている。 と思っていろいろ探したら自分のブログだった。

github.com

msyksphinz.hatenablog.com

  • src/main/scala/hello/Hello.scala
package Hello

import Chisel._

class Hello extends Module {
  val io = new Bundle {
    val out = UInt(OUTPUT, 8)
  }
  io.out := UInt(42)
}

class HelloTests(c: Hello) extends Tester(c) {
  step(1)
  expect(c.io.out, 42)
}

object Hello {
  def main(args: Array[String]): Unit = {
    val tutArgs = args.slice(1, args.length)
    chiselMainTest(tutArgs, () => Module(new Hello())) {
      c => new HelloTests(c) }
  }
}

Chisel3を触っているとわかる。微妙に違う。まず、import Chisel._ではなくChisel3だとimport chisel3._になる。次にBundleにカッコが入っていない。 次に、chiselMainTestなどという関数はない。これらを修正していく必要がある。

Chisel2では、chiselMainTestのargsを変更することによりC++を出力することができる。 つまり、--backend cを追加することでC++のコードが生成できる。

しかし、Chisel3ではできない。どのようにすればよいのか。

まず、上記のデザインはChisel3では以下のようになる。

package hello

import chisel3._
import chisel3.iotesters.{PeekPokeTester, Driver}

class Hello extends Module {
  val io = IO(new Bundle {
    val out = Output(UInt(8.W))
  })
  io.out := 42.U
}

class HelloTests(c: Hello) extends PeekPokeTester(c) {
  step(1)
  expect(c.io.out, 42)
}

object Hello {
  def main(args: Array[String]): Unit = {
    if (!Driver(() => new Hello())(c => new HelloTests(c))) System.exit(1)
  }
}

細かな文法の違いはあるが、大きくはchiselMainTest()が消える。その代わり、iotestersのDriverが直接呼び出される。

このAPIVerilogを生成するためのAPIである。じゃあC++を生成するためにはどうすればよいのか。

[https://chisel.eecs.berkeley.edu/api/latest/chisel3/Driver$.html#execute(args:Array[String],dut:()=%3Echisel3.experimental.RawModule):chisel3.ChiselExecutionResult:title]

同じiotestersにverilogToCppというメソッドがあるけど、これは何か違う気がする。

[https://chisel.eecs.berkeley.edu/api/latest/chisel3/Driver$.html#verilogToCpp(dutFile:String,dir:java.io.File,vSources:Seq[java.io.File],cppHarness:java.io.File,suppressVcd:Boolean):scala.sys.process.ProcessBuilder:title]

結論から言うと、Chisel3でC++を生成するのは直接的には不可能で、VerilatorでシミュレーションするためにC++を生成するという考え方に近い。

このiotestersのDriverにはどのような機能があるのか。オプションを与えるように変更していろいろと変えてみる。

object Hello {
  def main(args: Array[String]): Unit = {
    chisel3.Driver.execute(args, () => new Hello)
  }
}

sbt実行時に`sbt "run Hello.Hello --help"とか入れてみる。

common options
  -tn, --top-name <top-level-circuit-name>
                           This options defines the top level circuit, defaults to dut when possible
  -td, --target-dir <target-directory>
                           This options defines a work directory for intermediate files, default is .
  -ll, --log-level <Error|Warn|Info|Debug|Trace>
                           This options defines a work directory for intermediate files, default is .
  -cll, --class-log-level <FullClassName:[Error|Warn|Info|Debug|Trace]>[,...]
                           This options defines a work directory for intermediate files, default is .
  -ltf, --log-to-file      default logs to stdout, this flags writes to topName.log or firrtl.log if no topName
  -lcn, --log-class-names  shows class names and log level in logging output, useful for target --class-log-level
  --help                   prints this usage text
  <arg>...                 optional unbounded args
chisel3 options
  -chnrf, --no-run-firrtl  Stop after chisel emits chirrtl file
firrtl options
  -i, --input-file <firrtl-source>
                           use this to override the default input file name , default is empty
  -o, --output-file <output>
                           use this to override the default output file name, default is empty
  -faf, --annotation-file <input-anno-file>
                           Used to specify annotation files (can appear multiple times)
  -foaf, --output-annotation-file <output-anno-file>
                           use this to set the annotation output file
  -X, --compiler <high|middle|low|verilog|sverilog>
                           compiler to use, default is verilog
  --info-mode <ignore|use|gen|append>
                           specifies the source info handling, default is append
  -fct, --custom-transforms <package>.<class>
                           runs these custom transforms during compilation.
  -fil, --inline <circuit>[.<module>[.<instance>]][,..],
                           Inline one or more module (comma separated, no spaces) module looks like "MyModule" or "MyModule.myinstance
  -firw, --infer-rw <circuit>
                           Enable readwrite port inference for the target circuit
  -frsq, --repl-seq-mem -c:<circuit>:-i:<filename>:-o:<filename>
                           Replace sequential memories with blackboxes + configuration file
  -clks, --list-clocks -c:<circuit>:-m:<module>:-o:<filename>
                           List which signal drives each clock of every descendent of specified module
  -fsm, --split-modules    Emit each module to its own file in the target directory.
  --no-check-comb-loops    Do NOT check for combinational loops (not recommended)
  --no-dce                 Do NOT run dead code elimination

--compilerオプションがあるが、ここにはhigh|middle|low|verilog|sverilogとだけあり、high|middle|lowはFIRのレベル、sverilogに至っては生成されるものはVerilogと同一である。

このあたりのオプションがChisel2とChisel3ではかなり異なっている。注意して扱う必要がありそうだ。