15.2. Dyninst の使用
15.2.1. SystemTap での Dyninst の使用
SystemTap とともに Dyninst を使用して root ユーザー以外がユーザー空間の実行ファイル をインストルメント化できるようにするには、--dyninst (または --runtime=dyninst) コマンドラインオプション stap を指定してコマンドを実行します。これは、SystemTap スクリプト stap を、Dyninst ライブラリーを使用する C コードに変換し、この C コードを共有ライブラリーにコンパイルしてから、共有ライブラリーを読み込み、スクリプトを実行します。このように実行する場合は、stap コマンドに -c または -x コマンドラインオプションも指定する必要があることに注意してください。
Dyninst ランタイムを使用して実行ファイルをインストルメント化するには、以下を実行します。
$ scl enable devtoolset-9 "stap --dyninst -c 'command' option... argument..."同様に、Dyninst ランタイムを使用してユーザーのプロセスをインストルメント化するには、以下を実行します。
$ scl enable devtoolset-9 "stap --dyninst -x process_id option... argument..."SystemTap の Red Hat Developer Toolset バージョンの詳細は、12章SystemTap を参照してください。SystemTap とその使用方法の概要は、Red Hat Enterprise Linux 7 の SystemTap ビギナーズガイド を参照してください。
例15.1 SystemTap での Dyninst の使用
以下の内容を exercise.C 含むという名前のソースファイルについて考えてみましょう。
#include <stdio.h>
void print_iteration(int value) {
printf("Iteration number %d\n", value);
}
int main(int argc, char **argv) {
int i;
printf("Enter the starting number: ");
scanf("%d", &i);
for(; i>0; --i)
print_iteration(i);
return 0;
}
このプログラムは、開始番号の入力をユーザー要求し、1 までのカウントダウンを行います。これは、標準出力に番号を出力するために各反復に対して print_iteration() 関数を呼び出します。Red Hat Developer Toolset の g++ コンパイラーを使用して、このプログラムをコマンドラインでコンパイルします。
$ scl enable devtoolset-9 'g++ -g -o exercise exercise.C'
ここ count.stp で、以下の内容を含む別のソースファイルを考慮します。
#!/usr/bin/stap
global count = 0
probe process.function("print_iteration") {
count++
}
probe end {
printf("Function executed %d times.\n", count)
}
この SystemTap スクリプトは、プロセスの実行中に print_iteration() 関数が呼び出された回数を出力します。このスクリプトは、exercise バイナリーファイルで実行します。
$scl enable devtoolset-9 "stap --dyninst -c './exercise' count.stp"Enter the starting number:5Iteration number 5 Iteration number 4 Iteration number 3 Iteration number 2 Iteration number 1 Function executed 5 times.
15.2.2. Dyninst をスタンドアロンライブラリーとして使用
Dyninst ライブラリーをアプリケーションの一部として使用する前に、DYNINSTAPI_RT_LIB 環境変数の値をランタイムライブラリーファイルへのパスに設定します。
$ export DYNINSTAPI_RT_LIB=/opt/rh/devtoolset-9/root/usr/lib64/dyninst/libdyninstAPI_RT.so
これにより、現在のシェルセッションで DYNINSTAPI_RT_LIB 環境変数が設定されます。
例15.2「Dyninst をスタンドアロンアプリケーションとして使用する」 は、ユーザー空間プロセスの実行を監視するプログラムを作成およびビルドする方法を示しています。Dyninst の使用方法に関する詳細は、「関連資料」 に記載されているリソースを参照してください。
例15.2 Dyninst をスタンドアロンアプリケーションとして使用する
例15.1「SystemTap での Dyninst の使用」 の exercise.C ソースファイルを考慮します。このプログラムにより、ユーザーは開始番号を入力し、1 までカウントして各反復の print_iteration() 関数をカウントし、標準出力に番号を出力します。
ここ count.C で、以下の内容を含む別のソースファイルを考慮します。
#include <stdio.h>
#include <fcntl.h>
#include "BPatch.h"
#include "BPatch_process.h"
#include "BPatch_function.h"
#include "BPatch_Vector.h"
#include "BPatch_thread.h"
#include "BPatch_point.h"
void usage() {
fprintf(stderr, "Usage: count <process_id> <function>\n");
}
// Global information for counter
BPatch_variableExpr *counter = NULL;
void createCounter(BPatch_process *app, BPatch_image *appImage) {
int zero = 0;
counter = app->malloc(*appImage->findType("int"));
counter->writeValue(&zero);
}
bool interceptfunc(BPatch_process *app,
BPatch_image *appImage,
char *funcName) {
BPatch_Vector<BPatch_function *> func;
appImage->findFunction(funcName, func);
if(func.size() == 0) {
fprintf(stderr, "Unable to find function to instrument()\n");
exit (-1);
}
BPatch_Vector<BPatch_snippet *> incCount;
BPatch_Vector<BPatch_point *> *points;
points = func[0]->findPoint(BPatch_entry);
if ((*points).size() == 0) {
exit (-1);
}
BPatch_arithExpr counterPlusOne(BPatch_plus, *counter, BPatch_constExpr(1));
BPatch_arithExpr addCounter(BPatch_assign, *counter, counterPlusOne);
return app->insertSnippet(addCounter, *points);
}
void printCount(BPatch_thread *thread, BPatch_exitType) {
int val = 0;
counter->readValue(&val, sizeof(int));
fprintf(stderr, "Function executed %d times.\n", val);
}
int main(int argc, char *argv[]) {
int pid;
BPatch bpatch;
if (argc != 3) {
usage();
exit(1);
}
pid = atoi(argv[1]);
BPatch_process *app = bpatch.processAttach(NULL, pid);
if (!app) exit (-1);
BPatch_image *appImage = app->getImage();
createCounter(app, appImage);
fprintf(stderr, "Finding function %s(): ", argv[2]);
BPatch_Vector<BPatch_function*> countFuncs;
fprintf(stderr, "OK\nInstrumenting function %s(): ", argv[2]);
interceptfunc(app, appImage, argv[2]);
bpatch.registerExitCallback(printCount);
fprintf(stderr, "OK\nWaiting for process %d to exit...\n", pid);
app->continueExecution();
while (!app->isTerminated())
bpatch.waitForStatusChange();
return 0;
}
Dyninst ライブラリーのデストラクターが呼び出される前に、クライアントアプリケーションがすべての Bpatch オブジェクトを破棄することが期待されることに注意してください。それ以外の場合は、セグメンテーションフォールトでミューターが突然終了する可能性があります。この問題を回避するには、main() 関数で mutator の BPatch オブジェクトをローカル変数として設定します。または、グローバル変数として BPatch 使用する必要がある場合は、ミューテーターの終了前にすべての変更プロセスを手作業でデタッチします。
このプログラムは、プロセス ID および関数名をコマンドライン引数として受け入れ、プロセスの実行中に呼び出された関数の合計回数を出力します。これらの 2 つのファイル Makefile を構築するには、以下を使用します。
DTS = /opt/rh/devtoolset-4/root CXXFLAGS = -g -I$(DTS)/usr/include/dyninst LBITS := $(shell getconf LONG_BIT) ifeq ($(LBITS),64) DYNINSTLIBS = $(DTS)/usr/lib64/dyninst else DYNINSTLIBS = $(DTS)/usr/lib/dyninst endif .PHONY: all all: count exercise count: count.C g++ $(CXXFLAGS) count.C -I /usr/include/dyninst -c g++ $(CXXFLAGS) count.o -L $(DYNINSTLIBS) -ldyninstAPI -o count exercise: exercise.C g++ $(CXXFLAGS) exercise.C -o exercise .PHONY: clean clean: rm -rf *~ *.o count exercise
Red Hat Developer Toolset の g++ コンパイラーを使用してコマンドラインで 2 つのプログラムをコンパイルするには、make ユーティリティーを実行します。
$ scl enable devtoolset-9 make
g++ -g -I/opt/rh/devtoolset-9/root/usr/include/dyninst count.C -c
g++ -g -I/opt/rh/devtoolset-9/root/usr/include/dyninst count.o -L /opt/rh/devtoolset-9/root/usr/lib64/dyninst -ldyninstAPI -o count
g++ -g -I/opt/rh/devtoolset-9/root/usr/include/dyninst exercise.C -o exercise
これにより、exercise と count という名前のバイナリーファイルが、現在の作業ディレクトリーに作成されます。
あるシェルセッションで、以下のように exercise バイナリーファイルを実行し、開始番号の入力を求めるプロンプトを待ちます。
$ ./exercise
Enter the starting number:
この番号は入力しないでください。代わりに別のシェルセッションを開始し、プロンプトに以下のコマンドを入力します。DYNINSTAPI_RT_LIB 環境変数を設定して、count バイナリーファイルを実行します。
$export DYNINSTAPI_RT_LIB=/opt/rh/devtoolset-9/root/usr/lib64/dyninst/libdyninstAPI_RT.so$./count `pidof exercise` print_iterationFinding function print_iteration(): OK Instrumenting function print_iteration(): OK Waiting for process 8607 to exit...
最初のシェルセッションに切り替え、exercise プログラムで要求される開始番号を入力します。以下に例を示します。
Enter the starting number: 5
Iteration number 5
Iteration number 4
Iteration number 3
Iteration number 2
Iteration number 1
exercise プログラムが終了すると、count プログラムにより、print_iteration() 関数の実行回数が表示されます。
Function executed 5 times.