FPGA開発日記

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

LLVMベースの難読化ツールoLLVMの調査 (2. 生成CFGの調査)

LLVMをベースにしたコードの難読化ツールとしてoLLVMというのがあるというのを知った。どういうものなのか少し見てみようと思う。

github.com

論文は以下で取得可能だ。

ieeexplore.ieee.org

  • Obfuscator-LLVM: Software Protection for the Masses

https://crypto.junod.info/spro15.pdf

oLLVMのビルドが終わったので動かしてみる。以下のサイトを見ながらやってみた。

blog.quarkslab.com

以下のプログラムをコンパイルしてみる。

unsigned int target_function(unsigned int n)
{
  unsigned int mod = n % 4;
  unsigned int result = 0;

  if (mod == 0) result = (n | 0xBAAAD0BF) * (2 ^ n);

  else if (mod == 1) result = (n & 0xBAAAD0BF) * (3 + n);

  else if (mod == 2) result = (n ^ 0xBAAAD0BF) * (4 | n);

  else result = (n + 0xBAAAD0BF) * (5 & n);

  return result;
}

難読化をしない場合のオプション:

../build/bin/clang target.c -emit-llvm -O3 -o target.bc -c
../build/bin/llvm-dis target.bc -o -

難読化をした場合のオプション:

../build/bin/clang target.c -emit-llvm -O3 -o target_flat.bc -c -mllvm -fla
../build/bin/llvm-dis target_flat.bc -o -

確かに難読化した方が、CFGが良く分からないように拡張されている。どういう対応になっているのかは、これだけでは良く分からない。

; Function Attrs: norecurse nounwind uwtable
define i32 @target_function(i32) local_unnamed_addr #0 {
  %2 = alloca i32, align 4
  %3 = and i32 %0, 3
  store i32 %3, i32* %2, align 4
  %4 = or i32 %0, -1163210561
  %5 = xor i32 %0, 2
  %6 = mul i32 %4, %5
  %7 = and i32 %0, -1163210561
  %8 = add i32 %0, 3
  %9 = mul i32 %7, %8
  %10 = xor i32 %0, -1163210561
  %11 = or i32 %0, 4
  %12 = mul i32 %10, %11
  %13 = add i32 %0, -1163210561
  %14 = and i32 %0, 5
  %15 = mul i32 %13, %14
  br label %16

; <label>:16:                                     ; preds = %19, %1
  %17 = phi i32 [ undef, %1 ], [ %20, %19 ]
  %18 = phi i32 [ 55288252, %1 ], [ %21, %19 ]
  switch i32 %18, label %19 [
    i32 55288252, label %22
    i32 -1124015321, label %26
    i32 -877266683, label %27
    i32 -1108936387, label %31
    i32 -1480341395, label %32
    i32 818845743, label %36
    i32 -71238216, label %37
    i32 -803031240, label %38
  ]

; <label>:19:                                     ; preds = %16, %37, %36, %32, %31, %27, %26, %22
  %20 = phi i32 [ %15, %37 ], [ %12, %36 ], [ %17, %32 ], [ %9, %31 ], [ %17, %27 ], [ %6, %26 ], [ %17, %22 ], [ %17, %16 ]
  %21 = phi i32 [ -803031240, %37 ], [ -803031240, %36 ], [ %35, %32 ], [ -803031240, %31 ], [ %30, %27 ], [ -803031240, %26 ], [ %25, %22 ], [ %18, %16 ]
  br label %16

; <label>:22:                                     ; preds = %16
  %23 = load volatile i32, i32* %2, align 4
  %24 = icmp eq i32 %23, 0
  %25 = select i1 %24, i32 -1124015321, i32 -877266683
  br label %19

; <label>:26:                                     ; preds = %16
  br label %19

; <label>:27:                                     ; preds = %16
  %28 = load volatile i32, i32* %2, align 4
  %29 = icmp eq i32 %28, 1
  %30 = select i1 %29, i32 -1108936387, i32 -1480341395
  br label %19

; <label>:31:                                     ; preds = %16
  br label %19

; <label>:32:                                     ; preds = %16
  %33 = load volatile i32, i32* %2, align 4
  %34 = icmp eq i32 %33, 2
  %35 = select i1 %34, i32 818845743, i32 -71238216
  br label %19

; <label>:36:                                     ; preds = %16
  br label %19

; <label>:37:                                     ; preds = %16
  br label %19

; <label>:38:                                     ; preds = %16
  ret i32 %17
}

LLVMベースの難読化ツールoLLVMの調査

LLVMをベースにしたコードの難読化ツールとしてoLLVMというのがあるというのを知った。どういうものなのか少し見てみようと思う。

github.com

論文は以下で取得可能だ。

ieeexplore.ieee.org

  • Obfuscator-LLVM: Software Protection for the Masses

https://crypto.junod.info/spro15.pdf

これはどうもLLVM IRのCFGレベルでの難読化を行うためのツールっぽい。つまり、アセンブリレベルでの解読がしにくくなるためのツールというように理解できるかもしれない。

ダウンロードしてやってみる。

git clone -b llvm-4.0 https://github.com/obfuscator-llvm/obfuscator.git
mkdir build
cd build
cmake -DCMAKE_BUILD_TYPE=Release ../obfuscator

あれ、失敗した。何かおかしかったかな。

-- Targeting SystemZ
-- Targeting X86
-- Targeting XCore
-- Clang version: 4.0.1
CMake Error at cmake/modules/AddLLVM.cmake:1163 (add_custom_target):
  add_custom_target cannot create target "check-llvm-bindings-ocaml" because
  another target with the same name already exists.  The existing target is a
  custom target created in source directory
  "/home/msyksphinz/work/llvm/ollvm/obfuscator/test".  See documentation for
  policy CMP0002 for more details.
Call Stack (most recent call first):
  cmake/modules/AddLLVM.cmake:1226 (add_lit_target)
  test/CMakeLists.txt:150 (add_lit_testsuites)

ググると以下が見つかった。そのままだ。

github.com

-DLLVM_INCLUDE_TESTS=OFF
cmake -DCMAKE_BUILD_TYPE=Release -DLLVM_INCLUDE_TESTS=OFF ../obfuscator

なるほど。CMakeは通るようになった。次にmakeしてみるに続く。

自作RISC-V CPUコア実装(RET命令の再考)

自作CPUの実装、ロードストア命令の物理アドレスが決まらないうえでのハザードのボトルネックを解決した。 そうこうしているうちにいつの間にかRET命令のRAS予測が怪しくなってきているので修正しておきたい。

フロントエンドのパイプラインステージをs0, s1, ... と表記すると、これまではBTBとBIMの参照をs0で行い、その結果s1でBTBの結果からそのまま分岐予測を実行していた。この方式は遅延なく分岐予測を実行できるのがメリットだが、RET命令の検出は命令キャッシュから命令を取得するまで分からないので、RASがあまり活用されていない。

命令キャッシュからデータを取得するのはs2ステージのため、s2ステージからも分岐予測を実行してフェッチアドレスを更新できるように変更してみる。こうすることで、RET命令は1サイクル遅れて分岐予測を行うので、性能的には他の分岐命令に比べて1サイクル遅れることになるが、完全にパイプラインフラッシュするよりかはマシなはずだ。

分岐命令のフロントエンドを以下のように変更して、s1ステージ、s2ステージのどちらからも分岐予測を適用可能にしてみる。 s2ステージにおいてRET命令のRASによる予測を行うと、自動的にその一つ先のs1ステージはフラッシュする。

diff --git a/src/msrh_frontend.sv b/src/msrh_frontend.sv
index 1b1e859..5583f37 100644
--- a/src/msrh_frontend.sv
+++ b/src/msrh_frontend.sv
@@ -86,6 +86,11 @@ logic                          r_s1_tlb_miss;
 logic                          r_s1_tlb_except_valid;
 msrh_pkg::except_t             r_s1_tlb_except_cause;

+logic [riscv_pkg::VADDR_W-1: 0] w_s1_btb_target_vaddr;
+
+logic                           w_s1_predict_valid;
+logic [riscv_pkg::VADDR_W-1: 0] w_s1_predict_target_vaddr;
+
 // ==============
 // s2 stage
 // ==============
@@ -101,10 +106,8 @@ logic                           r_s2_tlb_miss;
 logic                           r_s2_tlb_except_valid;
 msrh_pkg::except_t              r_s2_tlb_except_cause;

-logic [riscv_pkg::VADDR_W-1: 0] w_s1_btb_target_vaddr;
-
-logic                           w_s1_predict_valid;
-logic [riscv_pkg::VADDR_W-1: 0] w_s1_predict_target_vaddr;
+logic                           w_s2_predict_valid;
+logic [riscv_pkg::VADDR_W-1: 0] w_s2_predict_target_vaddr;

 // =======================
 // Predictors
@@ -310,6 +313,9 @@ always_comb begin
           w_s0_vaddr_next = {w_s2_ic_resp.vaddr, 1'b0};
           w_if_state_next = WAIT_IBUF_FREE;
         end
+      end else if (w_s2_predict_valid) begin
+        w_s0_vaddr_next = (w_s2_predict_target_vaddr & ~((1 << $clog2(msrh_lsu_pkg::ICACHE_DATA_B_W))-1)) +
+                          (1 << $clog2(msrh_lsu_pkg::ICACHE_DATA_B_W));
       end else if (w_s1_predict_valid) begin
         w_s0_vaddr_next = (w_s1_predict_target_vaddr & ~((1 << $clog2(msrh_lsu_pkg::ICACHE_DATA_B_W))-1)) +
                           (1 << $clog2(msrh_lsu_pkg::ICACHE_DATA_B_W));
@@ -596,6 +602,9 @@ assign w_s1_predict_valid = w_s1_inst_valid &
 assign w_s1_predict_target_vaddr = |w_ras_search_if.s1_is_ret ? {w_ras_search_if.s1_ras_vaddr, 1'b0} :
                                    w_s1_btb_target_vaddr;

+assign w_s2_predict_valid        = w_s2_inst_valid & (|w_ras_search_if.s2_is_ret);  // from RAS
+assign w_s2_predict_target_vaddr = {w_ras_search_if.s2_ras_vaddr, 1'b0};
+

 msrh_predictor u_predictor
   (

自作RISC-V CPUコア実装(LDQ/STQ間のフラッシュインタフェースの実装 3)

自作CPUの実装、ロードストア命令の物理アドレスが決まらないうえでのハザードが性能ボトルネックになっているのを見た。 一応、簡単なベンチマークでこの問題が解決できることを見たので、次にDhrystoneを流してどれくらい実行速度が向上するのか見てみよう。

Dhrystoneの実行結果を見てみると、LDQ/STQ間のフラッシュが検出できていなかった。あれ? という訳で、いろいろデバッグしてANOTHER_FLUSHが検出できるように変更した。引き続きデバッグしているが、あまり性能が変わらない。 LDQ/STQのフラッシュは検出できており、フォワーディングも正しく動作しているがRET命令の分岐予測がいつの間にか動かなくなっている。なかなか性能が出ないなあ...

引き続きデバッグしている。

Chipyard v1.5.0のコンパイル手順メモ

ちょっと訳あってChipyardのバージョンアップ試行した。これまではv1.3.0を使っていたのだがあまりにも古いのでv1.5.0に切り替えてみる。

github.com

Chipyardの何が面倒くさいかって、GNUツールチェインもついているのでそれのビルドに時間がかかる。バイナリをダウンロードして来ればいいのだが一応念のためビルドがうまく行くことを確認している。

GNUツールチェインのビルドについては、いろいろ試行錯誤をした結果以下のコマンドラインオプションにに落ち着いた。

MAKEFLAGS="-j<ncores>" ./scripts/build-toolchains.sh riscv-tools --ignore-qemu 2&>1 | tee build.log

コア数(-j)は適宜変えてくれればいいのだが、私の場合はショボいラップトップで4コア8スレッド持っているが全部使い切るとメモリが足りなくなった。2コア使い切るのが関の山だ。あまり無理をしないように。

あと、WSLの環境では(というか最新のQEMU)を使う場合は割とビルドに失敗するので--igunore-qemuを付けておいた方が良い。わざわざスクリプトにこういうオプションを用意しているくらいなので、メンテナもQEMUのビルドできなさ具合に少し嫌気がさしているのだろう。

あと、GNUのビルドが嫌ならプレビルドバイナリをダウンロードするという手もある。 riscv-toolsではなくec2fastを指定するのが手っ取り早い。

usage() {
    echo "usage: ${0} [OPTIONS] [riscv-tools | esp-tools | ec2fast]"
    echo ""
    echo "Installation Types"
    echo "   riscv-tools: if set, builds the riscv toolchain (this is also the default)"
    echo "   esp-tools: if set, builds esp-tools toolchain used for the hwacha vector accelerator"
    echo "   ec2fast: if set, pulls in a pre-compiled RISC-V toolchain for an EC2 manager instance"
    echo ""
    echo "Options"
    echo "   --prefix PREFIX       : Install destination. If unset, defaults to $(pwd)/riscv-tools-install"
    echo "                           or $(pwd)/esp-tools-install"
    echo "   --ignore-qemu         : Ignore installing QEMU"
    echo "   --clean-after-install : Run make clean in calls to module_make and module_build"
    echo "   --arch -a             : Architecture (e.g., rv64gc)"
    echo "   --help -h             : Display this message"
    exit "$1"
}

RISC-V Summit 2021の資料が(一部)公開されている

RISC-V Summitは去年は完全にオンラインで実施していたようだけど、今年はハイブリッドでの開催となっているようだ。

RISC-V Summitの発表資料の一部が公開されていた。全部を見るのは厳しいけど、興味のあるやつだけ眺めてみようかな。 興味があるのは、CPUコアの実装に関する発表と、ソフトウェアツールチェイン、あとはベクトル拡張系の拡張命令のアップデートなどだ。

events.linuxfoundation.org

自作RISC-V CPUコア実装(LDQ/STQ間のフラッシュインタフェースの実装 2)

自作CPUの実装、ロードストア命令の物理アドレスが決まらないうえでのハザードが性能ボトルネックになっているのを見た。 一応、簡単なベンチマークでこの問題が解決できることを見たので、次にDhrystoneを流してどれくらい実行速度が向上するのか見てみよう。

Dhrystoneを流してみると、途中でフリーズしてしまった。これは私の実装ミス。

これまで、命令の「フラッシュ」の定義を「命令グループの中で、例外信号を含み、かつどれか1つ以上の命令が死んでいない状態」としていたが、ANOTHER_FLUSHは少し様子が違う。ANOTHER_FLUSHはフラッシュされる命令自体も死んでおり、有効な命令が1つも存在していない状態となる。この際のリカバリについて考慮する必要があった。

この際の実装についていろいろ変更しつつDhrystoneを実行している。まだ、Dhrystone内でANOTHER_FLUSHを発見できない... 早くそこまで到達したい...

f:id:msyksphinz:20211206004716p:plain