Краткий гайд по использованию GDB

  • Tutorial


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


GDB — переносимый отладчик проекта GNU, который работает на многих UNIX-подобных системах и умеет производить отладку многих языков программирования, включая Си, C++, Free Pascal, FreeBASIC, Ada, Фортран, Python3, Swift, NASM и Rust.


GDB


Почему именно GDB? Всё легко, он уже установлен на многих UNIX-подобных системах, лёгок в использовании и поддерживает много языков. Работа с ним оказывается очень лёгкой, а также его можно подключить к VSCode и другим редакторам кода (Включая Vim, NeoVim (ясное дело), Emacs, Atom и далее)


Подготовка файлов


Для примера мы возьмём файлы .cpp и будем проходиться по ним вдоль и поперёк.


Для того чтобы нам пройтись по такому файлу нам нужно скомпилировать его с помощью G++ с использованием флага -g (это действительно важно, без этого флага, программа не будет корректно работать в GDB).


g++ -g file_name.cpp -o output_name
gdb output_name

Python-файл вы можете продебажить с помощью этой команды:


gdb -ex r --args python program_name.py <arguments>

Для Java вы просто можете использовать jdb, который уже идёт в комплекте c JDK.


Также, если вам не хочется компилировать всё ручками, вы можете просто использовать сайт OnlineGDB, там просто нужно вставить код и нажать debug, а затем внизу откроется консоль, где вы сможете писать команды.


Использование GDB


Как только мы зашли в GDB нам выводится следующее сообщение:


GNU gdb (Ubuntu 8.1-0ubuntu3) 8.1.0.20180409-git
Copyright (C) 2018 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.

For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from 3_Hero's_Inventory.cpp...done.

Последняя строка говорит о том, нормально ли запустился файл.


Теперь нужно посмотреть, где в нашем файле точка вхождения (строка, откуда наша программа начинает свою работу), в случае cpp это метод main(). Находим номер этой строки c помощью команды list и пишем её порядковый номер с буквой b (также можно просто указать имя функции b main тоже работает):


(gdb) list
1   #include <iostream>
2   #include <string>
3   
4   using namespace std;
5   
6   int main(int argc, char *argv[])
7   {
8       // Hero's Inventory - скрипт, где мы имитируем инвентарь игрока
9   
10      const int MAX_ITEMS = 10; // Задаём константу, максимум по инвентарю игрока

(gdb) b 6
Breakpoint 1 at 0xcb5: file ./3_Hero's_Inventory.cpp, line 6.

Далее запускаем программу с помощью комманды r:


(gdb) r
Starting program: /home/username77177/gitprojects/learning/cpp/build_folder/3_Hero's_Inventory.cpp 

Breakpoint 1, main (argc=1, argv=0x7fffffffdd18) at ./3_Hero's_Inventory.cpp:7
7   {

Также вы можете включить TUI, с помощью комбинации клавиш <Ctrl-x a>


Для того, чтобы посмотреть на какой мы сейчас строке, нужно написать f:


(gdb) f
#0  main (argc=1, argv=0x7fffffffdd18) at ./3_Hero's_Inventory.cpp:14
14      items[itemnum++] = "Sword";

Для того, чтобы сделать шаг, нужно нажать n (от слова next):


(gdb) n
10      const int MAX_ITEMS = 10; // Задаём константу, максимум по инвентарю игрока

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


(gdb) s
11      string items[MAX_ITEMS]; // Создаём массив из строк c 10 элементами

(В примере нет функции, однако шаг step-in все равно будет работать и с обычными инициализациями, условиями и циклами)


Чтобы узнать какие переменные (локальные) сейчас инициализированны в программе нужно написать комманду info locals:


(gdb) info locals
MAX_ITEMS = 10
items = {"", "", "", "", "", "", "", "", "", ""}
itemnum = 0
game = 247

Чтобы вывести только одну переменную, нужно написать print имя_переменной:


(gdb) print MAX_ITEMS 
$1 = 10

Мы можем также изменить переменную с помощью set:


(gdb) set x = 77177
(gdb) print x
$1 = 77177

Мы можем также следить за переменными с помощью watch:


watch x

Также, если нужно можно посмотреть что в данный момент находится в регистрах (info registers):


(gdb) info registers 
rax            0x7fffffffdc00   140737488346112
rbx            0xffffffffffffffff   -1
rcx            0xa0 160
rdx            0x7fffffffdd28   140737488346408
rsi            0x7fffffffdd18   140737488346392
rdi            0x7fffffffdbf0   140737488346096
rbp            0x7fffffffdc30   0x7fffffffdc30
rsp            0x7fffffffdab0   0x7fffffffdab0
r8             0x7ffff782fd80   140737345944960
r9             0x0  0
r10            0x6  6
r11            0x7ffff7b77020   140737349382176
r12            0x7fffffffdc10   140737488346128
r13            0x7fffffffdd10   140737488346384
r14            0x0  0
r15            0x0  0
rip            0x555555554cfe   0x555555554cfe <main(int, char**)+100>
eflags         0x286    [ PF SF IF ]
cs             0x33 51
ss             0x2b 43
ds             0x0  0
es             0x0  0
fs             0x0  0
gs             0x0  0

Чтобы посмотреть какие в данный момент есть breakpoints (точки останова) нужно написать info breakpoints:


(gdb) info breakpoints 
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x0000555555554cb5 in main(int, char**) 
                                                   at ./3_Hero's_Inventory.cpp:6
    breakpoint already hit 1 time
2       breakpoint     keep y   0x0000555555554cfe in main(int, char**) 
                                                   at ./3_Hero's_Inventory.cpp:14

Чтобы удалить точку останова del breakpoint_num:


(gdb) info breakpoints 
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x0000555555554cb5 in main(int, char**) 
                                                   at ./3_Hero's_Inventory.cpp:6
    breakpoint already hit 1 time
2       breakpoint     keep y   0x0000555555554cfe in main(int, char**) 
                                                   at ./3_Hero's_Inventory.cpp:14

(gdb) del 1

(gdb) info breakpoints 
Num     Type           Disp Enb Address            What
2       breakpoint     keep y   0x0000555555554cfe in main(int, char**) 
                                                   at ./3_Hero's_Inventory.cpp:14

Чтобы прыгнуть к следующей точке останова нужно нажать c:


(gdb) r
Starting program: /home/username77177/gitprojects/learning/cpp/build_folder/3_Hero's_Inventory.cpp 

Breakpoint 3, main (argc=1, argv=0x7fffffffdd18) at ./3_Hero's_Inventory.cpp:7
7   {
(gdb) c
Continuing.

Breakpoint 2, main (argc=1, argv=0x7fffffffdd18) at ./3_Hero's_Inventory.cpp:14
14      items[itemnum++] = "Sword";

Мы можем вызывать функции из программы (локальные) с помощью call:


(gdb) call MyFunction()

Чтобы продолжить выполнение функции и остановить программу когда она (функция) завершится нужно написать finish или fin:


(gdb) fin

Стоит уточнить, что нельзя использовать finish в главном методе.


Чтобы завершить выполнение программы, нужно написать kill:


(gdb) kill
Kill the program being debugged? (y or n) y

Также можно написать help в любой момент и получить краткую справку, как пользоваться отладчиком


(gdb) help
List of classes of commands:

aliases -- Aliases of other commands
breakpoints -- Making program stop at certain points
data -- Examining data
files -- Specifying and examining files
internals -- Maintenance commands
obscure -- Obscure features
running -- Running the program
stack -- Examining the stack
status -- Status inquiries
support -- Support facilities
tracepoints -- Tracing of program execution without stopping the program
user-defined -- User-defined commands

Type "help" followed by a class name for a list of commands in that class.
Type "help all" for the list of all commands.
Type "help" followed by command name for full documentation.
Type "apropos word" to search for commands related to "word".
Command name abbreviations are allowed if unambiguous.

Комментарии 24

    +4
    Ставить breakpoint можно не только на номера строк, но и на символы функций. Например `b main`. Команда start поставит временную бряку на main и запустит программу.

    И ещё finish не «быстро завершает выполнение функции», а продолжает её выполнение и останавливает программу когда она завершится.
      0

      Дополнил, спасибо

      –1
        –7
        в норме от всего отладчика нужна ровно одна команда bt, посмотреть где оно упало.
        все остальное покрывает кучка printf раскиданая в нужных местах в коде.
          –2

          после ida olly x64 windbg с трудом себе представляю почему многие фанатеют от gdb, видимо чем больше боли тем лучше :D

            0

            Как раз на днях дебажил хаскель-код, в котором писал инлайн-ассемблер, через gdb (int $3 и вот это все). Не думаю, что содержимое регистров можно было бы посмотреть как-то иначе.

              0

              Так printf надо ещё поставить в нужное место и вывести в нём ровно то, что надо. Отладчиком можно поставить брейк примерно в том месте, а дальше пройтись по шагам. Ну и сразу все доступные переменные видны.

                0
                В идеале в коде уже стоят в нужных местах LOG(TRACE...). Ну и кому-то удобнее идти от ошибки в прошлое, а не с подозрительного места вперёд.
                  0
                  В идеале в коде нет ошибок и отладчики не требуются как класс.

                  Но люди, увы, не идеальны, они делают ошибки и они, зачастую, бывают не такими и не там, как изначально это автором программы планировалось…
                  0
                  С отладчиком к сожалению не всегда все получается. В основном не получается если программа динамическая и критичная ко времени исполнения. Остановился в точке останова — из-за этого клиент чего-то не послал серверу и сервер вовремя не получил какой-то пакет и разорвал соединение. И все дальше вести ручную отладку клиента уже нет смысла.
                  Но в целом, конечно отладчик — необходимый инструмент.
                  0
                  А если зависло: `thread apply all bt`
                  И `disas /m`, чтобы понять оптимизатора.
                  +2

                  А какие удобные графические фронтэнды к gdb используют хабравчане?
                  Вот IDA Pro, например умеет подключаться к gdb-серверу и предоставлять интерфейс со всеми своими фичами. Очень удобно, но очень дорого. Хотелось бы иметь аналог ollydbg, но что ни пробовал, даже близко по удобству не стояло.

                    0
                    я к ddd в свое время привык, сейчас отладчиком пользуюсь, так как написал в заминусованом каменте :-)
                      0
                      А какие удобные графические фронтэнды к gdb используют хабравчане?

                      Есть такая прикольная штука, правда лично не пользовался:
                      https://www.youtube.com/watch?v=6LNR8u19x6Y&feature=emb_title

                        0
                        А как у него в плане отображения исходников с разными кодировками или хотя бы русский текст с Utf-8?
                        0
                        GDB TUI работает прямо из коробки и превращает GDB в полезный инструмент даже при наличии всевозможных IDE. Полезен при пошаговой отладке простых консольных программ.
                        CppCon 2015: Greg Law " Give me 15 minutes & I'll change your view of GDB"
                        0
                        А как то питон дебажить так можно? Пробую
                        gdb -ex r --args python 1.py

                        Получаю:
                        [Thread debugging using libthread_db enabled]
                        Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
                        [New Thread 0x7fffacef0700 (LWP 19550)]


                        и код сразу исполняется. Команда list дает только:
                        (gdb) list
                        1	<built-in>: No such file or directory.
                        0
                        В статье есть неточности:

                        Для того чтобы нам пройтись по такому файлу нам нужно скомпилировать его с помощью G++ с использованием флага -g (это действительно важно, без этого флага, программа не будет корректно работать в GDB).

                        Программа будет работать корректно, просто в самом файле будет гораздо меньше отладочной информации. Я бы еще упомяннул про оптимизацию (-O0 или -Od) и флаг -fno-omit-frame-pointer.

                        > Python-файл вы можете продебажить с помощью этой команды:

                        Этим вы запускаете отладку интерпретатора Python, а не самого скрипта. Не пугайте путайте читателей. Для отладки скриптов есть pdb: docs.python.org/3/library/pdb.html.
                          0
                          Я бы еще упомяннул про оптимизацию (-O0 или -Od)
                          Я так понмаю вместо -Od следует читать -Og?
                            0

                            Фраза "не будет работать корректно в GDB", говорит о том что не сама программа будет работать некорректно, а о том, что в самом отладчике не будет никакого смысла запускать код. Я приведу пример, дабы не быть голословным:


                            g++ 4_hangman.cpp

                            (gdb) list
                            No symbol table is loaded.  Use the "file" command.
                            
                            (gdb) b main
                            Breakpoint 1 at 0x11de
                            
                            (gdb) r
                            Starting program: /home/username77177/gitprojects/learning/cpp/a.out
                            
                            (gdb) n
                            Single stepping until exit from function main,

                            Нет никакого смысла запускать так код, так как отладчик ничего не может сделать с программой. Вы можете ставить точки останова и только (и то только по названиям функции). Поэтому я и написал что флаг -g действительно важен.


                            А за ваши правки спасибо. Да, pdb будет намного удобнее, однако цель статьи показать, что gdb может многое, и запустить тот же код на Python тоже можно (худо-бедно, но можно)

                              +2
                              Что значит ничего не может? Вполне можно ставить брейкпоинты (на адреса), смотреть диссасемблер, память. С программой можно много чего делать, даже если нет символов.

                            Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                            Самое читаемое