質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.37%
LLVM

LLVM とは、コンパイル時や実行時に様々な最適化をするコンパイラ基盤です。 任意のプログラミング言語に対応可能で、LLVM自体はC++で実装されています。

Clang

Clangは、プログラミング言語 C、C++、Objective-C、Objective-C++ 向けのコンパイラである。

Q&A

解決済

1回答

424閲覧

LLVM バックエンド書き換えによる独自命令の挿入を試みるが139エラーが発生する。

nohahanon

総合スコア27

LLVM

LLVM とは、コンパイル時や実行時に様々な最適化をするコンパイラ基盤です。 任意のプログラミング言語に対応可能で、LLVM自体はC++で実装されています。

Clang

Clangは、プログラミング言語 C、C++、Objective-C、Objective-C++ 向けのコンパイラである。

0グッド

0クリップ

投稿2023/09/26 09:34

実現したいこと

最終的に実現したいことは前者の記事の内容で、そのためにllvmバックエンドを書き換える必要があるのですがその方法が記述されているのが後者の記事になります。
RISCVFrameLowering::emitPrologue を正しく書き換えたいです。正しいなら、記事でいうあとは、プロセスを終了させる stack_failure 関数が定義されたオブジェクトファイルを用意し、リンクさせれば完了です。をどのように行えばいいのか知りたいです。

前提

筆者さんの使用したllvmなどのバージョンが異なり、llvmバックエンドを記事の通りに書き換えたあとにclang経由で実行するとエラーが出ます。
記事ではRISCVFrameLowering::emitPrologueにsspushという独自命令をRISCVFrameLowering::emitEpilogueにsspop, bneの処理を追加することでclangを実行すると関数の頭にsspush, 終わりにsspopとリターンアドレスが書き換えられていないかの確かめが行われ一致していない場合は処理を停止するという仕組みを実装しています。
記事で紹介されていたプログラムの通りsspush, sspop命令を定義しclangに-Sを付けて実行すると自動的に挿入されていることが確認できるのです(以下に載せました)が、bneの追加でエラーが発生します。
発生するエラーコードが139ということで調べるとsegmentation faultのようですので、おそらくllvmが実行される中でbneでジャンプ先のオブジェクト(処理をすべて停止させる関数)のアドレスがわからないよ(リンクされていないから)というエラーだと思います。しかしこのオブジェクトをどのように用意してどのようにリンクすればいいのかがわからず詰まってしまいました。
llvm自体がC++で書かれているようだったのでstack_failureオブジェクトのソースはC++で書くべきだと思うのでsspushなどが自動挿入されない素のllvmを利用して作成しましたオブジェクトをどうにかリンクさせてあげたいです。
llvmはcmake --build .でビルドするのですが用意したオブジェクトをllvmに認識させてあげる方法を知りたいです。

エラーメッセージは長いので一番下にしました。
記事では"スタックフレームを破棄した後で"とあるので以下のようにRISCVFrameLowering::emitPrologue の最後らへんでDeallocate stackのコメントがある場所に紹介されていたプログラムを追加しました。

該当のソースコード

cpp

1 // Deallocate stack 2 if (StackSize != 0) { 3 RI->adjustReg(MBB, MBBI, DL, SPReg, SPReg, StackOffset::getFixed(StackSize), 4 MachineInstr::FrameDestroy, getStackAlign()); 5 // 今回の追加部分 (スタックフレームを破棄した後で sspop x5, x0, x0 命令の追加) 6 BuildMI(MBB, MBBI, DL, STI.getInstrInfo()->get(RISCV::SSPOP), RISCV::X5).addReg(RISCV::X0).addReg(RISCV::X0); 7 // 今回の追加部分 (リターンアドレスと Shadow Stack からポップした内容の比較処理を追加 命令としては、bne x1, x5, stack_failure を追加している) 8 BuildMI(MBB, MBBI, DL, STI.getInstrInfo()->get(RISCV::BNE)) 9 .addReg(RISCV::X1) 10 .addReg(RISCV::X5) 11 .addExternalSymbol("stack_failure") 12 .setMIFlag(MachineInstr::FrameSetup); // stack_failure 13 }

sspush, sspopは無事挿入できています。

assembly

1 sspush zero, ra, zero 2 addi sp, sp, -32 3(省略) 4 ld s0, 16(sp) # 8-byte Folded Reload 5 addi sp, sp, 32 6 sspop t0, zero, zero 7

試したこと

  • 当該ファイル内で他に2箇所でaddExternalSymbol()が以下のように実行されており、そのどちらも続いてsetMIFlagという関数を実行していました。なのでbneの追加部分でも続けて .setMIFlag(MachineInstr::FrameSetup);を書いてみましたが変わらずエラーがでました。

cpp

1 BuildMI(MBB, MI, DL, TII.get(RISCV::PseudoCALLReg), RISCV::X5) 2 .addExternalSymbol(SpillLibCall, RISCVII::MO_CALL) 3 .setMIFlag(MachineInstr::FrameSetup);

発生している問題・エラーメッセージ

Stack dump: 0. Program arguments: ./llvm-project/build/bin/clang --target=riscv64 -c --sysroot=./sysroot/usr/ test.c -march=rv64gc -mabi=lp64d -S 1. <eof> parser at end of file 2. Code generation 3. Running pass 'Function Pass Manager' on module 'test.c'. 4. Running pass 'Branch relaxation pass' on function '@func' #0 0x00007f5434dcd3a0 llvm::sys::PrintStackTrace(llvm::raw_ostream&, int) (/home/nohara/riscv/llvm-project/build/bin/../lib/libLLVMSupport.so.18git+0x1cd3a0) #1 0x00007f5434dcb274 llvm::sys::CleanupOnSignal(unsigned long) (/home/nohara/riscv/llvm-project/build/bin/../lib/libLLVMSupport.so.18git+0x1cb274) #2 0x00007f5434ccf198 CrashRecoverySignalHandler(int) CrashRecoveryContext.cpp:0:0 #3 0x00007f5434442520 (/lib/x86_64-linux-gnu/libc.so.6+0x42520) #4 0x00007f5437d4ab19 (anonymous namespace)::BranchRelaxation::runOnMachineFunction(llvm::MachineFunction&) BranchRelaxation.cpp:0:0 #5 0x00007f5437f3edd4 llvm::MachineFunctionPass::runOnFunction(llvm::Function&) (.part.0) MachineFunctionPass.cpp:0:0 #6 0x00007f5435a7570a llvm::FPPassManager::runOnFunction(llvm::Function&) (/home/nohara/riscv/llvm-project/build/bin/../lib/libLLVMCore.so.18git+0x27570a) #7 0x00007f5435a75894 llvm::FPPassManager::runOnModule(llvm::Module&) (/home/nohara/riscv/llvm-project/build/bin/../lib/libLLVMCore.so.18git+0x275894) #8 0x00007f5435a76294 llvm::legacy::PassManagerImpl::run(llvm::Module&) (/home/nohara/riscv/llvm-project/build/bin/../lib/libLLVMCore.so.18git+0x276294) #9 0x00007f54384f8482 clang::EmitBackendOutput(clang::DiagnosticsEngine&, clang::HeaderSearchOptions const&, clang::CodeGenOptions const&, clang::TargetOptions const&, clang::LangOptions const&, llvm::StringRef, llvm::Module*, clang::BackendAction, llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem>, std::unique_ptr<llvm::raw_pwrite_stream, std::default_delete<llvm::raw_pwrite_stream>>) (/home/nohara/riscv/llvm-project/build/bin/../lib/libclangCodeGen.so.18git+0xf8482) #10 0x00007f543897ac0c clang::BackendConsumer::HandleTranslationUnit(clang::ASTContext&) (/home/nohara/riscv/llvm-project/build/bin/../lib/libclangCodeGen.so.18git+0x57ac0c) #11 0x00007f5432f40cf9 clang::ParseAST(clang::Sema&, bool, bool) (/home/nohara/riscv/llvm-project/build/bin/../lib/../lib/libclangParse.so.18git+0x39cf9) #12 0x00007f543757ef59 clang::FrontendAction::Execute() (/home/nohara/riscv/llvm-project/build/bin/../lib/libclangFrontend.so.18git+0x114f59) #13 0x00007f54374f47e9 clang::CompilerInstance::ExecuteAction(clang::FrontendAction&) (/home/nohara/riscv/llvm-project/build/bin/../lib/libclangFrontend.so.18git+0x8a7e9) #14 0x00007f543946b023 clang::ExecuteCompilerInvocation(clang::CompilerInstance*) (/home/nohara/riscv/llvm-project/build/bin/../lib/libclangFrontendTool.so.18git+0x5023) #15 0x00005566c1d020ef cc1_main(llvm::ArrayRef<char const*>, char const*, void*) (./llvm-project/build/bin/clang+0x140ef) #16 0x00005566c1cfab4d ExecuteCC1Tool(llvm::SmallVectorImpl<char const*>&, llvm::ToolContext const&) driver.cpp:0:0 #17 0x00007f54370c1c2d void llvm::function_ref<void ()>::callback_fn<clang::driver::CC1Command::Execute(llvm::ArrayRef<std::optional<llvm::StringRef>>, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>*, bool*) const::'lambda'()>(long) Job.cpp:0:0 #18 0x00007f5434ccf5e7 llvm::CrashRecoveryContext::RunSafely(llvm::function_ref<void ()>) (/home/nohara/riscv/llvm-project/build/bin/../lib/libLLVMSupport.so.18git+0xcf5e7) #19 0x00007f54370c1fc7 clang::driver::CC1Command::Execute(llvm::ArrayRef<std::optional<llvm::StringRef>>, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>*, bool*) const (.part.0) Job.cpp:0:0 #20 0x00007f5437088041 clang::driver::Compilation::ExecuteCommand(clang::driver::Command const&, clang::driver::Command const*&, bool) const (/home/nohara/riscv/llvm-project/build/bin/../lib/libclangDriver.so.18git+0x88041) #21 0x00007f5437088afd clang::driver::Compilation::ExecuteJobs(clang::driver::JobList const&, llvm::SmallVectorImpl<std::pair<int, clang::driver::Command const*>>&, bool) const (/home/nohara/riscv/llvm-project/build/bin/../lib/libclangDriver.so.18git+0x88afd) #22 0x00007f543709c01c clang::driver::Driver::ExecuteCompilation(clang::driver::Compilation&, llvm::SmallVectorImpl<std::pair<int, clang::driver::Command const*>>&) (/home/nohara/riscv/llvm-project/build/bin/../lib/libclangDriver.so.18git+0x9c01c) #23 0x00005566c1cff699 clang_main(int, char**, llvm::ToolContext const&) (./llvm-project/build/bin/clang+0x11699) #24 0x00005566c1cf9f83 main (./llvm-project/build/bin/clang+0xbf83) #25 0x00007f5434429d90 __libc_start_call_main ./csu/../sysdeps/nptl/libc_start_call_main.h:58:16 #26 0x00007f5434429e40 call_init ./csu/../csu/libc-start.c:128:20 #27 0x00007f5434429e40 __libc_start_main ./csu/../csu/libc-start.c:379:5 #28 0x00005566c1cf9fc5 _start (./llvm-project/build/bin/clang+0xbfc5) clang: error: clang frontend command failed with exit code 139 (use -v to see invocation)

補足情報(FW/ツールのバージョンなど)

  • llvmは記事で紹介されている通りの手順でエラーなくビルドできました

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

まず、後者の記事「LLVM バックエンド書き換えによる独自命令の挿入」(FFRIエンジニアブログ) では

また、crosstool-ng 経由で risc-v をターゲットとした gcc も導入しておきます (これはあとでリンカーとして利用)。

と述べていますが、これはできているという前提で回答します。

そして、このあとで

ひとまず、何もカスタマイズを加えていない状態で正しく動作することを確認しておきましょう。

として、テスト用のメインプログラム test.c を使った次のような手順を実施しています (一部省略)。

$ ./llvm-project/build/bin/clang --target=riscv64 -c --sysroot=/etc/opt/riscv-toolchain/riscv64-unknown-elf/ test.c -march=rv64gc -mabi=lp64d $ riscv64-unknown-elf-gcc -march=rv64gc -mabi=lp64d test.o -o a.out

clangはtest.cをコンパイルしてオブジェクトファイルtest.oを作成します。

riscv64-unknown-elf-gccというのが上で述べられている「risc-vをターゲットとしたgcc」でしょう。この例では、オブジェクトファイルtest.oをリンクして実行ファイルa.outを作成しています。

一般にはオブジェクトファイルを複数指定してリンクすることができますが、ひとつだけでもリンクはできます (ただし、どれかひとつにmain関数が記述されていないとできあがった実行ファイルをプログラムとして実行することができません)。

さて、今回の改造 (関数エピローグとプロローグにstack buffer overflow検知のためのコードを挿入する) をしてリビルドしたLLVMを使う場合、最終的な実行ファイルにstack_failureという関数へのジャンプ命令が入るそうです。ですのでこの関数を用意して実行ファイルに含めなければなりません。そのため、

あとは、プロセスを終了させる stack_failure 関数が定義されたオブジェクトファイルを用意し、リンクさせれば完了です。

と述べられています。つまり、上述の手順を参考に次のような手順を実施します。

  1. メインプログラムのソースコードからオブジェクトファイルを作成する (clangを使う)。
  2. stack_failure関数を記述したソースコードからオブジェクトファイルを作成する (同上)。
  3. 上記1.、2.のオブジェクトファイルをリンクする (riscv64-unknown-elf-gccを使う)。

念のためですが、ここではLLVMのclangはC++コンパイラではなくCコンパイラとして使われていますから、上記のソースコードもCで記述するものと考えられます。コンパイラ自身を記述する言語とコンパイル対象の言語とは関係ないです。

投稿2023/09/28 03:05

ikedas

総合スコア4443

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

nohahanon

2023/09/28 03:48

ikedasさん とても丁寧な回答をありがとうございます。混乱しながら書いた読みづらい質問文で申し訳ありません。 gccのタイミングでリンクが行われるのですね。llvmのビルド(`cmake --build .`)の際にそのオブジェクトファイルを含めるべき?と考えていました。 となるとbneの命令追加箇所が誤っているものと見て調べ直してみます。 回答ありがとうございました。m(_ _)m
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.37%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問