4.5. Python Pretty-Printers
print
outputs comprehensive debugging information for a target application. GDB aims to provide as much debugging data as it can to users; however, this means that for highly complex programs the amount of data can become very cryptic.
print
output. GDB does not even empower users to easily create tools that can help decipher program data. This makes the practice of reading and understanding debugging data quite arcane, particularly for large, complex projects.
print
output (and make it more meaningful) is to revise and recompile GDB. However, very few developers can actually do this. Further, this practice will not scale well, particularly if the developer must also debug other programs that are heterogeneous and contain equally complex debugging data.
To pass program data to a set of registered Python pretty-printers, the GDB development team added hooks to the GDB printing code. These hooks were implemented with safety in mind: the built-in GDB printing code is still intact, allowing it to serve as a default fallback printing logic. As such, if no specialized printers are available, GDB will still print debugging data the way it always did. This ensures that GDB is backwards-compatible; users who do not require pretty-printers can still continue using GDB.
This new "Python-scripted" approach allows users to distill as much knowledge as required into specific printers. As such, a project can have an entire library of printer scripts that parses program data in a unique manner specific to its user's requirements. There is no limit to the number of printers a user can build for a specific project; what's more, being able to customize debugging data script by script offers users an easier way to re-use and re-purpose printer scripts — or even a whole library of them.
The best part about this approach is its lower barrier to entry. Python scripting is comparatively easy to learn and has a large library of free documentation available online. In addition, most programmers already have basic to intermediate experience in Python scripting, or in scripting in general.
enum Fruits {Orange, Apple, Banana}; class Fruit { int fruit; public: Fruit (int f) { fruit = f; } }; int main() { Fruit myFruit(Apple); return 0; // line 17 }
g++ -g fruit.cc -o fruit
. Now, examine this program with GDB.
gdb ./fruit [...] (gdb) break 17 Breakpoint 1 at 0x40056d: file fruit.cc, line 17. (gdb) run Breakpoint 1, main () at fruit.cc:17 17 return 0; // line 17 (gdb) print myFruit $1 = {fruit = 1}
{fruit = 1}
is correct because that is the internal representation of 'fruit' in the data structure 'Fruit'. However, this is not easily read by humans as it is difficult to tell which fruit the integer 1 represents.
fruit.py class FruitPrinter: def __init__(self, val): self.val = val def to_string (self): fruit = self.val['fruit'] if (fruit == 0): name = "Orange" elif (fruit == 1): name = "Apple" elif (fruit == 2): name = "Banana" else: name = "unknown" return "Our fruit is " + name def lookup_type (val): if str(val.type) == 'Fruit': return FruitPrinter(val) return None gdb.pretty_printers.append (lookup_type)
gdb.pretty_printers.append (lookup_type)
adds the function lookup_type
to GDB's list of printer lookup functions.
lookup_type
is responsible for examining the type of object to be printed, and returning an appropriate pretty printer. The object is passed by GDB in the parameter val
. val.type
is an attribute which represents the type of the pretty printer.
FruitPrinter
is where the actual work is done. More specifically in the to_string
function of that Class. In this function, the integer fruit
is retrieved using the python dictionary syntax self.val['fruit']
. Then the name is determined using that value. The string returned by this function is the string that will be printed to the user.
fruit.py
, it must then be loaded into GDB with the following command:
(gdb) python execfile("fruit.py")