Найти в Дзене
Сделай игру

Как я код на Rust отлаживал

Оглавление

Все мы привыкли, что написанный код отлично поддаётся отладке и неважно на чём писать. Однако Rust заставил меня поднапрячься в этом вопросе, о чём я и спешу с вами поделиться.

Каким был бы GDB, если бы он был космолётом
Каким был бы GDB, если бы он был космолётом

Преамбула

Некоторое время назад, я отказался от полновесных IDE (предпочитая Sublime Text или Neovim) и на то есть две причины: во-первых, меня это делает более расслабленным в плане настройки: всё работает из коробки; во-вторых, это не создаёт повода разобраться в том, как всё устроено и засчёт чего работает, что приводит к возникновению профессиональных пробелов).

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

Не то, чтобы он не существовал, просто я им не владел. И решил исправиться. Выбор пал, конечно, на отладчик GDB. Он, как раз, поддерживает Rust. Думаю, как скачать и установить отладчик вы разберётесь. Но, если по какой-то причине вам этого не то, чтобы сильно хочется делать, можете воспользоваться поставляемым с Rust - rust-gdb (в принципе, как я понял, тот же самый отладчик, просто устанавливается заодно; по крайней мере разницы не заметил).

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

Для начала пишем код

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

Для начала я написал небольшой кусочек кода, который буду исследовать. Всего 3 файла с простейшими функциями. Собираю проект в режиме отладки:

cargo build

Приложение собралось и теперь версия с текстом для отладки находится внутри. Начинается самое непривычное.

Запуск отладчика

У меня проект называется debug_test, так что запускаем в терминале:

gdb target/debug/debug_test
Приглашение отладчика
Приглашение отладчика

Сперва, посмотрим лист приложения (оно ещё не запущено). Для этого пишем команду: list (есть сокращённые версии команд, но я буду использовать полные).

Вывод кода
Вывод кода

Итак, мы посмотрели на код, давайте поставим точку остановки, откуда будем код изучать. Ставить точку остановки можно на номер строки, по названию функции, а ещё точка остановки может быть одноразовой, условной или выполняться по регулярному выражению: ну вы поняли уже, что тут без поллитра справки разобраться будет непросто. Но мы ограничимся, пока что, самым простым. Ставим точку остановки на функцию: break main.

Добавили точку остановки
Добавили точку остановки

И запускаем всё: run.

Отдадчик утверждает, что мы остановились
Отдадчик утверждает, что мы остановились

Теперь есть два пути: просто выполнить функцию или зайти в неё. Если выполним (команда next), то на отладку мы не посмотрим, а вот если зайдём внутрь (команда step) - окажемся внутри функции:

Много всего интересного и непонятного
Много всего интересного и непонятного

Теперь мы находимся где-то внутри функции, но непонятно где. Давайте осмотримся:

Судя по всему, всё хорошо
Судя по всему, всё хорошо

Сейчас много чего можно сделать: посмотреть состояние регистров или стека, однако это не то, что нам сейчас надо. Давайте поглядим, какое значение у переменной х: print/d x.

Шестнадцатеричный и десятичный выводы
Шестнадцатеричный и десятичный выводы

Тут надо объяснить: просмотр переменных может выполняться в различных форматах: двоичном, восьмеричном и прочих (у меня по-умолчанию - десятеричный). Формат задаётся через косую черту: x - значит HEX, то есть шестнадцатеричный, o - значин octo, то есть восьмеричный. Подробности - в руководстве, не станем останавливаться на этом.

Итак, мы увидели значение. Давайте поставим ещё одну точку остановки - на level2::run_it.

Кстати, если я, скажем, заблудился и забыл, где нахожусь, то можно просто использовать команду frame, которая показывает содержимое текущего кадра стека и, заодно, строку, где мы находимся:

Мы на 6 строке, но я немного пошагал уже, хотя об этом, кажется, не писал
Мы на 6 строке, но я немного пошагал уже, хотя об этом, кажется, не писал

Чтобы зайти внутрь функции level2::run_it - достаточно просто использовать команду step - она позволяет зайти внутрь. Единственное, пока не понятно, каким принципом руководствуется отладчик для выбора, в какую функцию зайти. Но с этим можно разобраться позже.

Итак, внутри
Итак, внутри

На рисунке выше виден исходник функции run_it и мы на 5 строчке. Давайте попробуем опять воспользоваться командой step, чтобы зайти внутрь.

Итак, мы внутри
Итак, мы внутри

Видим, что отладчик выбрал для входа первую функцию слева. Если продолжать - то зайдёт и во вторую. Там можно посмотреть значения переменных или содержимое стека: команда backtrace.

Стек выполнения программы, сейчас я в модуле level32, в функции main
Стек выполнения программы, сейчас я в модуле level32, в функции main
Сохранение ранее просмотренных переменных
Сохранение ранее просмотренных переменных

Кстати, обратите внимание: те переменные, что мы смотрели ранее - получают что-то вроде псевдонима (например $4): он позволяет обратиться к переменной постфактум.

Ради интереса, попробуем изменить значение x на другое: тут нам поможет команда set <переменная>=<значение>.

Изменили значение
Изменили значение

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

Но проблема оказалась чисто техническая: объявленная переменная была запрещена к изменению, а добавленный модификатор mut (при условии отсутствия изменений переменной внутри кода) убирался оптимизацией компилятора.

Вывод
Вывод

На выводе сверху видно, что значения изменились: по умолчанию они должны быть, как на снимке ниже.

25 и 148 вместо 2 и 36, которые выдала программа после изменения переменной
25 и 148 вместо 2 и 36, которые выдала программа после изменения переменной

Ну, как-то так, разобрались.

Отладка в браузере

Кстати, отладку можно производить и в браузере (по крайней мере так заявляется). Запускаем:

gdbgui target/debug/debug_test -r --port 5000
Так выглядит всё после запуска
Так выглядит всё после запуска

Немного пояснений: встроенный rust-gdbgui работать у меня не захотел, оттого я установил просто gdbgui по инструкции и запустил с нужными мне параметрами, по другой инструкции.

Выглядит довольно симпатично
Выглядит довольно симпатично

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

Попробовал делать то же самое, только в графической оболочке
Попробовал делать то же самое, только в графической оболочке

Ощущения, что всё сильно удобней - нет. Просто больше информации сразу выведено на экран.

Заключение

В общем и целом, отладка для Rust не просто существует, но и весьма доступная для использования, хотя подучить матчасть придётся. Кстати, у GDB довольно много поддерживаемых языков. Мало ли пригодится.