FPGA開発日記

カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages , English Version https://fpgadevdiary.hatenadiary.com/

Intel Software Guard Extensionのチュートリアルを読む(1. SGX Foundation)

CPU脆弱性についての論文を読んでいると必ずと言っていいほど出てくるIntel SGX(Software Guard Extension)。 なんとなく秘匿データを扱うためのIntelのハードウェア機構なんだろうなと思っていたけど、あまり知ったかぶりをしているのもよくないのでチュートリアルを読んでみることにした。

参考にしたのは、Intel® Software Guard Extensions Tutorial Series だ。ハードウェアの詳細について書いてあるわけではないが、使い方についていろいろ書いてあるので基礎知識を得るのにはよさそう。

software.intel.com

長々と書いているが要するに、

  • SGXは秘密情報を扱うためにアプリケーション階層内に「飛び地(enclave)」を作成する。この飛び地に秘密情報を格納して管理する。
  • enclaveに入れるのは、Intelの専用命令を実行した場合のみだ。新しく追加されたSGX用の専用命令により、コアはSGXの内部のデータにアクセスできるようになる。
  • 複数のenclaveがデータをアクセスするために、鍵を管理するための複数の機構が用意されている。

最初は1段落1まとめくらいでメモを取っていたのだが、だんだん翻訳になっていったのでそのまま掲載する。

ただし、あまり深入りせずに基礎知識を手に入れた段階で手を引くつもり。


Intel Software Guard Extension チュートリアル連載

Part-1 Intel SGX Foundation

Intel Software Guard Extensionテクノロジを理解する

ソフトウェアの中で、「パスワード・アカウント番号・秘密鍵・健康状態の記録」などといった秘密にしなければならないデータを扱うときに、Intel SGXの技術を使用すると、これらの情報をアプリケーションの秘密情報として使用することができる。

意図しないアプリケーションからの秘密情報へのアクセスや、権限を持っていないアプリケーションからOSの秘密情報へのアクセスは防がなければならない。アプリケーションは情報の暗号化などの秘密保持技術を使用して、秘密情報をサードパーティのストレージに転送したり、ネットワーク越しにデータの転送を行う。

しかしこれ以外に、コンピュータシステム内でも様々な脆弱性が存在する。あるアプリケーションが扱っている秘密情報を守ろうとしても、マルウェアがより高い権限を持って制限のないアクセスを知ってしまうと、システム上で動作しているすべてのシステムとすべてのアプリケーションの情報が取得されてしまう。洗練されたマルウェアはアプリケーションの保護機構を狙い、暗号カギを抽出して極秘データ自身を直接メモリから取得してしまう。

ソフトウェアによるこれらの攻撃を防ぐために、開発されたのがIntel SGXだ。Intel SGXはCPU命令セットの集合であり、アプリケーションが「飛び地」、つまりアプリケーションのアドレス領域において秘匿性やインテグリティを確保することができる領域を作成する。「飛び地」のコードは特殊命令によって有効化され、WindowsDynamic Link Library(DLL)ファイルによってロードされる。

Intel SGXはアプリケーションの境界による攻撃を削減することができる。図1. は、Intel SGXを使用した場合と使用しない場合での攻撃の海面を示している。

f:id:msyksphinz:20180820205944p:plain

Intel Software Guard Extensionテクノロジはどのようにしてデータを守るのか

  • 飛び地のメモリ領域は、現在の権限モードとCPUモードに関わらず、飛び地領域の外からアクセスすることはできない。
  • Production Enclavesは、ソフトウェア・ハードウェアデバッガを使ってデバッグすることはできない。
    • デバッグ属性のついたEnclaves領域は、特殊なデバッガ("Intel SGX Debugger")デノミ作成することができる。
    • これはソフトウェアの開発サイクルを支援するための機能である。
  • 通常の関数呼び出し・ジャンプ・レジスタ操作によってEnclave領域に入ることはできない。Enclave関数に入るための唯一の方法は、いくつかの保護チェックを実行する新しい命令を実行する以外にない。
  • EnclaveメモリはRelay Protectionを使用した業界標準の暗号化技術を使用して暗号化される。メモリをタッピングしたり、DRAMモジュールを他のシステムに接続すると、暗号化されたデータのみやり取りが行われる(図2. を参照のこと)
  • メモリの暗号化鍵は、すべての電源サイクル(例えば、ブート時・スリープやハイバネーション状態からの復帰時)でランダムに変更される。暗号化鍵はCPUに保持されアクセスすることはできない。
  • Enclaveにより保護されたデータはEnclaveを共有するコードによってのみアクセスすることができる。

保護メモリの領域はBIOSによって設定され、64MBか128MBである。いくつかのシステムではBIOSセットアップにより他のオプションを提供する場合がある。各Enclaveのフットプリントによって、メモリ中におおよそ5~20個のEnclaveをメモリ中に同時に作成することができる。

f:id:msyksphinz:20180820210010p:plain

Design Consideration

Intel SGXを使用するアプリケーションは、2つのコンポーネントに分割される必要がある(図3. を参照のこと)。

  • Trustedコンポーネント Enclaveのこと。Trustedコード中のコードはアプリケーションの秘密情報にアクセスすることができる。アプリケーションは、1つ以上のTrustedコンポーネント・Enclaveを持つことができる。
  • Untrusted コンポーネント アプリケーションないのTrustedでない領域のこと。OSやVMMなどはUntrustedコンポーネントであると考えることができる。

Trustedコンポーネントは可能な限り小さくするべきである。保護が必要なデータと、そのデータに対する捜査を行う操作のみをTrustedコンポーネントにすべきである。Enclaveを大きくしてしまい複雑なインタフェースを持たせてしまうとより多くの保護メモリを消費してしまい、また攻撃可能な境界を増やしてしまう。

また、EnclaveはTrustedとUntrustedなコンポーネントのインタフェースを最小限に抑えるべきである。Enclaveが保護されたメモリ領域を飛び出し、Untrustedなコンポーネントを呼び出すとき(これには特殊な命令が必要である)、TrustedとUntrustedの依存性を最小限に抑えることにより、Enclaveが攻撃される可能性を抑えることができる。

f:id:msyksphinz:20180820210033p:plain

Attestation(日本語訳: 証明・証拠)

Intel SGXアーキテクチャでは、Attestationとはプラットフォーム上で特定のEnclaveが構成されたことを示すためのプロセスを指す。2つのAttestationのメカニズムが存在する。

  • Local Attestation 同一プラットフォーム上の2つのEnclaveが互いに認証したときにこの状態を指す。
  • Remote Attestation Enclaveが、信頼できるリモートプロバイダから認証を得たときにこの状態を指す。

Local Attestation

Local Attestationは、1つのアプリケーションが複数のEnclaveを保持しており、これらが互いに強調して動作しなければならない場合や、2つのアプリケーションがEnclaveを通じて互いにコミュニケーションをとらなければならないときに有用である。互いのEnclaveは、互いが信頼できることを確認するために、互いの検証を行う必要がある。検証が完了すると、互いに保護されたセッションを構築し、ECDH鍵交換を使用してセッション鍵を交換する。このセッション鍵は2つのEnclave間でデータを共有するための暗号化に使用される。

Remote Attestation

Remote Attestationでは、サードパーティのサーバを使用して認証を獲得するのだが、そのためのくおーとを生成するためにIntel SGXソフトウェアとハードウェアを使用する。ソフトウェアにはアプリケーションのEnclaveとQuoting Enclave(QE)、Provisioning Enclave(PvE)が含まれている。QEとPvEはどちらともIntelから提供される。Attestationのハードウェアは、Intel SGXが有効化されたCPUである。ソフトウェアの情報はプラットフォームで単一な非対称鍵と掛け合わされ、信頼されたチャネルを通じてリモートサーバに転送される。リモートサーバが、転送されたEnclaveがIntel SGXが動作可能なプロセッサ上で正常にインスタンスされ実行されていること判断した場合、Enclaveを信頼し、認証されたチャネル上に秘密情報をプロビジョニングできるようになる。

データのシーリング(日本語訳: データの密閉)

データのシーリングは、データを暗号化する処理のことであり、これにより他社に内容を読み取られることなく、秘匿データを保護されていないメモリやストレージ上に配置することができる。データはEnclaveにより読み取られ、アンシール(複合化)することができる。暗号化鍵はオンデマンドに入手することができ、Enclaveの外に漏れることは無い。

データをシーリングするためには2つの方法がある:

  • Enclave Identity 特定のEnclaveに対してユニークな鍵を生成する。
  • Sealing Identity この方法ではEnclaveのSealing Authorityの一意性をベースにして鍵が生成される。複数のEnclaveでも、同一の署名権限により同一の鍵が生成される。

Enclave Identityによるデータのシーリング

Enclave Identityによりシーリングされたデータは、生成された鍵は特定のEnclaveで一意であり、署名が変わるようなEnclaveの変更により常に新しい鍵が再生成される。この方法を使用すると、古いEnclaveでは新しいEnclaveで作成されたデータにアクセスすることができない。この方式では、アプリケーションが新しくなると、古い鍵は常に使用されないようなケースで使用することができる。

Sealing Identityによるデータのシーリング

Sealing Identityによりシーリングされたデータは、複数のEnclaveが同一の権限を持っている場合に、データを互いにシーリングしたりアンシーリングすることができる。これによりある版のEnclaveが他のEnclaveに移植された場合や、同一ソフトウェアベンダのアプリケーションでデータを共有できるようになる。

もし古いソフトウェアとEnclaveが、新しいソフトウェアでシーリングされたデータにアクセスすることを禁止しなければならない場合、権限をSoftware Version Number(SVN)により管理することができる。Enclaveのバージョンが特定のSVNよりも古い場合、シーリングされたデータを扱うことができない。

チュートリアルにおけるIntel Software Guard Extensionの使用方法

私たちはIntel SGXの鍵となる3つのコンポーネントについて説明した: enclave, attestation, sealingである。このチュートリアルでは、Intel SGXの搭載されているコアでのenclaveの実装方法について焦点を当てる。最初はattestationとsealingについては触れない。私たちは本チュートリアルを管理できる量に収めたいからだ。

次にやること

チュートリアルのPart 2では、Intel Software Guard Extensions Tutorial Series: Part 2, アプリケーションデザイン においてパスワード管理アプリケーションに焦点を当て、Intel SGXを使用して構築する方法を示す。デザインの必要なツールや制限、そしてユーザインタフェースについてカバーする。チャンネルはそのまま!

チュートリアルのすべての文章は、Introducing the Intel® Software Guard Extensions Tutorial Series から見ることができる。

「量子コンピュータ・超並列計算のからくり」を読んだ

やっと全部読み終わった。といっても最後のほうはあまり真剣に読まずに流しながら読んでいたが。

量子ゲート方式の量子コンピュータについて基礎的なところから解説してあり、前提知識がなくても読み進めることができるのはありがたかった。

この本を読んだ理由

msyksphinz.hatenablog.com

師匠のブログでおすすめとのことで、すごく分かりやすいよとアピールを受けたので購入。

師匠のブログでお勧めしていたので購入。この本以外には、Youtubeにアップロードしてある慶応大学の講義ビデオを見たり、Microsoft Q#の資料を読みながら勉強したりしていたのだが、日本語で本として読めるものが欲しかったので購入した。

www.youtube.com

誰が読むべき本なのか

基本中の基本を扱う本であり、量子コンピュータについて勉強したことがあるとか、すでに研究していますとかいう人は不向きだと思う。そういう人にはあまりにも簡単すぎる。

量子コンピュータという言葉の響きに興味はあるけど、いったい何のことなのか良く分からない、という人はおすすめの入門書だと思う。 後述するが、量子力学の部分からしっかり解説してあるので、良い意味で分かりやすいし、量子力学に興味がない人(そんな人はこの本を手に取らないかもしれないが)にとっては猛烈に眠くなってしまう内容も入っている。

量子力学については、学生の時にもう少し積極的に勉強しておくべきだったかなと後悔した。

内容について

目次をそのまま書き下すのはあまり芸がないのだが、

  • 第1章 : 量子計算でできること
    • 本当にイントロダクションの内容。何の前提知識もなくても読み進められる。
  • 第2章 : 量子とは何か
    • 量子力学の開設が始まる。光と波の関係などがしっかりと語られている。ただし数式は少ない印象。もっと学生時代にしっかり勉強しておけばよかったと後悔。
  • 第3章 : 量子の不思議
    • より混乱が深まる。このあたりで量子ビット(Qubit)の概念が出てくる。Qubitさえ出てくればあとは量子の現象は抽象化されるので、数式をいじれば何とか理解できる範囲に落ち着きそう。
  • 第4章 : 量子を使った計算機
    • 第3章で登場した量子をつかってどのように計算を行うのかについて基礎が解説される。量子ゲートのしくみについて復習しながら読み進めないと再び混乱する。
  • 第5章 : 量子アルゴリズム
    • 3つの代表的な量子アルゴリズムについて解説がなされる。おおよその解説は日本語の文章として行われるため、一生懸命読めば何となく何を言っているのか分かる。
  • 第6章 : 実現に向けた挑戦
    • 実際に量子コンピュータをどのようにして実現するのかについていろんな事例が紹介される。90年代に書かれた内容なので情報としてはかなり古いと思う。D-Waveとか全く出てこない。
  • 第7章 : 量子コンピュータの周辺に広がる世界と量子暗号
    • 量子暗号の内容は非常に面白い。ざっくりではあるが量子暗号をどのようにして実現すればよいのかについて解説がなされる。

読んでみて

量子コンピュータについて、イントロダクションとして読むにはピッタリだと思った。

数式の細かいところまでは理解できなくても、「あーこういうことね」というのが分かれば十分ではなかろうか。 興味のある領域について細かい資料を探っていくための、良いスタートポイントになれる。

私はもっと詳細が知りたくなったので、もっといろんな情報を集めてみようっと。

Freedom-U-SDKで生成したLinuxバイナリを自作RISC-Vシミュレータで実行 (2. Linuxのブート成功)

HiFive Unleashedは高くて買えないのだけれども、RISC-V SDKであるFreedom-U-SDKを使ってみることにした。

自作RISC-Vシミュレータを使ってRISC-VのLinuxをブートさせたいのだけれども、なかなか動作しないので四苦八苦していた。 #自作RISC-Vシミュレータは、そのうちGitHubに公開できるように調整中。

いろいろ修正して、とりあえずログイン画面まで進むようになった。

f:id:msyksphinz:20180818195916p:plain f:id:msyksphinz:20180818201936p:plain
図. 自作RISC-VシミュレータでLinuxをブート

Freedom-U-SDKのビルド

freedom-u-sdkリポジトリをダウンロードして、makeをビルドするだけでよい。

github.com

$ git clone https://github.com/sifive/freedom-u-sdk.git
$ cd freedom-u-sdk
$ make -j4

work/riscv-pk/bblが生成されるので、これをRISC-Vシミュレータに食わせる。

ちなみに、bblを生成するためにbuildrootとvmlinuxの生成も行っているので、結構時間がかかる。4並列で実行しても30分程度必要だ。

参考にした資料

当然、RISC-VのISAの仕様書はすべて読み込まなければならない。 通常の算術命令から、wfiの仕様やAtomic演算・LR/SCの実装などもしっかり実装すること。

  • User-Level ISA Specification

riscv.org

  • Draft Privileged ISA Specification

riscv.org

U54-MCの仕様書

U54-MCの仕様書もしっかり読まなければならない。この中でCLINTの実装は必須。

www.sifive.com

さらに、RISC-Vの標準入出力やタイマレジスタの仕様などはほとんど公開されていないので、QEMURISC-V ISA Simの実装を見ながら真似ていく。

github.com

書籍

Linuxのブートに関してほとんど知識がなかったので、こんな本も買って勉強した。 もうSpikeを頼りにしたので、最終的にはあまり参考にしなかったけど...

Spikeシミュレータの実装

最初はRISC-Vの(公式?)シミュレータであるSpikeと全く同じ動きをするように自作命令セットシミュレータを調整していき、双方の出力するログを一致比較するようなツールを作って調整するのが手っ取り早い。

私も今回のRISC-V Linuxをブートさせるため、Spikeのログと一致比較するようにした。


というわけで、Linuxのログイン画面まで来たのだが、そこから先キーボードの入力を受け付けられないので、そこを改良していく必要がある。

RISC-VのAtomic Operation命令について

RISC-VにはいくつかのAtomic演算命令が定義されている。

riscv.org

Load-Reserved/Store-Conditional 命令

f:id:msyksphinz:20180818215746p:plain
図. Load-Reserved/Store Conditional命令

LR命令はアドレスrs1からのデータをレジスタrdにロードし、その時のアドレスをRegister Reservationに記憶する。 SC命令はレジスタrs2のデータをアドレスrs1にストアするのだが、Register Reservationに格納されているアドレスがrs1と一致していない場合、そのストア処理は無効となる。

  • ストアが無効の場合 : rdに非ゼロを格納される。
  • ストアが有効な場合 : rdにゼロを格納される。

使いどころなのだが、以下のようなサンプルプログラムが掲載されている。 lr.wで値をロードし、sc.wでアップデートする。

# a0 holds address of memory location
# a1 holds expected value
# a2 holds desired value
# a0 holds return value, 0 if successful, !0 otherwise
cas:
  lr.w  t0, (a0)     # Load original value.
  bne   t0, a1, fail # Doesn’t match, so fail.
  sc.w  a0, a2, (a0) # Try to update.
  jr    ra           # Return.
fail:
  li    a0, 1        # Set return to failure.
  jr    ra           # Return.

Load-Reserved / Store-Conditional の Memory Orderingビット

上記の図のように、LR/SC命令ではaqビットとrlビットが付加されている。 aqはacquire、rlはreleaseと考えるとよい。

  • aq : Acquire操作命令の後続のメモリアクセス命令はその命令を追い越すことができない。
  • rl : Release操作命令はその命令よりも前のメモリアクセス命令よりも前に実行することができない。
  • aqrlが両方1 : 完全に前後のメモリアクセス命令を追い越したり追い越されたりすることはできない。
f:id:msyksphinz:20180818231932p:plain

Atomicメモリ操作命令

f:id:msyksphinz:20180818224603p:plain
図. Atomicメモリ操作命令

Atomicメモリ操作は、rs1レジスタで指定されるアドレスからのデータをメモリから読み込み、rs2レジスタとの操作を行ったうえで、rs1レジスタで指定されるアドレスにデータをストアする。

Atomicメモリ操作の例外

Atomicメモリ操作命令は、2種類の例外が発生する可能性がある。

  • メモリアドレスMisaligned例外 : メモリアドレスが操作するデータ型のサイズに合っていない。
  • メモリページ例外 : 参照するメモリアドレスのページ変換処理に失敗する。

AMO命令にはロード処理・ストア処理が入るのだが、どちらもページ変換処理に失敗したときにはStore Page Faultが発生するらしい。 何故だ?

  • riscv-isa-sim/riscv/mmu.h
  // template for functions that perform an atomic memory operation
  #define amo_func(type) \
    template<typename op> \
    type##_t amo_##type(reg_t addr, op f) { \
      if (addr & (sizeof(type##_t)-1)) \
        throw trap_store_address_misaligned(addr); \
      try { \
        auto lhs = load_##type(addr); \
        store_##type(addr, f(lhs)); \
        return lhs; \
      } catch (trap_load_page_fault& t) { \
        /* AMO faults should be reported as store faults */ \
        throw trap_store_page_fault(t.get_tval()); \
      } catch (trap_load_access_fault& t) { \
        /* AMO faults should be reported as store faults */ \
        throw trap_store_access_fault(t.get_tval()); \
      } \
    }

AWS上で動作するRISC-VチップFireSimのチュートリアルを試す 8. FireSim v1.3を試す

f:id:msyksphinz:20180617195844p:plain

FireSim v1.3がリリースされた。変更点としてはAWS FPGA shell 1.4.0に対応したようだ。 これもFireSim用に独自の変更が加わっているらしい?

github.com

機能的な変更点はあまり入っていない。ただしどこが変わっているか、BOOMの起動がうまくいっていない件はどのようになっているのか確認してみよう。

git clone -b 1.3.0 https://github.com/firesim/firesim.git firesim_v13 --recurse-submodules

ただしダウンロードに非常に時間がかかり本日はここで終了...

2018/08/19追記。さらにFireSim v1.3.1がリリースされ、BOOMのデザインがアップデートされたようだ。 これでまともにプログラムを動かせるようになるかな?

github.com

RISC-Vシミュレータ改造のTips

RISC-Vシミュレータで最も信頼できる実装はSpikeシミュレータである。 SpikeシミュレータはC++で書かれており、比較的簡単に解析ができるが、RTLとの実装の違いを確認したり、ソフトウェアの動作を確認したい場合に適用できるTipsがいろいろある。

各命令でアップデートされたレジスタ値をログに残したい

ビルド時に以下のオプションを追加することでレジスタ書き込みのログを残すことができる。 --enable-commitlog=yesをオプションに追加することで、ログを増やすことが可能だ。

  • build.sh
diff --git a/build-spike-pk.sh b/build-spike-pk.sh
index 02f3202..09e1512 100755
--- a/build-spike-pk.sh
+++ b/build-spike-pk.sh
@@ -14,7 +14,7 @@ fi
 echo "Starting RISC-V Toolchain build process"

 build_project riscv-fesvr --prefix=$RISCV
-build_project riscv-isa-sim --prefix=$RISCV --with-fesvr=$RISCV
+build_project riscv-isa-sim --prefix=$RISCV --with-fesvr=$RISCV --enable-commitlog=yes
 CC= CXX= build_project riscv-pk --prefix=$RISCV --host=riscv64-unknown-elf

 echo -e "\\nRISC-V Toolchain installation completed!"

ついでに、sed等でログを処理したい場合に備えて、レジスタアドレスを2桁で0埋めしておく。

  • riscv-isa-sim/riscv/execute.cc
@@ -52,7 +54,7 @@ static void commit_log_print_insn(state_t* state, reg_t pc, insn_t insn)
     bool fp = reg.addr & 1;
     int rd = reg.addr >> 1;
     int size = fp ? flen : xlen;
-    fprintf(stderr, ") %c%2d ", fp ? 'f' : 'x', rd);
+    fprintf(stderr, ") %c%02d ", fp ? 'f' : 'x', rd);
     commit_log_print_value(size, reg.data.v[1], reg.data.v[0]);
  • --enable-commitlog=no (ログ無し版)
core   0: 0xffffffe00092bfd8 (0x00001101) addi    sp, sp, -32
core   0: 0xffffffe00092bfda (0x0000ec06) sd      ra, 24(sp)
core   0: 0xffffffe00092bfdc (0x0000e822) sd      s0, 16(sp)
core   0: 0xffffffe00092bfde (0x0000e426) sd      s1, 8(sp)
core   0: 0xffffffe00092bfe0 (0x00001000) addi    s0, sp, 32
core   0: 0xffffffe00092bfe2 (0x003d5497) auipc   s1, 0x3d5
core   0: 0xffffffe00092bfe6 (0x69e48493) addi    s1, s1, 1694
core   0: 0xffffffe00092bfea (0x0000e088) sd      a0, 0(s1)
core   0: 0xffffffe00092bfec (0x6e6980ef) jal     pc + 0x986e6
  • --enable-commitlog=yes (ログあり版)
core   0:  143191650: 0xffffffe00092bfd8 (0x00001101) addi    sp, sp, -32
1 0xffffffe00092bfd8 (0x1101) x02 0xffffffe079a51ea0
core   0:  143191651: 0xffffffe00092bfda (0x0000ec06) sd      ra, 24(sp)
1 0xffffffe00092bfda (0xec06)
core   0:  143191652: 0xffffffe00092bfdc (0x0000e822) sd      s0, 16(sp)
1 0xffffffe00092bfdc (0xe822)
core   0:  143191653: 0xffffffe00092bfde (0x0000e426) sd      s1, 8(sp)
1 0xffffffe00092bfde (0xe426)
core   0:  143191654: 0xffffffe00092bfe0 (0x00001000) addi    s0, sp, 32
1 0xffffffe00092bfe0 (0x1000) x08 0xffffffe079a51ec0
core   0:  143191655: 0xffffffe00092bfe2 (0x003d5497) auipc   s1, 0x3d5
1 0xffffffe00092bfe2 (0x003d5497) x09 0xffffffe000d00fe2
core   0:  143191656: 0xffffffe00092bfe6 (0x69e48493) addi    s1, s1, 1694
1 0xffffffe00092bfe6 (0x69e48493) x09 0xffffffe000d01680
core   0:  143191657: 0xffffffe00092bfea (0x0000e088) sd      a0, 0(s1)
1 0xffffffe00092bfea (0xe088)
core   0:  143191658: 0xffffffe00092bfec (0x6e6980ef) jal     pc + 0x986e6
1 0xffffffe00092bfec (0x6e6980ef) x01 0xffffffe00092bff0
core   0:  143191659: 0xffffffe0009c46d2 (0x00007139) addi    sp, sp, -64
1 0xffffffe0009c46d2 (0x7139) x02 0xffffffe079a51e60
core   0:  143191660: 0xffffffe0009c46d4 (0x0000f822) sd      s0, 48(sp)
1 0xffffffe0009c46d4 (0xf822)

ちなみに、追加されたログの意味であるが、

1 0xffffffe00092bfd8 (0x1101) x02 0xffffffe079a51ea0
- ------------------  ------  --- ------------------
|          |             |     |          |
|          |             |     |          +--------- レジスタ書き込みデータ
|          |             |     +-------------------- レジスタ書き込みアドレス
|          |             +-------------------------- 命令Hex
|          +---------------------------------------- 命令アドレス
+--------------------------------------------------- プロセッサの実行モード(0:User, 1:Supervisor, 3:Machine)

仮想アドレスから物理アドレスへの変換過程を追いかける

これはmmu.cに実装されている。walk()関数を見てみよう。

  • riscv-isa-sim/riscv/mmu.cc
reg_t mmu_t::walk(reg_t addr, access_type type, reg_t mode)
{
  vm_info vm = decode_vm_info(proc->max_xlen, mode, proc->get_state()->satp);
  if (vm.levels == 0)
    return addr & ((reg_t(2) << (proc->xlen-1))-1); // zero-extend from xlen
...
      reg_t value = (ppn | (vpn & ((reg_t(1) << ptshift) - 1))) << PGSHIFT;
      return value;
    }
  }

fail:
  switch (type) {
    case FETCH: throw trap_instruction_page_fault(addr);
    case LOAD: throw trap_load_page_fault(addr);
...

時によって物理アドレスへの変換が行われていない?

それはTLBが実装されているから。TLBを使用する、Load/Store系の命令は以下で定義されている。

  • riscv-isa-sim/riscv/mmu.h
  #define load_func(type) \
    inline type##_t load_##type(reg_t addr) { \
      if (unlikely(addr & (sizeof(type##_t)-1))) \
        return misaligned_load(addr, sizeof(type##_t)); \
      reg_t vpn = addr >> PGSHIFT; \
      if (likely(tlb_load_tag[vpn % TLB_ENTRIES] == vpn)) \
        return *(type##_t*)(tlb_data[vpn % TLB_ENTRIES].host_offset + addr); \
      if (unlikely(tlb_load_tag[vpn % TLB_ENTRIES] == (vpn | TLB_CHECK_TRIGGERS))) { \
        type##_t data = *(type##_t*)(tlb_data[vpn % TLB_ENTRIES].host_offset + addr); \
        if (!matched_trigger) { \
          matched_trigger = trigger_exception(OPERATION_LOAD, addr, data); \
          if (matched_trigger) \
            throw *matched_trigger; \
        } \
        return data; \
      } \
      type##_t res; \
      load_slow_path(addr, sizeof(type##_t), (uint8_t*)&res); \
      return res; \
    }

  // load value from memory at aligned address; zero extend to register width
  load_func(uint8)
  load_func(uint16)
  load_func(uint32)
  load_func(uint64)

したがって、#define load_funcから、TLB関係の処理をすべて抜けば、仮想アドレスから物理アドレスの変換が常に行われることになる。

Trapはどのように実装されているの?

それでは、タイマ割り込みを見てみる。 RISC-Vにおいて、MIE(Machine Mode Interrupt Enable)とMIP(Machine Mode Interrupt Pending)レジスタで、同じビットが両方とも1が立っていれば割り込みが発生することになる。 実装はriscv/processor.ccに記述されている。

  • riscv-isa-sim/riscv/processor.cc
void processor_t::take_interrupt(reg_t pending_interrupts)
{
...
    throw trap_t(((reg_t)1 << (max_xlen-1)) | ctz(enabled_interrupts));
  }
}

最後にトラップが発生していることが分かる。 このトラップはriscv/execute.ccでCatchされる。take_trapで実際の割り込み処理が実行される。

  • riscv-isa-sim/riscv/execute.cc
// fetch/decode/execute loop
void processor_t::step(size_t n)
{
  if (state.dcsr.cause == DCSR_CAUSE_NONE) {
    if (halt_request) {
      enter_debug_mode(DCSR_CAUSE_DEBUGINT);
...
    catch(trap_t& t)
    {
      take_trap(t, pc);
      n = instret;

      if (unlikely(state.single_step == state.STEP_STEPPED)) {
        state.single_step = state.STEP_NONE;
        enter_debug_mode(DCSR_CAUSE_STEP);
      }
    }
f:id:msyksphinz:20180815233949p:plain

「Linuxのブートプロセスを見る」を購入

自作RISC-VシミュレータでLinuxをブートを試行しているのだが、相変わらずうまくいかない。 initramfsのところで止まってしまうのでまったく原因がつかめずにいるのだが、何らかの問題解決の糸口にになるかと思い以下の書籍を購入した。

これを使ってなんとか前に進めばよいのだが...

#最近はずっとこの問題を追いかけているのでブログの内容もこんな薄いものに...