Что gdb можно как-то улучшать на питоне, знает каждый, кто хоть раз заглядывал в документацию. А кто хоть раз просматривал ее по диагонали, знает про такую штуку, как «Pretty Printers» — которые вроде позволяют gdb красиво печатать разные сложные структуры. Я документацию по диагонали просматривал, хотя особо и не вникал. Но однажды, набирая в очередной раз что-то вроде (все примеры из исходников MariaDB, которые я дебажу по много раз каждый божий день, иногда исключая выходные):
Я подумал «а фигли?». И все заверте…
Pretty Printer в питоне — это класс. В конструкторе ему передается значение, которое надо напечатать (это будет не просто скаляр, а
Это еще не все. Есть еще функция, которая проверяет тип этого
Там в аргументах у
На мой вкус — несколько сложновато. В классах много лишнего, реально нужен только метод
Да и указатели не обрабатываются. А хотелось бы, чтоб как в gdb:
То есть, если указатель, то выводится тип, адрес, и потом все равно значение, как у строки. Для этого можно склепать еще два класса, скажем,
Только по делу. Остальное работает само. Но, то ли это у меня руки такие, то ли декораторы и
Вкратце, как тут что работает. Декоратор просто берет имя функции, и регистрирует pretty printer на этот тип. Сама же функция и
Дальше, чтоб зарегистрировать в gdb наш pretty printer, нужен другой класс, который callable и выбирает, какой pretty printer использовать. В первом примере это родной gdb-шный
Потом оказалось, с таким именем, как
А как такое сделать? В питоне такой декоратор вызывается с одним аргументом и возвращает новый декоратор, который декорирует саму функцию. Тут мне уже поплохело, но останавливаться было поздно. Функция, которую создает декоратор, и которая сама создает объект PrettyPrinterWrapperWrapper, естественно, стала называться PrettyPrinterWrapperWrapperWrapper. К счастью, на этом этапе все заработало, и врапперная вакханалия остановилась.
Зато сами принтеры теперь пишутся легко и изящно, ничего лишнего:
В использовании все это совершенно автоматически наводит столь необходимую в отладке красоту:
Да, для желающих использовать такой декоратор у себя — это все python 2. Для третьего нужно будет обработать напильником. Патчи с благодарностью принимаются.
(gdb) p/t table->read_set->bitmap[0] @ (table->read_set->n_bits+7)/8
Я подумал «а фигли?». И все заверте…
Pretty Printer в питоне — это класс. В конструкторе ему передается значение, которое надо напечатать (это будет не просто скаляр, а
gdb.Value). И у класса есть to_string() метод, который, собственно, и возвращает то, что мы хотим увидеть. Например, так: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']) + '"'
Это еще не все. Есть еще функция, которая проверяет тип этого
gdb.Value и решает, надо ли использовать наш класс. Для простоты, в gdb.printing модуле есть готовая реализация, которая использует регулярные выражения, как-то так: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()
Там в аргументах у
pp.add_printer() — название, регулярка для типа, и класс, который этот тип печатает.На мой вкус — несколько сложновато. В классах много лишнего, реально нужен только метод
to_string(), все остальное будет копироваться из класса в класс. В коде регистрации принтеров тоже всего многовато, даже имя типа повторяется три раза подряд.Да и указатели не обрабатываются. А хотелось бы, чтоб как в gdb:
(gdb) p opt_tc_log_file $2 = 0x555556204273 "tc.log" (gdb) p table->field[0] $3 = (Field_varstring *) 0x555557437a88
То есть, если указатель, то выводится тип, адрес, и потом все равно значение, как у строки. Для этого можно склепать еще два класса, скажем,
StringPtrPrinter и BitmapPtrPrinter, у которых to_string() почти такой же, но еще выводит и адрес. А можно расширить уже написанные, добавив и туда и туда проверки, чтоб на ходу отлавливали, когда val это указатель, и соответственно печатали адрес. Так или иначе, копировать и копировать. Один и тот же код из класса в класс. Мне это быстро надоело, и я стал все упрощать. В идеале хотелось писать эти принтеры как-то так:@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']):] + "'"
Только по делу. Остальное работает само. Но, то ли это у меня руки такие, то ли декораторы и
gdb.printing сговорились, но получилось, что враппер сидит на враппере и враппером же погоняет.Вот код
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
Вкратце, как тут что работает. Декоратор просто берет имя функции, и регистрирует pretty printer на этот тип. Сама же функция и
gdb.Value значение, оба передаются в конструктор класса PrettyPrinterWrapper. И по to_string() он вызывает эту функцию с этим значением.Дальше, чтоб зарегистрировать в gdb наш pretty printer, нужен другой класс, который callable и выбирает, какой pretty printer использовать. В первом примере это родной gdb-шный
gdb.printing.RegexpCollectionPrettyPrinter, но я же его не хочу, так ��то у меня свой класс, с покером и куртизанками. Он автоматически определяет, когда значение — это указатель, и выводит (Typename *) address. А потом создает и возвращает соответствующий PrettyPrinterWrapper. И поддерживает typedef-ы. Называется он, конечно, PrettyPrinterWrapperWrapper. Тогда я думал еще, что это смешно.Потом оказалось, с таким именем, как
Alter_inplace_info::HA_ALTER_FLAGS, в питоне функцию не создать, так что пришлось сделать, чтоб декоратор принимал имя типа, как аргумент:@PrettyPrinter('Alter_inplace_info::HA_ALTER_FLAGS') def HA_ALTER_FLAGS(val): s='' ...
А как такое сделать? В питоне такой декоратор вызывается с одним аргументом и возвращает новый декоратор, который декорирует саму функцию. Тут мне уже поплохело, но останавливаться было поздно. Функция, которую создает декоратор, и которая сама создает объект PrettyPrinterWrapperWrapper, естественно, стала называться PrettyPrinterWrapperWrapperWrapper. К счастью, на этом этапе все заработало, и врапперная вакханалия остановилась.
Зато сами принтеры теперь пишутся легко и изящно, ничего лишнего:
@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:]
В использовании все это совершенно автоматически наводит столь необходимую в отладке красоту:
(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
Да, для желающих использовать такой декоратор у себя — это все python 2. Для третьего нужно будет обработать напильником. Патчи с благодарностью принимаются.
