Текст книги "Основы программирования в Linux"
Автор книги: Нейл Мэтью
Соавторы: Ричард Стоунс
Жанры:
Программирование
,сообщить о нарушении
Текущая страница: 39 (всего у книги 67 страниц)
Вернемся к примеру программы. У вас есть ошибка. Вы можете изменить программу, вставив в нее дополнительный код для вывода значений переменных по мере выполнения программы, или применить отладчик для контроля над выполнением программы и просмотра ее состояния в ходе выполнения.
В коммерческих UNIX-системах есть ряд отладчиков, набор которых зависит от поставщика системы. Наиболее распространенные – adb, sdb, idebug и dbx. Более сложные отладчики позволяют просматривать с некоторой степенью детализации состояние программы на уровне исходного кода. Именно к таким относится отладчик GNU, gdb, который может применяться в системах Linux и многих вариантах UNIX. Существуют и внешние интерфейсы (или программы-клиенты) для gdb, делающие его более удобным для пользователя; к таким программам относятся xxgdb, KDbg и ddd. Некоторые IDE, например, те, с которыми вы познакомились в главе 9, также предоставляют средства отладки или внешний интерфейс для gdb. У редактора Emacs даже есть средство (gdb-mode), позволяющее запускать gdb в вашей программе, устанавливать точки останова и построчно просматривать выполнение исходного кода.
Для подготовки программы к отладке необходимо откомпилировать ее с одной или несколькими специальными опциями. Эти опции заставляют компилятор вставлять в программу дополнительную отладочную информацию. Она включает в себя идентификаторы и номера строк – сведения, которые отладчик может использовать, чтобы показать пользователю, до какого места в исходном программном коде дошло выполнение.
Флаг -g
– один из обычно применяемых при компиляции программы с последующей отладкой. Вы должны указывать его при компиляции всех исходных файлов, которые нуждаются в отладке, а также для компоновщика, чтобы могли применяться специальные версии стандартной библиотеки С, обеспечивающие поддержку режима отладки в библиотечных функциях. Программа компилятора передаст флаг компоновщику автоматически. Отладка может применяться и с библиотеками, не откомпилированными для этой цели, но с меньшей гибкостью.
Отладочная информация может увеличить исполняемый файл во много (до десяти) раз. Несмотря на увеличение размера исполняемого файла (он займет больше места на диске), объем памяти, необходимый для выполнения программы, практически остается тем же самым. Перед вводом программы в эксплуатацию неплохо удалить из нее отладочную информацию, но только после того, как программа полностью отлажена.
Примечание
Удалить отладочную информацию из исполняемого файла без повторной компиляции можно, выполнив команду
strip <файл>
.
Отладка с помощью gdb
Для отладки программы вы можете применять отладчик проекта GNU, gdb. Это очень мощный отладчик, который распространяется бесплатно и может использоваться на многих платформах UNIX. Он также служит отладчиком по умолчанию в системах Linux. gdb перенесен на многие другие платформы и может применяться для отладки встроенных систем реального времени.
Запуск gdbПерекомпилируйте программу примера для отладки и запустите gdb:
$ cc-g -o debug3 debug3.c
$ gdb debug3
GNU gdb 6.6
Copyright (C) 2006 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you
are welcome to change it and/or distribute copies of it under certain
conditions.
Type «show copying» to see the conditions.
There is absolutely no warranty for GDB. Type «show warranty» for
details.
This GDB was configured as «i586-suse-linux»...
Using host libthread_db library «/lib/libthread_db.so.1».
(gdb)
У gdb есть довольно подробная интерактивная система помощи и полное справочное руководство, представляемое как набор файлов, которые можно просматривать с помощью программы info
или из редактора Emacs.
(gdb) help
List of classes of commands:
aliases – Aliases of other commands
breakpoints – Making program stop at certain points
data – Examining data
files – Specifying and examining files
internals – Maintenance commands
obscure – Obscure features
running – Running the program
stack – Examining the stack
status – Status inquiries
support – Support facilities
tracepoints – Tracing of program execution without stopping the program
user-defined – User-defined commands
Type «help» followed by a class name for a list of commands in that class.
Type «help all» for the list of all commands.
Type «help» followed by command name for full documentation.
Type «apropos word» to search for commands related to «word».
Command name abbreviations are allowed if unambiguous,
(gdb)
Сам по себе отладчик gdb – приложение, выполняющееся в текстовом режиме, но он предоставляет несколько сокращенных клавишных команд для выполнения повторяющихся задач. Во многих версиях есть редактирование в командной строке с хронологией команд, так что вы можете прокрутить список назад и выполнить ту же команду снова (попробуйте воспользоваться клавишами перемещения курсора). Все версии отладчика поддерживают "пустую команду"; нажатие клавиши step
или next
.
Для завершения работы gdb применяйте команду quit
.
Выполнить программу можно с помощью команды run
. Любые аргументы, переданные вами команде run
, пересылаются в программу как ее собственные аргументы. В данном случае вам не нужны никакие аргументы.
Предположим, что ваша система, как и системы обоих авторов, теперь генерирует сообщение о нарушении сегментации памяти. Если нет, читайте дальше. Вы узнаете, что делать, когда одна из ваших программ действительно сгенерирует сообщение о нарушении сегментации. Если вы не получили такого сообщения, но хотите поработать с этим примером во время чтения книги, когда первая из проблем, связанных с доступом к памяти, будет устранена, можно взять программу из файла debug4.c.
(gdb) run
Starting program: /home/neil/BLP4e/chapter10/debug3
Program received signal SIGSEGV, Segmentation fault.
0x0804846f in sort (a=0x804a040, n=5) at debug3.c:23
23 /* 23 */ if(a[j].key > a[j+1].key) {
(gdb)
Программа, как и прежде, выполняется неверно. Когда программа дает сбой, gdb указывает причину и местонахождение. Теперь вы можете выяснять первопричину проблемы.
В зависимости от ядра вашей системы, версий библиотеки С и компилятора сбой программы может произойти в другом месте, например в строке 25, когда элементы массива меняются местами, а не в строке 23, когда сравниваются поля key
элементов массива. Если это так, вы увидите следующее сообщение:
Program received signal SIGSEGV, Segmentation fault.
0x8000613 in sort (a=0x8001764, n=5) at debug3.c:25
25 /* 25 */ a[j] = a[j+1];
Вы все равно можете продолжать следить за примером сеанса работы gdb, который описывается далее.
Трассировка стекаПрограмма была остановлена при выполнении функции sort
в строке 23 исходного файла debug3.c. Если при компиляции вы не включили в программу дополнительную отладочную информацию (cc -g
), то не сможете увидеть, где программа дала сбой, и использовать имена переменных для просмотра данных.
Увидеть, как вы добрались до этого места, можно с помощью команды backtrace
:
(gdb) backtrace
#0 0x0804846f in sort (a=0x804a040, n=5) at debug3.c:23
#1 0x08048583 in main() at debug3.c:37
(gdb)
Это очень простая программа и трассировка у нее короткая, т.к. вы не вызывали много функций из других функций. Вы только видите, что sort
была вызвана из main
в строке 37 того же файла debug3.c. Обычно проблема гораздо сложнее, и команда backtrace
применяется для определения маршрута, который привел к месту ошибки. Эта информация очень полезна при отладке функций, вызываемых из множества разных мест.
У команды backtrace
есть сокращенная форма bt
и для совместимости с другими отладчиками есть команда where
, выполняющая ту же функцию.
Отладчик вывел данные в момент остановки программы, и в трассировке стека показаны значения аргументов функции.
Функция sort
была вызвана с параметром а
, значение которого 0х804а040. Это адрес массива. Обычно он в различных системах разный и зависит от используемых компилятора и операционной системы.
Сбойная строка 23 – сравнение одного элемента массива с другим:
/* 23 */ if (a[j].key > a[j+1].key) {
Отладчик можно применять для просмотра содержимого параметров функции, локальных переменных и глобальных данных. Команда print
отображает содержимое переменных и других выражений:
(gdb) print j
$1 = 4
Вы видите, что у локальной переменной j
значение 4
. Любые значения, выводимые командами gdb, подобными данной, сохраняются для будущего использования в псевдопеременных. В данном случае переменной $1
присвоено значение 4, на случай, если она вам позже понадобится. Последующие команды будут сохранять свои результаты в переменных $2
, $3
и т.д.
Значение переменной j
, равное 4, означает, что программа попыталась выполнить оператор
if (а[4].key > а[4+1].key)
У массива array
, который вы передали функции sort
, только пять элементов, которые пронумерованы от 0 до 4. Поэтому данный оператор считывает несуществующий элемент массива array[5]
. Переменная цикла j
приняла некорректное значение.
Если ваша программа завершилась в строке 25, система обнаружила чтение за пределами массива, только когда взялась за перестановку элементов массива, выполнив оператор
/* 25 */ а[j] = a[j+1];
который при j
, равной 4, дает в результате
а[4] = а[4+1];
Просмотреть элементы передаваемого массива можно, применив выражение в команде print
. В программе gdb вы можете использовать почти любое допустимое выражение языка С для вывода значения переменной, элемента массива или указателя.
(gdb) print а[3]
$2 = {data = «alex», ' '
(gdb)
Отладчик gdb сохраняет результаты выполнения команд в псевдопеременных вида $<номер>
. Результат последней команды всегда хранится в псевдопеременной $
, а предыдущей – в $$
. Это позволяет результат одной команды использовать в другой. Например:
(gdb) print j
$3 = 4
(gdb) print a[$-1].key
$4 = 1
Вы можете в программе gdb вывести на экран исходный текст программы с помощью команды list
. Она выводит фрагмент кода, расположенного рядом с текущей позицией. Последующие вызовы list
выведут остальной текст. Команде list
можно задать в качестве аргумента имя функции, и команда отобразит фрагмент текста в этом месте программы, или можно указать пару номеров строк, и на экране появится текст программы, находящийся между этими строками.
(gdb) list
18 /* 18 */ int s = 1;
19 /* 19 */
20 /* 20 */ for(; i < n && s != 0; i++) {
21 /* 21 */ s = 0;
22 /* 22 */ for(j = 0; j < n; j++) {
23 /* 23 */ if(a[j].key > a[j+1].key) {
24 /* 24 */ item t = a[j];
25 /* 25 */ a[j] = a[j+1];
26 /* 26 */ a[j+1] = t;
27 /* 27 */ s++;
(gdb)
В строке 22 задано выполнение цикла до тех пор, пока переменная j
меньше n
. В данном случае n
равна 5, поэтому у j
будет последнее значение 4, слишком большое. Значение 4 приводит к сравнению а[4]
с а[5]
и возможной их перестановке. Единственное решение этой конкретной проблемы – исправить условие завершения цикла на следующее: j < n-1
.
Давайте внесем это изменение, назовем новую программу debug4.c, откомпилируем ее и попробуем снова выполнить.
/* 22 */ for(j = 0; j < n-1; j++) {
$ cc -g -o debug4 debug4.с
$ ./debug4
array[0] = {john, 2}
array[1] = {alex, 1}
array[2] = {bill, 3}
array[3] = {neil, 4}
array[4] = {rick, 5}
Программа все еще не работает, поскольку она вывела неверно отсортированный список. Попробуем применить gdb для пошагового выполнения программы.
Установка точек остановаДля обнаружения места сбоя в программе необходимо иметь возможность проследить за тем, что делает программа во время выполнения. Остановить ее в любой момент можно с помощью точек останова. Они останавливают программу и передают управление отладчику. Вы сможете проверить переменные и затем разрешить программе продолжить выполнение.
В функции sort
есть два цикла. Внешний цикл с переменной цикла i
выполняется для каждого элемента массива один раз. Внутренний или вложенный цикл меняет местами элемент с последующим, расположенным ниже в списке. Это создает эффект всплытия пузырьков, поднимая вверх меньшие элементы. После каждого выполнения внешнего цикла самый большой элемент опускается на дно. Вы можете убедиться в этом, остановив программу на внешнем цикле и просмотрев состояние массива.
Для установки точек останова применяется ряд команд. Их перечень получен отладчиком gdb с помощью команды help breakpoint
:
(gdb) help breakpoint
Making program stop at certain points.
List of commands:
awatch – Set a watchpoint for an expression
break – Set breakpoint at specified line or function
catch – Set catchpoints to catch events
clear – Clear breakpoint at specified line or function
commands – Set commands to be executed when a breakpoint is hit
condition – Specify breakpoint number N to break only if COND is true
delete – Delete some breakpoints or auto-display expressions
delete breakpoints – Delete some breakpoints or auto-display expressions
delete checkpoint – Delete a fork/checkpoint (experimental)
delete mem – Delete memory region
delete tracepoints – Delete specified tracepoints
disable – Disable some breakpoints
disable breakpoints – Disable some breakpoints
disable display – Disable some expressions to be displayed when program stops
disable mem – Disable memory region
disable tracepoints – Disable specified tracepoints
enable – Enable some breakpoints
enable delete – Enable breakpoints and delete when hit
enable display – Enable some expressions to be displayed when program stops
enable mem – Enable memory region
enable once – Enable breakpoints for one hit
enable tracepoints – Enable specified tracepoints
hbreak – Set a hardware assisted breakpoint
ignore – Set ignore-count of breakpoint number N to COUNT
rbreak – Set a breakpoint for all functions matching REGEXP
rwatch – Set a read watchpoint for an expression
tbreak – Set a temporary breakpoint
tcatch – Set temporary catchpoints to catch events
thbreak – Set a temporary hardware assisted breakpoint
watch – Set a watchpoint for an expression
Type «help» followed by command name for full documentation.
Type «apropos word» to search for commands related to «word».
Command name abbreviations are allowed if unambiguous.
Установите точку останова в строке 21 и выполните программу:
$ gdb debug4
(gdb) break 21
Breakpoint 1 at 0x8048427: file debug4.c, line 21.
(gdb) run
Starting program: /home/neil/BLP4e/chapter10/debug4
Breakpoint 1, sort (a=0x804a040, n=5) at debug4.c:21
21 /* 21 */ s = 0;
Вы можете вывести значение массива и затем с помощью команды cont
разрешить программе продолжить выполнение. Это позволит программе выполняться до тех пор, пока она не натолкнется на следующую точку останова, в нашем случае это снова строка 21. В любой момент времени может быть активно несколько точек останова:
(gdb) print array[0]
$1 = (data = «bill», ' '
Для вывода нескольких последовательных элементов массива можно применить конструкцию @<число>
, чтобы заставить gdb вывести указанное количество элементов массива. Для того чтобы вывести все пять элементов, можно использовать следующую команду:
(gdb) print array[0]@5
$2 = {{data = «bill», ' '
data = «neil», ' '
data = «john», ' '
data = «rick», ' '
data = «alex», ' '
Учтите, что вывод немного подчищен, чтобы его легче было читать. Поскольку это первый проход цикла, массив еще не изменен. Когда вы разрешите программе продолжить выполнение, то увидите последовательные перестройки массива array, происходящие по мере выполнения программы:
(gdb) cont
Continuing.
Breakpoint 1, sort (a=0x8049580, n=4) at debug4.c:21
21 /* 21 */ s = 0;
(gdb) print array[0]@5
$3 = {{data = «bill», ' '
data = «john», ' '
data = «neil», ' '
data = «alex», ' '
data = «rick», ' '
(gdb)
Можно воспользоваться командой display
, чтобы задать в gdb автоматическое отображение массива при каждой остановке программы в точке останова:
(gdb) display array[0]@5
1: array[0]@5 = {{data = «bill», ' '
data = «john», ' '
data = «neil», ' '
data = «alex», ' '
data = «rick», ' '
Более того, вы можете изменить точку останова таким образом, что вместо остановки программы она просто отобразит данные, которые вы запросили, и продолжит выполнение. Для этого примените команду commands
. Она позволит указать, какие команды отладчика выполнять при попадании в точку останова. Поскольку вы уже указали отображение, вам нужно лишь задать команду в точке останова для продолжения выполнения:
(gdb) commands
Type commands for when breakpoint 1 is hit, one per line.
End with a line saying just «end».
> cont
> end
Теперь, когда вы разрешите программе продолжить выполнение, она продолжается до завершения, выводя значение массива каждый раз, когда оказывается вблизи внешнего цикла.
(gdb) cont
Continuing.
Breakpoint 1, sort (a=0x8049684, n=3) at debug4.c:21
21 /* 21 */ s = 0;
1: array[0]@5 = {{data = «john», ' 00'
data = «bill», ' 00'
data = «alex», ' 00'
data = «neil», ' 00'
data = «rick», ' 00'
array[0] = {john, 2}
array[1] = {alex, 1}
array[2] = {bill, 3}
array[3] = {neil, 4}
array[4] = {rick, 5}
Program exited with code 025.
(gdb)
Отладчик gdb сообщает о том, что программа завершается с необычным кодом завершения. Это происходит потому, что программа сама не вызывает exit
и не возвращает значение из функции main
. Код завершения в данном случае не имеет смысла, значимый код должен предоставляться вызовом функции exit
.
Кажется, что программа не выполняет внешний цикл столько раз, сколько ожидалось. Вы можете увидеть, что значение параметра n
, используемого в условии завершения цикла, уменьшается при каждом достижении точки останова. Это значит, что цикл не будет выполняться нужное число раз. Дело в уменьшении n
в строке 30.
/* 30 */ n–;
Это попытка оптимизировать программу за счет того, что в конце каждого прохода внешнего цикла наибольший, элемент array
окажется внизу и поэтому остается меньше элементов для сортировки. Но как видно, это мешает внешнему циклу и создает проблемы. Простейший способ исправления (хотя есть и другие) – удалить ошибочную строку. Давайте проверим, применив отладчик для корректировки, устранило ли такое исправление проблему.
Вы уже видели, что можно применять отладчик для установки точек останова и просмотра значений переменных. Применив точки останова с заданными действиями, можно проверить исправление, называемое «заплатой», перед тем, как изменять текст программы и выполнять ее повторную компиляцию. В данном случае нужно остановить программу в строке 30 и увеличить переменную n
. В дальнейшем, когда строка 30 выполнится, значение останется неизменным.
Давайте перезапустим программу с самого начала. Прежде всего вы должны удалить вашу точку останова и отладочный вывод. С помощью команды info можно увидеть, какие точки останова и какой вывод вы включили:
(gdb) info display
Auto-display expressions now in effect:
Num Enb Expression
1: y array[0]@5 (gdb) info break
Num Type Disp Enb Address What
1 breakpoint keep y 0x08048427 in sort at debug4.c:21
breakpoint already hit 3 times
cont
Вы можете либо отключить эти точки останова, либо удалить их совсем. Если их отключить, у вас останется возможность включить их позже, когда понадобится.
(gdb) disable break 1
(gdb) disable display 1
(gdb) break 30
Breakpoint 2 at 0x8048545: file debug4.c, line 30.
(gdb) commands 2
Type commands for when breakpoint 2 is hit, one per line.
End with a line saying just «end».
>set variable n = n+1
>cont
>end
(gdb) run
Starting program: /home/neil/BLP4e/chapter10/debug4
Breakpoint 2, sort (a=0x804a040, n=5) at debug4.c:30
30 /* 30 */ n–;
Breakpoint 2, sort (a=0x804a040, n=5) at debug4.c:30
30 /* 30 */ n–;
Breakpoint 2, sort (a=0x804a040, n=5) at debug4.c:30
30 /* 30 */ n–;
Breakpoint 2, sort (a=0x804a040, n=5) at debug4.c:30
30 /* 30 */ n–;
Breakpoint 2, sort (a=0x804a040, n=5) at debug4.c:30
30 /* 30 */ n–;
array[0] = {alex, 1}
array[1] = {john, 2}
array[2] = {bill, 3}
array[3] = {neil, 4}
array[4] = {rick, 5}
Program exited with code 025.
(gdb)
Программа выполняется полностью и выводит корректный результат. Теперь можно внести изменения и переходить к тестированию ее с большим объемом данных.