14.2. Using Dyninst

14.2.1. Using Dyninst with SystemTap

To use Dyninst along with SystemTap to allow non-root users to instrument user-space executables, run the stap command with the --dyninst (or --runtime=dyninst) command line option. This tells stap to translate a SystemTap script into C code that uses the Dyninst library, compile this C code into a shared library, and then load the shared library and run the script. Note that when executed like this, the stap command also requires the -c or -x command line option to be specified.
To use the Dyninst runtime to instrument an executable file, type the following at a shell prompt:
scl enable devtoolset-4 "stap --dyninst -c 'command' [option...] [argument...]"
Similarly, to use the Dyninst runtime to instrument a user's process, type:
scl enable devtoolset-4 "stap --dyninst -x process_id [option...] [argument...]"
See Chapter 11, SystemTap for more information about the Red Hat Developer Toolset version of SystemTap. For a general introduction to SystemTap and its usage, see the SystemTap Beginners Guide for Red Hat Enterprise Linux 6 or the SystemTap Beginners Guide for Red Hat Enterprise Linux 7.

Example 14.1. Using Dyninst with SystemTap

Consider a source file named exercise.C that has the following contents:
#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;
}
This program prompts the user to enter a starting number and then counts down to 1, calling the print_iteration() function for each iteration in order to print the number to the standard output. To compile this program on the command line using the g++ compiler from Red Hat Developer Toolset, type the following at a shell prompt:
~]$ scl enable devtoolset-4 'g++ -g -o exercise exercise.C'
Now consider another source file named count.stp with the following contents:
#!/usr/bin/stap

global count = 0

probe process.function("print_iteration") {
  count++
}

probe end {
  printf("Function executed %d times.\n", count)
}
This SystemTap script prints the total number of times the print_iteration() function was called during the execution of a process. To run this script on the exercise binary file, type:
~]$ scl enable devtoolset-4 "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.

14.2.2. Using Dyninst as a Stand-alone Application

Before using the Dyninst library as a stand-alone application, set the value of the DYNINSTAPI_RT_LIB environment variable to the path to the runtime library file. You can do so by typing the following at a shell prompt:
export DYNINSTAPI_RT_LIB=/opt/rh/devtoolset-4/root/usr/lib64/dyninst/libdyninstAPI_RT.so
This sets the DYNINSTAPI_RT_LIB environment variable in the current shell session.
Example 14.2, “Using Dyninst as a Stand-alone Application” illustrates how to write and build a program to monitor the execution of a user-space process. For a detailed explanation of how to use Dyninst, see the resources listed in Section 14.3, “Additional Resources”.

Example 14.2. Using Dyninst as a Stand-alone Application

Consider the exercise.C source file from Example 14.1, “Using Dyninst with SystemTap”: this program prompts the user to enter a starting number and then counts down to 1, calling the print_iteration() function for each iteration in order to print the number to standard output.
Now consider another source file named count.C with the following contents:
#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;
}
Note that a client application is expected to destroy all Bpatch objects before any of the Dyninst library destructors are called. Otherwise the mutator might terminate unexpectedly with a segmentation fault. To work around this problem, set the BPatch object of the mutator as a local variable in the main() function. Or, if you need to use BPatch as a global variable, manually detach all the mutatee processes before the mutator exits.
This program accepts a process ID and a function name as command line arguments and then prints the total number of times the function was called during the execution of the process. You can use the following Makefile to build these two files:
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
To compile the two programs on the command line using the g++ compiler from Red Hat Developer Toolset, run the make utility as follows:
~]$ scl enable devtoolset-4 make
g++ -g -I/opt/rh/devtoolset-4/root/usr/include/dyninst count.C -c
g++ -g -I/opt/rh/devtoolset-4/root/usr/include/dyninst count.o -L /opt/rh/devtoolset-4/root/usr/lib64/dyninst -ldyninstAPI -o count
g++ -g -I/opt/rh/devtoolset-4/root/usr/include/dyninst exercise.C -o exercise
This creates new binary files called exercise and count in the current working directory.
In one shell session, execute the exercise binary file as follows and wait for it to prompt you to enter the starting number:
~]$ ./exercise
Enter the starting number:
Do not enter this number. Instead, start another shell session and type the following at its prompt to set the DYNINSTAPI_RT_LIB environment variable and execute the count binary file:
~]$ export DYNINSTAPI_RT_LIB=/opt/rh/devtoolset-4/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...
Now switch back to the first shell session and enter the starting number as requested by the exercise program. For example:
Enter the starting number: 5
Iteration number 5
Iteration number 4
Iteration number 3
Iteration number 2
Iteration number 1
When the exercise program terminates, the count program displays the number of times the print_iteration() function was executed:
Function executed 5 times.