Ответ 1
У меня недостаточно опыта работы с apd gdb Python, чтобы называть это ответом; Я рассматриваю это только некоторые исследовательские заметки от другого разработчика. Мой код, приведенный ниже, довольно груб и уродлив. Однако это работает с gdb-7.4 и python-2.7.3. Пример прогона отладки:
$ gcc -Wall -g3 tiny.c -o tiny
$ gdb tiny
(gdb) b 58
(gdb) run
(gdb) print iseq3
$1 = (struct boxsequence_st *) 0x602050
(gdb) print iv42
$2 = (struct boxint_st *) 0x602010
(gdb) print istrhello
$3 = (struct boxstring_st *) 0x602030
Все вышесказанное является стандартным довольно печальным выходом - мои рассуждения состоят в том, что я часто хочу видеть, что представляют собой указатели, поэтому я не хотел их переопределять. Однако, для привязки указателей используется симплексный указатель, показанный ниже:
(gdb) print *iseq3
$4 = (struct boxsequence_st)(3) = {(struct boxint_st)42, (struct boxstring_st)"hello"(5), NULL}
(gdb) print *iv42
$5 = (struct boxint_st)42
(gdb) print *istrhello
$6 = (struct boxstring_st)"hello"(5)
(gdb) set print array
(gdb) print *iseq3
$7 = (struct boxsequence_st)(3) = {
(struct boxint_st)42,
(struct boxstring_st)"hello"(5),
NULL
}
(gdb) info auto-load
Loaded Script
Yes /home/.../tiny-gdb.py
В последней строке показано, что при отладке tiny
, tiny-gdb.py
в том же каталоге автоматически загружается (хотя вы можете отключить это, я считаю, что это поведение по умолчанию).
Файл tiny-gdb.py
, используемый выше:
def deref(reference):
target = reference.dereference()
if str(target.address) == '0x0':
return 'NULL'
else:
return target
class cstringprinter:
def __init__(self, value, maxlen=4096):
try:
ends = gdb.selected_inferior().search_memory(value.address, maxlen, b'\0')
if ends is not None:
maxlen = ends - int(str(value.address), 16)
self.size = str(maxlen)
else:
self.size = '%s+' % str(maxlen)
self.data = bytearray(gdb.selected_inferior().read_memory(value.address, maxlen))
except:
self.data = None
def to_string(self):
if self.data is None:
return 'NULL'
else:
return '\"%s\"(%s)' % (str(self.data).encode('string_escape').replace('"', '\\"').replace("'", "\\\\'"), self.size)
class boxintprinter:
def __init__(self, value):
self.value = value.cast(gdb.lookup_type('struct boxint_st'))
def to_string(self):
return '(struct boxint_st)%s' % str(self.value['ival'])
class boxstringprinter:
def __init__(self, value):
self.value = value.cast(gdb.lookup_type('struct boxstring_st'))
def to_string(self):
return '(struct boxstring_st)%s' % (self.value['strval'])
class boxsequenceprinter:
def __init__(self, value):
self.value = value.cast(gdb.lookup_type('struct boxsequence_st'))
def display_hint(self):
return 'array'
def to_string(self):
return '(struct boxsequence_st)(%s)' % str(self.value['slen'])
def children(self):
value = self.value
tag = str(value['tag'])
count = int(str(value['slen']))
result = []
if tag == 'tag_none':
for i in xrange(0, count):
result.append( ( '#%d' % i, deref(value['valtab'][i]['ptag']) ))
elif tag == 'tag_int':
for i in xrange(0, count):
result.append( ( '#%d' % i, deref(value['valtab'][i]['pint']) ))
elif tag == 'tag_string':
for i in xrange(0, count):
result.append( ( '#%d' % i, deref(value['valtab'][i]['pstr']) ))
elif tag == 'tag_sequence':
for i in xrange(0, count):
result.append( ( '#%d' % i, deref(value['valtab'][i]['pseq']) ))
return result
def typefilter(value):
"Pick a pretty-printer for 'value'."
typename = str(value.type.strip_typedefs().unqualified())
if typename == 'char []':
return cstringprinter(value)
if (typename == 'struct boxint_st' or
typename == 'struct boxstring_st' or
typename == 'struct boxsequence_st'):
tag = str(value['tag'])
if tag == 'tag_int':
return boxintprinter(value)
if tag == 'tag_string':
return boxstringprinter(value)
if tag == 'tag_sequence':
return boxsequenceprinter(value)
return None
gdb.pretty_printers.append(typefilter)
Мотивы выбора следующие:
-
Как установить красивые принтеры в gdb?
В этом вопросе есть две части: где установить файлы Python и как подключить симпатичные принтеры к gdb.
Поскольку выбор симпатичного принтера не может полагаться только на выведенный тип, но должен заглянуть в фактические поля данных, вы не можете использовать функции регулярного выражения. Вместо этого я решил добавить свою собственную функцию выбора красивого принтера
typefilter()
в список глобальных симпатичных принтеров, как описано в документации, Я не реализовал функцию enable/disable, потому что считаю, что проще просто загрузить/не загружать соответствующий Python script.(
typefilter()
вызывается один раз для каждой ссылки на переменные, если какой-либо другой симпатичный принтер уже не принял его.)Проблема размещения файлов является более сложной. Для приложений-принтеров, ориентированных на приложения, размещение их в одном файле Python script звучит разумно, но для библиотеки некоторые расщепления, похоже, в порядке. Документация рекомендует упаковывать функции в модуль Python, так что простой
python import module
позволяет использовать довольно-принтер. К счастью, упаковка Python довольно проста. Если вы были вimport gdb
вверху и сохраните его до/usr/lib/pythonX.Y/tiny.py
, гдеX.Y
- используемая версия python, вам нужно запуститьpython import tiny
в gdb, чтобы включить симпатичный принтер.Конечно, правильно packaging красивый принтер - очень хорошая идея, особенно если вы собираетесь его распространять, но это в значительной степени сводится к добавлению некоторых переменных и так далее к началу script, предполагая, что вы храните его как один файл. Для более сложных довольно-принтеров использование макета каталога может быть хорошей идеей.
-
Если у вас есть значение
val
, тоval.type
- объект gdb.Type, описывающий его тип; преобразование его в строку дает имя, читаемое человеком.val.type.strip_typedefs()
дает фактический тип, если все typedefs лишены. Я даже добавил.unqualified()
, так что все const/volatile/etc. типы классификаторов удаляются. -
Обнаружение указателя NULL немного сложнее.
Лучший способ, которым я нашел, - проверить стробированный член
.address
целевого объекта gdb.Value и посмотреть, есть ли он"0x0"
.Чтобы облегчить жизнь, я смог написать простую функцию
deref()
, которая пытается разыменовать указатель. Если цель указывает на (void *) 0, она возвращает строку"NULL"
, в противном случае она возвращает целевой объект gdb.Value.Способ, которым я пользуюсь
deref()
, основан на том факте, что"array"
типа pretty-printers дает список из 2-х кортежей, где первый элемент - это строка имени, а второй элемент - либо gdb.Value объект или строку. Этот список возвращается с помощью методаchildren()
объекта с красивым принтером. -
Обработка типов с дискриминационным объединением была бы намного проще, если бы у вас был отдельный тип для общего объекта. То есть, если у вас
struct box_st { enum tag_en tag; };
и он использовался повсюду, когда значение
tag
все еще остается неопределенным; и конкретные типы структуры используются только там, где их значениеtag
фиксировано. Это позволило бы сделать гораздо более простой вывод типа.Как и в
tiny.c
, типыstruct box*_st
могут использоваться взаимозаменяемо. (Или, более конкретно, мы не можем полагаться на определенное значение тега, основанное только на типе.)Случай последовательности на самом деле довольно прост, потому что
valtab[]
можно рассматривать как просто массив указателей void. Тег последовательности используется для выбора правильного члена объединения. Фактически, если valtab [] был просто массивом указателей void, то gdb.Value.cast(gdb.lookup_type()) или gdb.Value.reinterpret_cast (gdb.lookup_type()) может использоваться для изменения каждого типа указателя по мере необходимости, так же, как и для типов в штучной упаковке. -
Пределы рекурсии?
Вы можете использовать оператор
@
в командеprint
, чтобы указать, сколько элементов напечатано, но это не помогает при вложенности.Если вы добавите
iseq3->valtab[2] = (myval_t)iseq3;
вtiny.c
, вы получите бесконечно рекурсивную последовательность. gdb действительно печатает его, особенно сset print array
, но он не замечает и не заботится о рекурсии.
На мой взгляд, вы можете написать команду gdb в дополнение к довольно принтеру для глубоко вложенных или рекурсивных структур данных. Во время моего тестирования я написал команду, которая использует Graphviz для рисования двоичных древовидных структур непосредственно изнутри gdb; Я абсолютно уверен, что это простое выведение текста.
Добавлено: Если вы сохраните следующее как /usr/lib/pythonX.Y/tree.py
:
import subprocess
import gdb
def pretty(value, field, otherwise=''):
try:
if str(value[field].type) == 'char []':
data = str(gdb.selected_inferior().read_memory(value[field].address, 64))
try:
size = data.index("\0")
return '\\"%s\\"' % data[0:size].encode('string_escape').replace('"', '\\"').replace("'", "\\'")
except:
return '\\"%s\\"..' % data.encode('string_escape').replace('"', '\\"').replace("'", "\\'")
else:
return str(value[field])
except:
return otherwise
class tee:
def __init__(self, cmd, filename):
self.file = open(filename, 'wb')
gdb.write("Saving DOT to '%s'.\n" % filename)
self.cmd = cmd
def __del__(self):
if self.file is not None:
self.file.flush()
self.file.close()
self.file = None
def __call__(self, arg):
self.cmd(arg)
if self.file is not None:
self.file.write(arg)
def do_dot(value, output, visited, source, leg, label, left, right):
if value.type.code != gdb.TYPE_CODE_PTR:
return
target = value.dereference()
target_addr = int(str(target.address), 16)
if target_addr == 0:
return
if target_addr in visited:
if source is not None:
path='%s.%s' % (source, target_addr)
if path not in visited:
visited.add(path)
output('\t"%s" -> "%s" [ taillabel="%s" ];\n' % (source, target_addr, leg))
return
visited.add(target_addr)
if source is not None:
path='%s.%s' % (source, target_addr)
if path not in visited:
visited.add(path)
output('\t"%s" -> "%s" [ taillabel="%s" ];\n' % (source, target_addr, leg))
if label is None:
output('\t"%s" [ label="%s" ];\n' % (target_addr, target_addr))
elif "," in label:
lab = ''
for one in label.split(","):
cur = pretty(target, one, '')
if len(cur) > 0:
if len(lab) > 0:
lab = '|'.join((lab,cur))
else:
lab = cur
output('\t"%s" [ shape=record, label="{%s}" ];\n' % (target_addr, lab))
else:
output('\t"%s" [ label="%s" ];\n' % (target_addr, pretty(target, label, target_addr)))
if left is not None:
try:
target_left = target[left]
do_dot(target_left, output, visited, target_addr, left, label, left, right)
except:
pass
if right is not None:
try:
target_right = target[right]
do_dot(target_right, output, visited, target_addr, right, label, left, right)
except:
pass
class Tree(gdb.Command):
def __init__(self):
super(Tree, self).__init__('tree', gdb.COMMAND_DATA, gdb.COMPLETE_SYMBOL, False)
def do_invoke(self, name, filename, left, right, label, cmd, arg):
try:
node = gdb.selected_frame().read_var(name)
except:
gdb.write('No symbol "%s" in current context.\n' % str(name))
return
if len(arg) < 1:
cmdlist = [ cmd ]
else:
cmdlist = [ cmd, arg ]
sub = subprocess.Popen(cmdlist, bufsize=16384, stdin=subprocess.PIPE, stdout=None, stderr=None)
if filename is None:
output = sub.stdin.write
else:
output = tee(sub.stdin.write, filename)
output('digraph {\n')
output('\ttitle = "%s";\n' % name)
if len(label) < 1: label = None
if len(left) < 1: left = None
if len(right) < 1: right = None
visited = set((0,))
do_dot(node, output, visited, None, None, label, left, right)
output('}\n')
sub.communicate()
sub.wait()
def help(self):
gdb.write('Usage: tree [OPTIONS] variable\n')
gdb.write('Options:\n')
gdb.write(' left=name Name member pointing to left child\n')
gdb.write(' right=name Name right child pointer\n')
gdb.write(' label=name[,name] Define node fields\n')
gdb.write(' cmd=dot arg=-Tx11 Specify the command (and one option)\n')
gdb.write(' dot=filename.dot Save .dot to a file\n')
gdb.write('Suggestions:\n')
gdb.write(' tree cmd=neato variable\n')
def invoke(self, argument, from_tty):
args = argument.split()
if len(args) < 1:
self.help()
return
num = 0
cfg = { 'left':'left', 'right':'right', 'label':'value', 'cmd':'dot', 'arg':'-Tx11', 'dot':None }
for arg in args[0:]:
if '=' in arg:
key, val = arg.split('=', 1)
cfg[key] = val
else:
num += 1
self.do_invoke(arg, cfg['dot'], cfg['left'], cfg['right'], cfg['label'], cfg['cmd'], cfg['arg'])
if num < 1:
self.help()
Tree()
вы можете использовать его в gdb:
(gdb) python import tree
(gdb) tree
Usage: tree [OPTIONS] variable
Options:
left=name Name member pointing to left child
right=name Name right child pointer
label=name[,name] Define node fields
cmd=dot arg=-Tx11 Specify the command (and one option)
dot=filename.dot Save .dot to a file
Suggestions:
tree cmd=neato variable
Если у вас есть, например,
struct node {
struct node *le;
struct node *gt;
long key;
char val[];
}
struct node *sometree;
и у вас есть X11 (локальное или удаленное) соединение и установлен Graphviz, вы можете использовать
(gdb) tree left=le right=gt label=key,val sometree
чтобы просмотреть древовидную структуру. Поскольку он сохраняет список уже посещенных узлов (как набор Python), он не становится в курсе рекурсивных структур.
Я, вероятно, должен был очистить мои фрагменты Python перед публикацией, но неважно. Пожалуйста, рассмотрите эти только исходные версии тестирования; Используйте на свой риск.:)