The term probe refers to a software instrumentation code patch that your analysis tool can insert into one or more target application processes. Probes are created by the analysis tool, and therefore are custom designed to perform whatever work is required by the tool. For example, depending on the needs of the analysis tool, probes could be inserted into the target application to collect and report performance information (such as execution time), keep track of pass counts for test coverage tools, or report or modify the contents of variables for debuggers. For an overview of probes, refer to What is a probe?.
For the purposes of this book, a probe is defined as "a probe expression that may optionally call functions". A probe expression (described in more detail in What is a probe expression?) is a simple instruction or sequence of instructions that represents the executable code to be inserted into the target application. An analysis tool can create probe expressions to perform conditional control flow, integer arithmetic, and bitwise operations. For instructions detailing how an analysis tool can create probe expressions, refer to Creating probe expressions.
When more complicated logic is needed (such as iteration, recursion, and complex data structure manipulation), a probe expression can call functions. Specifically, a probe expression can call:
The sections that follow (Creating probe expressions and Creating and calling probe module functions) describe how to create probes that can execute as part of the target application process. For instructions on actually executing the probes, refer to Chapter 9, Executing probes in target application processes.
A probe expression (described in more detail in What is a probe expression?) is a simple instruction or sequence of instructions that represent the executable code to be inserted into one or more target application processes. A probe expression is a type of data structure called an abstract syntax tree (a term we have borrowed from compiler technology). These data structures are called "abstract syntax trees" (as opposed to simply "syntax trees") because they are removed from the syntactic representation of the code. Compilers need to create abstract syntax trees from a program's source code as an intermediary stage before manipulating and converting the data structure into executable instructions. Since the DPCL system also needs to create executable instructions (for insertion into one or more target application processes), it also needs to create these abstract syntax trees. To create a probe expression, you need to:
The following steps describe these tasks in greater detail. For sample code, see Example: Creating probe expressions.
The next step (Step 2: Build the probe expression) describes how an analysis tool can create a probe expression that it can later execute within a target application process. The procedure for creating a probe expression can be a "building block" task in which smaller probe expressions are eventually combined and sequenced into the full probe expression.
For example, the analysis tool can create probe expressions representing constant or variable values, and then combine these into more complex probe expressions representing simple operations on the values, or function calls that pass the values as parameters to the function. The analysis tool could then take two of these more complex probe expressions and combine them into a single probe expression that represents a sequence of the two existing expressions. Then the analysis tool could join two such sequences into a longer sequence or combine them into a conditional statement. And so on, depending on the complexity of the probe logic, until the analysis tool has a single probe expression representing the full probe logic.
While the procedure for building a probe expression is the topic of the next step (Step 2: Build the probe expression), you should at this point determine the basic logic that the probe expression will perform. To adequately do this, you should understand the programmatic capabilities of probe expressions. A probe expression can represent:
The analysis tool can combine these probe expressions representing values into probe expressions representing operations or function calls.
The analysis tool can combine these probe expressions representing operations into probe expressions representing more complex operations, a sequence of instructions, or parts of a conditional statement.
The analysis tool can combine these probe expressions into longer sequences or parts of a conditional statement.
The analysis tool can combine these probe expressions into probe expressions representing a sequence of instructions or a conditional statement.
The analysis tool can combine these probe expressions representing conditional statements into more complex conditional statements or a sequence of instructions.
Since the procedure for creating a full probe expression can be a "building block" task in which simple probe expressions are combined to form more complex ones, you might want to sketch out the logic in C code or pseudocode. This will give you a map for building the probe expression (as described next in Step 2: Build the probe expression).
Once you have determined the basic logic you want to build into the probe expression, you can create the analysis tool code that builds the probe expression. The analysis tool must first create ProbeExp objects to represent the various parts of the probe expression logic -- the individual "nodes" of the abstract syntax tree -- and combine and sequence these ProbeExp objects into a final ProbeExp object that represents the complete code patch to be inserted into the target application process. To build a probe expression, the analysis tool can:
The following substeps describe these tasks in greater detail.
Like most programming vehicles, probes require scratch space for both temporary and persistent data. The DPCL system automatically allocates probe temporary data each time a probe expression executes, and deallocates the data when the probe expression completes. Probe persistent data, on the other hand, must be explicitly allocated by the analysis tool code. Creating probe expressions to represent probe temporary data describes how an analysis tool can use ProbeExp class constructors to create probe expressions representing temporary data. Creating probe expressions to represent persistent data describes how an analysis tool can explicitly allocate probe persistent data.
The DPCL system automatically allocates probe temporary data each time the
probe expression is executed, and deallocates the data when the probe
expression completes. Unlike probe persistent data (described next in Creating probe expressions to represent persistent data), the analysis tool does not need to explicitly allocate
memory for the data. To create a probe expression to represent
temporary data, the analysis tool uses the ProbeExp class
constructor to specify the data type and initial value of the data.
Table 36. Creating probe expressions to represent temporary data
For example, to create a probe expression pe that represents this data type: | And has an initial value of: | The analysis tool code would be: |
---|---|---|
int | 16 |
ProbeExp pe32 = ProbeExp(16); |
string | "jason" |
ProbeExp pestr = ProbeExp("jason"); |
For more information on the ProbeExp class constructor, refer to the DPCL Class Reference.
Using the SourceObj::reference function, the analysis tool is able to create a probe expression that references a global or static data variable in the target application. To do this, the analysis tool must navigate the target application's source code structure to identify the variable of interest. (If you are unfamiliar with SourceObj objects and the concept of source hierarchies, you may want to refer to What is the SourceObj class? before reading the following example code.
The following example code:
#include <dpcl.h> #include <libgen.h> // for basename() // find the variable variable_name defined in the module module_name // for a given Process p // return: SourceObj contains the variable. // or src_type()==SOT_unknown_type if the variable not found SourceObj find_variable(Process p, char * module_name, char * variable_name) { // get the source object associated with a process SourceObj progobj = p.get_program_object(); SourceObj ret; // return variable SourceObj mod; // module object // identifies the program module that contains the variable // we preallocate a buffer to hold the module name int mod_name_length=1024; char * mod_name = new char [mod_name_length]; int mod_index; for(mod_index = 0;mod_index < progobj.child_count();mod_index++) { mod = progobj.child(mod_index); // enlarge the name buffer if necessary. if (mod.module_name_length() >= mod_name_length) { // double the length while(mod_name_length < mod.module_name_length()) mod_name_length*=2; delete [] mod_name; mod_name = new char [mod_name_length]; } /* endif */ // get the module name mod.module_name(mod_name,mod_name_length); // depending on how the target application was compiled, the module // name may contain path information; for purposes of illustration, we compare // against the basename only. if (0 == strcmp(basename(mod_name),module_name)) { // found break; } /* endif */ } delete [] mod_name; if(mod_index == progobj.child_count()) // module not found return ret; // now we need to expand the module's SourceObj if necessary. // since only one module needs to expand, bexpand() will be easier if(mod.child_count() == 0) { // AisStatus sts = mod.bexpand(p); if(sts.status()!=ASC_success) // expand failed return ret; } // preallocate the variable buffer to hold the variable int var_name_length=1024; char * var_name = new char [var_name_length]; SourceObj var; for (int i=0; i<mod.child_count(); i++) { var = mod.child(i); if(var.src_type()!=SOT_data) continue; if (var.get_variable_name_length() >= var_name_length) { while(var_name_length < var.get_variable_name_length()) var_name_length*=2; delete [] var_name; var_name = new char [var_name_length]; } /* endif */ var.get_variable_name(var_name,var_name_length); if(0 == strcmp(var_name,variable_name)) { // found the variable ret = var; break; } } /* endfor */ delete [] var_name; return ret; } main() { Process p; // ... SourceObj var = find_variable(p,"hello.c","var"); if(var.src_type() == SOT_unknown_type) { // error report } ProbeExp pevar = var.reference(); }
Unlike temporary data, probe persistent data must be explicitly allocated
and deallocated by the analysis tool code. If your analysis tool code
requires probe data to be persistent from one invocation of the probe to the
next, it must allocate memory within the target application
process(es). To create a probe expression to allocate persistent data
in a single process, the analysis tool can use the
Process::alloc_mem or its blocking equivalent
Process::balloc_mem. To create a probe
expression to allocate persistent data on an application-wide basis (for all
Process objects managed by the Application object), the
analysis tool can use the functions
Application::alloc_mem or
Application::balloc_mem.
Table 37. Allocating memory in one or more target application processes
Keep in mind that, as with traditional programming, if your code allocates
memory, it must later free that memory or it will create a memory leak.
To create a probe expression that frees memory in a single process, the
analysis tool can use the Process::free_mem function or
its blocking equivalent Process::bfree_mem. To
create a probe expression that frees memory on an application-wide basis (in
all Process objects managed by an Application object),
the analysis tool can use the functions
Application::free_mem and
Application::bfree_mem.
Table 38. Deallocating memory in one or more target application processes
For more information on the functions described in the tables above, refer to their UNIX man pages or their entries in the DPCL Class Reference.
Using the ProbeType::get_actual function, the analysis tool can create a probe expression that represents the actual value of a particular function parameter in the target application. When executed within the target application, the probe expression will determine the actual value of the parameter at that point in time. If the analysis tool were a debugger, for example, it might want to display this information to the user. The probe expression representing the actual parameter value can then be combined into a more complex probe expression that determines the actual parameter value and sends that information back to the analysis tool which displays it to the user.
In order to use the ProbeType::get_actual function, the analysis tool must first create a ProbeType object (using the ProbeType::function_type function) that represents the prototype (or type signature) of the function. The DPCL system needs to know this type information in order to correctly identify an actual parameter value for the function; if the ProbeType object created using the ProbeType::function_type function does not match the function prototype in the target application, the ProbeType::get_actual function will not return the correct information.
The following example code calls the ProbeType::function_type function to create a ProbeType object that represents a function prototype. This example then uses the ProbeType::get_actual function to create a probe expression that represents the actual parameter value of one of the function's parameters.
// create a prototype for a function that has an two arguments: and int // and a pointer to int; and has no return value ProbeType pr_args[2]; pr_args[0] = int32_type(); pr_args[1] = pointer_type(int32_type()); ProbeType proto = function_type(void_type(), 2, pr_args); // create a probe expression representing the first parameter in a call // to a function having this prototype ProbeExp ap = proto.get_actual(0);
Creating a probe expression to represent a call to the Ais_send function expands on this example to show how an analysis tool can create a probe to send the actual parameter information back to the analysis tool.
For more information on the ProbeType object, refer to What is the ProbeType class?. For more information on the ProbeType::function_type or ProbeType::get_actual function, refer to the function's UNIX man page or the DPCL Class Reference
Writing the analysis tool code that creates probe expressions representing operations is a fairly straightforward task. This is because the ProbeExp class has overloaded common operators so that expressions written within the context of the class do not execute locally, but instead call member functions that create the probe expression. For example, the following line of code:
ProbeExp pe3 = pe1 + pe2;
creates the probe expression pe3 which represents the addition of probe expressions pe1 and pe2. With the exception of the simple assignment operator (=), and the unary address operator (&), the ProbeExp class has overloaded all of the C++ operators in this way. This includes arithmetic operators (+, -, *, /, %), bitwise operators (<<, >>, ~, ^, &, |), relational operators (<, >, ==, !=, <=, >=), logical operators (&&, ||, !), assignment operators (+=, -=, *=, /=, %=, <<=, >>=, ^=, &=, |=), and dereference operators (*, []). Whenever any of these operators are used within the context of a probe expression, they create a probe expression that represents the operation. The operands can either be objects in memory, or probe expressions that evaluate to values. This means that a probe expression representing an operation could itself be used as an operand when creating another probe expression.
As already mentioned, the two operators that are not overloaded by the ProbeExp class are the simple assignment operator (=) and the unary address operator (&). So these two operators retain their original semantics -- "pe2 = pe1" performs the assignment of probe expression pe1 into probe expression pe2 within the analysis tool, and "&pe1" takes the address of the probe expression pe1 within the analysis tool. Instead of overloading the = and & operators, the ProbeExp class instead provides the member functions assign and address. For example, the following line of code:
ProbeExp pea = pe1.assign(pe2);
creates a probe expression that assigns the value computed by the probe expression pe2 into the storage location indicated by pe1, and this next line of code:
ProbeExp peb = pe.address();
creates a probe expression peb that represents the address of the probe expression pe.
The following tables outline the various operations that can be represented
in probe expressions. Be aware that not all of the operator functions
summarized in these tables are compatible with all operator types. For
more information on any of the functions listed in these tables (including
information on which types are valid for each overloaded operator function),
refer to theDPCL Class Reference.
Table 39. Creating probe expressions to represent arithmetic operations
Table 40. Creating probe expressions to represent bitwise operations
Table 41. Creating probe expressions to represent logical operations
Table 42. Creating probe expressions to represent relational operations
Table 43. Creating probe expressions to represent assignment operations
Table 44. Creating probe expressions to represent pointer operations
Once the analysis tool has created a probe expression that represents an operation, it can:
The preceding substep (Step 2b: Create probe expressions to represent operations) illustrates how an analysis tool can create probe expressions that represent operations. This substep now shows how an analysis tool can combine two probe expressions into a single probe expression that represents a sequence of the two operations. Such sequence probe expressions can then themselves be combined to form even longer sequences of instructions. To combine two probe expressions into a sequence, the analysis tool uses the ProbeExp::sequence function. For example, say the logic that an analysis tool wants to build into a probe expression can be represented in C code as:
pe1 = pe1 + 1; fcn(pe1); pe2 = pe1; send(pe2);
To mimic this logic in a single probe expression, the analysis tool first creates probe expressions that represent the four basic operations:
ProbeExp stmt1 = pe1.assign(pe1 + ProbeExp(1)); ProbeExp stmt2 = fcn.call(1, &pe1); ProbeExp stmt3 = pe2.assign(pe1); args[0] = Ais_msg_handle; args[1] = pe2.address(); args[2] = ProbeExp(4); ProbeExp stmt4 = Ais_send.call(3, args);
Next, these four probe expressions are combined into two probe expressions -- each representing the sequence of two operations.
ProbeExp seq1 = stmt1.sequence(stmt2); ProbeExp seq2 = stmt3.sequence(stmt4);
Finally, these two sequence probe expressions are combined into a longer sequence representing the entire probe logic.
ProbeExp seqall = seq1.sequence(seq2);
Once the analysis tool has created a sequence probe expression, it can:
This substep shows how an analysis tool can create a probe expression to perform conditional logic. To do this the analysis tool:
Using the ProbeExp::ifelse function, the analysis tool is able to mimic an If or If/Else expression in C.
For example, say the logic that an analysis tool wants to build into a probe expression can be represented in C code as the If expression:
if (pe1 > 0) send(pe1);
To mimic this logic in a probe expression, the analysis tool first creates probe expressions to represent the test condition and the code to execute if the condition tests true.
// first the test condition ProbeExp ce = pe1 > ProbeExp(0); // now the then clause args[0] = Ais_msg_handle; args[1] = pe1.address(); args[2] = ProbeExp(4); ProbeExp te = Ais_send.call(3, args);
Next, the probe expression representing the test condition calls the ifelse function. The probe expression to execute if the condition tests true is supplied as a parameter to the function. The ifelse function returns a single probe expression that represents the entire conditional statement.
ProbeExp exp = ce.ifelse(te);
So in the above example, if the probe expression ce evaluates to a non-zero value, the probe expression te executes. If the probe expression ce evaluates to zero, however, te does not execute. Instead, execution continues past the conditional statement. In the above example, this entire conditional logic is stored in the ProbeExp object exp.
The above example illustrates how a probe expression can mimic an If statement. The analysis tool can also use the ProbeExp::ifelse function to create a probe expression that mimics an If/Else statement. All it needs to do is supply an additional parameter to the ifelse function -- one representing the code to execute if the test condition tests false. For example, say the logic that an analysis tool wants to build into a probe expression can be represented in C code as the If/Else expression:
if (pe1 > 0) send(pe1); else send(pe2);
To mimic this logic in a probe expression, the analysis tool first creates probe expressions to represent the test condition, the code to execute if the condition tests true, and the code to execute if the condition tests false.
// first the test condition ProbeExp ce = pe1 > ProbeExp(0); // now the then clause args[0] = Ais_msg_handle; args[1] = pe1.address(); args[2] = ProbeExp(4); ProbeExp te = Ais_send.call(3, args); // and the else clause args[0] = Ais_msg_handle; args[1] = pe2.address(); args[2] = ProbeExp(4); ProbeExp ee = Ais_send.call(3, args);
Next, the probe expression representing the test condition calls the ifelse function. The probe expression to execute if the condition tests true, and the probe expression to execute if the condition tests false are both supplied as parameters to the function. The ifelse function returns a single probe expression that represents the entire conditional statement.
ProbeExp exp = ce.ifelse(te, ee);
So in the above example, if the probe expression ce evaluates to a non-zero value, the probe expression te executes. If the probe expression ce evaluates to zero, the probe expression ee executes. In the above example, this entire conditional logic is stored in the ProbeExp object exp.
Once the analysis tool has created a conditional probe expression, it can:
This substep shows how an analysis tool can create a probe expression that represents a function call. The analysis tool can create such a probe expression to represent a call to:
In all four of these situations, the analysis tool uses the ProbeExp::call function to create a probe expression that represents a function call. For more information on the ProbeExp::call function, refer to its UNIX man page, or its entry in the DPCL Class Reference.
Once the analysis tool has created a probe expression that represents a function call, it can:
The Ais_send function enables a probe to send data back to the analysis tool. The Ais_send function takes three parameters -- a message handle for managing where the data is sent, the address of the data to send, and the size of the data being sent. To send the data located at the address &pcount back to the analysis tool, the call to the Ais_send function, if written in C code, would be:
Ais_send(handle, &pcount, 4);
To mimic this function call in a probe expression, the analysis tool first creates an array of probe expressions, with each expression in the array representing one of the parameters to the Ais_send function. As described in Step 2b: Create probe expressions to represent operations, the ProbeExp class did not overload the unary address operator (&). Note that the following code calls the ProbeExp::address function to mimic the unary address operator used in the preceding C code.
ProbeExp parms[3]; parms[0] = Ais_msg_handle; parms[1] = pcount.address(); parms[2] = ProbeExp (4);
Next the analysis tool creates a probe expression that represents the call to Ais_send. The parameter values intended for the Ais_send function are passed as the probe expression array parms to the ProbeExp::call function. The first parameter to the ProbeExp::call function indicates the number of probe expressions in the array. In this example, there are three probe expressions in the array.
ProbeExp sendexp = Ais_send.call(3, parms);
Creating probe expressions to represent actual function parameter values in the target application contains example code that shows how the analysis tool can create a probe expression that, when executed within a target application process, will determine the actual value of a function parameter at that point in time. The following example code expands on this earlier example to show how the analysis tool can create a probe expression that, when executed within a target application process, will call the Ais_send function to send this actual parameter information back to the analysis tool.
// allocate an integer variable ProbeExp v = P.balloc_mem(int32_type(), NULL, sts); // create a prototype for a function that has an two arguments: and int // and a pointer to int; and has no return value ProbeType pr_args[2]; pr_args[0] = int32_type(); pr_args[1] = pointer_type(int32_type()); ProbeType proto = function_type(void_type(), 2, pr_args); // create a probe expression representing the first parameter in a call // to a function having this prototype ProbeExp ap = proto.get_actual(0); // create a probe expression to copy the value of the actual parameter to // the variable we allocated and then send that value back ProbeExp p1 = v.assign(ap); ProbeExp sendargs[3]; sendargs[0] = Ais_msg_handle; sendargs[1] = v.address(); sendargs[2] = ProbeExp(4); ProbeExp p2 = Ais_send.call(3, sendargs); ProbeExp p3 = p1.sequence(p2);
For more information on the Ais_send function, refer to its UNIX man page, or its entry in the DPCL Class Reference.
The analysis tool can also use the ProbeExp::call function to create a probe expression that represents a call to a UNIX function like getrusage, times, or vtimes. The ability to call UNIX functions enables a probe to get performance and system-resource information for a target application process. For example, say that, in order to get system resource usage for a target application process, the analysis tool needs to create a probe expression that calls the UNIX function getrusage. In C code, a call to this function would look like:
#include <sys/resources.h> struct rusage ru; getrusage(RUSAGE_SELF, &ru);
To mimic this call in a probe expression, the analysis tool first creates an array of probe expressions, with each expression in the array representing one of the parameters to the getrusage function.
#include <sys/resources.h> ProbeExp buf = P.balloc_mem( unspecified_type(sizeof(struct rusage)), NULL, sts); if (sts.status() != ASC_success) printf("balloc_mem error: %s\n", sts.status_name(); else { ProbeExp params[2]; params[0] = ProbeExp(RUSAGE_SELF); params[1] = buf.address(); }
Next the analysis tool creates a probe expression that represents a call to the getrusage function. In this example, moduleobj is the module in the source tree that contains the getrusage function, and i is the index of the getrusage function in that module.
SourceObj getrusage_fcn = moduleobj.child(i); ProbeExp getrusage_ref = getrusage_fcn.reference(); ProbeExp the_call = getrusage_ref.call(2, params);
For more information about the UNIX function getrusage, refer to its UNIX man page.
The analysis tool can also use the ProbeExp::call function to create a probe expression that represents a call to a function contained in a probe module (a compiled object file containing one or more functions written in C) that has been loaded into a target application process. Once an analysis tool loads a particular probe module into a target application process, a probe expression is able to call any of the functions contained in the module. See Creating and calling probe module functions for more information on creating probe modules.
Say that a probe module you have created contains a function count that is designed to count the number of times the subroutine is called. This probe module function takes one parameter -- the predefined global variable Ais_msg_handle (which is used by the DPCL system when the probe sends data back to the analysis tool). In C code, a call to this function would look like:
count(handle);
To mimic this call in a probe expression, the analysis tool:
char name[128]; ProbeModule pm("my_module"); int fcncount = pm.get_count(); int found = 0; for (int i=0; !found && i<fcncount; ++i) { pm.get_name(i, name, 128); if ( strcmp(name, "count") == 0) found = 1; } if ( found == 0 ) printf("function 'count' not found in probe module\n"); else { ProbeExp count_ref = pm.get_reference(i);
For more information on the ProbeModule::get_reference, ProbeModule::get_count, and ProbeModule::get_name function, refer to their UNIX man pages, or their entries in the DPCL Class Reference.
ProbeExp parms[1]; parms[0] = Ais_msg_handle;
ProbeExp the_call = count_ref.call(1, parms);
Using the SourceObj::reference function, the analysis tool is able to create a probe expression that represents a function in the target application. This probe expression can then be combined by the analysis tool into a probe expression representing a call to that function. To create a probe expression that represents a target application function, however, the analysis tool must first navigate the target application's source code structure to identify the function of interest. If you are unfamiliar with SourceObj objects and the concept of source hierarchies, you may want to refer to What is the SourceObj class? before reading the following example code.
The following example code:
#include <dpcl.h> #include <libgen.h> // for basename() // find the function function_name defined in the module module_name // for a given Process p // return: SourceObj contains the function. // or src_type()==SOT_unknown_type if the function is not found SourceObj find_function(Process p, char * module_name, char * function_name) { // get the source object associated with a process SourceObj progobj = p.get_program_object(); SourceObj ret; // return object SourceObj mod; // module object // identifies the program module that contains the function // we preallocate a buffer to hold the module name int mod_name_length=1024; char * mod_name = new char [mod_name_length]; int mod_index; for(mod_index = 0;mod_index < progobj.child_count();mod_index++) { mod = progobj.child(mod_index); // enlarge the name buffer if necessary. if (mod.module_name_length() >= mod_name_length) { // double the length while(mod_name_length < mod.module_name_length()) mod_name_length*=2; delete [] mod_name; mod_name = new char [mod_name_length]; } /* endif */ // get the module name mod.module_name(mod_name,mod_name_length); // depending on how the target application was compiled, the module // name may contain path information; for purposes of illustration, we compare // against the basename only. if (0 == strcmp(basename(mod_name),module_name)) { // found break; } /* endif */ } delete [] mod_name; if(mod_index == progobj.child_count()) // module not found return ret; // now we need to expand the module's SourceObj if necessary. // since only one module needs to expand, bexpand() will be easier if(mod.child_count() == 0) { // AisStatus sts = mod.bexpand(p); if(sts.status()!=ASC_success) // expand failed return ret; } // preallocate the function buffer to hold the function name int fun_name_length=1024; char * fun_name = new char [fun_name_length]; SourceObj fun; for (int i=0; i<mod.child_count(); i++) { fun = mod.child(i); if(fun.src_type()!=SOT_function) continue; if (fun.get_demangled_name_length() >= fun_name_length) { while(fun_name_length < fun.get_demangled_name_length()) fun_name_length*=2; delete [] fun_name; fun_name = new char [fun_name_length]; } /* endif */ fun.get_demangled_name(fun_name,fun_name_length); if(0 == strcmp(fun_name,function_name)) { // found the function ret = fun; break; } } /* endfor */ delete [] fun_name; return ret; } main() { Process p; // .... SourceObj fun = find_function(p,"hello.c","foo"); if(fun.src_type() == SOT_unknown_type) { // error report } ProbeExp foo = fun.reference(); ProbeExp parms[1]; // parms[0]=... ProbeExp the_call=foo.call(1,parms); }
The following sample code creates a probe expression pass counter. To do this, it:
Since this example uses the Ais_send function, note that it also provides a data callback routine for handling the data sent. Refer to Chapter 10, Creating data callback routines for more information on data callback routines.
// define a pass-counter probe ProbeExp pcount = A.balloc_mem(int32_type(), NULL, sts); if (sts.status() != ASC_success) printf("error from balloc_mem: %s\n", sts.status_name()); else { ProbeExp addexpr = pcount.assign(pcount + ProbeExp(1)); ProbeExp parms[3]; parms[0] = Ais_msg_handle; parms[1] = pcount.address(); parms[2] = ProbeExp(4); ProbeExp send_call = Ais_send.call(3, parms); ProbeExp pass_ctr = addexpr.sequence(send_call); // install the probe GCBFuncType cbarr[1]; GCBTagType tagarr[1]; ProbeHandle ph; cbarr[0] = count_cb; tagarr[0] = (GCBTagType) 0; // assume that point has already been set sts = A.binstall_probe(1, &pass_ctr, &point, cbarr, tagarr, &ph); if (sts.status() != ASC_success) printf("error from binstall_probe: %s\n", sts.status_name()); . . . } // end of program // the callback function void count_cb(GCBSysType sys, GCBTagType tag, GCBObjType obj, GCBMsgType msg) { Process *P = (Process *) obj; int *count = (int *) msg; int task = P->get_task(); printf("task %d count = %d\n", task, *count); }
A probe module is a compiled object file containing one or more functions written in C. Once an analysis tool loads a particular probe module into a target application, a probe expression is able to call any of the functions contained in the module. It is often preferable for a probe expression to call a probe module function rather than try to create the same probe logic with probe expressions alone. This is because probe modules:
In addition to these advantages, be aware that, if the probe is to be executed as a phase probe, than its logic must be contained in a probe module function. This is because phase probes, unlike point probes and one-shot probes, cannot be a simple probe expression that does not call a probe module function. To create a probe module and call one of its functions, you must:
The following steps describe these tasks in more detail. For sample code, see Example: Creating and calling a probe module function.
The first step in creating and calling a probe module function is to write the actual function. To send data back to the analysis tool, the probe module can call the built-in DPCL function Ais_send. The Ais_send function takes three parameters -- a message handle for managing where the data is sent, the address of the data to send, and the size of the data being sent. In order to make the prototype of the Ais_send function available to the compiler, your analysis tool code should include the header file dpclExt.h.
For example, the following probe module function is a generic pass counter that, when installed within a subroutine in a target application process, will count the number of times the subroutine is called. Each time the counter is incremented 10 times, the probe will call the Ais_send function to send a message back to the analysis tool.
#include <dpclExt.h> count(void *msg_handle) { static int pcount = 0; char msg[100]; pcount++; if ((pcount % 10) == 0) { sprintf(msg, "I have been called %d times\n", pcount); Ais_send(msg_handle, (void *) msg, 1 + strlen(msg)); } }
If your probe module function calls the Ais_send function as in this example, your analysis tool code will need to include a data callback routine to respond to data sent by the probe module. Refer to Chapter 10, Creating data callback routines for more information on data callback routines. For more information on the Ais_send function, refer to its UNIX man page, or its entry in the DPCL Class Reference.
Once you have written your probe module function, you'll need to compile it into an object file. This object file is the probe module. Before compiling the probe module, you'll need to create an export file to export its functions. What's more, if the probe module function calls any functions outside of the probe module, you'll need to create an import file. For example, in the preceding step, we encapsulated a simple pass counter into a function called count. Here's our export file:
* Any line started with a '*' is a comment * We have a single function to export count
Also, since the count function calls the built-in DPCL function Ais_send, we also create an import file:
#! . Ais_send
Finally, we compile the file. Our export file is named count.exp, and our import file is named count.imp.
cc -o count count.c -bE:count.exp -bI:count.imp -bnoentry -I/usr/lpp/ppe.dpcl/include
In order to load a probe module into one or more target application
processes, the analysis tool must create a ProbeModule class object
that represents the probe module. The probe module class is defined in
the header file ProbeModule.h. To assign a probe
module file name to a ProbeModule class object, you can use a
non-default constructor, a non-default constructor with a copy constructor, or
the default constructor with an assignment operator.
Table 45. Instantiating a ProbeModule object
To create a ProbeModule class object, the analysis tool can: | For example: |
---|---|
Use a default constructor and an assignment operator to assign the file name of the probe module to the ProbeModule object. |
ProbeModule my_probe_mod; my_probe_mod = ProbeModule("count"); |
Use a non-default constructor to directly assign the file name of the probe module to the ProbeModule object. |
ProbeModule my_probe_mod("count"); |
Use a non-default constructor and a copy constructor to assign the file name of the probe module to the ProbeModule object. |
ProbeModule my_probe_mod = ProbeModule("count"); |
For more information on the probe module class and its constructors, refer to the DPCL Class Reference.
In order for a probe expression to call a probe module function, the probe
module function must be loaded into the same target application process(es)
within which the probe expression will execute. To load a probe module
on a single process basis, the analysis tool can use the asynchronous function
Process::load_module or its blocking equivalent
Process::bload_module. To load a probe module on
an application-wide basis (for all Process objects managed by an
Application object), the analysis tool can use the functions
Application::load_module or
Application::bload_module.
Table 46. Loading a probe module into one or more target application processes
For more information on the Process::load_module, Process::bload_module, Application::load_module, and Application::bload_module functions, refer to their UNIX man pages, or their entries in the DPCL Class Reference.
To execute a probe module function as a probe, your analysis tool code must create a probe expression that calls or references the probe module function.
For additional information on the ProbeExp::call and ProbeModule::get_reference functions, refer to their UNIX man pages, or their entries in the DPCL Class Reference.
As described in Step 1: Create probe module function, a probe module function can send data back to the analysis tool by calling the DPCL system-defined function Ais_send. If your probe module function does call the Ais_send function, then the analysis tool code must contain a data callback function that will handle the data that the Ais_send function will send. See Chapter 10, Creating data callback routines for more information on how to do this.
The following example code:
count.c
#include <dpclExt.h> count(void *msg_handle) { static int pcount = 0; char msg[100]; pcount++; if ((pcount % 10) == 0) { sprintf(msg, "I have been called %d times\n", pcount); Ais_send(msg_handle, (void *) msg, 1 + strlen(msg)); } }
count.exp
* Any line started with a '*' is a comment * We have a single function to export count
count.imp
#! . Ais_send
compilation command:
cc -o count count.c -bE:count.exp -bI:count.imp -bnoentry -I/usr/lpp/ppe.dpcl/include
analysis tool code:
#include <dpcl.h> void count_cb(GCBSysType sys, GCBTagType tag, GCBObjType obj, GCBMsgType msg); main(int argc, char *argv[]) { Process P("phantom.pok.ibm.com", 12345); Ais_initialize(); ProbeModule my_probe_mod("count"); AisStatus sts = P.bconnect(); sts = P.bload_module(&my_probe_mod); // look for a specific function object in the probe module ProbeExp count_fun; const int bufSize = 128; char bufname[bufSize]; // buffer for module_name(..) for (int i=0; i < my_probe_mod.get_count(); i++) { char *funct_name = my_probe_mod.get_name(i, bufname, bufSize); // is this function count ?? if ( strcmp(funct_name, "count") == 0 ) { count_fun = my_probe_mod.get_reference(i); break; // yes. } } ProbeExp args[1]; args[0] = Ais_msg_handle; ProbeExp count_call = count_fun.call(1, args); // now search the source obj tree for a particular function SourceObj my_program = P.get_program_object(); // Look for the correct Module SourceObj my_module; char bufmname[bufSize]; // buffer for module_name(..) for (i=0; i < my_program.child_count(); i++) { my_module = my_program.child(i); char *mod_name = my_module.module_name(bufmname,bufSize); if ( strcmp(mod_name, "stencil.f") == 0 ) { // expand the module sts = my_module.bexpand(P); break; } } InstPoint one_point; ProbeHandle phandle; char buffname[bufSize]; GCBFuncType cbarr[1]; GCBTagType tagarr[1]; cbarr[0] = count_cb; tagarr[0] = 0; for ( i=0; i < my_module.inclusive_point_count(); i++) { one_point = my_module.inclusive_point(i); if ( (one_point.get_type() == IPT_function_call) && (one_point.get_location() == IPL_before) ) { char *funct_name = one_point.get_demangled_name(buffname, bufSize); if ( strcmp(funct_name, "compute_stencil") == 0 ) { // Install the trace probe at the function call site sts = P.binstall_probe( 1, &count_call, &one_point, cbarr, tagarr, &phandle); //activate the probe sts = P.bactivate_probe(1, &phandle); } } } Ais_main_loop(); } // collect data from the callback void count_cb(GCBSysType sys, GCBTagType tag, GCBTagType obj, GCBMsgType msg) { // the message being send is a string char * count_message = (char *) msg; printf("%s\n", count_message); }