Вы можете запустить отлаживаемую программу, возможно, с аргументами, в любой среде по вашему выбору. Вы можете перенаправлять ввод и вывод программы, отлаживать уже работающий процесс или уничтожить процесс-потомок.
Чтобы запустить выполнение отлаживаемой программы, выберите `Program => Run'. У вас спросят, какие аргументы нужно передать программе. Можно либо выбрать из списка ранее использовавшиеся аргументы, либо ввести новые в текстовом поле. Потом нажмите кнопку `Run', и начнется выполнение программы с указанными аргументами.
Чтобы повторно запустить программу с теми же аргументами, выберите
`Program => Run Again' или нажмите кнопку `Run' на
панели команд. Или можно ввести run
и затем аргументы в
подсказке отладчика.
При щелчке на `Run' программа сразу начинает выполняться. См. раздел 5. Останов программы, обсуждение того, как подготовить останов программы. Как только программа остановилась, вы можете вызывать ее функции для исследования данных. См. раздел 7. Исследование данных, для получения подробностей.
Если время изменения файла символов изменилось с последнего раза, когда GDB считывал его, GDB сбрасывает таблицу символов и считывает ее снова. При этом GDB и DDD стараются сохранить текущее состояние отладчика, в частности, точки останова.
Аргументы программы задаются аргументами команды `run', они указываются через `Program => Run'.
В GDB, эти аргументами передаются оболочке, которая раскрывает знаки
подстановки и производит перенаправление ввода/вывода, а от оболочки ---
вашей программе. Переменная среды SHELL
(если такая есть)
указывает, какую оболочку использует GDB. Если вы не определяете
SHELL
, GDB использует `/bin/sh'.
Если вы пользуетесь другим подчиненным отладчиком, точная семантика интерпретации аргументов зависит от вашего подчиненного отладчика. Как правило для передачи аргументов применяется оболочка, чтобы вы могли использовать при описании аргументов обычные соглашения (такие как раскрытие символов подстановки или переменные).
Обычно ваша программа наследует среду от подчиненного отладчика, который наследует среду от DDD, а он в свою очередь -- от процесса-предка (обычно это оболочка).
В GDB, вы можете изменять те части среды, которые затрагивают вашу
программу, используя команды set environment
и unset
environment
. См. раздел `Your Program's Environment' в Debugging with GDB, для дополнительной информации.
DDD устанавливает следующие переменные среды:
DDD
DDD
, отлаживаемая программа (или подчиненный отладчик) может
узнать, что ее вызвал DDD.
TERM
TERMCAP
PAGER
Подчиненный отладчик, в свою очередь, может устанавливать или снимать некоторые переменные среды.
Программа обычно наследует рабочий каталог от подчиненного отладчика, который наследует его от DDD, а он в свою очередь -- от ПРОЦЕССА-предка (обычно это оболочка).
Вы можете изменить рабочий каталог подчиненного отладчика через `File => Change Directory' или с помощью его команды `cd'.
По умолчанию отлаживаемая программа производит ввод и вывод в консоли отладчика. Обычно вы можете перенаправить ввод и/или вывод вашей программы с помощью средств перенаправления оболочки с аргументами --- то есть задавая дополнительные аргументы вида `< ввод' или `> вывод'. Такие перенаправления можно писать точно так же, как и остальные аргументы (см. раздел 6.1.1 Аргументы программы).
Внимание: Хотя перенаправление ввода и вывода работают, вы не можете использовать конвейры для передачи вывода отлаживаемой программы другой программе; если вы попытаетесь это сделать, DDD может начать отлаживать не ту программу. См. раздел 6.3 Подсоединение к процессу, описание альтернативного способа.
Если вывод команд посылается на консоль отладчика, DDD не может отличить вывод отлаживаемой программы от вывода подчиненного отладчика.
Определенные виды вывода программ могут смущать DDD, сюда входят:
Если ваша программа выводит любые из этих строк, вы можете столкнуться с проблемами, когда DDD ошибочно принимает их за вывод отладчика. Эти проблемы можно легко обойти, перенаправив ввод/вывод программы, например, в отдельное окно выполнения (см. раздел 6.2 Использование окна выполнения).
Если подчиненный отладчик изменяет принимаемые по умолчанию установки
терминала, например, через команду stty
в файле инициализации,
DDD также может сбиться. То же относится к случаю, когда установки
терминала изменяет отлаживаемая программа.
Поведением консоли отладчика можно управлять с помощью следующего ресурса:
По умолчанию ввод и вывод вашей программы передаются на консоль отладчика. В качестве альтернативы DDD также может вызвать окно выполнения, где показывается терминальный ввод и вывод программы.(21)
Чтобы активизировать окно выполнения, выберите `Program => Run in Execution Window'.
Окно выполнения открывается автоматически, как только вы запускаете отлаживаемую программу. Пока оно активно, DDD перенаправляет в него стандартный ввод, вывод и протокол ошибок вашей программы. Заметьте, что устройство `/dev/tty' по-прежнему ссылается на консоль отладчика, не на окно выполнения.
Вы можете перекрыть установки потоков DDD, задавая в качестве аргументов альтернативные операции перенаправления. Например, чтобы программа читала из файла, но писала в окно выполнения, вызовите ее с аргументом `< файл'. Аналогично, чтобы перенаправить стандартный протокол ошибок на консоль отладчика, используйте `2> /dev/tty' (предполагается, что подчиненный отладчик и/или ваша оболочка UNIX поддерживают перенаправление стандартного протокола ошибок).
Вы можете настраивать окно выполнения DDD и использовать различные терминальные программы. Команда устанавливается через `Edit => Preferences => Helpers => Execution Window':
Ddd*termCommand: xterm -fn @FONT@ -e /bin/sh -c
Можно также установить тип терминала:
TERM
, которое следует передать
отлаживаемой программе. По умолчанию `xterm'.
Следующий ресурс говорит о том, активно окно выполнения или нет, как указано в `Program => Run in Execution Window'.
Если отлаживаемая программа уже работает в каком-то процессе, вы можете подсоединиться к этому процессу (вместо запуска нового с помощью `Run').(22)
Чтобы подсоединить DDD к процессу, выберите `File => Attach to Process'. Теперь вы можете выбрать процесс из списка. Выбрав, нажмите кнопку `Attach'.
После подготовки к отладке указанного процесса DDD первым делом останавливает его. Вы можете исследовать и изменять подсоединенный процесс, используя все команды DDD, которые обычно доступны, когда вы запустили процесс с помощью `Run'. Вы можете вставлять точки останова; пошагово проходить программу и продолжать ее выполнение; можете изменять память. Если вы сочли, что лучше оставить процесс работающим, вы можете использовать после подсоединения `Continue'.
При использовании `Attach to Process' вам нужно сначала указать, что за программа выполняется в процессе, с помощью `Open Program' и загрузить ее таблицу символов.
Закончив отладку подсоединенного процесса, вы можете освободить его от контроля DDD, используя `File => Detach Process'. При отсоединении процесс продолжает работу. После `Detach Process' этот процесс и DDD снова становятся совершенно не зависящими друг от друга, а вы можете подсоединиться к другому процессу или запустить новый с помощью `Run'.
Вы можете настраивать вид, в котором вам показывается список процессов, определяя другие команды для перечисления процессов. Смотрите `Edit => Preferences => Helpers => List Processes'; См. раздел 6.3.1 Настройка подсоединения к процессу, для получения подробностей.
При подсоединении к процессу (см. раздел 6.3 Подсоединение к процессу), DDD
использует для получения списка процессов команду ps
. Эта
команда определяется ресурсом `psCommand'.
ps
. В
зависимости от вашей системы, полезные альтернативы включают ps
-ef
и ps ux
. Первая строка вывода должна содержать заголовок
`PID', либо каждая строка должна начинаться с ID процесса.
Заметьте, что DDD фильтрует вывод этой команды; процесс показывается, только если к нему можно подсоединиться. Процесс самого DDD, а также процесс подчиненного отладчика также опускаются.
После того как программа запущена, она работает, пока не произойдет одно из следующих событий:
DDD показывает текущее состояние программы в консоли отладчика. Текущая выполняемая позиция отмечается стрелкой.
Если установлено `Edit => Preferences => General => Uniconify When Ready', DDD автоматически разворачивает себя, когда программа останавливается. Таким образом вы можете свернуть DDD на время длительных вычислений, и он сам развернется, когда программа остановится.
Чтобы возобновить выполнение в текущей позиции, щелкните на кнопке `Continue'. Все точки останова в текущей выполняемой позиции пропускаются.
Чтобы выполнить ровно одну строку исходного кода, щелкните на кнопке `Step'. Программа будет выполняться до тех пор, пока не достигнет другой исходной строки, которая может оказаться в другой функции. Потом программа останавливается, а управление возвращается DDD.
Внимание: Если вы используете кнопку `Step', когда управление принадлежит функции, которая была скомпилирована без отладочной информации, выполнение продолжается до тех пор, пока управление не достигнет функции, имеющей отладочную информацию. Аналогично, управление не будет заходить в функции без отладочной информации. Чтобы пошагово пройти такие функции, используйте кнопку `Stepi' (см. раздел 8.2 Выполнение машинного кода).
В GDB кнопка `Step' останавливается только на первой инструкции
исходной строки. Это предотвращает повторные остановы, которые раньше
случались в выражениях switch
, циклах for
и так далее.
`Step' продолжает до останова, если в пределах строки вызвана
функция с отладочной информацией.
Кроме того, `Step' в GDB входит в подпрограмму только в том случае, если для нее есть информация о номерах строк. Иначе эта кнопка действует как `Next'.
Чтобы продолжить до следующей строки в текущей функции, щелкните на кнопке `Next'. Это похоже на `Step', но все вызовы функций, появившиеся внутри текущей строки кода, выполняются без останова.
Выполнение останавливается, когда управление достигает другой строки кода в первоначальном уровне стека, в том, который выполнялся, когда вы нажали `Next'.
Чтобы продолжить выполнение до тех пор, пока не будет достигнута некая позиция, используйте средство `Continue Until Here' из всплывающего меню для строки. См. раздел 5.1.4 Временные точки останова, обсуждение этой темы.
Чтобы продолжить до тех пор, пока не будет достигнута строка, расположенная дальше текущей, щелкните на кнопке `Until'. Это полезно в тех случаях, когда нежелательно пошагово проходить цикл более одного раза.
`Until' похожа на `Next', только когда `Until' встречает переход, она автоматически продолжает выполнение, пока счетчик инструкций не станет больше адреса этого перехода.
Это означает, что когда вы достигаете конца цикла после его пошагового прохода, `until' делает так, что программа продолжает выполняться до выхода из этого цикла. Напротив, щелчок на `Next' в конце цикла просто переносит на его начало, и вы вынуждены пошагово проходить следующую итерацию.
`Until' всегда останавливает вашу программу, если она пытается покинуть текущий фрейм стека.
`Until' работает посредством пошагового выполнения инструкций, и следовательно, работает медленнее, чем продолжение до точки останова.
Чтобы продолжить выполнение до тех пор, пока текущая функция не вернется, используйте кнопку `Finish'. Возвращенное значение (если оно есть) будет напечатано.
Обычно, когда вы продолжаете выполнение программы, вы делаете это из того места, где она остановилась. Вместо этого вы можете продолжить с другого адреса по вашему выбору.
Чаще всего эта возможность применяется для возврата к старому состоянию --- вероятно, после установки новых точек останова в какой-то уже выполнившейся части программы, с целью более детального изучения ее работы.
Чтобы установить выполняемую позицию в текущей точке, используйте `Set Execution Position' из всплывающего меню точки останова. Этот пункт также доступен по нажатию и удержанию кнопки `Break/Clear'.(23)
В качестве более быстрой альтернативы, вы также можете нажать на стрелке первую кнопку мыши и перенести ее в другую позицию.(24)
Перемещение выполняемой позиции не изменяет ни текущий фрейм стека, ни указатель стека, ни содержимое какой-либо ячейки памяти, ни какие-либо регистры, кроме счетчика инструкций.
Некоторые подчиненные отладчики (в частности GDB) позволяют устанавливать новую выполняемую позицию в другой функции, не в текущей. Это может привести к странным результатам, если две эти функции получают разные наборы аргументов или локальных переменных. По этой причине при перемещении выполняемой позиции запрашивается подтверждение, если указанная строка не входит в текущую функцию.
После переноса выполняемой позиции щелкните на `Continue', чтобы продолжить выполнение.
Когда ваша программа остановилась, первое, что вам нужно знать, -- это где она остановилась, и как она там оказалась.
При каждом вызове функции, генерируется информация об этом вызове. Сюда включаются позиция вызова в программе, его аргументы и локальные переменные вызываемой функции. Эта информация сохраняется в блоке данных, называемом фреймом стека. Фреймы стека размещаются в области памяти, называемой стеком вызовов.
Когда программа останавливается, команды DDD для исследования стека позволяют вам увидеть всю эту информацию.
Один из фреймов стека является выбранным, и многие команды DDD неявно ссылаются именно на выбранный фрейм. В частности, всякий раз, когда вы запрашиваете у DDD значение некоторой переменной из вашей программы, это значение находится в текущем фрейме. Есть специальные команды DDD для выбора интересующего вас фрейма.
Стек вызовов разделяется на непрерывные фрагменты, называемые фреймами стека, или просто фреймами, для краткости; каждый фрейм представляет собой данные об одном вызове одной функции. Фрейм содержит переданные этой функции аргументы, ее локальные переменные и адрес, на котором она выполняется.
Когда программа начинает выполняться, стек содержит только один фрейм,
фрейм функции main
. Он называется начальным или
внешним фреймом. При каждом вызове функции создается новый фрейм.
При каждом возврате функции ее фрейм удаляется. Если функция
рекурсивная, у нее может быть несколько фреймов. Фрейм функции, в
которой на самом деле производятся вычисления, называется самым
внутренним фреймом. Это последний созданный фрейм, который еще
существует.
Внутри программы фреймы стека идентифицируются по адресам. Фрейм стека состоит из многих байт, у каждого свой адрес; для каждого типа компьютеров существует соглашение, по которому выбирается один байт, служащий адресом всего фрейма. Обычно, пока выполнение имеет место в каком-то фрейме, его адрес хранится в регистре, называемом регистр указателя фрейма.
GDB присваивает всем существующим фреймам стека номера, начиная с нуля для самого внутреннего, единицы для фрейма, вызвавшего нулевой, и так далее наверх. Эти номера на самом деле не существуют в вашей программе; GDB присваивает их, чтобы у вас была возможность адресации фреймов стека в командах GDB.
DDD предоставляет окно следа вызовов, дающее обзор пути, по которому прошла программа. В нем показывается одна строка на каждый фрейм, начиная от текущего (нулевого), далее следует фрейм, вызвавший текущий (первый), и так до вершины стека.
Чтобы включить окно следа вызовов, выберите `Status => Backtrace'.
При использовании GDB каждая строка следа вызовов показывает номер фрейма и имя его функции. Также показывается значение счетчика инструкций -- если только вы не применили команду GDB `set print address off'. Кроме того, выводятся имя исходного файла и номер строки, а также аргументы функции. Значение счетчика инструкций опускается, если оно находится в начале кода для этого номера строки.
Большинство команд для исследования стека и других данных программы действуют на фрейм, который выбран в данный момент. Вот команды для выбора фрейма стека.(25)
В окне следа вызовов вы можете выбирать произвольный фрейм, чтобы перемещаться от одного фрейма к другому. Просто щелкните на нужном фрейме.
Кнопка `Up' выбирает функцию, которая вызвала текущую -- то есть перемещает на один фрейм вверх.
Кнопка `Down' выбирает функцию, которая была вызвана из текущей --- то есть перемещает на один фрейм вниз.
Вы также можете непосредственно напечатать команды up
и
down
в подсказке отладчика. Ctrl+Up и Ctrl+Down,
соответственно, также перемещают по стеку.
Операции `Up' и `Down' можно отменить через `Edit => Undo'.
Если вы взглянете на пункт меню `Edit => Undo' после команды выполнения, вы обнаружите, что DDD предоставляет возможность отменять команды выполнения, так же, как любые другие команды. Значит ли это, что DDD позволяет перемещаться назад во времени, отменяя и выполнение программы, и все ее побочные эффекты?
К сожалению, должны вас разочаровать. DDD не может отменить то, что сделала ваша программа. (После недолгого размышления вы поймете, что это невозможно в общем случае.) Однако, DDD может сделать кое-что другое: он может показать ранее записанные состояния вашей программы.
После "отмены" команды выполнения (через `Edit => Undo' или кнопку `Undo'), позиция выполнения возвращается на старое место, а отображаемые переменные принимают более ранние значения. Состояние программы в действительности не меняется, но DDD показывает записанный вид более раннего состояния.
В этом, так называемом историческом режиме, большинство обычных команд DDD, которые запрашивают у программы информацию, выключаются, поскольку у отладчика нельзя получать сведения о предыдущем состоянии. Однако, вы можете исследовать текущую выполняемую позицию или отображаемые переменные. Используя `Undo' и `Redo' вы можете перемещаться назад и вперед во времени, чтобы понять, как программа достигла текущего состояния.
Чтобы вы знали, что действует исторический режим, стрелка выполнения (которая показывает прошлую позицию выполнения) и отображения переменных рисуются штриховыми линиями. Более того, в строке состояния сообщается, что вы видите прошлое состояние программы.
Исторический режим работает так: при каждой остановке программы DDD сохраняет текущую выполняемую позицию и отображаемые переменные. Также сохраняется информация о следе вызовов, нитях и регистрах, если открыты соответствующие диалоговые окна. При "отмене" команды выполнения DDD обновляет свой вид по этим сохраненным данным вместо обращения к программе.
Если вы хотите собирать такую информацию без прерывания программы ---
внутри цикла, к примеру, -- вы можете поставить точку останова с
командой cont
(см. раздел 5.1.8 Команды точек останова). Когда
достигается подобная точка останова, DDD останавливается, сохраняет
данные и выполняет команду `cont', продолжая выполнение. Используя
позднее `Undo', вы можете вернуться и посмотреть на каждую
отдельную итерацию цикла.
Чтобы покинуть исторический режим, можно применять команду `Redo' до тех пор, пока не вернетесь к текущему состоянию программы. Однако, любая команда DDD, которая ссылается на состояние программы, также сразу выводит из исторического режима, применяясь к текущему состоянию программы. Например, `Up' сразу покидает исторический режим и выбирает другой фрейм в восстановленном текущем состоянии программы.
Если вы хотите увидеть историю конкретной переменной, как она записана
во время остановок программы, вы можете ввести команду DDD
graph history имя
Она вернет список всех ранее записанных значений переменной имя в форме массива. Обратите внимание: чтобы значения переменной имя сохранялись, она должна отображаться при останове программы.
В некоторых операционных системах одна программа может иметь более одной нити выполнения. Точная семантика нитей меняется от системы к системе, но в общем нити одной программы подобны нескольким процессам --- за исключением того, что они разделяют одно адресное пространство (то есть они могут считывать и изменять одни и те же переменные). С другой стороны, каждая нить имеет свои собственные регистры и стек выполнения и, возможно, личную память.
Для целей отладки DDD позволяет получать список активных в данный момент нитей и выбирать текущую нить -- на которой фокусируется отладка. Всю информацию о программе DDD показывает с точки зрения текущей нити.(26)
Чтобы получить список всех активных в текущий момент нитей вашей программы, выберите `Status => Threads'. Текущая нить подсвечивается. Чтобы сделать какую-то нить текущей, выберите ее.
При использовании JDB доступны дополнительные функции:
Для получения более подробной информации о нитях, смотрите документацию JDB и GDB (см. раздел `Debugging Programs with Multiple Threads' в Debugging with GDB).
Сигнал -- это асинхронное событие, которое может произойти в программе.
Операционная система определяет возможные виды сигналов и дает каждому
виду имя и номер. Например, SIGINT
в UNIX -- это сигнал,
который программа получает, когда вы нажимаете клавишу прерывания;
SIGSEGV
-- это сигнал, который программа получает, когда
ссылается на место в памяти, находящееся за пределами используемых
областей; SIGALRM
случается, когда истекает таймер (что
происходит, только если программа запросила сигнал по таймеру).
Некоторые сигналы, включая SIGALRM
, -- обычная часть работы
программы. Другие, такие как SIGSEGV
, указывают на ошибки; эти
сигналы фатальны (сразу уничтожают программу), если программа
заранее не определила другой способ их обработки. SIGINT
не
указывает на ошибку в программе, но обычно он фатален, так что может
служить для прерывания: для уничтожения программы.
GDB умеет детектировать любое появление сигнала в программе. Вы можете заранее сказать GDB, что нужно делать для каждого вида сигналов.
Обычно DDD настроен на игнорирование сигналов, не указывающих на
ошибку, вроде SIGALRM
(чтобы не вмешиваться в их роль в работе
программы), но он сразу останавливает вашу программу, когда приходит
сигнал ошибки. В DDD, вы можете просматривать и редактировать эти
установки через `Status => Signals'.
`Status => Signals' выводит панель, где показаны все виды сигналов, а также как GDB обрабатывает каждый из них. Для каждого сигнала доступны такие установки:
Stop
Print
Pass
Пункт `All Signals' особый. Изменение установки для него
влияет на все сигналы сразу -- кроме тех, что используются
отладчиком, обычно это SIGTRAP
и SIGINT
.
Чтобы отменить все изменения, используйте `Edit => Undo'. Кнопка `Reset' восстанавливает сохраненные параметры.
Когда сигнал останавливает вашу программу, он невидим, пока вы не продолжите выполнение. Затем программа видит сигнал, если в этот момент для данного сигнала действует установка `Pass'. Другими словами, после того как GDB сообщает о сигнале, вы можете изменить установку `Pass' в `Status => Signals' и контролировать таким образом, увидит программа сигнал или нет после продолжения.
Вы также можете сделать так, чтобы программа увидела сигнал, который она обычно не видит, или послать ей любой сигнал в любое время. Кнопка `Send' продолжит выполнение с того места, где программа остановилась, но сразу же пошлет ей указанный сигнал.
С другой стороны, вы также можете сделать так, чтобы программа не видела какой-то сигнал. Например, если программа остановилась из-за ошибки при обращении к памяти, вы могли бы записать в поврежденные переменные правильные значения и продолжить, надеясь увидеть дальнейшее выполнение; но программа, вероятно, может сразу же завершиться, если увидит этот фатальный сигнал. Чтобы предотвратить это, вы можете продолжить выполнение с помощью `Commands => Continue Without Signal'.
`Edit => Save Options' не сохраняет измененные установки для сигналов, поскольку они обычно бывают полезны только для какого-то одного проекта. Вместо этого установки сохраняются с текущим сеансом при использовании `File => Save Session As'.
Вы можете в любое время уничтожить процесс отлаживаемой программы с помощью кнопки `Kill'.
Уничтожение процесса бывает полезно, если вы хотите отлаживать дамп памяти, а не работающий процесс. GDB игнорирует дампы памяти, если ваша программа выполняется.
Кнопка `Kill' также полезна, если вы хотите перекомпилировать и перекомпоновать программу, поскольку на многих системах нельзя изменить выполняемый файл, когда он работает в каком-то процессе. В этом случае, когда вы в следующий раз щелкните на `Run', GDB заметит, что файл изменился, и считает таблицу символов заново (стараясь в то же время сохранить текущее состояние отладчика).