Статья охватывает особенности использования отладчика
Оглавление:
Реализация GDB для Нейтрино включает в себя следующие расширения:
Для отладки приложения выполните следующие команды:
gdb
file my_application
target qnx com_port_specifier | host:port | pty
upload my_application /tmp/my_application
set break main
run
Вы можете сократить команду GDB до нескольких первых букв имени команды, если это сокращение однозначно; и вы можете повторять выполнение определенных команд GDB, просто нажав Enter
. Вы также можете использовать клавишу Tab
, чтобы GDB заполнял оставшуюся часть слова в команде (или чтобы показать вам доступные альтернативы, если они есть).
Вы также можете поместить команды GDB в файл инициализации, и эти команды будут запускаться до тех команд, которые были введены через командную строку. Для получения дополнительной информации см.:
Команда GDB - это одна строка, подающаяся на ввод. Нет никаких ограничений на то, какой длины она может быть. Строка начинается с имени команды, за которым следуют аргументы, значение которых зависит от имени команды. Например, команда step принимает аргумент, который представляет собой количество шагов, которые выполняются программой, пример: step 5. Вы также можете использовать команду step без аргументов. Некоторые имена команд не допускают никаких аргументов.
Имена команд GDB всегда могут быть сокращены, если это сокращение однозначно. Другие возможные сокращения команд перечислены в документации для отдельных команд. В некоторых случаях разрешены даже двусмысленные сокращения; например, s специально определяется как эквивалент step, хотя есть другие команды, имена которых начинаются с s. Вы можете проверить сокращения, используя их в качестве аргументов команды help.
Пустая строка в качестве входных данных для GDB (ввод просто Enter
) означает повторение предыдущей команды. Некоторые команды (например, run) таким образом не повторяются; это команды, непреднамеренное повторение которых может вызвать проблемы и которые вы вряд ли захотите повторять.
Команды list и x, при их повторе нажатием Enter
, вместо точного повтора создают новые параметры. Это позволяет легко просматривать исходный текст или память.
GDB может также использовать Enter
по-другому: для разделения длинных выходных данных способом, похожим на утилиту more. Так как в этой ситуации легко выполнить непреднамеренный повторный ввод команды Enter
, GDB запрещает повторение команд после любой команды, которая генерирует такой вид отображения.
Любой текст от #
до конца строки является комментарием. Это полезно в основном в командных файлах.
GDB может заполнить за вас оставшуюся часть слова в команде, если есть только однин вариант подсказки; он также может показать вам, каковы допустимые возможности для следующего слова в команде, в любое время. Это работает для команд GDB, подкоманд GDB и символьных имен в вашей программе.
Нажимите клавишу Tab
, если вы хотите, чтобы GDB заполнил оставшуюся часть слова. Если есть только однин вариант подсказки, GDB подставляет подсказку и ждет, пока вы завершите команду (или нажмете Enter
, чтобы ввести ее). Например, если вы наберете:
(gdb) info bre Tab
GDB заполнит оставшиеся символы в слове breakpoints, поскольку это единственная подкоманда команды info, начинающаяся с bre:
(gdb) info breakpoints
Вы можете либо нажать Enter
, чтобы запустить команду info breakpoints, либо Backspace
, чтобы ввести что-нибудь еще, если подстановка breakpoints не похожа на то, что вы ожидали. (Если вы были уверены, что вам нужна была команда info breakpoints, вы могли просто нажать Enter
сразу после info bre, чтобы использовать сокращение команды, а не полное название команды).
Если при нажатии Tab
есть несколько возможностей подсказки для следующего слова, GDB подает сигнал. Вы можете ввести больше символов и попробовать еще раз или просто нажать Tab
второй раз; GDB отобразит все возможные варианты завершения для этого слова. Например, вы можете захотеть установить точку останова для подпрограммы, имя которой начинается с make_, но когда вы наберете:
b make_Tab
GDB просто подаст сигнал. При вводе Tab
снова отображаются все имена функций в вашей программе, которые начинаются с этих символов, например:
make_a_section_from_file make_environ make_abs_section make_function_type make_blockvector make_pointer_type make_cleanup make_reference_type make_command make_symbol_completion_list (gdb) b make_
После отображения доступных подсказок GDB копирует ваш частичный ввод (b make_ в примере), чтобы вы могли завершить команду.
Если вы просто хотите сначала увидеть список альтернатив, вы можете нажать Esc
, а затем ?
(вместо того, чтобы дважды нажимать Tab
).
Иногда нужная вам строка, являясь логически «словом», может содержать круглые скобки или другие символы, которые GDB обычно исключает из своего понятия слова. Чтобы разрешить автозавершение слов в этой ситуации, вы можете заключить слова в '
(одинарные кавычки) в командах GDB.
Наиболее вероятная ситуация, когда вам это может понадобиться, - это ввод имени функции C++. Это связано с тем, что C++ допускает перегрузку функций (несколько определений одной и той же функции, различающиеся по типу аргумента). Например, когда вы хотите установить точку останова, вам может потребоваться различить, имеете ли вы в виду версию команду name, которая принимает параметр int
, name (int), или версию, которая принимает параметр float, name (float). Чтобы использовать в этой ситуации средства завершения слов, введите одинарную кавычку '
в начале имени функции. В таком случае GDB поймет, что ему может потребоваться рассмотреть больше информации, чем обычно, когда вы нажимаете Tab
или Esc
с последующим ?
, чтобы запросить завершение слова:
(gdb) b 'bubble(Esc? bubble(double,double) bubble(int,int) (gdb) b 'bubble(
В некоторых случаях GDB может вывести сообщение о том, что завершение имени требует использования кавычек. Когда это происходит, GDB вставляет кавычку за вас (при этом заполняя столько, сколько может), если вы не вводите кавычку с самого начала:
(gdb) b bub Tab
GDB изменяет вашу строку ввода на следующую и подает сигнал:
(gdb) b 'bubble(
В общем, GDB может показать, что кавычка необходима (и вставить ее), если вы еще не начали вводить список аргументов, когда вы запрашиваете завершение для перегруженного символа.
Вы всегда можете запросить информацию о командах у самого GDB, используя команду help.
Помимо команды help, вы также можете использовать GDB-команды info и show, чтобы узнать о состоянии вашей программы или о состоянии самого GDB. Каждая команда поддерживает множество возможностей для применения; это руководство представляет каждый из них в соответствующем контексте. Списки из info и show могут указывть на подкоманды.
Вот три разных подкоманды show, каждая из которых является исключением из-за отсутствия аналогов для команды set:
Прежде чем выполнять программу под управлением GDB, при компиляции вы должны сгенерировать отладочную информацию. Вы можете запустить GDB с аргументами, если они есть, в любой среде по вашему выбору. Вы можете перенаправить ввод и вывод вашей программы, отлаживать уже выполняющийся процесс, или убить дочерний процесс.
Отладочная информация сохраняется в объектном файле; она описывает тип данных каждой переменной или функции, и соответствие между номерами строк исходного текста и адресами в выполняемом коде.
Чтобы запросить генерацию отладочной информации, укажите опцию -g при вызове компилятора.
GCC, GNU компилятор языка С, поддерживает опцию -g с или без опции -O, делая возможным отладку оптимизированного кода. Мы рекомендуем, чтобы вы всегда использовали -g при компиляции программ. Вы можете думать, что ваша программа правильная, но нет никакого смысла испытывать удачу.
Когда вы отлаживаете программу, откомпилированную с помощью оцпий -g -O, помните, что оптимизатор перестраивает ваш код; отладчик же показывает то, что там находится в действительности. Не удивляйтесь, если порядок выполнения не будет в точности соответствовать вашему исходному файлу! Например: если вы определяете переменную, но нигде ее не используете, GDB никогда не увидит этой переменной, потому что при оптимизации компилятор ее исключит.
Некоторые вещи не работают с опциями -g -O также, как просто с опцией -g, в частности, на машинах с планированием инструкций. Если сомневаетесь, перекомпилируйте программу используя только опцию -g, и если это устранит проблему, пожалуйста, сообщите нам об этом как об ошибке (не забудьте включить в отчет тестовый пример!).
Если вы занимаетесь отладкой локально, то вам не нужно указывать целевую платформу (или вы можете указать целевую procfs).
Если вы занимаетесь отладкой удаленно, то вам нужно указать целевую платформу для использования:
target qnx com_port_specifier | host:port | pty
Опция pty запускает сервер pdebug на локальной машине и подключается к нему через pty.
![]() | Менеджер devc-pty должен быть запущен на машине, на которой запущен pdebug, и пара ptyp/ttyp должна быть доступной. |
Выполнение программы зависит от определенной информации, которую она получает от породившего ее процесса. GDB предоставляет способы задать эту информацию, что вы должны сделать до запуска программы. (Вы можете изменить ее после запуска, но такие изменения воздействуют на вашу программу только при следующем запуске.) Эта информация может быть разделена категории:
SHELL
. См. Аргументы вашей программы.
![]() | Пока работает перенаправление ввода и вывода, вы не можете использовать каналы (pipe) для передачи вывода отлаживаемой программы другой программе; если вы попытаетесь это сделать, GDB, скорее всего, выполнит отладку не той программы. |
Если время модификации вашего символьного файла изменилось с того момента, когда GDB последний раз считывал символы, то GDB уничтожает свою символьную таблицу и считывает ее заново. При этом GDB старается сохранить ваши текущие точки останова.
Пример запуска программы для локальной отладки:
(gdb) file /tmp/helloworld Reading symbols from /tmp/helloworld...done. (gdb) b main Breakpoint 1 at 0x804860c: file ./main.c, line 5. (gdb) r Starting program: Remote: /tmp/helloworld Breakpoint 1, main () at ./main.c:5 5 { (gdb)
Пример запуска программы для удаленной отладки:
(gdb) target qnx mytst:8000 Remote debugging using mytst:8000 Remote target is little-endian (gdb) file /tmp/helloworld Reading symbols from /tmp/helloworld...done. (gdb) upload /tmp/helloworld /tmp/helloworld (gdb) b main Breakpoint 1 at 0x804860c: file ./main.c, line 5. (gdb) r Starting program: Remote: /tmp/helloworld Breakpoint 1, main () at ./main.c:5 5 { (gdb)
Если ваша линия связи медленная, вам может потребоваться установить тайм-аут для удаленного чтения:
set nto-timeout time
где time это время ожидания в секундах. По умолчанию - 10 секунд.
Аргументы к вашей программе могут быть заданы с помощью аргументов команды run.
Команда run без аргументов использует те же аргументы, которые использовались предыдущим запуском команды run, или те, которые были заданы командой set args.
Среда окружения состоит из набора переменных среды и их значений. Переменные окружения обычно хранят такие данные, как ваше имя пользователя, домашний каталог, тип терминала и путь поиска для запуска программ. Как правило, вы устанавливаете переменные окружения с помощью оболочки и они наследуются всеми другими программами, которые вы запускаете. При отладке может оказаться полезным попробовать запустить программу в измененной среде, не перезапуская GDB.
По умолчанию, программа, которую вы запускаете под управлением GDB, осуществляет ввод и вывод на тот же терминал, что и GDB. Для взаимодействия с вами, GDB переключает терминал в свой собственный терминальный режим, но он записывает терминальные режимы, которые использовала ваша программа, и переключается назад к ним, когда вы продолжаете выполнение программы.
Вы можете перенаправить ввод и/или вывод вашей программы, используя перенаправление оболочки с помощью команды run. Например,
run > outfile
запускает вашу программу, перенаправляя ее вывод в файл ./outfile
.
Чтобы использовать attach вы также должны обладать полномочиями для посылки сигнала процессу.
Когда вы используете attach, вы должны сначала использовать команду file, чтобы указать программу, выполняемую в процессе, и загрузить ее таблицу символов.
Первое, что GDB делает после подготовки указанного процесса к отладке - останавливает его. Вы можете проверять и изменять присоединенный процесс всеми командами GDB, которые обычно доступны, когда вы запускаете процессы с помощью run. Вы можете устанавливать точки останова; вы можете пошагово выполнять программу и продолжить ее обычное выполнение, вы можете изменять области хранилища. Если вы решите продолжить выполнение процесса после присоединения к нему GDB, вы можете использовать команду continue.
Если вы выйдете из GDB или используете команду run, пока у вас есть присоединенный процесс, вы убьете этот процесс. По умолчанию, GDB запрашивает подтверждение, если вы пытаетесь сделать одну из этих вещей; вы можете контролировать, нужно вам это подтверждение или нет, используя команду set confirm.
Эта команда полезна, если вы хотите отладить дамп памяти, а не выполняющийся процесс. GDB игнорирует любые дампы памяти, пока ваша программа выполняется.
Команда kill также полезна, если вы хотите перекомпилировать и перекомпоновать вашу программу. В ЗОСРВ «Нейтрино» можно изменять исполняемый файл во время его работы в процессе. Если вы хотите запустить новую версию, завершите текущий процесс ; в этом случае, когда вы в следующий раз введете run, GDB заметит, что файл изменился, и заново прочитает символьную таблицу (стараясь при этом сохранить ваши точки останова).
В ЗОСРВ «Нейтрино» одна программа может иметь несколько потоков выполнения. Каждый поток имеет свои собственные регистры и стек выполнения, и, возможно, свои собственные участки памяти.
GDB предоставляет следующие возможности для отладки многопоточных программ:
Возможности отладки потоков GDB позволяют вам наблюдать все потоки во время выполнения вашей программы, но когда управление переходит к GDB, один конкретный поток выделяется для отладки. Он называется текущим потоком. Отладочные команды показывают информацию о программе с точки зрения текущего потока. Для отладочных целей, GDB присваивает свои собственные номера потоков, всегда в виде одного целого числа, каждому потоку в вашей программе.
Когда GDB останавливает вашу программу, вследствие точки останова или по сигналу, он автоматически выбирает поток, в котором появилась точка останова или сигнал. GDB предупреждает вас о переключении контекста сообщением в форме [Switching to systag] для идентификации потока.
См. Остановка и запуск многопоточных программ, для дополнительной информации о поведении GDB, когда вы останавливаете и запускаете многопоточную программу.
См. Установка точек наблюдения, для информации о точках наблюдения в многопоточных программах.
GDB не имеет специальных возможностей для отладки программ, создающих дополнительные процессы с помощью функции fork(). Когда программа вызывает fork(), GDB будет продолжать отладку родительского процесса, а дочерний процесс будет выполняться беспрепятственно. Если выполнение дочернего процесса дойдет до места, где вы установили точку останова, дочерний процесс получит сигнал SIGTRAP
, который приведет к остановке процесса (если он не перехватывает этот сигнал).
Однако, если вы хотите отладить дочерний процесс, существует достаточно простое решение:
Внутри GDB ваша программа может остановиться по нескольким причинам, таким как сигнал, точка останова, или достижение новой строки после команды GDB, такой как step. Затем вы можете просматривать и изменять значения переменных, устанавливать новые точки останова и удалять старые, и затем продолжить выполнение. Обычно, выводимые GDB сообщения предоставляют достаточную информацию о состоянии вашей программы, но вы также можете запросить эту информацию в любое время.
Точка останова останавливает вашу программу всякий раз, когда ее выполнение достигает определенной точки. Для каждой точки останова вы можете добавлять условия для лучшего управления условиями остановки. Вы можете устанавливать точки останова командой break и ее вариантами (см. Установка точек останова), чтобы задать место, где должна остановиться ваша программа, по номеру строки, имени функции или точному адресу. В языках с обработкой исключений (таких как GNU C++), вы также можете установить точку останова там, где появляется исключение (см. Точки останова и исключения).
Точка наблюдения - это специальная точка останова, которая останавливает вашу программу при изменении значения выражения. Вы должны использовать другую команду для установки точки наблюдения (см. Установка точек наблюдения), но помимо этого, вы можете обращаться с ней так же, как с любой другой точкой останова: вы включаете, отключаете и удаляете точки останова и точки наблюдения при помощи одних и тех же команд.
Вы можете установить, что значения из вашей программы должны отображаться автоматически, когда GDB останавливается на точке останова. см. Автоматическое отображение.
Когда вы создаете точку останова, наблюдения или перехвата, GDB присваивает ей номер; эти номера являются последовательными целыми числами, начинающимися с 1. Во многих командах для управления различными возможностями точек останова, вы используете эти номера для указания, какую точку останова вы хотите изменить. Каждая точка останова может быть включена или отключена; если точка останова отключена, она не оказывает никакого влияния на вашу программу, пока вы снова не включите ее.
Точки останова устанавливаются командой break (сокращенно b). Вспомогательная переменная отладчика $bpnum
хранит номер последней установленной вами точки останова; см. Вспомогательные переменные, для обсуждения того, что вы можете делать со вспомогательными переменными.
Вы можете задавать место для установки новой точки останова несколькими способами:
Существует несколько вариантов команды break, использующих тот же синтаксис, что и выше:
Следующие команды отображают информацию о точках останова и точках наблюдения:
GDB позволяет вам установить любое число точек останова в одном и том же месте вашей программы. В этом нет ничего глупого или бессмысленного. Когда точки останова являются условными, это даже полезно (см. Условия останова).
GDB сам иногда устанавливает точки останова в вашей программе для специальных целей, таких как правильная обработка longjmp() (в программах на С). Этим внутренним точкам останова присваиваются отрицательные номера, начиная с -1; info breakpoints не отображает их.
Вы можете увидеть эти точки останова с помощью служебной команды GDB maint info breakpoints.
Вы можете использовать точку наблюдения для остановки выполнения, как только изменится значение какого-либо выражения, не предсказывая конкретное место, где это может произойти.
Хотя в настоящее время точки наблюдения выполняются на два порядка медленнее, чем точки останова, они могут помочь выявить ошибки в тех случаях, когда вы не знаете, в какой именно части вашей программы находится ошибка.
![]() | В многопоточных программах точки наблюдения являются лишь частично полезными. С текущей реализацией точек наблюдения, GDB может наблюдать только за значением выражения в одном потоке. Если вы уверены, что выражение может измениться только вследствие действий внутри текущего потока (и если вы также уверены, что никакой другой поток не может стать текущим), то вы можете использовать точки наблюдения как обычно. Однако, GDB может не заметить, когда действия в не текущем потоке изменяют выражение. |
Некоторые языки, например GNU C++, реализуют обработку исключений. Вы можете использовать GDB, чтобы узнать, что заставило вашу программу вызвать исключение, и перечислить исключения, которые ваша программа готова обработать в данный момент времени.
Вы можете использовать info cath, чтобы увидеть список активных обработчиков исключений. См. Информация о кадре.
В настоящее время существуют некоторые ограничения на обработку исключений в GDB:
Иногда catch - не лучший способ отладки обработки исключений: если вам нужно точно знать, где возникает исключение, лучше остановиться до вызова обработчика исключения, так как таким образом вы можете увидеть стек до того, как будет выполнено какое-либо разворачивание стека. Если вместо этого вы установите точку останова в обработчике исключений, может быть нелегко выяснить, где возникло исключение.
Чтобы остановиться непосредственно перед вызовом обработчика исключений, вам необходимо знать реализацию. В случае GNU C++ исключения вызываются вызовом библиотечной функции с именем __raise_exception(), которая имеет следующий интерфейс ANSI C:
void __raise_exception( void **addr, void *id );/* addr - это адрес, где хранится идентификатор исключения.id - идентификатор исключения. */
Чтобы отладчик перехватил все исключения до того, как произойдет разворачивание стека, установите точку останова на __raise_exception(). См. Точки останова, точки наблюдения и исключения.
С условной точкой останова (см. Условия останова), которая зависит от значения id, вы можете остановить свою программу при возникновении определенного исключения. Вы можете использовать несколько условных точек останова, чтобы остановить вашу программу при возникновении любого исключения.
Часто бывает необходимо уничтожить точку останова или точку наблюдения, когда она сделала свое дело и вы больше не хотите останавливать там свою программу. Это называется удалением точки останова. Точка останова, которая была удалена, более не существует; она забыта.
С помощью команды clear вы можете удалять точки останова в соответствии с тем, где они находятся в вашей программе. С помощью команды delete вы можете удалять отдельные точки останова или точки наблюдения, указывая их номера.
Не обязательно удалять точку останова, чтобы продолжить выполнение после нее. GDB автоматически игнорирует точки останова на первой инструкции, которая должна быть выполнена, когда вы продолжаете исполнение без изменения адреса выполнения.
Вместо того, чтобы удалять точку останова или точку наблюдения, вам может быть удобнее отключить ее. Это делает точку останова бездействующей, как если бы она была удалена, но информация о ней запоминается, так что вы можете позже включить ее снова.
Вы отключаете и включаете точки останова, и точки наблюдения командами enable и disable, возможно указывая один или более номеров точек останова в качестве аргументов. Используйте info break или info watch для вывода списка точек останова, и точек наблюдения, если вы не знаете какие номера использовать.
Точка останова или точка наблюдения может находиться в одном из четырех состояний:
Вы можете использовать следующие команды для включения или отключения точек останова, и точек наблюдения:
Кроме точек останова, установленных командой tbreak (см Установка точек останова), установленные вами точки останова изначально включены; следовательно, они становятся отключенными или включенными только когда вы используете одну из вышеперечисленных команд. (Команда until может устанавливать и удалять свою собственную точку останова, но она не изменяет состояние ваших других точек останова; см. Продолжение и выполнение по шагам).
Простейшая точка останова останавливает вашу программу каждый раз, когда управление достигает заданного места. Вы можете также указать условие для точки останова. Условие является простым булевым выражением в вашем языке программирования (см. Выражения). Точка останова с условием вычисляет выражение каждый раз, когда ваша программа достигает ее, и ваша программа остановится только в том случае, если условие истинно.
Это противоположно использованию утверждений для проверки правильности программы; в этом случае, вы хотите остановиться, когда утверждение нарушается - то есть, когда условие ложно. В С, если вы хотите проверить утверждение, выраженное условием assert, вы должны установить условие !assert на соответствующей точке останова.
Условия также допускаются для точек наблюдения; вам они могут не понадобиться, так как точка наблюдения так или иначе контролирует значение выражения - но может оказаться проще, скажем, просто установить точку наблюдения на имя переменной и указать условие, проверяющее, является ли новое значение тем, которое нас интересует.
Условия останова могут иметь побочные эффекты, и даже могут вызывать функции в вашей программе. Это может быть полезным, например, для активации функций, которые запоминают продвижение выполнения вашей программы, или для использования ваших собственных функций вывода для форматирования специальных структур данных. Результаты полностью предсказуемы, если нет другой включенной точки останова по тому же адресу. (В этом случае, GDB может сначала увидеть другую точку останова и остановить вашу программу программу без проверки условия первой точки останова.) Заметьте, что команды точек останова обычно более удобны и гибки, чем условия останова, для выполнения побочных эффектов, когда достигается точка останова (см. Список команд точки останова).
Условия останова могут быть заданы в момент установки точки останова, используя if в аргументах команды break. См. Установка точек останова. Они могут быть также изменены в любой момент с помощью команды condition. Команда watch не распознает ключевое слово if; condition является единственным способом наложить дальнейшие условия на точку наблюдения.
Специальным случаем условия для точки останова является остановка только когда точка останова была достигнута определенное число раз. Это настолько полезно, что существует специальный способ сделать это, используя счетчик игнорирования точки останова. Каждая точка останова имеет счетчик игнорирования, являющийся целым числом. Как правило, счетчик игнорирования равен нулю, и, следовательно, не производит никакого действия. Но если ваша программа достигает точки останова, чей счетчик игнорирования положителен, тогда вместо того чтобы остановиться, она лишь уменьшит его на единицу и продолжит выполнение. В результате, если величина счетчика игнорирования равна n, точка останова не остановит программу следующие n раз, когда программа его достигнет.
Вы можете подать любой точке останова ( или точке наблюдения ) ряд команд, которые будут выполняться при остановке вашей программы на этой точке останова. Например, вы можете захотеть вывести значения определенных выражений, или включить другие точки останова.
Нажатие Enter
, как средство повторения последней команды GDB, отключено внутри command-list.
Вы можете использовать команды для точки останова, чтобы снова запустить вашу программу на выполнение. Просто используйте команду continue, или step, или любую другую команду, возобновляющую выполнение.
После команды, возобновляющей выполнение, любые другие команды в списке игнорируются. Так сделано потому, что каждый раз, когда вы возобновляете выполнение (даже просто с помощью next или step), вы можете встретить другую точку останова - которая может иметь свой собственный список команд, что приведет к неоднозначности, какой из списков выполнять.
Если в качестве первой команды в списке команд вы укажете silent, обычное сообщение об остановке на точке останова не будет выводиться. Это может быть желательно для точек останова, которые должны вывести определенное сообщение, и затем продолжить выполнение. Если никакая из оставшихся команд ничего не выводит, вы не увидите никакого знака о том, что точка останова была достигнута. silent имеет смысл только в начале списка команд точки останова.
Команды echo, output и printf позволяют вам более точно контролировать выводимый текст, и часто полезны в "тихих" точках останова.
Например, вот как вы можете использовать команды точки останова для вывода величины x на входе в foo(), когда x положительна:
break foo if x>0 commands silent printf "x is %d\n",x cont end
Одним из применений команд точки останова является компенсация одной ошибки, так, чтобы вы могли искать другую. Поместите точку останова сразу после строки кода, содержащей ошибку, задайте ей условие для определения случая, в котором было сделано что-то ошибочное, и определите команды для присвоения правильных значений тем переменным, для которых это требуется. Закончите командой continue, чтобы ваше программа не останавливалась, а начните с команды silent, чтобы не было никакого вывода. Вот пример:
break 403 commands silent set x = y + 4 cont end
Некоторые языки программирования (особенно С++) допускают, чтобы одно и то же имя функции было определено несколько раз, для применения в различных контекстах. Это называется перегрузкой. Когда имя функции перегружается, команды break function не достаточно, чтобы указать GDB, где вы хотите установить точку останова.
Если вы столкнулись с этой проблемой, вы можете использовать команду типа break function(types) для указания, какую конкретную версию функции вы имеете в виду. В противном случае, GDB предлагает вам выбор из пронумерованных вариантов для различных возможных точек останова, и ждет вашего выбора с выводом символа ">". Первыми двумя вариантами всегда являются "[0] cancel" и "[1] all". Ввод "1" устанавливает точку останова на каждом определении функции function, а ввод "0" прерывает команду break без установки новых точек останова.
Например, следующая выдержка из сеанса иллюстрирует попытку установить точку останова на перегруженной функции String::after(). Мы выбрали три конкретных определения имени функции:
(gdb) b String::after [0] cancel [1] all [2] file:String.cc; line number:867 [3] file:String.cc; line number:860 [4] file:String.cc; line number:875 [5] file:String.cc; line number:853 [6] file:String.cc; line number:846 [7] file:String.cc; line number:735 > 2 4 6 Breakpoint 1 at 0xb26c: file String.cc, line 867. Breakpoint 2 at 0xb344: file String.cc, line 875. Breakpoint 3 at 0xafcc: file String.cc, line 846. Multiple breakpoints were set. Use the "delete" command to delete unwanted breakpoints. (gdb)
Продолжение означает возобновление выполнения программы до ее нормального завершения. А пошаговое выполнение означает выполнение еще одного "шага" (step) вашей программы, где "шаг" (step) может быть либо одной строкой исходного кода, либо одной машинной инструкцией (в зависимости от того, какую именно команду вы используете). И в случае продолжения, и в случае выполнения по шагам, ваша программа может остановиться и раньше, вследствие точки останова или сигнала. (Если она останавливается по сигналу, вы можете использовать handle, или signal 0 для возобновления выполнения. См. Сигналы).
Чтобы возобновить выполнение с другого места, вы можете использовать return (см. Возврат из функции) чтобы вернуться назад к вызывающей функции; или jump (см. Продолжение исполнения с другого адреса) для перехода к произвольному месту в вашей программе.
Типичная техника для использования пошагового выполнения заключается в установке точки останова (см. Точки останова, точки наблюдения и исключения) на начале функции или раздела вашей программы, где предположительно находится ошибка, выполнении вашей программы до остановки на этой точке останова, и затем пошаговом выполнении подозреваемого участка, с проверкой интересуемых переменных, пока вы не увидите, что случилась ошибка.
Сигнал - это асинхронное событие, которое может произойти в программе. Операционная система определяет возможные типы сигналов и дает каждому типу имя и номер. В таблице ниже приведены некоторые примеры сигналов:
Таблица сигналов:
Сигнал | Когда применятся |
---|---|
SIGINT | Когда вы вводите знак прерывания, Ctrl - C |
SIGSEGV | Когда программа ссылается на область памяти, отличную от всех используемых областей. |
SIGALARM | При срабатывании интервального таймера (возникает только, если ваша программа запросила временной сигнал). |
Некоторые сигналы, такие как SIGALRM
, являются обычной частью функционирования вашей программы. Другие, такие как SIGSEGV
, обозначают ошибки; эти сигналы являются фатальными (они немедленно убивают вашу программу), если программа не определила заранее другой способ их обработки. SIGINT
не указывает на ошибку в вашей программе, но обычно является фатальным, так что он может выполнять функцию прерывания: убить программу.
GDB способен обнаружить любое появление сигнала в вашей программе. Вы можете заранее сообщить GDB, что делать для каждого типа сигнала.
Обычно, GDB установлен так, чтобы:
SIGALRM
(чтобы не мешать их действию при исполнении вашей программы). Вы можете изменить эти установки командой handle.
Ключевые слова, допускаемые командой handle, могут быть сокращены. Вот их полные имена:
Когда сигнал останавливает вашу программу, он невидим для нее, пока вы не продолжите выполнение. Затем ваша программа видит сигнал, если в данный момент на рассматриваемый сигнал распространяется действие команды pass. Другими словами, после того, как GDB сообщит о сигнале, вы можете использовать команду handle c pass или nopass, чтобы указать, должна ли ваша программа увидеть этот сигнал при продолжении.
Вы также можете использовать команду signal для того, чтобы помешать вашей программе увидеть сигнал или, наоборот, заставить ее заметить обычно игнорируемый сигнал, или чтобы подать ей произвольный сигнал в любое время. Например, если ваша программа остановилась вследствие какой-либо ошибки обращения к памяти, вы можете сохранить правильные значения в ошибочные переменные и продолжить выполнение, в надежде посмотреть на дальнейшее выполнение, но ваша программа вероятно немедленно остановилась бы из-за фатального сигнала, как только она бы его заметила. Чтобы помешать этому, вы можете продолжить выполнение с "signal 0". См. Подача сигнала вашей программе.
Когда ваша программа имеет несколько потоков выполнения (см. Отладка программ с несколькими потоками), вы можете выбрать, установить точки останова либо во всех, либо в каких-то отдельных потоках.
При любой остановке вашей программы под управлением GDB, прекращается выполнение всех потоков, а не только текущего. Это позволяет вам проверить полное состояние программы, включая переключение между потоками, не опасаясь, что это может изменить что-либо в дальнейшем.
Наоборот, когда вы снова запускаете программу, все потоки начинают выполняться. Это верно даже при пошаговом выполнении такими командами, как step или next.
В частности, GDB не может пошагово выполнять все потоки параллельно. Так как планированием выполнения потока занимается микроядро Нейтрино (не контролируется GDB), то пока в текущем потоке выполняется один шаг, в других может выполниться несколько. Более того, когда выполнение программы останавливается, другие потоки вообще могут остановиться в середине операторов, а не на границе между ними.
Вы даже можете обнаружить, что после продолжения исполнения или после пошагового выполнения ваша программа остановилась в другом потоке. Это случается всякий раз, когда другой поток достигает точки останова, получает сигнал или в нем возникает исключение, прежде чем перваый поток завершает выполнение того, что вы запросили.
Когда ваша программа остановилась, первое, что вам нужно знать - где она остановилась и как она туда попала.
Каждый раз, когда ваша программа производит вызов функции, о нем создается определенная информация. Она включает положение вызова в вашей программе, параметры вызова и локальные переменные вызываемой функции. Информация сохраняется в блоке данных, называемом кадром стека. Кадры стека размещаются в области памяти, называемой стеком вызовов.
Команды GDB для проверки стека позволяют вам увидеть всю эту информацию при остановке вашей программы.
Один из кадров стека является выбранным GDB, и многие команды GDB неявно относятся к нему. В частности, когда вы запрашиваете у GDB значение переменной вашей программы, это значение находится в выбранном кадре. Для выбора интересующего вас кадра существуют специальные команды GDB. См Выбор кадра.
Когда ваша программа останавливается, GDB автоматически выбирает текущий выполняющийся кадр и выдает его краткое описание, аналогично команде frame (см. Информация о кадре).
Cтек вызовов разделен на непрерывные участки, называемые кадрами стека, или кадрами для краткости; каждый кадр является данными, связанными с одним вызовом одной функции. Кадр содержит аргументы, переданные функции, ее локальные переменные и адрес, с которого она выполняется.
Когда ваша программа стартует, стек содержит только один кадр - для функции main(). Он называется начальным или внешним кадром. Каждый раз при вызове функции создается новый кадр. При каждом выходе из функции, кадр этого вызова функции уничтожается. Если функция является рекурсивной, для нее может существовать множество кадров. Кадр для функции, исполняемой в данный момент, называется внутренним кадром. Это кадр, созданный самым последним из всех существующих кадров стека.
Внутри вашей программы кадры стека идентифицируются своим адресом. Кадр стека состоит из множества байт, каждый из которых имеет свой собственный адрес; каждый тип компьютеров имеет свой способ для выбора одного байта, чей адрес служит адресом кадра. Обычно, пока выполнение происходит в данном кадре, этот адрес содержится в регистре, называемом регистром указателя кадра.
GDB присваивает номера всем существующим кадрам стека, начиная с "0" для внутреннего кадра, "1" для вызвавшего его кадра, и так далее. В действительности, эти номера не существуют в вашей программе; они назначаются GDB, чтобы предоставить вам способ различать кадры стека в командах GDB.
Некоторые компиляторы позволяют компилировать функции так, чтобы они выполнялись без создания кадров стека. (Например, опция gcc -fomit-frame-pointer создает функции без кадра.) Это иногда делается с часто используемыми библиотечными функциями, чтобы сохранить время, требуемое для установки кадра. GDB имеет ограниченные возможности для обработки таких функциональных вызовов. Если вызов внутренней функции происходит без создания кадра стека, GDB, тем не менее, описывает его так, как если бы он имел отдельный кадр, который имеет, как обычно, номер "0", позволяя корректно трассировать цепочку функциональных вызовов. Однако, GDB не имеет средств для работы с функциями без кадра в другом месте стека.
Цепочка вызовов предоставляет собой информацию о том, как ваша программа оказалась там, где она есть. Она отображает по одной строке для каждого кадра, начиная с текущего выполняющегося кадра (кадра "0"), за которым следует кадр, из которого он был вызван (кадр "1"), и далее вверх по стеку.
where и info stack (сокращенно info s) - дополнительные синонимы для backtrace.
Каждая строка в цепочке вызовов показывает номер кадра и имя функции. Счетчик команд также показывается, если только вы не используете set print address off. Цепочка вызовов также показывает имя исходного файла, номер строки и аргументы функции. Значение счетчика команд опускается, если он указывает на начало кода для данной строки.
Ниже приведен пример цепочки вызовов. Она была получена командой bt 3, так что она показывает три внутренних кадра:
#0 m4_traceon (obs=0x24eb0, argc=1, argv=0x2b8c8) at builtin.c:993 #1 0x6e38 in expand_macro (sym=0x2b600) at macro.c:242 #2 0x6840 in expand_token (obs=0x0, t=177664, td=0xf7fffb08) at macro.c:71 (More stack frames follow...)
Информация о кадре с номером "0" не начинается со значения счетчика команд, что указывает на то, что ваша программа остановилась в начале кода для строки "993" файла builtin.c
.
Большинство команд для проверки стека и других данных в вашей программе применяются к выбранному в данный момент кадру. Здесь приведены команды для выбора кадра стека; все они завершаются выводом краткого описания выбранного кадра стека.
Все эти команды заканчиваются выводом двух строк, описывающих кадр. Первая строка показывает номер кадра, имя функции, аргументы, имя исходного файла и номер выполняемой строки в этом кадре. Вторая строка показывает содержимое этой строки исходного текста.
Например:
(gdb) up #1 0x22f0 in main (argc=1, argv=0xf7fffbf4, env=0xf7fffbfc) at env.c:10 10 read_input_file (argv[i]);
После такого вывода, команда list без аргументов выводит десять строк, расположенных вокруг точки выполнения в кадре. См. Вывод строк исходного текста.
Существует несколько других команд для вывода информации о выбранном кадре стека:
Компьютеры на базе MIPS используют необычный кадр стека, что иногда требует от GDB поиска в обратном направлении в объектном коде для нахождения начала функции.
Чтобы улучшить время отклика - особенно для встроенных приложений, где GDB может быть ограничен одной медленной последовательной строкой для этого поиска - вы можете ограничить предел этого поиска, используя одну из этих команд:
Эти команды доступны только в том случае, если GDB настроен для отладки программ на процессорах MIPS.
GDB может выводить части исходных текстов вашей программы, так как отладочная информация, записанная в ней, сообщает GDB, какие исходные файлы использовались при создании программы. Когда ваша программа останавливается, GDB сам выводит строку, на которой она остановилась. Аналогично, когда вы выбираете кадр стека (см. Выбор кадра), GDB выводит строку, на которой остановилось выполнение в этом кадре. Вы можете выводить другие части исходных файлов с помощью явных команд.
Чтобы вывести строки файла с исходным текстом, используйте команду list (сокращенно l). По умолчанию выводятся десять строк. Существует несколько способов определения того, какую часть файла вы хотите вывести.
Здесь представлены наиболее часто используемые формы команды list:
По умолчанию, для любой из этих форм команды list GDB выводит десять строк исходного текста. Вы можете изменить это командой set listsize:
Повторение команды list нажатием Enter
отбрасывает аргумент, так что это эквивалентно вводу просто list. Это полезнее, чем вывод тех же самых строк снова. Исключение сделано для параметра "-"; этот параметр сохраняется при повторе команды, так что каждое повторение приводит к перемещению вверх по исходному файлу.
Обычно команда list ожидает от вас ноль, один или два указателя строк linespec. Указатели строк linespec определяют строки исходного текста; существует несколько способов их задания, но результат всегда заключается в задании строки исходного текста. Вот полное описание возможных параметров команды list:
Ниже перечислены способы указания одиночной строки исходного текста - все виды указателей строк (linespec).
Существуют две команды для поиска по регулярному выражению в текущем исходном файле.
Исполняемые программы иногда не сохраняют имена каталогов, в которых находились исходные файлы, из которых они скомпилированы, а хранят лишь имена файлов. Даже если они их сохранили, каталоги могли быть перемещены в период между компиляцией и сеансом отладки. У GDB есть список каталогов для поиска исходных файлов; он называется путь для исходных файлов. Каждый раз, когда GDB требуется исходный файл, он перебирает по порядку все каталоги из этого списка, пока не находит файл с требуемым именем.
![]() | Пути поиска исполняемых файлов для этой цели не используются, как не используется и текущий рабочий каталог, если только он не присутствует в пути для исходных файлов. |
Если GDB не может найти исходный файл, используя путь для исходных файлов, а в объектном файле программы указан какой-либо каталог, GDB просматривает также и его. В последнюю очередь, если путь для исходных файлов пуст и запись о каталоге компиляции отсутствует, GDB просматривает текущий каталог.
При переустановке или переупорядочивании пути для исходных файлов, GDB очищает любую запомненную им информацию о том, где исходные файлы были найдены и о расположении строк в них.
Когда вы запускаете GDB, путь для исходных файлов пуст. Для добавления каталогов, используйте команду directory.
Если ваш путь для исходных файлов перемешан с уже неиспользуемыми каталогами, GDB может иногда вызвать недоумение, найдя неправильный вариант исходного файла. Вы можете исправить ситуацию следующим образом:
Вы можете использовать команду info line, чтобы отобразить cтроки исходного текста в программные адреса (и наоборот), и команду disassemble, чтобы вывести диапазон адресов в виде машинных инструкций. При запуске в режиме GNU Emacs, команда info line выводит стрелку, указывающую на заданную строку. Также info line выводит адреса как в символьной форме, так и в шестнадцатеричной.
Например, мы можем использовать info line для определения положения объектного кода первой строки функции m4_changequote:
(gdb) info line m4_changequote Line 895 of "builtin.c" starts at pc 0x634c and ends at 0x6350.
Мы также можем запросить (используя *адрес как форму задания linespec), какая строка исходного текста соответствует определенному адресу:
(gdb) info line *0x63ff Line 926 of "builtin.c" starts at pc 0x63e4 and ends at 0x6404.
После info line, адрес, используемый по умолчанию для команды x, меняется на начальный адрес строки, так что x/i достаточно для начала проверки машинного кода (см. Проверка памяти). Этот адрес также сохраняется как значение вспомогательной переменной $_
(см. Вспомогательные переменные).
Мы можем использовать disassemble для проверки диапазона объектного кода, показанного в последнем примере info line (в этом примере показаны машинные инструкции SPARC):
(gdb) disas 0x63e4 0x6404 Dump of assembler code from 0x63e4 to 0x6404: 0x63e4 <builtin_init+5340>: ble 0x63f8 <builtin_init+5360> 0x63e8 <builtin_init+5344>: sethi %hi(0x4c00), %o0 0x63ec <builtin_init+5348>: ld [%i1+4], %o0 0x63f0 <builtin_init+5352>: b 0x63fc <builtin_init+5364> 0x63f4 <builtin_init+5356>: ld [%o0+4], %o0 0x63f8 <builtin_init+5360>: or %o0, 0x1a4, %o0 0x63fc <builtin_init+5364>: call 0x9288 <path_search> 0x6400 <builtin_init+5368>: nop End of assembler dump.
Вы можете использовать следующие команды для работы с разделяемыми библиотеками:
Следующие параметры применяются к разделяемым библиотекам:
Вы можете запросить настройки этих параметров с помощью команд show solib-search-path, show solib-absolute-prefix и show auto-solib-add.
Для проверки данных в вашей программе обычно используется команда print (сокращенно p) или ее синоним inspect. Она вычисляет и выводит значение выражения, записанного на том же языке, что и ваша программа.
Команда x позволяет проверять данные на более низком уровне. Она исследует данные в памяти по указанному адресу и выводит их в указанном формате. см. Проверка памяти.
Если вас интересует информация о типах или о том, как объявлены поля структуры или класса, используйте команду ptype exp вместо print. См. Проверка символьной таблицы.
print и многие другие команды GDB допускают в качестве параметра выражение и вычисляют его значение. В выражении GDB допустимо использование любого типа констант, переменных или операторов, определенных в используемом вами языке программирования, включая условные выражения, вызовы функций, приведение типов и строковые постоянные. К сожалению, исключением являются символы, определенные командами препроцессора #define.
GDB поддерживает константы-массивы в выражениях, введенных пользователем. Синтаксис следующий: {элемент, элемент...}. Например, вы можете использовать команду print {1, 2, 3}, чтобы создать в памяти массив, который будет доступен в программе так же, как выделенный функцией malloc().
По причине широкого распространения С, большинство выражений в примерах этого руководства написаны на С. См. Использование GDB с различными языками программирования, для информации об использовании выражений в других языках. В этом разделе мы обсуждаем операторы, которые вы можете использовать в выражениях GDB независимо от используемого вами языка программирования.
Приведения типов поддерживается во всех языках, а не только в С, так как бывает очень полезно преобразовать число в указатель, чтобы проверить структуру, расположенную по этому адресу в памяти.
GDB поддерживает эти операторы, в дополнении к следующим, являющимися общими для языков программирования:
Чаще всего в качестве выражения используется имя переменной вашей программы.
Переменные в выражениях трактуются в контексте выбранного кадра стека (см. Выбор кадра); они могут быть:
Это означает, что в функции:
foo( a )int a;{bar( a );{int b = test();bar( b );}}
вы можете проверять и использовать переменную a всякий раз, когда ваша программа выполняется в пределах функции foo(), но вы можете использовать или проверять переменную b только тогда, когда ваша программа выполняется внутри блока, в котором она объявлена.
Есть исключение: вы можете ссылаться на переменную или функцию, областью видимости которой является единственный исходный файл, даже если точка текущего выполнения в нем не находится. Допускается существование нескольких переменных или функций с одинаковым именем (в различных исходных файлах). Если это так, обращение к этому имени приводит к непредсказуемым результатам. Если хотите, вы можете указать статическую переменную в конкретной функции или в файле, используя двойное двоеточие:
file::variable function::variable
Здесь файл file или функция function – название контекста для статической переменной variable. В первом случае вы можете использовать кавычки, чтобы GDB рассматривал имя файла как одно слово; например, чтобы вывести глобальное значение переменной x, определенной в f2.c
:
(gdb) p 'f2.c'::x
Такое использование ::
крайне редко конфликтует с похожим использованием той же записи в С++. GDB также поддерживает использование оператора определения области видимости С++ в выражениях.
![]() | В некоторых случаях, в определенной точке функции (сразу после входа в новую область видимости, и непосредственно перед выходом из нее) может показаться, что локальная переменная имеет неверное значение.
Вы можете столкнуться с этой проблемой при пошаговом выполнении по одной машинной инструкции. Она возникает из-за того, что на большинстве машин процедура установки кадра стека (включая определения локальных переменных) занимает более одной инструкции; если вы производите пошаговое выполнение по одной машинной инструкции, может показаться, что переменная имеет неверное значение, пока кадр стека не будет полностью построен. При выходе, для уничтожения кадра стека обычно также требуется более одной инструкции; после начала пошагового выполнения этой группы инструкций, определения локальных переменных могут пропасть. |
Часто бывает полезным вывести несколько объектов одного типа, расположенных в памяти последовательно; часть массива или динамический массив, для которого в программе существует только указатель.
Вы можете это сделать, обращаясь к непрерывному участку памяти как к искусственному массиву, используя бинарный оператор @
. Левым операндом для @
должен быть первый элемент желаемого массива, и он должен быть индивидуальным объектом. Правым операндом должна быть длина массива. Результатом операции будет массив, все элементы которого имеют тот же тип, что и левый аргумент. Первым элементом массива является левый операнд; второй элемент формируется из байт памяти, непосредственно следующих за байтами, содержащими первый элемент, и так далее. Например, если в программе есть строка:
int *array = (int *) malloc (len * sizeof (int));
то вы можете вывести содержимое array с помощью:
p *array@len
Левый операнд операции @
должен находиться в памяти. Значения массивов, полученных операцией @
, при индескации ведут себя точно так же, как и другие массивы, и приводятся к указателям при использовании в выражениях. Искусственные массивы чаще всего появляются в выражениях через историю значений (см. История значений), после вывода одного из них.
Другой способ создания искусственного массива - использование приведения типов. Оно заново интерпретирует значение так, как если бы оно было массивом. Значение не обязано находиться в памяти:
(gdb) p/x (short[2])0x12345678 $1 = {0x1234, 0x5678}
Если вы опускаете длину массива (как в (type[])value), GDB для удобства вычисляет его размер для заполнения значениями как sizeof( value ) / sizeof( type ). Например:
(gdb) p/x (short[])0x12345678 $2 = {0x1234, 0x5678}
Иногда механизма искусственных массивов бывает недостаточно; в сравнительно сложных структурах данных, интересующие нас элементы могут не быть смежными - например, если вас интересуют значения указателей в массиве. Одно из полезных решений этой проблемы - использование вспомогательной переменной (см. Вспомогательные переменные) в качестве счетчика в выражении, выводящем первое интересующее нас значение, а затем повторять это выражение нажатием Enter
. Предположим, например, у вас есть массив dtab указателей на структуры, и вас интересуют значения полей fv в каждой структуре. Ниже приведен пример ваших возможных действий:
set $i = 0 p dtab[$i++]->fv Enter Enter ...
По умолчанию, GDB печатает значение в соответствии с его типом. Это не всегда отвечает вашему желанию. Например, вы можете захотеть вывести число в шестнадцатеричной записи, или указатель в десятичной. Или вы можете захотеть просмотреть данные по некоторому адресу в памяти в виде строки символов или в виде инструкций. Для этого, при выводе значения укажите формат вывода.
Простейшим применением форматов вывода является форматирование вывода уже вычисленного выражения. Это осуществляется путем начала параметров команды print с косой черты и символа формата. Поддерживаются следующие символы формата:
Например, чтобы вывести счетчик программы в шестнадцатеричном виде (см. Регистры), введите:
p/x $pc
![]() | Обратите внимание, что перед косой чертой не требуется пробела, потому что имена команд в GDB не могут содержать косую черту. |
Чтобы вывести последнее значение из истории значений в другом формате, вы можете воспользоваться командой print только с указанием формата и без выражения. Например, p/x выведет последнее значение в шестнадцатеричной форме.
Вы можете использовать команду x (от слова examine) для проверки памяти в одном из нескольких форматов, независимо от типов данных вашей программы.
n, f и u - необязательные параметры, определяющие, сколько памяти отобразить и в каком формате; addr - это выражение, задающее адрес, с которого вы хотите начать отображение памяти. Если вы используете значения по умолчанию для nfu, то вам не нужно вводить косую черту "/". Некоторые команды устанавливают удобные значения по умолчанию для адреса addr.
1
. Он определяет, сколько памяти отобразить (считая в единицах u). Например, x/3uh 0x54320 - запрос на вывод трех полуслов (h) памяти в формате беззнаковых десятичных целых (u), начиная с адреса 0x54320. x/4xw $sp
выводит четыре слова (w) памяти, расположенные над указателем стека (здесь "$sp"; см. Регистры), в шестнадцатеричном виде (x).
Так как все буквы, обозначающие размер единиц измерения, отличаются от букв, определяющих форматы вывода, вы не должны запоминать, формат или размер единиц измерений указывается раньше; это можно делать в любом порядке. Спецификации вывода "4xw" и "4wx" означают в точности одно и то же. (Однако, число n должно быть первым; "wx4" не сработает).
Хотя размер единицы измерения u игнорируется для форматов s и i, тем не менее вы можете воспользоваться счетчиком повторений n; например, 3i указывает, что вы хотите вывести три машинные инструкции, включая любые операнды. Команда disassemble предоставляет альтернативный способ просмотра машинных инструкций; см. Исходный текст и машинный код.
Все значения по умолчанию для аргументов команды x разработаны таким образом, чтобы облегчить продолжение сканирования памяти с минимальными конкретизациями при очередном использовании x. Например, после того, как вы просмотрели три машинные инструкции с помощью x/3i addr, вы можете просмотреть следующие семь, используя просто x/7. Если вы повторяете команду x нажатием Enter
, число повторений n остается прежним; другие аргументы берутся по умолчанию, как для последовательных использований x.
Адреса и их содержимое, выводимые командой x, не сохраняются в истории значений, так как они мешали бы. Вместо этого, GDB делает их доступными для последующего использования в выражениях как значения вспомогательных переменных $_
и $__
. После команды x, последний проверенный адрес доступен для использования в выражениях в вспомогательной переменной $_
. Содержимое этого адреса, проверенного только что, доступно во вспомогательной переменной $__
.
Если команде x задан счетчик повторений, адрес и его содержимое сохраняются из последнего выведенного элемента памяти; это не то же самое, что последний выведенный адрес, если в последней строке вывода были отображены несколько элементов.
Если вам необходимо часто выводить значение какого-либо выражения (чтобы увидеть, как оно меняется), вы можете добавить его в список автоматического отображения, чтобы GDB выводил его значение каждый раз при остановке вашей программы. Каждому выражению, добавленному в список, присваивается идентификационный номер; чтобы удалить выражение из списка, вы указываете этот номер. Автоматическое отображение выглядит следующим образом:
2: foo = 38 3: bar[5] = (struct hack *) 0x3804
Это отображение показывает номера элементов, выражения и их текущие значения. Как и при отображении, запрашиваемом вручную с помощью x или print, вы можете указать предпочитаемый формат вывода; фактически, display определяет, следует использовать print или x, в зависимости от того, на сколько жесткая ваша спецификация формата: используется x, если вы указываете размер элемента или один из двух форматов (i и s), которые поддерживаются только x; в остальных случаях используется print.
Например, команда display/i $pc
может быть полезна, чтобы при каждой остановке видеть машинную инструкцию, которая будет выполняться следующей ($pc
- это общее обозначение счетчика программы; см. Регистры).
Если отображаемое выражение обращается к локальным переменным, оно не имеет смысла вне того лексического контекста, для которого оно устанавливалось. Такое выражения отключается, как только выполнение входит в контекст, где одна из его переменных становится неопределенной.
Например, если вы дадите команду display last_char, находясь внутри функции с аргументом last_char, GDB будет отображать этот аргумент, пока программа останавливается внутри этой функции. Как только она остановится где-то еще - где нет переменной last_char - отображение будет отключено автоматически. Вы можете снова включить его при следующей остановке программы там, где last_char будет вновь иметь смысл.
GDB предоставляет следующие способы управления выводом массивов, структур и символов.
Данные параметры полезны при отладке программ на любом языке:
При выводе адреса в символьной форме, GDB обычно выводит ближайший предшествующий символ плюс смещение. Если этот символ не определяет адрес однозначно (например, это имя, областью действия которого является один исходный файл), вам может потребоваться дать пояснения. Один из способов это сделать - с помощью info line; например, info line *0x4537. Альтернативный способ заключается в том, чтобы GDB выводил имя исходного файла и номер строки при выводе символьного адреса:
Другая ситуация, в которой полезно показывать имена файлов и номера строк, возникает при дизассемблировании кода; GDB показывает вам номер строки и исходный файл, которые соответствуют каждой инструкции.
Вы также можете захотеть видеть символьную форму только в том случае, если выводимый адрес достаточно близок к ближайшему предшествующему символу:
Если у вас есть указатель, и вы не знаете, на что он указывает, попробуйте использовать команду set print symbol-filename on. Затем вы можете определить название и исходный файл переменной, на которую он указывает, используя p/a указатель. Это интерпретирует адрес в символьной форме. Например, здесь GDB показывает, что переменная ptt указывает на другую переменную t, определенную в файле hi2.c
:
(gdb) set print symbol-filename on (gdb) p/a ptt $4 = 0xe008 <t in hi2.c>
![]() | Для указателей, указывающих на локальные переменные, p/a не показывает символьное имя и имя файла, которому принадлежит объект ссылки, даже если установлена соответствующая опция set print. |
Другие установки управляют выводом объектов различных типов:
Следующие установки представляют интерес при отладке программ на С++:
Значения, выведенные командой print, сохраняются в истории значений GDB. Это позволяет вам обращаться к ним в других выражениях. Значения сохраняются, пока таблица символов не будет заново считана или уничтожена (например, командами file или symbol-file). При изменении таблицы символов, история значений уничтожается, так как значения могут содержать указатели на типы, определенные в таблице символов.
Выведенным значениям присваиваются номера в истории, по которым вы можете на них ссылаться. Эти номера являются последовательными целыми числами, начинающимися с 1
. Команда print показывает номер в истории, присвоенный значению, выводя перед ним $num
= , где num - это номер в истории.
Для обращения к какому-либо предшествующему значению, используйте $
, за которым следует номер в истории. Способ, которым print маркирует вывод продуман так, чтобы напоминать вам об этом. Просто $
ссылается на самое последнее значение в истории, а $$
- на предпоследнее. $$n
ссылается на n-е с конца значение; $$2
- значение, находящееся перед $$
, $$1
эквивалентно $$
, а $$0
эквивалентно $
.
Предположим, например, вы только что вывели указатель на структуру и хотите посмотреть ее содержимое. Для этого достаточно ввести:
p *$
Если у вас есть цепочка структур, где компонента next указывает на следующую структуру, вы можете вывести содержимое следующей структуры так:
p *$.next
Вы можете выводить последовательные звенья в цепочке повторяя эту команду. Это можно сделать простым нажатием Enter
.
![]() | В историю записываются значения, а не выражения. Если значение x равно 4, и вы наберете:
print x set x=5 то значение, записанное в историю значений командой print, будет по-прежнему равно 4, хотя значение x изменилось. |
Нажатие Enter
для повтора show values n действует точно так же, как show values +.
GDB предоставляет вспомогательные переменные, которые вы можете в нем использовать, чтобы сохранить значение и обратиться к нему позже. Эти переменные существуют только в GDB; они не являются частью вашей программы и установка вспомогательной переменной не оказывает непосредственного влияния на ее дальшейшее выполнение. Поэтому вы можете пользоваться ими совершенно свободно.
Имена вспомогательных переменных начинаются с $
. Любое имя с приставкой $
может использоваться для вспомогательной переменной, если только оно не является предопределенным машинно-зависимым именем регистра, (см. Регистры). Ссылки на историю значений, напротив, есть числа, которым предшествует $
. см. История значений.
Вы можете сохранить значение во вспомогательной переменной с помощью выражения присваивания, как если бы вы устанавливали переменную в вашей программе. Например:
set $foo = *object_ptr
сохранит в $foo
значение объекта, на который указывает object_ptr.
Первое использование вспомогательной переменной создает ее, но значением переменной будет void
, пока вы не присвоите ей новое. С помощью другого присваивания вы можете в любое время изменить значение.
Вспомогательные переменные не имеют фиксированного типа. Вы можете присваивать вспомогательной переменной значение любого типа, включая структуры и массивы, даже если у этой переменной уже было значение другого типа. Будучи использованной в выражении, вспомогательная переменная имеет тип своего текущего значения.
Один из способов использования вспомогательных переменных - в качестве увеличивающегося счетчика или продвигающегося указателя. Например, чтобы напечатать поле из последовательных элементов массива структур:
set $i = 0 print bar[$i++]->contents
Повторяйте эту команду нажатием Enter
.
Некоторые вспомогательные переменные создаются GDB автоматически, и им присваиваются значения, которые вероятно могут оказаться полезными.
$_
$_
устанавливается автоматически командой x в последний проверенный адрес (см. Проверка памяти). Другие команды, которые устанавливают адрес по умолчанию для проверки командой x, также присваивают $_
упомянутый адрес; эти команды включают info line и info breakpoint. Переменная $_
имеет тип void *
, если только она не установлена командой x; в этом случае она является указателем на тип переменной $__
. $__
$__
устанавливается автоматически командой x в значение, находящееся по последнему проверенному адресу. Ее тип выбирается соответствующим формату, в котором это значение было выведено. $_exitcode
$_exitcode
автоматически присваивается код завершения, когда отлаживаемая программа завершается.
В выражениях, вы можете обращаться к содержимому машинных регистров, обозначая их как переменные с именами, начинающимися с $
. Имена регистров различаются от машины к машине; для вывода имен регистров, используемых на вашей машине, воспользуйтесь командой info registers.
GDB распознает четыре "стандартных" имени регистров, которые доступны (в выражениях) на большинстве машин - если только они не конфликтуют с каноническими для архитектуры мнемониками регистров:
$pc
$sp
$fp
$ps
Например, вы можете вывести счетчик программы в шестнадцатеричной записи с помощью:
p/x $pc
или вывести следующую исполняемую инструкцию, используя:
x/i $pc
или увеличить указатель вершины стека на четыре с помощью:
set $sp += 4
![]() | Это способ удаления одного слова из стека на машинах, где стеки растут по убыванию в памяти (большинство современных машин). Это предполагает, что выбран самый внутренний кадр стека; установка $sp недопустима, когда выбраны другие кадры стека. Чтобы удалить целые кадры из стека, независимо от архитектуры машины, используйте клавишу Enter . |
Когда возможно, эти четыре стандартных имени регистров доступны на вашей машине, даже если она имеет другие канонические мнемоники, если не возникает конфликта. Команда info registers выводит канонические имена.
Когда регистр проверяется таким образом, GDB всегда рассматривает содержимое обычного регистра как целое. Некоторые машины имеют специальные регистры, которые могут содержать только значение с плавающей точкой; их значения трактуются как величины с плавающей точкой. Не существует способа сослаться на содержимое обычного регистра как на величину с плавающей точкой (хотя вы можете вывести его значение командой print как величину с плавающей точкой, используя print/f $regname
).
Некоторые регистры имеют различные "необработанные" и "виртуальные" форматы данных. Это означает, что формат данных, в котором операционная система сохраняет содержимое регистра, не совпадает с тем, который обычно воспринимается вашей программой. Например, регистры сопроцессора с плавающей точкой 68881 всегда сохраняются в "расширенном" (необработанном) формате, но все программы на С работают с "двойным" (виртуальным) форматом. В подобных случаях, GDB обычно работает только с виртуальным форматом (форматом, имеющим смысл в вашей программе), но команда info registers выводит данные в обоих форматах.
Обычно значения регистров относятся к выбранному кадру стека (см. Выбор кадра). Это значит, что вы получаете значение, которое содержалось бы в регистре, если бы произошел выход из всех внутренних кадров стека и их сохраненные регистры были бы восстановлены. Для того чтобы увидеть истинное содержимое аппаратных регистров, вы должны выбрать самый внутренний кадр (с помощью "frame 0").
Однако, GDB, исходя из машинного кода, сгенерированного вашим компилятором, должен установить, где сохранены регистры. Если некоторые регистры не сохранены, или если GDB не в состоянии найти сохраненные регистры, выбранный кадр стека не имеет значения.
В зависимости от конфигурации, GDB может выдать вам больше информации о состоянии аппаратных средств поддержки вычислений с плавающей точкой.
Команды, описанные в этой главе, позволяют вам получить информацию о символах (именах переменных, функций и типов), определенных в вашей программе. Эта информация присуща тексту вашей программы и не изменяется при ее выполнении. GDB находит эту информацию в таблице символов вашей программы, в файле, определенном при вызове GDB (см. описание утилиты gdb).
Иногда вам может потребоваться сослаться на символы, содержащие необычные знаки, которые GDB обычно трактует как разделители слов. Наиболее часто это встречается при ссылках на статические переменные в других исходных файлах (см. Переменные программы). Имена файлов записаны в объектных файлах как отладочные символы, но GDB обычно производит разбор типичного имени файла, например foo.c
, как три слова: "foo", ".", "c". Чтобы GDB идентифицировал foo.c
как одно слово, заключите его в одинарные кавычки; например:
p 'foo.c'::x
ищет значение x в области видимости файла foo.c
.
Если вы думаете, что нашли ошибку в своей программе, вы можете захотеть выяснить наверняка, приведет ли исправление кажущейся ошибки к правильным результатам в остальной части программы. Вы можете получить ответ экспериментируя, используя средства GDB для изменения исполнения программы.
Например, вы можете сохранить новые значения в переменных или ячейках памяти, подать своей программе сигнал, перезапустить ее с другого адреса или даже преждевременно вернуться из функции.
Для изменения значения переменной, вычислите выражение присваивания. См. Выражения. Например,
print x=4
сохраняет значение "4" в переменной x и затем выводит значение данного выражения (которое равно "4").
Если вы не хотите видеть значение присваивания, используйте команду set вместо print. Команда set аналогична команде print за исключением того, что значение выражения не выводится и не заносится в историю значений (см. История значений). Выражение вычисляется только ради его действия.
Если начало строки параметров команды set выглядит идентично подкоманде set, используйте вместо нее команду set variable. Эта команда аналогична set, но не имеет подкоманд. Например, если в вашей программе есть переменная width, то вы получите ошибку, если попытаетесь установить новое значение просто с помощью set width=13, потому что GDB имеет команду set width:
(gdb) whatis width type = double (gdb) p width $4 = 13 (gdb) set width=47 Invalid syntax in expression.
Недопустимое выражение, это, конечно, "=47". Для того чтобы действительно установить переменную программы width, используйте
(gdb) set var width=47
GDB допускает больше неявных преобразований в присваиваниях, чем С; вы можете свободно сохранить целое значение в переменной-указателе и наоборот, преобразовать любую структуру к любой другой, которая имеет ту же длину или короче.
Для сохранения значений в произвольных местах памяти, используйте конструкцию "{...}" для создания значения определенного типа по определенному адресу памяти (см. Выражения). Например, {int
}0x83040 ссылается на ячейку памяти 0x83040 как на целое (что предполагает соответствующий размер и представление в памяти), и:
set {int}0x83040 = 4
записывает в эту ячейку памяти значение "4".
Обычно, когда вы продолжаете выполнение программы, вы делаете это с того места, где она остановилась, командой continue. Вместо этого, вы можете продолжить выполнение с любого выбранного адреса при помощи следующих команд:
На многих системах, вы можете достичь такого же результата, как и с командой jump, сохранением нового значения в регистр $pc
. Отличие заключается в том, что это не начинает выполнение вашей программы, а лишь изменяет адрес, с которого будет выполняться программа, когда вы продолжите выполнение. Например:
set $pc = 0x485
выполняет следующую команду continue или команду пошагового выполнения с адреса 0x485, а не с того адреса, где ваша программа остановилась. См. Продолжение и выполнение по шагам.
Наиболее общий случай использования команды jump состоит в возврате к выполнению части программы, возможно с большим количеством установленных точек останова, которая уже выполнилась, для того чтобы исследовать выполнение более детально.
Вызов команды signal отличается от вызова утилиты kill из оболочки. Подача сигнала посредством kill заставляет GDB решать, что делать с сигналом, в зависимости от таблиц обработки сигналов (см. Сигналы). Команда signal передает сигнал непосредственно вашей программе.
Когда вы используете return, GDB уничтожает выбранный кадр стека (и все кадры внутри него). Вы можете считать это преждевременным возвратом из уничтоженного кадра. Если вы хотите указать возвращаемое значение, задайте его в качестве аргумента к return.
Это выталкивает выбранный кадр стека (см. Выбор кадра) и все другие кадры внутри него, оставляя самым внутренним кадр, из которого произошел вызов. Этот кадр становится выбранным. Указанное значение сохраняется в регистрах, используемых для возвращаемых функцией значений.
Команда return не возобновляет выполнение; она оставляет программу остановленной в том состоянии, в котором бы она была сразу после возврата из функции. Напротив, команда finish (см. Продолжение и выполнение по шагам) возобновляет выполнение до естественного возврата из выбранного кадра стека.
Вы можете использовать этот вариант команды print, если хотите выполнить функцию из вашей программы, не засоряя вывод пустыми возвращенными значениями. Если результат не пустой (void
), он выводится и сохраняется в истории значений.
Устанавливаемая пользователем переменная call_scratch_address задает положение рабочей области, которая будет использоваться, когда GDB вызывает функцию на целевой машине. Это необходимо, так как обычный метод размещения рабочей области в стеке не работает в системах с раздельными областями команд и данных.
По умолчанию, GDB открывает файл, содержащий исполняемый код вашей программы (или файл дампа памяти), в режиме только для чтения. Это предотвращает случайные изменения машинного кода; но это также предотвращает и преднамеренное исправление бинарного файла вашей программы.
Если вы хотите иметь возможность исправлять двоичный код, вы можете указать это явно с помощью команды set write. Например, вы можете захотеть установить внутренние флаги отладки или даже сделать аварийные исправления.
Предыдущий раздел: Тематические статьи