Первый раз я увидел команду
duel
в gdb на каком-то древнем IRIX-е, лет пятнадцать назад. Это была невероятно крутая штука для просмотра разных связанных списков, массивов структур, и прочих подобных конструкций. Помечтал, мол, если б в Линуксе такая была, и забыл. Лет десять назад вспомнил, погуглил — оказалось, что DUEL, это вообще-то патч 93-го года для gdb 4.6, а вовсе не что-то уникальное в IRIX. Только автор по идейным соображениям выпустил его как public domain, а gdb-шники были тоже идейные и хотели GPL, так что попасть в upstream этому патчу не грозило. Я портировал его на тогдашний gdb 6.6.2, отдал в gentoo и до выхода 7-го gdb наслаждался жизнью. Потом duel из gentoo выкинули, портировать на новый gdb было сложно, никто не взялся. А недавно я его попробовал оживить. Только вместо патча (надо собирать вместе с gdb из исходников, использует всякие внутренние gdb-шные функции) я его написал с нуля на питоне. Теперь Duel.py (так называется новая реализация Duel-а) грузится в gdb на лету, и, надеюсь, Python API не будет меняться от версии к версии так, как недокументированные gdb-шные потроха. Итак, встречайте: DUEL — высокоуровневый язык анализа данных для gdb.Примеры
Сразу, чтоб показать, на что он способен:
(gdb) dl table_list-->next_local->table_name
tables->table_name = 0x7fffc40126b8 "t2"
tables->next_local->table_name = 0x7fffc4012d18 "t1"
tables-->next_local[[2]]->table_name = 0x7fffc4013388 "t1"
Это из отладки MariaDB. Команда проходит односвязный список структур
TABLE_LIST
и для каждого элемента списка выводит TABLE_LIST::table_name
.(gdb) dl longopts[0..].name @0
longopts[0].name = "help"
longopts[1].name = "allow-suspicious-udfs"
longopts[2].name = "ansi"
<... cut ...>
longopts[403].name = "session_track_schema"
longopts[404].name = "session_track_transaction_info"
longopts[405].name = "session_track_state_change"
Оттуда же (я вырезал адреса, чтоб не захламлять текст). Есть массив структур, задающий опции командной строки. Команда выводит только имена опций, проходя весь массив до
name == 0
. А можно просто посчитать, сколько их:(gdb) dl #/longopts[0..].name @0
#/longopts[0..].name@0 = 406
Основная идея
Duel построен на том, что выражение может возвращать много значений. Например,
(gdb) dl 1..4
1 = 1
2 = 2
3 = 3
4 = 4
или вот
(gdb) dl my_long_options[1..4].(name,def_value)
my_long_options[1].(name) = "allow-suspicious-udfs"
my_long_options[1].(def_value) = 0
my_long_options[2].(name) = "ansi"
my_long_options[2].(def_value) = 0
my_long_options[3].(name) = "autocommit"
my_long_options[3].(def_value) = 1
my_long_options[4].(name) = "bind-address"
my_long_options[4].(def_value) = 0
При этом такие выражения-генераторы допускается использовать везде, где ожидается одно значение. Это можно рассматривать как автоматический цикл по всем возможным значениям. А во втором примере даже два цикла, по индексам массива и по полям структуры. Такая штука работает очень хорошо, когда надо пройтись по массиву, или списку, или, например, дереву. Специальные операторы позволяют удобно записывать условия остановки генератора.
Операторы
Синтаксис похож на С, и C-шные операторы работают, как обычно. Гораздо интереснее, новые, специфичные для DUEL, операторы. Рассмотрим самые полезные из них:
Диапазон и перечисление, .. и ,
Выше я приводил пример обоих операторов. Это знакомые конструкции, они есть и в других языках. При этом в диапазоне можно опустить один из концов. Если указать только конец диапазона, например,
..20
, то диапазон начнется с нуля и в нем будет 20 значений, так же, как если бы было написано 0..19
. Если же указать только начало, то получится открытый диапазон! Чтобы duel не продолжал генерировать числа до тепловой смерти вселенной (или до переполнения счетчика, смотря что случится раньше), вместе с открытым диапазоном обычно используют оператор остановки по условию, @
.Остановка по условию, @
В выражении
x@y
, выражение x
будет генерировать значения до тех пор, пока y
ложно. Например,(gdb) dl arr[0..]@(count > 10)
И duel будет выводить элементы массива
arr[]
до тех пор, пока arr[i].count
будет не больше десяти.Как удобное сокращение, можно в качестве второго операнда использовать константу. Тогда генерация остановится, когда выводимое значение сравняется с этой константой. То есть,
(gdb) dl str[0..]@0
вернет все символы строки, вплоть до
'\0'
. Более практичный пример — вывести все опции командной строки из argv
:(gdb) dl argv[0..]@0
argv[0] = "./mysqld"
argv[1] = "--log-output=file"
argv[2] = "--gdb"
argv[3] = "--core-file"
Хотя тот же эффект достигается и
(gdb) dl argv[..argc]
argv[0] = "./mysqld"
argv[1] = "--log-output=file"
argv[2] = "--gdb"
argv[3] = "--core-file"
Перейти по указателю, -->
Генератор
a-->b
порождает множество значений a
, a->b
, a->b->b
, и так далее, пока не уткнется в NULL. Я уже приводил пример, как таким образом можно пройтись по односвязному списку. Но это точно так же работает и для деревьев, например:(gdb) dl tree-->(left,right)->info
Вычисляющие скобки {}
Фигурные скобки работают как обычные круглые, но они дополнительно заменяют выражение его значением при выводе. Проще показать на примере:
(gdb) dl i:=5
i = 5
(gdb) dl i+6
i+6 = 11
(gdb) dl {i}+6
5+6 = 11
(gdb) dl {i+6}
11 = 11
Это, в основном, нужно для массивов:
(gdb) dl if (my_long_options[i:=1..20].name[0] == 'd') my_long_options[i].name
if(my_long_options[i].name[0] == 'd') my_long_options[i].name = "debug-abort-slave-event-count"
if(my_long_options[i].name[0] == 'd') my_long_options[i].name = "debug-assert-on-error"
if(my_long_options[i].name[0] == 'd') my_long_options[i].name = "debug-assert-if-crashed-table"
if(my_long_options[i].name[0] == 'd') my_long_options[i].name = "debug-disconnect-slave-event-count"
if(my_long_options[i].name[0] == 'd') my_long_options[i].name = "debug-exit-info"
(gdb) dl if (my_long_options[i:=1..20].name[0] == 'd') my_long_options[{i}].name
if(my_long_options[i].name[0] == 'd') my_long_options[16].name = "debug-abort-slave-event-count"
if(my_long_options[i].name[0] == 'd') my_long_options[17].name = "debug-assert-on-error"
if(my_long_options[i].name[0] == 'd') my_long_options[18].name = "debug-assert-if-crashed-table"
if(my_long_options[i].name[0] == 'd') my_long_options[19].name = "debug-disconnect-slave-event-count"
if(my_long_options[i].name[0] == 'd') my_long_options[20].name = "debug-exit-info"
Тут фигурные скобки сразу показывают, какие элементы массива удовлетворяют условию.
Фильтры <? >? <=? >=? ==? !=?
Эти вариации на тему операторов сравнения фактически работают как фильтры. То есть в
x !=? y
, из множества значений x
выбираются только те, которые не равны y
. Выше был пример с условным оператором if
. С фильтром такой же результат получается проще:(gdb) dl my_long_options[1..20].(name[0] ==? 'd' => name)
my_long_options[16].(name) = "debug-abort-slave-event-count"
my_long_options[17].(name) = "debug-assert-on-error"
my_long_options[18].(name) = "debug-assert-if-crashed-table"
my_long_options[19].(name) = "debug-disconnect-slave-event-count"
my_long_options[20].(name) = "debug-exit-info"
Еще операторы
Кроме того есть алиасы, групповые операторы, условный оператор (
if
) и прочие редко нужные штуки.Advanced
Вот еще несколько примеров из документации.
Пройтись по циклическому списку (начинаем с
head
и идем по указателям, пока опять не дойдем до head
):(gdb) head-->(next!=?head)
Найти второй положительный элемент в массиве
x[]
. Оператор [[ ]]
выбирает элементы из последовательности:(gdb) dl (x[0..] >? 0)[[2]]
Найти последний элемент в односвязном списке. Тут используется унарный оператор
#/
, который возвращает количество элементов в последовательности, и оператор выбора [[ ]]
:(gdb) head-->next[[#/head-->next - 1]]
Выдает только те элементы массива, которые больше следующего элемента, фактически, проверяет отсортирован ли массив по возрастанию:
(gdb) dl x[i:=..100] >? x[i+1]
Еще раз, самое главное
А самое главное вот что — duel в gdb работает (опять). Он дико удобен при отладке чего-то, сложнее чем hello world. Для нормального использования практически достаточно четырех конструкций — две точки, запятая, длинная стрелка
-->
и @0
.Взять можно у меня в репозитории: github.com/vuvova/gdb-tools
Disclaimer (отмазка): хотя сам DUEL — весьма почтенный и проверенный временем интерпретатор, Duel.py совсем новый, наверняка есть баги.