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 作为独立应用程序

考虑来自 例 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 对象。否则,mutator 可能会意外终止分段错误。要临时解决这个问题,请将 mutator 的 BPatch 对象设置为 main() 函数中的本地变量。或者,如果您需要使用 BPatch 作为全局变量,请在变异退出前手动分离所有 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.