Linux gdb команды. Отладка с помощью GDB. Определение точек останова

Nokia 02.07.2020
Nokia

Поговорим об отладчиках для Microsoft Windows. Их существует довольно много, вспомнить хотя бы всеми любимый OllyDbg, некогда популярный, но в настоящее время практически умерший SoftIce, а также Sycer, Immunity Debugger, x64dbg и бесчисленное количество отладчиков, встроенных в IDE. По моим наблюдениям, WinDbg нравится далеко не всем. Думаю, в основном это связано с командным интерфейсом отладчика. Любителям Linux и FreeBSD, бесспорно, он пришелся бы по душе. Но закоренелым пользователям Windows он кажется странным и неудобным. А тем временем, по функционалу WinDbg ничем не уступает другим отладчикам. Как минимум, он точно нечем не хуже классического GDB или там LLDB . В чем мы сегодня с вами и убедимся.

В мире Windows, все, как обычно, немного через жопу. Официальный инсталятор WinDbg можно скачать на сайте MS. Инсталятор этот помимо WinDbg также поставит вам свежую версию.NET Framework и перезагрузит систему без спроса. После установки не факт, что отладчик вообще заработает, особенно под старыми версиями Windows. Поэтому лучше скачать неофициальную сборку WinDbg или . Настоятельно советую воспользоваться именно одной из этих версий — это самый простой и быстрый способ установить WinDbg.

Есть две версии WinDbg, x86 и x64. Чтобы не возникало никаких проблем, отлаживайте x86 приложения с помощью x86 дебагера, а x64 приложения — с помощью x64 дебагера. После первого запуска выглядеть WinDbg будет довольно убого. Но не беспокойтесь по этому поводу. Поработав буквально несколько минут с WinDbg, вы подстроите его под себя и выглядеть он будет вполне няшненько. Например, как-то так (кликабельно, 51 Кб, 1156 x 785):

На этом скриншоте изображена отладка программы из заметки Получаем список запущенных процессов на Windows API . Как видите, WinDbg подцепил исходники программы. Справа отображаются значения локальных переменных. Внизу находится окно для ввода команд, где при помощи команды kn был выведен стэк вызовов. В верхней части отладчика находятся кнопки, при помощи которых можно выполнять простые действия вроде «шаг вперед», а также открыть дополнительные окна. При помощи этих окон можно посмотреть содержимое оперативной памяти, значения регистров, дизассемблерный листинг программы и много других интересных вещей.

Вообще, очень многое в WinDbg можно делать через GUI. Например, в окне с исходным кодом можно поставить курсор на нужном месте и нажатием на икноку с ладонью создать там точку останова. Или выполнить run to cursor. Также выполнение команды можно в любой момент остановить при помощи кнопки Break в панели вверху. На этом всем подробно останавливаться не будем. Но учтите, что такие возможности есть и они заслуживают изучения!

Прежде, чем приступить к отладке при помощи WinDbg, нужно сделать несколько несложных телодвижений. Откройте File → Symbol File Path и введите:

SRV*C:\symbols*http://msdl.microsoft.com/download/symbols

Затем нажмите Browse и укажите путь до файлов c отладочной информацией (.pdb) вашего проекта. Аналогичным образом в File → Source File Path укажите путь до каталога с исходниками. Если сомневаетесь, укажите путь, по которому находится файл проекта Visual Studio, не ошибетесь. Затем скажите File → Save Workspace, чтобы все эти пути не приходилось указывать заново при каждом запуске WinDbg.

Теперь, когда все настроено, есть несколько способов начать отладку. Можно запустить новый процесс под отладчиком, можно подключиться к уже существующему, можно открыть крэшдамп. Все это делается через меню File. Особого внимания заслуживает возможность удаленной отладки. Например, если при помощи WinDbg вы отлаживаете драйверы, то особого выбора, кроме как использовать удаленную отладку, у вас как бы и нет. Что не удивительно, ведь малейшая ошибка в коде драйвера может привести к BSOD.

Если вы уже отлаживаете процесс, но хотели бы начать делать это удаленно, говорим:

Server tcp:port=3003

Нужно проверить, что порт открыт, что особенно актуально на Windows Server. На клиенте делаем File → Connect to a Remote Session, вводим:

tcp:Port=3003,Server=10.110.0.10

Кроме того, можно запустить сервер, позволяющий отлаживать любой процесс в системе:

dbgsrv.exe -t tcp:port=3003

На клиенте подключаемся через File → Connect to Remote Stub. После этого через интерфейс можно как обычно выбрать процесс из списка или запустить новый. Только работать процессы будут на удаленной машине.

Теперь, наконец-то, рассмотрим основные команды WinDbg.

Важно! Иногда команды могут выполняется очень долго, например, если вы решили загрузить сразу все отладочные символы. Если устанете ждать, просто нажмите Ctr+C в поле ввода команды, и WinDbg тут же перестанет делать то, что он сейчас делает.

Help
.hh команда

Очистить вывод в окне Command:

Добавить путь, по которому WinDbg будет искать отладочные символы (та же команда без знака плюс затрет все ранее прописанные пути):

Sympath+ c:\pdbs

Перезагрузить символы:

Показать список модулей:

Ищем символы:

x *!Ololo::My::Namespace::*

Загрузить символы для модуля:

ld имя_модуля

Если WinDbg почему-то не находит.pdb файлы и в стектрейсах вместо имен.c/.cpp файлов с номерами строк вы видите что-то вроде module+0x19bc, попробуйте выполнить следующую последовательность команд — так вы получите больше информации о возможных причинах проблемы:

Sym noisy
.reload MyModule.dll

Указать путь к каталогу исходников:

Поставить точку останова:

bp kernel32!CreateProcessA
bp `mysorucefile.cpp:123`
bp `MyModule!mysorucefile.cpp:123`
bp @@(Full::Class:Name::method)

Поставить точку остановка, которая сработает только один раз:

Поставить точку останова, которая сработает на 5-ый раз, после 4-х проходов:

Можно автоматически выполнять команды каждый раз, когда срабатывает точка останова:

bp kernel32!LoadLibraryA ".echo \"Variables:\n\"; dv"

У аппаратных точек останова синтаксис такой:

ba

Где mode это e, r или w — выполнение, чтение, запись. При mode = e параметр size может быть только 1. Например:

ba e 1 kernel32!LoadLibraryA

Список точек останова:

Деактивировать точку останова:

Активировать точку останова:

Полное удаление точек останова:

bc номер
bc *

Показать команды, которые нужно ввести для восстановления текущих точек останова:

Писать лог в файл:

Logopen c:\1.txt

Перестать писать лог в файл:

Выполнить команды из файла:

Для сохранения точек останова в файл можно выполнить (обязательно в одну строку!) следующие команды:

Logopen c:\1.txt; .bpcmds; .logclose

Показать дизассемблерный листинг:

Показать значение регистров:

Показать стек вызовов:

То же самое с номерами фреймов:

Перемещение к фрейму:

Frame номер

Показать локальные переменные:

Показать структуру:

dt имя_переменной

Показать структуру рекурсивно:

dt -r имя_переменной

Дамп памяти по адресу:

Дамп памяти в виде word/dword/qword:

dw адрес
dd адрес
dq адрес

Дамп в виде битов:

Дамп ascii строки:

Дамп unicode строки:

Редактирование памяти.

Перевод статьи Аллана О’Доннелла Learning C with GDB .

Исходя из особенностей таких высокоуровневых языков, как Ruby, Scheme или Haskell, изучение C может быть сложной задачей. В придачу к преодолению таких низкоуровневых особенностей C, как ручное управление памятью и указатели, вы еще должны обходиться без REPL . Как только Вы привыкнете к исследовательскому программированию в REPL, иметь дело с циклом написал-скомпилировал-запустил будет для Вас небольшим разочарованием.

Недавно мне пришло в голову, что я мог бы использовать GDB как псевдо-REPL для C. Я поэкспериментировал, используя GDB как инструмент для изучения языка, а не просто для отладки, и оказалось, что это очень весело.

Цель этого поста – показать Вам, что GDB является отличным инструментом для изучения С. Я познакомлю Вас с несколькими моими самыми любимыми командами из GDB, и продемонстрирую каким образом Вы можете использовать GDB, чтобы понять одну из сложных частей языка С: разницу между массивами и указателями.

Введение в GDB

Начнем с создания следующей небольшой программы на С – minimal.c :

Int main() { int i = 1337; return 0; }
Обратите внимание, что программа не делает абсолютно ничего, и даже не имеет ни одной команды printf . Теперь окунемся в новый мир изучения С используя GBD.

Скомпилируем эту программу с флагом -g для генерирования отладочной информации, с которой будет работать GDB, и подкинем ему эту самую информацию:

$ gcc -g minimal.c -o minimal $ gdb minimal
Теперь Вы должны молниеносно оказаться в командной строке GDB. Я обещал вам REPL, так получите:

(gdb) print 1 + 2 $1 = 3
Удивительно! print – это встроенная команда GDB, которая вычисляет результат С-ного выражения. Если Вы не знаете, что именно делает какая-то команда GDB, просто воспользуйтесь помощью – наберите help name-of-the-command в командной строке GDB.

Вот Вам более интересный пример:

(gbd) print (int) 2147483648 $2 = -2147483648
Я упущу разъяснение того, почему 2147483648 == -2147483648 . Главная суть здесь в том, что даже арифметика может быть коварная в С, а GDB отлично понимает арифметику С.

Теперь давайте поставим точку останова в функции main и запустим программу:

(gdb) break main (gdb) run
Программа остановилась на третьей строчке, как раз там, где инициализируется переменная i . Интересно то, что хотя переменная пока и не проинициализирована, но мы уже сейчас можем посмотреть ее значение, используя команду print :

(gdb) print i $3 = 32767
В С значение локальной неинициализированной переменной не определено, поэтому полученный Вами результат может отличаться.

Мы можем выполнить текущую строку кода, воспользовавшись командой next :

(gdb) next (gdb) print i $4 = 1337

Исследуем память используя команду X

Переменные в С – это непрерывные блоки памяти. При этом блок каждой переменной характеризуется двумя числами:

1. Числовой адрес первого байта в блоке.
2. Размер блока в байтах. Этот размер определяется типом переменной.

Одна из отличительных особенностей языка С в том, что у Вас есть прямой доступ к блоку памяти переменной. Оператор & дает нам адрес переменной в памяти, а sizeof вычисляет размер, занимаемый переменной памяти.

Вы можете поиграть с обеими возможностями в GDB:

(gdb) print &i $5 = (int *) 0x7fff5fbff584 (gdb) print sizeof(i) $6 = 4
Говоря нормальным языком, это значит, что переменная i размещается по адресу 0x7fff5fbff5b4 и занимает в памяти 4 байта.

Я уже упоминал выше, что размер переменной в памяти зависит от ее типа, да и вообще говоря, оператор sizeof может оперировать и самими типами данных:

(gdb) print sizeof(int) $7 = 4 (gdb) print sizeof(double) $8 = 8
Это означает, что по меньшей мере на моей машине, переменные типа int занимают четыре байта, а типа double – восемь байт.

В GDB есть мощный инструмент для непосредственного исследования памяти – команда x . Эта команда проверяет память, начиная с определенного адреса. Также она имеет ряд команд форматирования, которые обеспечиваю точный контроль над количеством байт, которые Вы захотите проверить, и над тем, в каком виде Вы захотите вывести их на экран. В случае каких либо трудностей, наберите help x в командной строке GDB.

Как Вы уже знаете, оператор & вычисляет адрес переменной, а это значит, что можно передать команде x значение &i и тем самым получить возможность взглянуть на отдельные байты, скрывающиеся за переменной i :

(gdb) x/4xb &i 0x7fff5fbff584: 0x39 0x05 0x00 0x00
Флаги форматирования указывают на то, что я хочу получить четыре (4 ) значения, выведенные в шестнадцатеричном (hex ) виде по одному байту (b yte). Я указал проверку только четырех байт, потому что именно столько занимает в памяти переменная i . Вывод показывает побайтовое представление переменной в памяти.

Но с побайтовым выводом связана одна тонкость, которую нужно постоянно держать в голове – на машинах Intel байты хранятся в порядке “от младшего к старшему ” (справа налево), в отличии от более привычной для человека записи, где младший байт должен был бы находиться в конце (слева направо).

Один из способов прояснить этот вопрос – это присвоить переменной i более интересное значение и опять проверить этот участок памяти:

(gdb) set var i = 0x12345678 (gdb) x/4xb &i 0x7fff5fbff584: 0x78 0x56 0x34 0x12

Исследуем память с командой ptype

Команда ptype возможно одна из моих самых любимых. Она показывает тип С-го выражения:

(gdb) ptype i type = int (gdb) ptype &i type = int * (gdb) ptype main type = int (void)
Типы в С могут становиться сложными , но ptype позволяет исследовать их в интерактивном режиме.

Указатели и массивы

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

Итак, нам нужен код программы с массивом array.c :

Int main() { int a = {1, 2, 3}; return 0; }
Скомпилируйте ее с флагом -g , запустите в GDB, и с помощь next перейдите в строку инициализации:

$ gcc -g arrays.c -o arrays $ gdb arrays (gdb) break main (gdb) run (gdb) next
На этом этапе Вы сможете вывести содержимое переменной и выяснить ее тип:

(gdb) print a $1 = {1, 2, 3} (gdb) ptype a type = int
Теперь, когда наша программа правильно настроена в GDB, первое, что стоит сделать – это использовать команду x для того, чтобы увидеть, как выглядит переменная a “под капотом”:

(gdb) x/12xb &a 0x7fff5fbff56c: 0x01 0x00 0x00 0x00 0x02 0x00 0x00 0x00 0x7fff5fbff574: 0x03 0x00 0x00 0x00
Это означает, что участок памяти для массива a начинается по адресу 0x7fff5fbff56c . Первые четыре байта содержат a , следующие четыре – a , и последние четыре хранят a . Действительно, Вы можете проверить и убедится, что sizeof знает, что a занимает в памяти ровно двенадцать байт:

(gdb) print sizeof(a) $2 = 12
До этого момента массивы выглядят такими, какими и должны быть. У них есть соответствующий массивам типы и они хранят все значения в смежных участках памяти. Однако, в определенных ситуациях, массивы ведут себя очень схоже с указателями! К примеру, мы можем применять арифметические операции к a :

(gdb) print a + 1 $3 = (int *) 0x7fff5fbff570
Нормальными словами, это означает, что a + 1 – это указатель на int , который имеет адрес 0x7fff5fbff570 . К этому моменту Вы должны уже рефлекторно передавать указатели в команду x , итак посмотрим, что же получилось:

(gdb) x/4xb a + 1 0x7fff5fbff570: 0x02 0x00 0x00 0x00

Обратите внимание, что адрес 0x7fff5fbff570 ровно на четыре единицы больше, чем 0x7fff5fbff56c , то есть адрес первого байта массива a . Учитывая, что тип int занимает в памяти четыре байта, можно сделать вывод, что a + 1 указывает на a .

На самом деле, индексация массивов в С является синтаксическим сахаром для арифметики указателей: a[i] эквивалентно *(a + i) . Вы можете проверить это в GDB:

(gdb) print a $4 = 1 (gdb) print *(a + 0) $5 = 1 (gdb) print a $6 = 2 (gdb) print *(a + 1) $7 = 2 (gdb) print a $8 = 3 (gdb) print *(a + 2) $9 = 3
Итак, мы увидели, что в некоторых ситуациях a ведет себя как массив, а в некоторых – как указатель на свой первый элемент. Что же происходит?

Ответ состоит в следующем, когда имя массива используется в выражении в С, то оно “распадается (decay)” на указатель на первый элемент. Есть только два исключения из этого правила: когда имя массива передается в sizeof и когда имя массива используется с оператором взятия адреса & .

Тот факт, что имя a не распадается на указатель на первый элемент при использовании оператора & , порождает интересный вопрос: в чем же разница между указателем, на который распадается a и &a ?

Численно они оба представляют один и тот же адрес:

(gdb) x/4xb a 0x7fff5fbff56c: 0x01 0x00 0x00 0x00 (gdb) x/4xb &a 0x7fff5fbff56c: 0x01 0x00 0x00 0x00
Тем не менее, типы их различны. Как мы уже видели, имя массива распадается на указатель на его первый элемент и значит должно иметь тип int * . Что же касается типа &a , то мы можем спросить об этом GDB:

(gdb) ptype &a type = int (*)
Говоря проще, &a – это указатель на массив из трех целых чисел. Это имеет смысл: a не распадается при передаче оператору & и a имеет тип int .

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

(gdb) print a + 1 $10 = (int *) 0x7fff5fbff570 (gdb) print &a + 1 $11 = (int (*)) 0x7fff5fbff578
Обратите внимание, что добавление 1 к a увеличивает адрес на четыре единицы, в то время, как прибавление 1 к &a добавляет к адресу двенадцать.

Указатель, на который на самом деле распадается a имеет вид &a :

(gdb) print &a $11 = (int *) 0x7fff5fbff56c

Заключение

Надеюсь, я убедил Вас, что GDB – это изящная исследовательская среда для изучения С. Она позволяет выводить значение выражений с помощью команды print , побайтово исследовать память командой x и работать с типами с помощью команды ptype .

1. Используйте GDB для работы над The Ksplice Pointer Challenge .
2. Разберитесь, как структуры хранятся в памяти. Как они соотносятся с массивами?
3. Используйте дизассемблерные команды GDB, чтобы лучше разобраться с программированием на ассемблере. Особенно весело исследовать, как работает стек вызова функции.
4. Зацените “TUI” режим GDB, который обеспечивает графическую ncurses надстройку над привычным GDB. На OS X, Вам вероятно придется собрать GDB из исходников.

От переводчика: Традиционно для указания ошибок воспользуйтесь ЛС. Буду рад конструктивной критике.

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

GDB встроен во многие UNIX -подобные системы и умеет производить отладку нескольких языков программирования. Си - в их числе.

Чтобы вызвать GDB введите в терминале команду

Gdb [ имя_программы_которую_вы_хотите_отладить]

Чтобы выйти из GDB : введите команду

Quit или С –d

Другие важные команды GDB

run [аргументы командной строки программы] Запустить программу на выполнение. break [ номер строки / имя функции] Установить точку остановки программы на определенной строке или функции. next Перейти на следующую строку, не заходя внутрь функций. step Перейти на следующую строку. Если на строке вызов функции - зайти внутрь нее. list Вывести фрагмент кода программы (несколько строк вокруг того места, где сейчас установлена точка) print [ переменная] Вывести значение переменной на экран. info locals Вывести текущие значения всех локальных переменных внутри цикла, функции и так далее. display [ переменная] Вывести значение переменной на каждом шаге отладки. help Показать список всех команд GDB.

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

Итак, переходим в папку pset2 (думаем, вы уже помните, как это сделать) в «Виртуальной лаборатории cs50» или CS50 IDE. Вводим команду:

Gdb . /caesar

В программе caesar есть одна функция, main. Установим точку остановки программы на функции main:

break main

Запустим программу caesar с аргументом «3»:

Run 13

Допустим, нам надо проверить значение argc:

Print argc

Вот как всё это должно выглядеть в окне терминала:

Теперь выполняем программу пошагово с помощью команды next . Выполним несколько раз.

Здесь переменной key присваивают значение. Проверим, какое значение она имеет этой строке:

При первом вызве next переменной key присваивается значение «0». ПОчему так, если мы ввели число 3? Дело в том, что команда ещё не была выполнена. Когда вы вводим next ещё несколько раз, программа предлагает ввести текст.

Выполнив команду next еще раз, мы зайдем внутрь цикла с условием.

Введение

Откровенно говоря, программа GNU GDB довольно многофункциональная. Пошаговая от-лад-ка — лишь одна из ее возможностей. В этой статье я попытался описать те лишь команды GDB, которые позволяют проводить удобную пошаговую отладку программ, на-пи-сан-ных на Free Pascal.

Чтобы программу можно было отлаживать, она должна быть откомпилирована с ключом -g .

Поскольку GDB ориентирован не на Pascal, а на C и C++, то использование GDB для от-лад-ки Pascal-программ иногда сопряжено с неудобствами.

Приведу список подводных камней, обнаруженных мною и разработчиками Free Pascal (пе-ре-чис-лен-ных в user"s manual).

  1. Отладочная информация в Free Pascal генерируется в верхнем регистре. Поэтому имена всех переменных, процедур, функций при использовании GDB должны указываться БОЛЬШИМИ БУКВАМИ.
  2. GDB не воспринимает тип extended (ведь в C такого типа нет). Обойти эту неприятность можно, если, например, включить в код такие строки...
    type
    {$IFDEF DEBUG}
    dbl = double;
    {$ELSE}
    dbl = extended;
    {$ENDIF}
    ...

    var x: dbl;
    ...

  3. К элементам многомерных массивов нужно обращаться в C-шной манере, а именно, команда (gdb) print A выдаст первую строку массива A. Для просмотра требуемого элемента следует писать (gdb) print A
  4. GDB не воспринимает множества.
  5. Есть трудности с поддержкой объектов (см. user"s manual за подробностями).
  6. Есть трудности с глобально переопределенными функциями. (за подробностями см. user"s manual).
  7. При отладке процедур, функций, расположенных в разных файлах, часто возникает несоответствие — смещение строк. Та строка, которую GDB показывает текущей, таковой не является, а текущая расположена строк эдак на двадцать выше. Это приводит к большим неудобствам при пошаговой отладке. Я для себя сделал из этого такую мораль — хоть GDB и позволяет отлаживать процедуры, описанные в разных файлах, но лучше этой возможностью не пользоваться, а на время отладки все вызываемые процедуры, работа которых вас заинтересует, помещать в одном файле.

Все примеры отлаживались с использованием GNU GDB 5.0.

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

gdb [опции] [имя_файла | ID процесса]

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

Ниже приводится краткий перечень команд GDB.

Краткую справку о любой команде можно получить, введя
(gdb) help [имя_команды, можно краткое]

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

Команда file

(gdb) file <имя исполняемого файла, который подлежит отладке>


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

Команда list (сокращенно l)

Пролистывает 10 строк вниз, начиная с текущей. Для пролистывания вверх следует набрать

Команда run (сокращенно r)

Запускает отлаживаемую программу под GDB. Если требуется, то после команды можно ука-зать список аргументов программы. Так-же допускается перенаправление потоков ввода и вы-во-да в другие файлы, например

(gdb) run > outfile

Если никаких точек останова не определено, то программа выполняется тихо, при этом нам со-об-ща-ет-ся:

(gdb) run
Starting program: test
Program exited normally.
(gdb)

Если же отладчик встречает точку останова, он выдает ее номер, адрес и дополнительную ин-фор-ма-цию — текущую строку, имя про-це-ду-ры, и т.п.

(gdb) r
Breakpoint 1, main () at test.pp:3
Current language: auto; currently pascal
3 x:= x + 1;
(gdb)

И ожидает ввода команды.

Остановка отладки программы

Команда kill (k). Следует запрос

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

Здесь введено y (то есть "да"), и отладка программы прекращается. Командой run ее можно на-чать заново, при этом все точки ос-та-но-ва (breakpoints), точки просмотра (watchpoints) и точ-ки отлова (catchpoints) сохраняются.

Выход из отладчика

Команда quit (q).

(gdb) q
$

Точки останова


(gdb) help breakpoints

Точки останова — такие, когда GDB приостанавливает выполнение программы. Как уже упо-ми-на-лось, имеется 3 типа точек ос-та-но-ва:

  1. Breakpoints — точка останова как таковая. Остановка происходит, когда выполнение доходит до определенной строки, адреса или процедуры/функции.
  2. Watchpoints — точка просмотра. Выполнение программы приостанавливается, если программа обратилась к определенной переменной — либо считала ее значение, либо изменила его.
  3. Catchpoints — точка отлова. Приостановка происходит при определенном событии (например, получение сигнала). Я не буду касаться точек останова этого типа.

Определение точек останова

Breakpoint

Команда break
(gdb) break [аргумент]
или, сокращенно
(gdb) b [аргумент]
определяет точку останова. В качестве аргумента может выступать

  • номер строки . Остановка произойдет при достижении строки программы с этим номером. То, что написано в самой строке, выполняться не будет. Например (gdb) b 394 Breakpoint 1 at 0x805a650: file maeq.pas, line 394.
  • имя процедуры (функции) . Отладчик зайдет в эту процедуру и остановит выполнение программы. NB!! Имя процедуры (функции) должно быть указано БОЛЬШИМИ БУКВАМИ. Приведу пример: (gdb) b CALC Breakpoint 2 at 0x7657c7a: file maeq.pas, line 26.
  • если вызвать команду break без аргументов , то точка останова поставится на текущей строке.
  • также можно явно указывать адрес точки останова (перед адресом надо поставить знак *). Приведу лишь пример для полноты описания: (gdb) b *0x805a650 Breakpoint 3 at 0x805a650: file maeq.pas, line 394.

Допускается использование нескольких точек останова на одной строке (функции, адресе).

Watchpoint

Существуют различные виды точек просмотра, и задаются они различными командами:

  • команда watch (сокращенно wa) (gdb) wa <переменная> Выполнение программы приостанавливается всякий раз, когда значение указанной переменной изменяется.
  • команда rwatch (сокращенно rw) (gdb) rw <переменная> Выполнение приостанавливается всякий раз, когда программа считывает значение указанной переменной.
    NB!! Имя переменной должно быть указано БОЛЬШИМИ БУКВАМИ.
  • команда awatch (сокращенно aw) (gdb) aw <переменная> Выполнение приостанавливается всякий раз, когда программа обращается к указанной переменной, как для считывания, так и для записи.
    NB!! Имя переменной должно быть указано БОЛЬШИМИ БУКВАМИ.

Замечу от себя, что команды rwatch и awatch у меня почему-то капризничают — часто не ус-та-нав-ли-ва-ют точки просмотра на пе-ре-мен-ную. Зато команда watch работала всегда.

Управление точками останова

Информацию о всех установленных точках останова можно вывести командой info .

Команда info имеет много возможностей, но в данном случае воспользуемся лишь сле-дую-щим ее форматом:
(gdb) info breakpoints
или, кратко
(gdb) i b

Выводится подробная информация о всех точках останова (как breakpoints, так и watch-points), включающая - номер - breakpoint или watchpoint - активность - сколько раз прог-рам-ма натыкалась на эту точку - иные характеристики, значение которых мне не со-всем понятно

Если какая-то точка останова не нужна, то ее можно сделать неактивной с помощью ко-ман-ды disable:

(gdb) disable breakpoint <номер этой точки>

Обратно, деактивированная точка останова активируется командой enable:

(gdb) enable breakpoint <номер этой точки>

Статус точки останова — активна она или нет, легко обозреть той же командой info .

Если же точка останова не требуется вообще, то она может быть удалена насовсем.

(gdb) delete breakpoint [номер точки]

(gdb) d b [номер точки]

Ввод этой команды без аргумента удалит ВСЕ точки останова.

Возобновление выполнения, пошаговая отладка

Информацию о командах этого раздела можно получить, введя

(gdb) help running

Команда continue (c)

(gdb) с [аргумент]

Продолжает выполнение остановленной программы. Выполнение будет происходить, пока сно-ва не встретится точка останова. В ка-чест-ве аргумента может использоваться целое чис-ло N. Это укажет отладчику проигнорировать (N-1) точку останова (вы-пол-не-ние остановится на N-ой).

Команда step (s)

(gdb) s [аргумент]

Аналог действия клавиши F7 (Trace into) в IDE. Происходит выполнение программы до тех пор, пока не будет достигнута сле-дую-щая строка ее кода. При указании аргумента — це-ло-го чис-ла N, отладчик выполняет команду step N раз (если не останавливает вы-пол-не-ние из-за точек останова или по иным причинам).

Команда next (n)

(gdb) n [аргумент]

Аналог действия клавиши F8 (Step over) в IDE. В отличие от step вызов процедуры счи-та-ет-ся единой инструкцией (не заходит в вы-зы-вае-мые процедуры, функции). Аргумент N ра-бо-та-ет так же, как и для step .

Команда finish (fin)

(gdb) fin

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

Команда until (u)

Производит выполнение программы до тех пор, пока не будет достигнута строка с номером, бо ль-шим текущего. Команду until удоб-но применять при отладке циклов. Остановка про-и-зой-дет также, если программа при выполнении цикла выйдет из текущей про-це-ду-ры, функ-ции.

Команда stepi (si)

(gdb) si [аргумент]

Действие подобно step , но выполняется не строка, а ровно одна инструкция в этой строке прог-рам-мы. Аргумент N нужен, если тре-бу-ет-ся выполнить N инструкций.

Команда nexti (ni)

(gdb) ni [аргумент]

Аналогична stepi , но вызовы процедур трактуются как одна инструкция.

Управление состоянием (просмотр, изменение) переменных при отладке

Информацию о командах этого раздела можно получить, введя

(gdb) help data

Команда print (p)

(gdb) print <выражение>

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

Часто требуется отслеживать значения нескольких переменных. Чтобы не утруждать себя мно-го-крат-ным вводом команды print , ис-поль-зуй-те команду display .

Команда display

(gdb) display [аргумент]

В качестве аргумента обычно указывают переменную или выражение. При этом указанная пе-ре-мен-ная (выражение) занесется в дис-плей, то есть станет выводиться при каждой ос-та-нов-ке программы (при попадании на точку останова, при пошаговом вы-пол-не-нии командами step и next , etc). Если вызвать display без аргументов, то GDB выдаст значения всех пе-ре-мен-ных (вы-ра-же-ний), занесенных в дисплей.

Управление списком этих переменных осуществляется аналогично точкам останова. А имен-но, команда info display

(gdb) info display

выдаст все переменные, занесенные в дисплей. Любая переменная в списке дисплея может быть дезактивирована

(gdb) disable display <номер переменной в списке дисплея>

или активирована заново

(gdb) enable display <номер переменной в списке дисплея>

Удаление переменной из списка дисплея производится командой delete или командой undisplay . Так, команда

(gdb) delete display [номер переменной в списке дисплея]

делает то же, что и

(gdb) undisplay [номер переменной в списке дисплея]

Опять-таки, если не указать номер переменной, то очистится весь список отображаемых пе-ре-мен-ных.

Изменение значения переменной

И последнее. Изменение значения переменной на другое можно, например, произвести с по-мощью команд set или print .

(gdb) set <оператор присваивания> (gdb) print <оператор присваивания>

Например,

(gdb) whatis x
TYPE = WORD
(gdb) p x
$1 = 1
(gdb) set x:=2
(gdb)

При использовании set присваивание происходит "тихо". То же самое можно сделать, но с по-мощью команды print .

Например,

(gdb) p x
$2 = 2
(gdb) p x:=x-2
$3 = 0
(gdb)

При этом, как видно, выводится новое значение переменной.

Вот и все.

Удачной отладки!



Рекомендуем почитать

Наверх