LLVM を使用した C/C++ コードのインストルメント 質問する

LLVM を使用した C/C++ コードのインストルメント 質問する

LLVM プロジェクトについて読みましたが、LLVM のフロントエンドであるアナライザー Clang を使用して C/C++ コードの静的解析を行うことができるとのことでした。LLVM を使用してソース コード内のメモリ (変数、ローカルおよびグローバル) へのすべてのアクセスを抽出できるかどうかを知りたいです。

LLVM には、この情報を抽出するために使用できる組み込みライブラリがありますか。ない場合は、同じことを実行する関数の書き方を教えてください。(既存のソース コード、リファレンス、チュートリアル、例など) 私が考えたのは、まずソース コードを LLVM bc に変換し、次にそれをインストルメント化して分析を行うことですが、その方法が正確にわかりません。


私は、自分の目的にどの IR を使用すべきか (Clang の抽象構文木 (AST) または LLVM の SSA 中間表現 (IR)) を自分で考えようとしましたが、どちらを使用すべきかはよくわかりませんでした。私がやろうとしていることは次のとおりです。任意の C/C++ プログラム (以下に示すようなもの) で、メモリの読み取り/書き込みを行うすべての命令の前後に、何らかの関数の呼び出しを挿入しようとしています。たとえば、以下の C++ プログラム (Account.cpp) を考えてみましょう。

#include <stdio.h>

class Account {
  int balance;

public:
  Account(int b) {
    balance = b;
  }

  int read() {
    int r;
    r = balance;
    return r;
  }

  void deposit(int n) {
    balance = balance + n;
  }

  void withdraw(int n) {
    int r = read();
    balance = r - n;
  }
};

int main () {
  Account* a = new Account(10);
  a->deposit(1);
  a->withdraw(2);
  delete a;
}

したがって、インストルメンテーション後のプログラムは次のようになります。

#include <stdio.h>

class Account {
  int balance;

public:
  Account(int b) {
    balance = b;
  }

  int read() {
    int r;
    foo();
    r = balance;
    foo();
    return r;
  }

  void deposit(int n) {
    foo();
    balance = balance + n;
    foo();
  }

  void withdraw(int n) {
    foo();
    int r = read();
    foo();
    foo();
    balance = r - n;
    foo();
  }
};

int main () {
  Account* a = new Account(10);
  a->deposit(1);
  a->withdraw(2);
  delete a;
}

ここで、foo() は、現在のシステム時間を取得したり、カウンターを増分したりする関数です。上記のような関数を挿入するには、まず IR を取得し、次に IR に対してインストルメンテーション パスを実行して、そのような呼び出しを IR に挿入する必要があることは理解していますが、実際にそれを実現する方法がわかりません。実行方法の例を示して教えてください。

また、プログラムを IR にコンパイルすると、元のプログラムとインストルメントされた IR の間で 1:1 マッピングを取得するのは非常に難しいことを理解しています。それでは、IR で行われた変更 (インストルメンテーションのため) を元のプログラムに反映することは可能ですか。

LLVMパスを使い始めて、自分で作る方法を知るために、LLVM IRロードとストアに実行時チェックを追加するパスの例、SAFECodeのロード/ストアインストルメンテーションパス(http://llvm.org/viewvc/llvm-project/safecode/trunk/include/safecode/LoadStoreChecks.h?view=markupそしてhttp://llvm.org/viewvc/llvm-project/safecode/trunk/lib/InsertPoolChecks/LoadStoreChecks.cpp?view=markup)。しかし、このパスを実行する方法がわかりませんでした。上記の Account.cpp などのプログラムでこのパスを実行する手順を教えてください。

ベストアンサー1

まず、clang と LLVM のどちらを使用するかを決める必要があります。どちらも非常に異なるデータ構造で動作し、それぞれに長所と短所があります。

問題の説明があまり詳しくないので、LLVM で最適化パスを実行することをお勧めします。IR を使用すると、コードのサニタイズ、分析、挿入がはるかに簡単になります。これは、IR が設計されている目的だからです。欠点は、プロジェクトが LLVM に依存することです。これは問題になる場合とならない場合があります。C バックエンドを使用して結果を出力することもできますが、人間が使用することはできません。

Value最適化パスを使用する場合のもう1つの重要な欠点は、元のソースコードのすべてのシンボルも失われることです。クラス(後で詳しく説明します)にメソッドがある場合でもgetName一度もない意味のある内容が含まれているかどうかは、このファイルに依存します。これは、パスのデバッグに役立つことを目的としており、それ以外の目的には使用できません。

また、コンパイラに関する基本的な知識も必要です。例えば、以下のことを知っておくことは必須です。基本ブロックそして静的単一割り当てフォーム幸いなことに、これらは学習したり理解したりするのがそれほど難しい概念ではありません (Wikipedia の記事で十分でしょう)。

コーディングを始める前に、まずいくつか読む必要があります。そのため、始めるためのリンクをいくつか示します。

  • アーキテクチャの概要: LLVM のアーキテクチャの簡単な概要。作業内容や LLVM が適切なツールであるかどうかについて、適切なアイデアが得られます。

  • ドキュメンテーションヘッド: 以下に挙げたすべてのリンクとその他のリンクが見つかります。何か見逃したものがあれば、こちらを参照してください。

  • LLVM の IR リファレンス: これは、これから操作する LLVM IR の完全な説明です。言語は比較的単純なので、学ぶことはそれほど多くありません。

  • プログラマーマニュアル: LLVM を使用する際に知っておく必要のある基本的な事項の簡単な概要。

  • パスの書き方: 変換パスまたは分析パスを記述するために必要なすべての情報。

  • LLVM パス: LLVM によって提供され、使用できる、また使用すべきすべてのパスの包括的なリスト。これらは、コードを整理し、分析しやすくするのに非常に役立ちます。たとえば、ループを操作する場合、、およびlcssaパスsimplify-loopindvar役立ちます。

  • 値の継承ツリー: これは Value クラスの doxygen ページです。ここで重要なのは、IR リファレンス ページで定義されているすべての命令のドキュメントを取得するためにたどることができる継承ツリーです。コラボレーション ダイアグラムと呼ばれる不気味な怪物は無視してください。

  • 型継承ツリー: 上記と同じですが、タイプについては異なります。

一度それをすべて理解すれば、あとは簡単です。メモリ アクセスを見つけるには? 命令を検索します storeload インストルメントするには? クラスの適切なサブクラスを使用して必要なものを作成し Value 、それをストア命令とロード命令の前または後に挿入するだけです。質問が少し広すぎるため、これ以上お手伝いすることはできません。 (下記訂正参照)

ちなみに、数週間前に似たようなことをしなければなりませんでした。約 2 ~ 3 週間で、LLVM について必要なことはすべて学習し、ループ内のメモリ アクセス (など) を見つけるための分析パスを作成し、作成した変換パスでそれらを計測することができました。複雑なアルゴリズムは使用せず (LLVM が提供するものを除く)、すべてが非常に簡単でした。この話の教訓は、LLVM は学習しやすく、使いやすいということです。


修正: 検索して指示するloadだけと言ったのは間違いでしたstore

および命令loadは、storeポインタを使用してヒープに行われたアクセスのみを許可します。すべてのメモリ アクセスを取得するには、スタック上のメモリ位置を表す値も確認する必要があります。値がスタックに書き込まれるか、レジスタに格納されるかは、バックエンドの最適化パスで発生するレジスタ割り当てフェーズで決定されます。つまり、これはプラットフォームに依存しており、依存すべきではありません。

どのような種類のメモリ アクセスを探しているのか、どのようなコンテキストで、どのようにインストルメント化しようとしているのかという詳細情報を提供していただけない限り、これ以上のお手伝いはできません。

おすすめ記事