Pencils down

Я что-то совсем обленился и давно не писал про своё GSoC. А тем временем подкралось 17 августа — дата окончания работы над проектами.

За прошедшее время много чего стряслось, поэтому стоит рассказать о своём прогрессе.

Улучшения в поддержке многопоточной отладки с GDB

В недельной окрестности от Mid-term Evaluation (13 июля) я реализовал уже всё, что нужно, в оставшееся время тщательно шлифуя недостатки и баги (которые, впрочем, остались).

Помимо дописывания нереализованных буферов для gdb-mi.el, что я сделал уже к началу июля (хотя множество улучшений я продолжаю вносить в связанный с этим код), в первоначальный план проекта входило следующее:

  1. Сделать так, чтобы можно было следить за несколькими потоками одновременно (с помощью опции --thread);

  2. Сделать так, чтобы можно было пользоваться безостановочным режимом отладки GDB (в котором остановка одного из потоков не приводит к останову всех остальных).

На сегодняшний день всё реализованное уже в Emacs CVS, а документация в разделе «GDB Graphical Interface» была обновлена с учётом изменений и описывает появившиеся фичи.

(info "(emacs)GDB Graphical Interface")

Обзёрвер овладевает

Первое потребовало некоторых архитектурных изменений в gdb-mi.el. Пакет позволяет по вызову команды gdb разместить информацию о выполняемой под отладчиком программе в разных буфера Емакса, и обновлять эту информацию по ходу отладки.

До сих пор мог существовать только один буфер для дизассемблера, или для стека, и т. д. С каждым типом буфера было связано определённое символьное значение (которое доступно изнутри этого буфера и позволяет определить его «тип»), основной режим того буфера и команда, которая его обновляет.

Каждый раз, когда требовалось обновить информацию в связанных с GDB буферах, некоторая процедура вызывала пачку функций, каждая из которых обновляет буфер какого-то определённого типа:

(gdb-invalidate-breakpoints)
(gdb-invalidate-threads)
(gdb-invalidate-locals)
(gdb-invalidate-registers)
(gdb-invalidate-frames)
(gdb-invalidate-disassembly)
(gdb-invalidate-memory)

Проблемой каждой из них было то, что они просто просматривали весь список открытых буферов, среди которых искали один-единственный, в котором символьное значение типа буфера было равно нужному. Помимо того, что это просто некрасиво, тратились лишние циклы на поиск нередко заведомо ещё не открытых буферов (например, дизассемблера, который по умолчанию не создаётся). Плюс надо было как-то избавиться от заточенности под существование лишь одного буфера каждого типа.

Хотелось бы просто поддерживать список связанных с GDB буферов в актуальном состоянии, и при необходимости просто пробегаться по этому списку, обновляя каждый буфер. Если рассмотреть задачу с немного более высокого уровня, то хотелось бы оповещать буферы о необходимости обновления. В итоге я сделал так, чтобы при обновлении буфер подписывался на уведомления, а при уничтожении его подписка аннулировалась. Ещё немного сахара, и вышеозначенная куча вызовов заменилась одним-единственным

(gdb-emit-signal gdb-buf-publisher 'update)

После этого проблем с расширением кода для поддержки нескольких однотипных буферов уже не было. В буфере со списком потоков можно нажимать кнопочки s/f/l/d, которые открывают буферы, привязанные к определённым потокам. Из того же буфера с потоками можно и останавливать/продолжать треды. Возможность наблюдать и управлять несколькими потоками одновременно в итоге делает понятие «текущего» треда, принятого в интерфейсе командной строки GDB, менее значимым.

Non-stop

Казалось бы, надо было только сделать так, чтобы GDB переключался в безостановочный режим, если юзер хочет. Простейшие операции заработали сразу, и можно было лицезреть запущенные потоки бок о бок с остановленными.

Тем не менее, здесь тоже потребовалась некоторая работа, например, реализовать хоть какой-то контроль за политикой переключения текущего потока в безостановочном режиме. Когда какой-то поток уже остановился и был выбран в качестве текущего, ВНЕЗАПНАЯ остановка ещё одного и не менее ВНЕЗАПНЫЙ его выбор как-то запутывает весь процесс с соответствующей сменой содержимого всех буферов, поэтому переключение с уже остановленного треда на другой остановившийся сделано опциональным (gdb-switch-when-another-stopped). GDB/MI выдаёт информацию и о причине остановки тоже (точка останова, сигнал и т.д. и т.п.), Emacs может использовать эту информацию, чтобы решить, переключаться на тред или причина его остановки не требует немедленного вмешательства (gdb-switch-reasons). Мой проект был бы неполон, не добавив я хоть один хук в код: gdb-stopped-hook выполняется каждый раз, когда останавливается какой-то поток, причём ему передаётся и информация о причине останова. Можно вести логи на уровне Емакса, или там, свистеть в динамик, что ли.

Буфер с потоками теперь обновляется по мере поступления информации от GDB. Можно наблюдать, как плодятся потоки.

gdb-mi.el где-то в глубине до сих пор немного использует команды GUD (унифицированного интерфейса к разным отладчикам, которых присутствует в Emacs уже многие годы). Требовалось сохранить некоторую совместимость в интерфейсе со старыми соглашениями без потери гибкости: например, сделана возможность выбирать, к каким потокам относятся команды Go/Stop — ко всем или лишь к текущему (gdb-gud-control-all-threads). Мне пришлось даже нарисовать новую кнопку на панель инструментов, которая как бы намекает, в каком режиме мы находимся.

New GUD button [T]

New GUD button [A]

Как Дима в CVS коммитил

Где-то в середине июля мне наконец дали доступ в CVS Емакса и я смог закидывать более-менее стабильные изменения прямо апстрим (до того пару коммитов моего кода туда делал мой ментор).

Некоторую трудность поначалу вызвала синхронизация изменений в моём Mercurial-репозитории и Emacs CVS, потому что мой ментор делал некоторые коммиты прямо в CVS Емакса, откуда я их потом вытягивал в свой репозиторий, так что в какой-то момент истории изменений в двух репозиториях полностью рассинхронизировались. Нельзя было и просто сочинить какой-то скрипт, который бы импортировал каждый мой коммит в CVS Емакса, потому что я и коммичу очень много, и ChangeLog не пишу обычно (потому что это полезно только при отсутствии нормальной SCM).

С приходом первых моих крупных изменений на диски конечных юзеров, я стал получать багрепорты, с которыми решительно расправлялся.

Забавно, но у двух багов, связанных с моим пакетом, номера состоят из цифр 3, 4, 7 и 9. В связи с этим прогнозирую, что баг 4379, возможно, будет относится к моему коду.

За кадром

Мне иногда кажется, что большая часть изменений осталась невидимой для «пользователя». Я иногда ловил себя на мысли, что внезапно замеченный в пакете код какой-то ну уж совсем страшный. Например, чтобы расставить красненькие кружочки-брейкпонты на кромке буферов с исходниками, раньше перепарсивался уже отформатированный буфер с этими самыми точками останова. Я старался уходить от такого и больше разделять вид и представление. Не перепарсивать текст в буфере, а сразу брать всю информацию из хранимого под капотом в структурированном виде древовидного представления. тем более с учётом структурности MI можно просто сохранять ранее разобранный ответ от GDB. Думаю, всё это было в духе объявленной цели проекта повысить «robustness» пакета и вообще всячески причесать его. Как сказал мой ментор,

It’s easy for me to forget how bad the mode was when you started

Так что помимо основных целей работа в целом была полезна. Этот код заменить ныне используемых gdb-ui.el в следующих релизах GNU Emacs.

Многое ещё можно было бы переписать, хотя передо мной такой задачи не стояло. Ещё больше MVC и более богатый и интересный интерфейс, например, древовидный буфер с потоками, в котором стеки представлены в ниспадающих списках (в Eclipse так — открыл-закрыл, прикольно). С другой стороны, сейчас пакет прост и его легко изменять, а на тотальное его переписывание у меня нет ни ресурсов, ни собственно неотложной необходимости. Быть может однажды кто-нибудь сделать вторую Grand Unification, сделав уже графический единобразный интерфейс к разным отладчикам.

Онгоинг

Есть ещё несколько нерешённых проблем.

Например, я почему-то решил, что mapcar* из пакета cl (это нормальная (без ограничений арности функции) версия mapcar) — это макрос, и без опаски применил эту функцию в своём коде, чего делать нельзя (потому что нельзя исползовать в пакетах Емакса фичи из cl в рантайме).

GDB/MI требует информацию о файле/строке или адресах для того, чтобы показать дизассемблированный код для функции. Нельзя как в CLI просто сказать disassemble printf и позырить, что там. Из-за этого буфер с дизассемблером перестаёт показывать код, когда мы вшагиваем на библиотечную функцию, например. А узнать адреса для символа через GDB/MI тоже нельзя, потому что аналог команды info address там не реализован ещё.

И так далее и тому подобное. Я в ответе за написанное, так что буду следить за кодом и причёсывать его. Во всяком случае, Емакс теперь умеет использовать новые фишки GDB. Там ещё пилят поддержку pretty-printing для крестовых шаблонов (чтобы можно было по-человечески смотреть содержимое std::string), хотелось бы, чтобы Емакс умел пользоваться этой информацией.

git