Using DTrace SDT provider with C++
UPDATE One of my blog readers has pointed me to a much better way of accomplishing the same thing. Thanks!
The DTrace SDT provider allows the developer to put probe points into the
source code. When the probe fires, the dtrace utility shows the arguments
with which the probe has been invoked. Akin to printf() debugging, but much
more powerful - the probes may be turned on or off during the program
execution.
Let's say that you have testprov.d file with some probes defined:
provider testprov {
probe some_event(int);
};
A program that wants to signal occurence of some_event would do so as
follows:
#include <sys/sdt.h>
void f(void)
{
// some code
DTRACE_PROBE1(testprov, some_event, 42);
}
The problem arises when you have a C++ program, for example something as
#include <sys/sdt.h>
namespace some_namespace {
void f()
{
DTRACE_PROBE1(testprov, some_event, 42);
}
}
If you follow the procedure described in the DTrace manual:
$ dtrace -G -32 -s testprov.d src1.o src2.o ...
$ CC -o testprov testprov.o src1.o src2.o ..
you will get linking errors referring to some mangled C++ names containing
__dtrace inside. This is the typical definition of a DTRACE_PROBE macro,
taken from sys/sdt.h file:
#define DTRACE_PROBE(provider, name) { \
extern void __dtrace_##provider##___##name(void); \
__dtrace_##provider##___##name(); \
}
When compiled in a C++ program, this will declare an external function with
C++ linkage. But DTrace expects C linkage! Changing the extern
declaration in the macro to
extern "C" void __dtrace_##provider##___##name(void);
doesn't work either, because the C++ compiler gives an error saying that linkage declarations can only be specified at the top-level scope. The only other way to specify linkage is to declare function with C linkage before it is used, and to remove the internal declaration.
Thus, I have copied the sys/sdt.h file to my working directory and changed
all DTRACE_PROBE macros to read similar to [notice the deleted extern
function prototype]:
#define DTRACE_PROBE(provider, name) { \
__dtrace_##provider##___##name(); \
}
In addition, I have defined another macro
#define DLINKAGE(provider, name) extern void __dtrace_##provider##___##name(...)
that produces just the function prototype, but does not invoke it. The small example program listed above now looks like [some additional stuff has been added so that it can be compiled]:
// This includes sdt.h with DTRACE macros modified as explained above.
#include "sdt.h"
#define DLINKAGE(provider, name) extern void __dtrace_##provider##___##name(...)
extern "C" {
DLINKAGE(testprov, some_event);
}
namespace some_namespace {
void f()
{
DTRACE_PROBE1(testprov, some_event, 42);
}
}
int main()
{
some_namespace::f();
return 0;
}
Save this file into main.c, compile it and link it as follows:
$ CC -c main.cc
$ dtrace -G -32 -s testprov.d main.o
$ CC testprov.o main.o -o main
If you run the program nothing will happen. Now write a small D script to
test whether the thing works [save into dscript.d]:
testprov$target:::some_event
{
printf("some_event fired, value=%d\n", arg0);
}
And run as follows; the role of the c++filt program is to demangle ugly C++
names into something readable. This is what you should get:
$ dtrace -s dscript.d -c ./main | c++filt
dtrace: script 'dscript.d' matched 1 probe
dtrace: pid 13050 has exited
CPU ID FUNCTION:NAME
0 38977 void some_namespace::f():some_event some_event fired, value=42