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.