Interposition can be used to understand the behavior of programs by capturing information of service invocations. In this example, we describe an extension used to monitor interactions between a program and its runtime. This extension will provide an execution profile containing function entry and exit information, and the corresponding timestamps. Once processed, the output of the extension can give a profile like the following one, coming from the execution of the NAS BT benchmark on a SGI Origin 2000:
2623262 +178 x_solve_
2623486 +175 lhsx_
2638738 +186 x_solve_cell_
2673412 +185 x_backsubstitute_
2674493 +191 y_solve_
2674630 +176 lhsy_
2678449 +199 y_solve_cell_
2741403 +198 y_backsubstitute_
The first column in this profile contains the event timestamps in nanoseconds, followed by a sign (+/-) indicating procedure entry/exit, then the event identifier, and finally the event name (in this case, a procedure name). Tabulation represents invocation nesting.
In this extension, we decided to use the callback mode (see `Extension execution modes' in section 2.4), because it allows to capture all the references using only two handlers.
The steps to be followed to build and install this extension can be summarized as follows:
1-Write the event selector function. When using the DITOOLS callback dispatcher, the user should provide a routine (di_callback_required) in the backend to select which hooks will trigger the user-provided callback handlers, and to give an identifier for each possible event. The function will be invoked at extension time (see section 2 for a description of the stages) once for each potential event. A return value of zero can be used to ignore the event. The event identifiers will be passed at execution time to the callback handler.
int di_callback_required(char *func)
static int event_id=0;
2-Implement callback handlers and support routines, i.e. the code which should record the time and the event. Callback handlers will be invoked by the callback dispatcher before (di_pre_handler) and after (di_post_handler) dynamically-linked hooks selected by the event selector function. Callback handlers receive two arguments from the dispatcher: a virtual processor identifier and an event identifier. In this example, we use a macro (PUT_EVENT) to record time-stamped events in a buffer managed by the extension code. This buffer is set-up at startup time (di_init_backend), and processed at the end of execution (di_fini_backend).
void di_pre_handler(long vpid, long event_id)
PUT_EVENT(vpid, event_id, START);
void di_post_handler(long vpid, long event_id)
PUT_EVENT(vpid, event_id, END);
int fd=open(tracefile, flags);
PROCESS_TRACE(fd, funcs, buffer);
Note that backend portability is determined only by the extension code, since there are no platform-specific details in the DITOOLS interface. Platform-dependent pieces (e.g. the callback dispatcher) are provided by the DITOOLS runtime.
3-Build the extension module, by compiling the above code like a standard shared library.
cc -shared -o progmon.so progmon.c
4-Write the configuration file to be parsed by DITOOLS at extension time. It should declare which backend should be loaded (1) and specify that our backend routines should be invoked as callbacks at every reference to dynamically-linked runtime services (2).
// begin of backends section
// (1) request the module "progmon.so":
// end of backends section
// begin of commands section
// (2) request the installation of
// the callback dispatcher at every
// dynamically-linked reference done
// by the MAIN module
MAIN * DIRUNTIME callback_dispatcher
// end of commands section
5-Run your unmodified program. Specify your config file and set-up the system to load the DI runtime at program startup. This last step is system dependent, as explained in 2.2.
$ setenv DI_CONFIG_FILE progmon.conf
$ setenv LD_PRELOAD diruntime.so
$ <your program>