Лабораторная работа 0.3 для студентов курса “Основы программирования” 1 курса кафедры ИУ5 МГТУ им Н.Э. Баумана.
Научиться осуществлять отладку приложения с использованием дебаггера.
В данной лабораторной работе будет рассказано как:
Проверьте, что у вас установлен дебаггер GDB или LLDB. Для современных MacBook (с чипами M1 и новее) на текущий момент доступен только LLDB.
gdb --version
или
lldb --version
Прочитайте вывод в консоли. Если данная команда не найдена, то установите новейший доступный gdb или lldb через предпочитаемый пакетный менеджер (например, apt для Ubuntu, brew для MacOS).
Откройте нужный проект в VS Code. В корне проекта откройте директорию .vscode (создайте ее, если она отсутствует).
Например, для проекта Debugger-Example в одноименной директории иерархия директорий будет выглядеть так:
Debugger-Example/
└── .vscode
Где Debugger-Example - это корень проекта.
В директории .vscode создайте файл launch.json.
Debugger-Example/
└── .vscode
└── launch.json
Скопируйте в этот файл следующее содержимое. Это конфиг для запуска приложения под gdb. Внимательно прочитайте сам конфиг и описание к нему (ниже).
{
"configurations": [
{
"name": "***",
"type": "cppdbg",
"request": "launch",
"program": "***",
"args": [],
"stopAtEntry": false,
"cwd": "${fileDirname}",
"environment": [],
"externalConsole": false,
"MIMode": "gdb",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
},
{
"description": "Set Disassembly Flavor to Intel",
"text": "-gdb-set disassembly-flavor intel",
"ignoreFailures": true
}
]
}
]
}
Соответствующий конфиг для lldb:
{
"configurations": [
{
"name": "***",
"type": "cppdbg",
"request": "launch",
"program": "***",
"args": [],
"stopAtEntry": false,
"cwd": "${fileDirname}",
"environment": [],
"externalConsole": false,
"MIMode": "lldb"
}
]
}
Сохраните конфиг. После этого во вкладке Run and Debug в списке конфигураций (сверху) появится новая конфигурация с тем именем, которое вы указали до этого.
Дебаггер позволяет осуществлять отладку приложения, используя следующие инструменты:
Чтобы дебаггер мог получить эту информацию из приложения, при компиляции следует подать флаг -g. Этот флаг встроит нужную информацию (известную как “дебажные символы”) прямо в исполняемый файл.
Например, следующая команда создаст исполняемый файл app с дебажными символами, используя main.cpp в качестве файла с кодом.
g++ -std=c++20 -g -Wall -Werror -Wextra -Wpedantic main.cpp -o app
Какие-то флаги можно опускать, если в них нет необходимости. В частности, не стоит постоянно компилировать приложение с флагом -g, т.к. это влияет на производительность самого приложения.
В проекте, о котором говорилось выше, создадим файл main.cpp и вставим туда следующий код:
#include <iostream>
struct S {
int a = 0;
double b = 0.;
};
void foo(int a, int* b, S& c) {
std::cout << a << std::endl;
int d = 10;
std::cout << d << std::endl;
if (b) {
*b = 5;
}
c.a = 11;
c.b = 15.25;
}
int main(int, char**) {
int a = 17;
S s{};
foo(15, &a, s);
std::cout << a << std::endl;
std::cout << s.a << ' ' << s.b << std::endl;
return 0;
}
С помощью следующей команды скомпилируем исполняемый файл a.out, добавив туда дебажные символы:
g++ -g main.cpp
Добавим точки останова на 23 и 28 строках. Для этого надо кликнуть левее номера строчки, чтобы появилась красная точка:
Теперь запустим приложение под дебаггером. Для этого надо во вкладке Run and Debug выбрать нужную конфигурацию, которую создали ранее, и нажать на зеленую стрелочку. Или нажать на кнопку f5 (по дефолту), если выбрана нужная конфигурация:
Приложение должно остановить свое исполнение на строке 23. Выглядеть это должно примерно следующим образом:
На скриншоте слева сверху в разделе Variables перечислены еще не созданные (но видимые дебаггеру, т.к. они находятся на стеке) локальные переменные. Слева снизу в разделе Call Stack находится стек вызовов, в котором содержится информация о том, какая функция сейчас исполняется и из каких функций был произведен вызов. Еще ниже находится список точек останова Breakpoints.
Сверху над кодом находится панель управления, с помощью которой можно управлять потоком исполнения приложения (слева направо):
В данном случае остановка произошла на строке 23, причем перед ее исполнением. Нажав на кнопку Step Over, можно перейти к следующей строке. При этом можно увидеть, что переменная a внутри функции main инициализировалась соответствующим значением.
При нажатии на кнопку Step Over еще один раз произойдет переход на строку 26 (пустые строчки пропускаются автоматически).
При нажатии на кнопку Step Into произойдет заход в функцию foo:
Список переменных изменился. Теперь показываются локальные переменные для функции foo: параметры a (со значением 15), b (с адресом переменной a функции main и значением, которое лежит по этому адресу, равным 17), c (переданной по ссылке из функции main), а также локальная переменная d.
Стек вызовов также теперь отображает вверху текущую функцию, в которой сейчас находится поток исполнения приложения, а ниже указана функция main, из которой был произведен вызов.
Дальше можно продолжить пошаговое выполнение приложения через Step Over, либо нажать на Step Out или Continue. В данном конкретном случае оба этих варианта приведут к остановке на строчке 28 в функции main.
В списке переменных можно увидеть как поменялись значения локальных переменных a и s, после того, как они были переданы в функцию foo по адресу и ссылке соответственно.
Теперь можно либо позволить приложению продолжить исполнение, нажав на Continue, либо прервать его через Stop.
Таким образом, с помощью дебаггера можно смотреть, как ведет себя приложение и отслеживать его состояние, используя пошаговое исполнение и точки останова.
Более продвинутое использование дебаггера оставляется на самостоятельное изучение.
Важное примечание:
При запуске приложения под дебаггером создается отдельный терминал, в который осуществляется ввод и вывод данных приложения. Найти его можно в списке терминалов в соответствующей вкладке. Дебажная консоль и терминал, к которому привязаны ввод/вывод приложения - это не одно и то же.