GDB and SystemTap Probes -- part 3
Table of Contents
Hi everybody :-).
I finally got some time to finish this series of posts, and I hope you like the overall result. For those of you who are reading this blog for the first time, you can access the first post here, and the second here.
My goal with this third post is to talk a little bit about how you can
use the SDT
probes with tracepoints
inside GDB
. Maybe this
particular feature will not be so helpful to you, but I recommend
reading the post either way. I will also give a brief explanation about
how the SDT
probes are laid out inside the binary. So, let’s start!
Complementary information
In my last post, I forgot to mention that the SDT
probe support
present on older versions of Fedora GDB
is not exactly as the way I
described here. This is because Fedora GDB
adopted this feature much
earlier than upstream GDB
itself, so while this has a great positive
aspect in terms of how the distro’s philosophy works (i.e., Fedora
contains leading-edge features, so if you want to know how to FLOSS
community will be in a few months, use it!), it also has the downside of
delivering older/different versions of features in older Fedoras. But of
course, this SDT
feature will be fully available on Fedora 18, to be
announced soon.
My suggestion is that if you use a not-so-recent Fedora (like Fedora 16,
15, etc), please upgrade it to the last version, or compile your own
version of GDB
yourself (it’s not that hard, I will make a post about
it in the next days/weeks!).
With that said, let’s move on to our main topic here.
SDT Probes and Tracepoint
Before anything else, let me explain what a tracepoint
is. Think of it
as a breakpoint which doesn’t stop the program’s execution
when it hits. In fact, it’s a bit more than that: you can define
actions associated with a tracepoint
, and those actions will be
performed when the tracepoint
is hit. Neat, huh? :-)
There is a nice description of what a tracepoint
in the GDB
documentation,
I recommend you give it a reading to understand the concept.
Ok, so now we have to learn how to put tracepoints
in our code, and
how to define actions for them. But before that, let’s remember our
example program:
#include <sys/sdt.h>
int
main (int argc, char *argv[])
{
int a = 10;
STAP_PROBE1 (test_program, my_probe, a);
return 0;
}
Very simple, isn’t it? Ok, to the tracepoints
now, my friends.
Using tracepoints
inside GDB
In order to properly use tracepoints
inside GDB
, you will need to
use gdbserver
, a tiny version of GDB
suitable for debugging programs
remotely, over the net or serial line. In short, this is because GDB
cannot put tracepoints on a program running directly under it, so we
have to run it inside gdbserver
and then connect GDB
to it.
Running our program inside gdbserver
In our case, we will just start gdbserver
in our machine, order it to
listen to some high port, and connect to it through localhost
, so
there will be no need to have access to another computer or device.
First of all, make sure you have gdbserver
installed. If you use
Fedora, the package name you will have to install is gdb-gdbserver
. If
you have it installed, you can do:
$ gdbserver :3001 ./test_program
Process ./test_program created; pid = 17793
Listening on port 3001
The second argument passed to gdbserver
instructs it to listen on the
port 3001 of your loopback interface, a.k.a. localhost
.
You will notice that gdbserver
will stay there indefinitely, waiting
for new connections to arrive. Don’t worry, we will connect to it soon!
Connecting an instance of GDB
to gdbserver
Now, go to another terminal and start GDB
with our program:
$ gdb ./test_program
...
(gdb) target remote :3001
Remote debugging using :3001
Reading symbols from /lib64/ld-linux-x86-64.so.2...(no debugging symbols found)...done.
Loaded symbols for /lib64/ld-linux-x86-64.so.2
0x0000003d60401530 in _start () from /lib64/ld-linux-x86-64.so.2
The command you have to use inside GDB
is target remote
. It takes as
an argument the host and the port to which you want to connect. In our
case, we just want it to connect to localhost
, port 3001. If you saw
an output like the above, great, things are working for you (don’t pay
attention to the messages about
glibc debug information). If you didn’t see it, please check to see if
you’re connecting to the right port, and if no other service is using
it.
Ok, so now it is time to start our trace experiment!
Creating the tracepoints
Every command should be issued on GDB, not on gdbserver!
In your GDB
prompt, put a tracepoint
in the probe named my_probe
:
(gdb) trace -probe-stap my_probe
Tracepoint 1 at 0x4005a9
As you can see, the trace
command takes exactly the same arguments as
the break
command. Thus, you need to use the -probe-stap
modified in
order to instruct GDB
to put the tracepoint
in the probe.
And now, let’s define the actions associated with this tracepoint
.
To do that, we use the actions
command, which is an interactive
command inside GDB
. It takes some specific keywords, and if you want
to learn more about it, please take a look at this
link.
For this example, we will use only the collect
keyword, which tells
GDB
to… hm… collect something :-). In our case, it will collect
the probe’s first argument, or $_probe_arg0
, as you may remember.
(gdb) actions
Enter actions for tracepoint 1, one per line.
End with a line saying just "end".
>collect $_probe_arg0
>end
(gdb)
Simple as that. Finally, we have to define a breakpoint
in the last
instruction of our program, because it is necessary to keep it running
on gdbserver
in order to examine the tracepoints
later. If we didn’t
put this breakpoint
, our program would finish and gdbserver
would
not be able to provide information about what happened with our
tracepoints
. In our case, we will simply put a breakpoint
on line
10, i.e., on the return 0;
:
Running the trace experiment
Ok, time to run our trace experiment. First, we must issue a tstart
to
tell GDB
to start monitoring the tracepoints
. And then, we can
continue our program normally.
(gdb) tstart
(gdb) continue
Continuing.
Breakpoint 1, main (argc=1, argv=0x7fffffffde88) at /tmp/test_program.c:10
10 return 0;
(gdb) tstop
(gdb)
Remember, GDB
is not going to stop your program, because
tracepoints
are designed to not interfere with the execution of it.
Also notice that we have also stopped the trace experiment after the
breakpoint
hit, by using the tstop
command.
Now, we will be able to examine what the tracepoint
has collected.
First, we will the tfind
command to make sure the tracepoint
has
hit, and then we can inspect what we ordered it to collect:
(gdb) tfind start
Found trace frame 0, tracepoint 1
8 STAP_PROBE1 (test_program, my_probe, a);
(gdb) p $_probe_arg0
$1 = 10
And it works! Notice that we are printing the probe argument using the
same notation as with breakpoints
, even though we are not exactly
executing the STAP_PROBE1
instruction. What does it mean? Well, with
the tfind start
command we tell GDB
to actually use the trace frame
collected during the program’s execution, which, in this case, is the
probe argument. If you know GDB
, think of it as if we were using the
frame
command to jump back to a specific frame, where we would have
access to its state.
This is a very simple example of how to use the SDT
probe support in
GDB
with tracepoints
. There is much more you can do, but I hope I
could explain the basics so that you can start playing with this
feature.
How the SDT
probe is laid out in the binary
You might be interested in learning how the probes are created inside
the binary. Other than reading the source code of
/usr/include/sys/sdt.h
, which is the heart of the whole feature, I
also recommend this
page,
which explains in detail what’s going on under the hood. I also
recommend that you study a little about how the ELF format works,
specifically about notes in the ELF file.
Conclusion
After this series of blog posts, I expect that you will now be able to
use the not-so-new feature of SDT
probe support on GDB
. Of course,
if you find some bug while using this, please feel free to report it
using our bugzilla. And if you have
some question, use the comment system below and I will answer ASAP :-).
See ya, and thanks for reading!