Chapter 15. Dyninst

The Dyninst library provides an application programming interface (API) for instrumenting and working with user-space executables during their execution. It can be used to insert code into a running program, change certain subroutine calls, or even remove them from the program. It serves as a valuable debugging and performance-monitoring tool. The Dyninst API is also commonly used along with SystemTap to allow non-root users to instrument user-space executables.

Red Hat Developer Toolset is distributed with Dyninst 10.1.0.

15.1. Installing Dyninst

In Red Hat Developer Toolset, the Dyninst library is provided by the devtoolset-9-dyninst package and is automatically installed with devtoolset-9-perftools as described in Section 1.5, “Installing Red Hat Developer Toolset”. In addition, it is recommended that you also install the GNU Compiler Collection provided by the devtoolset-9-toolchain package.

If you intend to write a custom instrumentation for binaries, install the relevant header files:

# yum install devtoolset-9-dyninst-devel

You can also install API documentation for this library:

# yum install devtoolset-9-dyninst-doc

For a complete list of documents that are included in the devtoolset-9-dyninst-doc package, see Section 15.3, “Additional Resources”. For detailed instructions on how to install optional packages to your system, see Section 1.5, “Installing Red Hat Developer Toolset”.

15.2. Using Dyninst

15.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:

$ scl enable devtoolset-9 "stap --dyninst -c 'command' option... argument..."

Similarly, to use the Dyninst runtime to instrument a user’s process:

$ scl enable devtoolset-9 "stap --dyninst -x process_id option... argument..."

See Chapter 12, 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 7.

Example 15.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. Compile this program on the command line using the g++ compiler from Red Hat Developer Toolset:

$ scl enable devtoolset-9 '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. Run this script on the exercise binary file:

$ scl enable devtoolset-9 "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. Using Dyninst as a Stand-alone Library

Before using the Dyninst library as a part of your application, set the value of the DYNINSTAPI_RT_LIB environment variable to the path to the runtime library file:

$ export DYNINSTAPI_RT_LIB=/opt/rh/devtoolset-9/root/usr/lib64/dyninst/libdyninstAPI_RT.so

This sets the DYNINSTAPI_RT_LIB environment variable in the current shell session.

Example 15.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 15.3, “Additional Resources”.

Example 15.2. Using Dyninst as a Stand-alone Application

Consider the exercise.C source file from Example 15.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-9/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:

$ 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

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-9/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.

15.3. Additional Resources

For more information about Dyninst and its features, see the resources listed below.

Installed Documentation

The devtoolset-9-dyninst-doc package installs the following documents in the /opt/rh/devtoolset-9/root/usr/share/doc/devtoolset-9-dyninst-doc-10.1.0/ directory:

  • Dyninst Programmer’s Guide — A detailed description of the Dyninst API is stored in the DyninstAPI.pdf file.
  • DynC API Programmer’s Guide — An introduction to DynC API is stored in the dynC_API.pdf file.
  • ParseAPI Programmer’s Guide — An introduction to the ParseAPI is stored in the ParseAPI.pdf file.
  • PatchAPI Programmer’s Guide — An introduction to PatchAPI is stored in the PatchAPI.pdf file.
  • ProcControlAPI Programmer’s Guide — A detailed description of ProcControlAPI is stored in the ProcControlAPI.pdf file.
  • StackwalkerAPI Programmer’s Guide — A detailed description of StackwalkerAPI is stored in the stackwalker.pdf file.
  • SymtabAPI Programmer’s Guide — An introduction to SymtabAPI is stored in the SymtabAPI.pdf file.
  • InstructionAPI Reference Manual — A detailed description of the InstructionAPI is stored in the InstructionAPI.pdf file.

For information on how to install this package on your system, see Section 15.1, “Installing Dyninst”.

Online Documentation

See Also

  • Chapter 1, Red Hat Developer Toolset — An overview of Red Hat Developer Toolset and more information on how to install it on your system.
  • Chapter 12, SystemTap — An introduction to SystemTap and instructions on how to use it to monitor the activities of a running system.
  • Chapter 13, Valgrind — Instructions on using the Valgrind tool to profile applications and detect memory errors and memory management problems, such as the use of uninitialized memory, improper allocation and freeing of memory, and the use of improper arguments in system calls.
  • Chapter 14, OProfile — Instructions on using the OProfile tool to determine which sections of code consume the greatest amount of CPU time and why.