📜 ⬆️ ⬇️

Decorating life with gdb PrettyPrinting API

That gdb can somehow be improved on the python, everyone knows who has ever looked into the documentation. And whoever looked at it diagonally at least once, knows about such a thing as “Pretty Printers” - which, it seems, allows gdb to print various complex structures nicely. I looked through the diagonal documentation, although I didn’t really go into it. But once, typing something like that once again (all examples from the source code of MariaDB, which I debug many times every single day, sometimes excluding weekends):

(gdb) p/t table->read_set->bitmap[0] @ (table->read_set->n_bits+7)/8 

I thought, "what about figley?" And wrap it all up ...

Pretty Printer in Python is a class. In the constructor, it is passed the value that needs to be printed (this will be not just a scalar, but gdb.Value ). And the class has a to_string() method, which, in fact, returns what we want to see. For example:

 class BitmapPrinter: def __init__(self, val): self.val = val def to_string(self): s='' for i in range((self.val['n_bits']+7)//8): s = format(int(self.val['bitmap'][i]), '032b') + s return "b'" + s[-int(self.val['n_bits']):] + "'" class StringPrinter: def __init__(self, val): self.val = val def to_string(self): return '_' + self.val['str_charset']['name'].string() + \ ' "' + self.val['Ptr'].string('ascii', 'strict', self.val['str_length']) + '"' 

That's not all. There is another function that checks the type of this gdb.Value and decides whether to use our class. For simplicity, the gdb.printing module has a ready-made implementation that uses regular expressions, something like this:
')
 import gdb.printing def build_pretty_printer(): pp = gdb.printing.RegexpCollectionPrettyPrinter( "my_library") pp.add_printer('String', '^String$', StringPrinter) pp.add_printer('bitmap', '^st_bitmap$', BitmapPrinter) return pp gdb.printing.register_pretty_printer( gdb.current_objfile(), my_library.build_pretty_printer() 

There in the arguments of pp.add_printer() is the name, regular for the type, and the class that this type prints.

For my taste - somewhat complicated. In classes, a lot of superfluous, really only need the method to_string() , everything else will be copied from class to class. The printer registration code is also a bit too much, even the type name is repeated three times in a row.

Yes, and pointers are not processed. And I would like to, as in gdb:

 (gdb) p opt_tc_log_file $2 = 0x555556204273 "tc.log" (gdb) p table->field[0] $3 = (Field_varstring *) 0x555557437a88 

That is, if the pointer, then the type is displayed, the address, and then all the same value, like a string. To do this, you can rivet two more classes, say, StringPtrPrinter and BitmapPtrPrinter , which have to_string() almost the same, but also displays the address. And you can expand already written ones by adding both there and there checks, so that they catch them on the fly, when val is a pointer, and the address is typed accordingly. One way or another, copy and copy. The same code from class to class. I quickly got tired of it, and I began to simplify everything. Ideally, I wanted to write these printers like this:

 @PrettyPrinter def String(val): return '_' + val['str_charset']['name'].string() + \ ' "' + val['Ptr'].string('ascii', 'strict', val['str_length']) + '"' @PrettyPrinter def st_bitmap(val): s='' for i in range((val['n_bits']+7)//8): s = format(int(val['bitmap'][i]), '032b') + s return "b'" + s[-int(val['n_bits']):] + "'" 

Only in the case. The rest works by itself. But, whether it is my hands such, or the decorators and gdb.printing agreed, but it turned out that the wrapper is sitting on the wrapper and the wrapper is chasing.

Here is the code
 import gdb.printing def PrettyPrinter(arg): name = getattr(arg, '__name__', arg) def PrettyPrinterWrapperWrapperWrapper(func): class PrettyPrinterWrapperWrapper: class PrettyPrinterWrapper: def __init__(self, prefix, val, cb): self.prefix = prefix self.val = val self.cb = cb def to_string(self): return self.prefix + self.cb(self.val) def __init__(self, name, cb): self.name = name self.enabled = True self.cb = cb def __call__(self, val): prefix = '' if val.type.code == gdb.TYPE_CODE_PTR: prefix = '({}) {:#08x} '.format(str(val.type), long(val)) try: val = val.dereference() except: return None valtype = val.type.unqualified() if valtype.name == self.name: return self.PrettyPrinterWrapper(prefix, val, self.cb) if valtype.strip_typedefs().name == self.name: return self.PrettyPrinterWrapper(prefix, val, self.cb) return None pp = PrettyPrinterWrapperWrapper(name, func) gdb.printing.register_pretty_printer(None, pp, True) return func if callable(arg): return PrettyPrinterWrapperWrapperWrapper(arg) return PrettyPrinterWrapperWrapperWrapper 


In short, how it works here. The decorator simply takes the name of the function, and registers a pretty printer for this type. The function itself and the gdb.Value value are both passed to the constructor of the PrettyPrinterWrapper class. And by to_string() it calls this function with this value.

Next, in order to register our pretty printer with gdb, we need another class that is callable and chooses which pretty printer to use. In the first example, this is the native gdb-shny gdb.printing.RegexpCollectionPrettyPrinter , but I do not want it, so I have my own class, with poker and courtesans. It automatically determines when the value is a pointer, and outputs (Typename *) address . And then it creates and returns the corresponding PrettyPrinterWrapper. And supports typedefs. It is called, of course, PrettyPrinterWrapperWrapper. Then I thought it was funny.

Then it turned out, with such a name as Alter_inplace_info::HA_ALTER_FLAGS , the function was not created in python, so I had to make the decorator accept the name of the type as an argument:

 @PrettyPrinter('Alter_inplace_info::HA_ALTER_FLAGS') def HA_ALTER_FLAGS(val): s='' ... 

How to do this? In python, such a decorator is called with one argument and returns a new decorator, which decorates the function itself. Here I have already lost my temper, but it was too late to stop. The function that the decorator creates, and which itself creates the PrettyPrinterWrapperWrapper object, naturally became known as PrettyPrinterWrapperWrapperWrapper. Fortunately, at this stage everything worked, and the wrapping orgy stopped.

But the printers themselves are now written easily and elegantly, nothing superfluous:

 @PrettyPrinter def sql_mode_t(val): s='' modes=['STRICT_TRANS_TABLES', 'STRICT_ALL_TABLES', 'NO_ZERO_IN_DATE', 'TRADITIONAL', 'NO_AUTO_CREATE_USER', 'HIGH_NOT_PRECEDENCE', 'NO_ENGINE_SUBSTITUTION', 'PAD_CHAR_TO_FULL_LENGTH'] for i in range(0,len(modes)): if val & (1 << i): s += ',' + modes[i] return s[1:] 

In use, all this automatically automatically induces the beauty that is so necessary in debugging:

 (gdb) p table->alias $1 = _binary "t3" (gdb) p &table->alias $2 = (String *) 0x7fffd409c250 _binary "t3" (gdb) p table->read_set[0] $3 = b'10011' (gdb) p thd->variables.sql_mode $4 = STRICT_TRANS_TABLES,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION (gdb) p ha_alter_info.handler_flags $5 = ADD_INDEX,DROP_INDEX,ADD_PK_INDEX,ALTER_STORED_COLUMN_ORDER 

Yes, for those who want to use such a decorator in their home, this is all python 2. For the third one, you will need to process it with a file. Patches are gratefully accepted.

Source: https://habr.com/ru/post/325472/


All Articles