GDB/MI выводит информацию в весьма структурированном виде. Например,
информация от фреймах, возвращаемая командой -stack-list-frames,
выглядит так:
(gdb) -stack-list-frames ^done,stack=[frame={level="0",addr="0x0804868f",func="hello",file="hello.c",fullname="/home/sphinx/projects/gsoc/hello.c",line="6"},frame={level="1",addr="0xb7faa900",func="start_thread",from="/lib/libpthread.so.0"}]
Выглядит хорошо! Как с этим работать?
Регвырам бой!
На данный момент в gdb-mi.el используются для разбора подобных
ответов используются регулярные выражения. Вот, например, выражения для
обработки приведённого выше вывода команды -stack-list-frames:
(defconst gdb-stack-list-frames-regexp
"level="\(.?\)",addr="\(.?\)",func="\(.?\)",
\(?:file=".?",fullname="\(.?\)",line="\(.?\)"\|
from="\(.*?\)"\)")
На ответы GDB/MI натравливается re-search-forward, после чего из них
извлекаются с помощью match-string нужные значения.
Вроде бы работает, но основанный на таком подходе код:
-
слишком быстро ломается при малейших изменениях выходного формата GDB/MI, а попытки захватить с его помощью как можно больше частных случаях приводят к излишним усложнениям;
-
поддерживается без удовольствия;
-
что самое главное, ошибочен идейно, потому что использование регулярных выражений снижает весь выигрыш от общения с отладчиком при помощи машинного интерфейса.
Язык GDB/MI в общем случае не является регулярным, поэтому все методы разбора сообщений на нём с помощью регвыров априори обречены быть если не провальными, то недостаточно общными как минимум.
По степени костыльности разбирать ответы GDB/MI с помощью регвыров — примерно то же самое, что обрабатывать XML теми же регвырами вместо XSLT.
Итак, ситуация в коде, которым я занимаюсь, требует перемен в лучшую сторону.
Выше!
Оценив синтаксис ответов GDB/MI, я пришёл к выводу, что (с небольшими исключениями) он хорошо ложится на следующую модель:
-
рассматриваются объекты, в которых инкапсулированы пары
поле=значение; -
значениями могут быть (строковые) константы, списки или другие объекты;
-
списки представляют собой неупорядоченные наборы других списков, констант или объектов.
Такая модель легко представляется простой рекурсивной списочной структурой данных в виде вложенных друг в друга ассоциативных и не очень списков.
Остаётся только обернуть весь вывод GDB/MI в фигурные скобочки, тогда на
Лисп он переводится так (тут в качестве примера рассматривается ответ на
запрос -thread-info):
было:
{threads=[{id="1", target-id="LWP18334", frame={level="0", addr="0x08048b9a", func="mult_matrices_mt", args=[{name="m1", value="0x804ba30"}, {name="m2", value="0x804ba30"}], file="test.c", fullname="/home/sphinx/projects/gsoc/test.c", line="142"}, state="stopped"}], current-thread-id="1"}
стало:
((threads . (((id . "1") (target-id . "LWP18334") (frame . ((level . "0") (addr . "0x08048b9a") (func . "mult_matrices_mt") (args . (((name . "m1") (value . "0x804ba30")) ((name . "m2") (value . "0x804ba30")))) (file . "test.c") (fullname . "/home/sphinx/projects/gsoc/test.c") (line . "142"))) (state . "stopped")))) (current-thread-id . "1"))
Стало очевидно, что нужно переводить строковые ответы GDB/MI в структуры данных более высокого уровня и работать уже с ними.
Я посоветовался в рассылке GDB, и авторы нескольких морд к GDB (CDT для Eclipse, Qt Creator) ожидаемо ответили, что работают с информацией от MI на объектной основе.
Использование JSON
Я уже было решил, что мне нужно писать какой-то парсер для MI, чтобы он мне это всё по круглым скобкам распихал.
Когда я первый раз поработал с GDB/MI, я подумал: «как же это похоже на
JSON». Действительно, если не рассматривать один поистине уродский
случай в синтаксисе GDB/MI, остаётся только обернуть имена полей в
символы "", да знаки равенства заменить двоеточиями:
(defun json-partial-output () (with-current-buffer (gdb-get-buffer-create 'gdb-partial-output-buffer) (goto-char (point-min)) (insert "{") (replace-regexp "\([[:alpha:]-_]+\)=" ""\1":") (goto-char (point-max)) (insert "}") (goto-char (point-min)) (let ((json-array-type 'list)) (json-read))))
Ну да, двоеточия опять регекспом добавляю :) Это слабость в моём коде, но ей управлять проще.
Попробовал работать так — стало гораздо удобнее. Написал пару обёрток для того, чтобы можно было быстро обращаться к элементам рекурсивных списков и с их помощью новый код для буфера с информацией о потоках обогатился подобными конструкциями:
(insert (fadr-format "~.id (~.target-id) ~.state\tin ~.frame.func" thread))
Здесь thread — ассоциативный список представленной выше структуры.
Функция fadr-format у меня просто заменяет в своём первом аргументе
подстроки типа
~.frame.func
на результат вызова
(cdr (assoc 'func (cdr (assoc 'frame thread))))
то есть я просто обращаюсь к вложенным членам структуры thread:
thread → frame → func
Так что вместо ~.frame.func у меня в строке появляется значение
соответствующего элемента структуры (конкретно здесь — имя функции, в
которой находится поток).
Если бы GDB для вывода использовал JSON, всё было бы вообще хорошо. Разработчики морд могли бы использовать более распространённые и имеющии более широкое распространение парсеры JSON вместо поддержки своих собственных (очень похожих) функций.
Для этого не понадобится переписывать много кода, поскольку изменения в GDB требуются чисто косметические. С другой стороны, всегда можно чуточку поправить вывод GDB/MI уже в своей программе и работать тем же JSON-парсером.
Что пакостно
Вот неприятное место в синтаксисе MI, о котором я упоминал выше:
result ==> variable "=" value
tuple ==> "{}" | "{" result ( "," result )* "}"
list ==> "[]" | "[" value ( "," value )* "]" | "[" result ( "," result )* "]"
Тут получается, что и кортежи (tuple), и списки (list) могут содержать в
себе разделённые запятыми пары поле=значение, что немного портит всю
картину. Получается, что кортежи по смыслу являются подмножеством
списков, а потому их наличие в синтаксисе как отдельной сущности
избыточно. Было бы хорошо, если списки могли содержать просто набор
значений:
list ==> "[]" | "[" value ( "," value )* "]"
То есть чтобы было спискам списково, а кортежам кортежево.
Тогда для использования описанной выше модели не было бы вообще никаких препятствий.
Сейчас некоторые команды нагло (и совершенно без всякой на то причины)
пользуются данным недостатком синтаксиса; таковы, например
-stack-list-frames, -break-info):
Ошмёток вывода -break-info:
body=[bkpt={number="1", … },bkpt={number="2", …}]
Ну нафига тут эти bkpt=? Должно быть так:
body=[{number="1", … },{number="2", …}]
А всё от недостатка планирования (из сообщения в рассылке):
I wonder why was GDB/MI syntax designed this way.
What makes you think it was designed? GDB/MI is not result of a design session, but of somewhat long evolution, so some early decisions are prominent.