Команды GDB, о которых вы возможно не знали
Отладка кода — это как охота. Охота на баги.
— Amit Kalantri
Что такое GDB
GNU Debugger — переносимый отладчик проекта GNU, который работает на многих UNIX-подобных системах и умеет производить отладку многих языков программирования, включая Си, C++, Free Pascal, FreeBASIC, Ada, Фортран и Rust. GDB — свободное программное обеспечение, распространяемое по лицензии GPL.
Источник: GNU Debugger — Википедия
Проще говоря GDB — отладчик, который работает прямо из консоли.
В данной статье я опишу комманды, о которых раньше не знал, однако, они оказались очень полезными при отладке в GDB.
Рассматривать работу отладчика будем на одной из моих курсовых работ, её тема — шифр Цезаря, так что не нужно удивляться названиям переменных или классов. Самого кода вы тут не увидите, ибо это будет лишним для понимания сути действия, однако стоит привести несколько замечаний для полного понимания:
Интерфейс в консоли (TUI)
Я всегда включаю TUI, как только зайду в GDB, он позволяет посмотреть где именно находится выполняемая строка, что идёт перед ней, что дальше. Включается TUI в GDB командой tui enable
С помощью колесика мыши (или стрелок) вы можете перемещаться по псевдо-интерфейсу вверху. С помощью комбинации клавиш Ctrl + p (previous), Ctrl + n (next), вы можете перемещаться между введёнными командами в терминале (окно внизу).
Тезис: Для включения псевдо-интерфейса используют команду tui enable
, для выключения — tui disable
.
Точки останова
Точки останова можно ставить с помощью команды breakpoint
(сокращение — b).
Можно ставить точки останова исходя из следующих принципов:
- Точкой останова может являться имя функции (метода) или переменной
- Точкой останова может являться номер строки
- Точкой останова может являться адрес памяти, в котором располагается инструкция
- Точкой останова может являться условие
Если с тремя первыми всё понятно, то четвёртый принцип — достаточно интересный.
b Method if variable == value
— примерно такой синтаксис используется для задания точки останова исходя из выполнения условия.
Например:
(gdb) b Crypt if Caesar::shift == 1
Note: breakpoint 2 also set at pc 0x4026e3.
Breakpoint 4 at 0x4026e3: file Caesar.h, line 55.
Тут сказано поставить точку останова на методе Crypt()
, если свойство класса Caesar
под названием shift
будет равно 1.
Дополнительно: точки останова можно удалять с помощью команды delete
, или её сокращения d
.
Тезис: Точки останова можно ставить четыремя способами: с помощью имени объекта, с помощью номера строки, с помощью адреса, с помощью условия.
Шаг с входом в Scope, шаг без входа
Различие между командами next
и step
(сокращения: n и s) состоит в том, что next выполняет код не входя в Scope (не входя внутрь функции или метода), тогда как step просто выполняет шаг и аналогичен команде Step In в обычных дебагерах.
Дополнительно: для выполнения программы до точки останова используют continue
, для выхода из функции в которую вы вошли используют finish
или fin
.
Тезис: Для вхождения в функцию используется step
, для обычного выполнения — next
.
Просмотр переменных разными способами
Одной из самых главных частей в процессе отладки является просмотр переменных в разных частях программы. Можно делать это с помощью команды print
или p
, однако есть и более удобные методы для просмотра переменных.
Команда display
Команда display
будет показывать значение переменных каждый раз, когда терминал будет доступен для ввода
(gdb) display Caesar::shift
1: Caesar::shift =
(gdb) n
Breakpoint 2, Caesar::Crypt (this=0x7fffffffdc30, text="Crypt this text! Btw, xyz") at Caesar.h:55
1: Caesar::shift = 1
(gdb) n
1: Caesar::shift = 1
(gdb)
Таким образом вы можете просматривать что находится внутри одной переменной или даже целого массива:
(gdb) display *stringArrayForOutput@lengthOfArray
1: *stringArrayForOutput@lengthOfArray = {"Etarv vjku vgzv! Dvy, zab", "Fubsw wklv whaw! Ewz, abc", "Gvctx xlmw xibx! Fxa, bcd"}
(gdb)
Думаю не стоит говорить, что массив может быть динамический и по мере выполнения программы lengthOfArray
(длина массива) может изменяться. GDB сам разберётся с этим и всё время будет выводить актуальную информацию.
Команда explore
Иногда бывает такое, что информации до того много, что путаешься какой объект за что отвечает, для этого создали explore
.
explore
помогает найти что за объект мелькает перед вашими глазами и рассказывает о нём всё, что может
(gdb) explore Caesar::shift
'Caesar::shift' is a scalar value of type 'int'.
Caesar::shift = 6
(gdb) explore Caesar
'Caesar' is a struct/class with the following fields:
letters =
shift =
Тезис: Кроме команды print
можно использовать команды explore
и display
.
Запись логов работы программы
Иногда нужно «перемотать вспять» момент, где случилась ошибка, для этого придумана команда record
, которая может полностью записывать логи программы в файл или просто в память, дабы перемотать некоторые моменты вспять или посмотреть что именно вызвало ошибку
(gdb) target record-full
(gdb) p Caesar::shift
$1 = 6
(gdb) n
(gdb) p Caesar::shift
$2 = 7
(gdb) reverse-next
No more reverse-execution history.
(gdb) p Caesar::shift
$3 = 6
Для того, чтобы остановить запись и удалить все логи — нужно ввести record stop
, для того, чтобы сохранить все логи в файл нужно ввести record save <имя файла>
.
Тезис: GDB имеет возможность создать лог работы программы, с помощью которого, мы можем перемещаться по программе вспять.
Просмотр информации
Просмотреть сразу все локальные переменные можно с помощью info local
:
(gdb) info local
mainObj = {letters = "abcdefghijklmnopqrstuvwxyz", shift = 1}
textForC = "Crypt this text! Btw, xyz"
cryptedText = "Dszqu uijt ufyu! Cux, yza"
outputVariants = 0x402280 <_start>
Просмотреть все переменные, которые инициализированы можно с помощью info variables
:
(gdb) info variables
All defined variables:
File /usr/include/c++/10/bits/basic_string.h:
101: const std::__cxx11::basic_string, std::allocator >::size_type std::__cxx11::basic_string, std::allocator >::npos;
File /usr/include/c++/10/bits/stl_pair.h:
83: static const std::piecewise_construct_t std::piecewise_construct;
File /usr/include/c++/10/iostream:
74: static std::ios_base::Init std::__ioinit;
Non-debugging symbols:
0x00000000004002e8 __abi_tag
0x0000000000403000 _IO_stdin_used
0x00000000004031b8 typeinfo for std::__cxx11::basic_string, std::allocator >
0x00000000004031e0 typeinfo name for std::__cxx11::basic_string, std::allocator >
--Type for more, q to quit, c to continue without paging--
Чтобы посмотреть все точки останова можно ввести info b
:
(gdb) info b
Num Type Disp Enb Address What
1 breakpoint keep y 0x0000000000402633 in Caesar::ChangeShift() at Caesar.h:31
breakpoint already hit 1 time
2 breakpoint keep y 0x00000000004026e3 in Caesar::Crypt(std::__cxx11::basic_string, std::allocator >) at Caesar.h:55
breakpoint already hit 1 time
Вы также можете посмотреть тип переменной с помощью команды whatis
:
(gdb) whatis Caesar::shift
type = int
(gdb) whatis Caesar
type = Caesar
Чтобы посмотреть адрес функции, вы можете использовать info address
:
(gdb) info address Caesar::Crypt
Symbol "Caesar::Crypt(std::__cxx11::basic_string, std::allocator >)" is a function at address 0x4026be.
Есть ещё куча комманд, которые я не привёл в данной статье, однако, на мой взгляд они являются столь полезными, как те, что я перечислил выше. Вы можете почитать больше о разработке на Linux в моём телеграм-канале, если вам это нужно. Я периодически пишу о фулл-стак разработке, а также о Unix-подобных системах.
В данной статье не указаны некоторые комманды, которые легки для понимания, например: run, stop, quit, и т.д. Не указал я их специально, так как статья пытается нести информацию, о которой пользователи GDB возможно могли не знать. Если у вас есть предложения чем можно дополнить статью, с радостью почитаю комментарии.