Consider this simple C++ program:
#include <string>
std::string str = "hello world";
int main ()
{
return 0;
}
Compile it and start it under gdb. Look what happens when you print the string:
(gdb) print str
$1 = {static npos = 4294967295,
_M_dataplus = {<std::allocator<char>> = {<__gnu_cxx::new_allocator<char>> = {<No data fields>}, <No data fields>}, _M_p = 0x804a014 "hello world"}}
Crazy! And worse, if you’ve done any debugging of a program using libstdc++, you’ll know this is one of the better cases — various clever implementation techniques in the library will send you scrambling to the gcc source tree, just to figure out how to print the contents of some container. At least with string, you eventually got to see the contents.
Here’s how that looks in python-gdb:
(gdb) print str
$1 = hello world
Aside from the missing quotes (oops on me), you can see this is much nicer. And, if you really want to see the raw bits, you can use “print /r
“.
So, how do we do this? Python, of course! More concretely, you can register a pretty-printer class by matching the name of a type; any time gdb tries to print a value whose type matches that regular expression, your printer will be used instead.
Here’s a quick implementation of the std::string
printer (the real implementation is more complicated because it handles wide strings, and encodings — but those details would obscure more than they reveal):
class StdStringPrinter:
def __init__(self, val):
self.val = val
def to_string(self):
return self.val['_M_dataplus']['_M_p'].string()
gdb.pretty_printers['^std::basic_string<char,.*>$'] = StdStringPrinter
The printer itself is easy to follow — an initializer that takes a value as an argument, and stores it for later; and a to_string
method that returns the appropriate bit of the object.
This example also shows registration. We associate a regular expression, matching the full type name, with the constructor.
One thing to note here is that the pretty-printer knows the details of the implementation of the class. This means that, in the long term, printers must be maintained alongside the applications and libraries they work with. (Right now, the libstdc++ printers are in archer. But, that will change.)
Also, you can see how useful this will be with the auto-loading feature. If your program uses libstdc++ — or uses a library that uses libstdc++ — the helpful pretty-printers will automatically be loaded, and by default you will see the contents of containers, not their implementation details.
See how we registered the printer in gdb.pretty_printers
? It turns out that this is second-best — it is nice for a demo or a quick hack, but in production code we want something more robust.
Why? In the near future, gdb will be able to debug multiple processes at once. In that case, you might have different processes using different versions of the same library. But, since printers are registered by type name, and since different versions of the same library probably use the same type names, you need another way to differentiate printers.
Naturally, we’ve implemented this. Each gdb.Objfile
— the Python wrapper class for gdb’s internal objfile
structure (which we briefly discussed in an earlier post) — has its own pretty_printers
dictionary. When the “-gdb.py
” file is auto-loaded, gdb makes sure to set the “current objfile”, which you can retrieve with “gdb.get_current_objfile
“. Pulling it all together, your auto-loaded code could look something like:
import gdb.libstdcxx.v6.printers
gdb.libstdcxx.v6.printers.register_libstdcxx_printers(gdb.get_current_objfile())
Where the latter is defined as:
def register_libstdcxx_printers(objfile):
objfile.pretty_printers['^std::basic_string<char,.*>$'] = StdStringPrinter
When printing a value, gdb first searches the pretty_printers
dictionaries associated with the program’s objfiles — and when gdb has multiple inferiors, it will restrict its search to the current one, which is exactly what you want. A program using libstdc++.so.6 will print using the v6 printers, and (presumably) a program using libstdc++.so.7 will use the v7 printers.
As I mentioned in the previous post, we don’t currently have a good solution for statically-linked executables. That is, we don’t have an automatic way to pick up the correct printers. You can always write a custom auto-load file that imports the right library printers. I think at the very least we’ll publish some guidelines for naming printer packages and registration functions, so that this could be automated by an IDE.
The above is just the simplest form of a pretty-printer. We also have special support for pretty-printing containers. We’ll learn about that, and about using pretty-printers with the MI interface, next time.