15.2. 使用 Dyninst
15.2.1. 在 SystemTap 中使用 Dyninst 复制链接链接已复制到粘贴板!
要将 Dyninst 与 SystemTap 一起使用,以允许非root 用户检测用户空间可执行文件,请使用-- dyninst (or- )命令行选项运行 runtime=dyninststap 命令。这会告知 stap 将 SystemTap 脚本转换为使用 Dyninst 库的 C 代码,将这个 C 代码编译到共享库,然后加载共享库并运行脚本。请注意,执行此操作时,stap 命令还需要指定 -c or -x 命令行选项。
使用 Dyninst 运行时检测可执行文件:
$ scl enable devtoolset-11 "stap --dyninst -c 'command' option... argument..."
同样,使用 Dyninst 运行时检测用户的进程:
$ scl enable devtoolset-11 "stap --dyninst -x process_id option... argument..."
有关 Red Hat Developer Toolset 版本 SystemTap 的更多信息,请参阅 第 12 章 SystemTap。有关 SystemTap 及其用法的一般介绍,请参阅 Red Hat Enterprise Linux 7 的 SystemTap Beginners 指南。
例 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-11 '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 () 函数的次数。在 练习 二进制文件中运行这个脚本:
$ scl enable devtoolset-11 "stap --dyninst -c './exercise' count.stp"
Enter the starting number: 5
Iteration 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-11/root/usr/lib64/dyninst/libdyninstAPI_RT.so
这会在当前 shell 会话中设置 DYNINSTAPI_RT_LIB 环境变量。
例 15.2 “使用 Dyninst 作为独立应用程序” 演示了如何编写和构建程序来监控用户空间进程的执行。有关如何使用 Dyninst 的详细信息,请查看 第 15.3 节 “其它资源” 中列出的资源。
例 15.2. 使用 Dyninst 作为独立应用程序
考虑 exercise.C 源文件 例 15.1 “在 SystemTap 中使用 Dyninst” :该程序提示用户输入起始号,然后计数为 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 对象。否则,变异程序可能会意外终止并出现分段错误。要临时解决这个问题,请将 mutator 的 BPatch 对象设置为 main () 函数中的本地变量。或者,如果您需要使用 BPatch 作为全局变量,请在 mutator 退出前手动分离所有 mutatee 进程。
该程序接受进程 ID 和函数名称作为命令行参数,然后打印执行过程中调用函数的次数。您可以使用以下 Makefile 来构建这两个文件:
DTS = /opt/rh/devtoolset-11/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++ 编译器编译两个程序,请运行 make 工具:
$ scl enable devtoolset-11 make
g++ -g -I/opt/rh/devtoolset-11/root/usr/include/dyninst count.C -c
g++ -g -I/opt/rh/devtoolset-11/root/usr/include/dyninst count.o -L /opt/rh/devtoolset-11/root/usr/lib64/dyninst -ldyninstAPI -o count
g++ -g -I/opt/rh/devtoolset-11/root/usr/include/dyninst exercise.C -o exercise
这会创建一个名为 exercise 的新二进制文件,并在当前工作目录中 计数。
在一个 shell 会话中,按如下所示执行 练习 二进制文件,并等待它提示您输入起始号:
$ ./exercise
Enter the starting number:
不要输入这个数字。相反,启动另一个 shell 会话,并在提示符下键入以下内容来设置 DYNINSTAPI_RT_LIB 环境变量,并执行 计数 二进制文件:
$ export DYNINSTAPI_RT_LIB=/opt/rh/devtoolset-11/root/usr/lib64/dyninst/libdyninstAPI_RT.so
$ ./count `pidof exercise` print_iteration
Finding function print_iteration(): OK
Instrumenting function print_iteration(): OK
Waiting for process 8607 to exit...
现在,切换回第一个 shell 会话,并根据 练习 程序的要求输入起始号。例如:
Enter the starting number: 5
Iteration number 5
Iteration number 4
Iteration number 3
Iteration number 2
Iteration number 1
当 练习 程序终止时,计数 程序会显示 print_iteration () 函数执行的次数:
Function executed 5 times.