Для того чтобы получить справку по работе с cuneiform, вам не обязательно выходить из интерактивного режима. Команда help позволит вам получить справочную информацию. Вы можете получить информацию о любой команде gdb с помощью команды help имя_команды. Например, команда (gdb) help exec-file распечатает справочную информацию о команде exec-file. Получать справку о конкретной команде имеет смысл, если вы знаете, какая команда вам нужна. Если вы новичок в работе с отладчиком, вам прежде всего понадобится информация о том, какие вообще команды существуют в системе. В интерактивном режиме отладчика GNU программисту доступны сотни команд (большая часть которых никогда ему не понадобится). Для удобства навигации в справочной системе gdb команды сгруппированы по разным разделам:
Например, для того, чтобы получить сведения о командах, управляющих точками останова, командуем: (gdb) help breakpoints.
Мы должны указать отладчику имя программы, которую мы собираемся отлаживать. Это можно сделать во время запуска gdb:
$ gdb cuneiform
А можно и после запуска cuneiform с помощью команды exec-file:
(gdb) exec-file cuneiform
В любом случае отладчик лишь подготовит среду для отладки программы, но не запустит программу на выполнение. Вообще следует помнить, что отладчик всегда находится в одном из трех режимов: отлаживаемая программа работает, отлаживаемая программа приостановлена и программа не выполняется. Большую часть команд отладчика можно вводить в последних двух режимах. Для запуска программы служит команда run:
(gdb) run rf2.bmp -l rus_fra -o out.txt
Все аргументы, которые мы передаем команде run, передаются программе, запускаемой на отладку. После ввода приведенной выше команды мы увидим:
Starting program: /usr/local/bin/cuneiform
rf2.bmp
-l rus_fra -o out.txt Cuneiform for Linux 0.7.0 (multilang)
Program exited normally.
Текст, распечатанный между строками «Starting program:...» и «Program exited normally.» Представляет собой консольный вывод отлаживаемой программы. Пока что отладчик не сообщил нам каких-либо полезных сведений о работе программы, но даже от запуска программы в этом режиме может быть толк. Если во время выполнения программы произойдет ошибка сегментации, отладчик, помимо прочего, покажет нам состояние стека процедур на момент возникновения ошибки. Также, если программа зависнет и мы прервем ее выполнение с помощью Ctrl-C, нам будут выданы сведения о том, в каком месте программы ее работа была прервана, например:
Program received signal SIGINT,
Interrupt.
0xb7d8c8b4 in IntervalsBuild (y=979) at /home/andrei/bazaar/cuneiform-multilang/cuneiform_src/Kern/rblock/sources/c/ltexcomp.c:144
144 while (x < nWidth && (pLine [x] & BlackMask) == 0)
Здесь мы видим имя функции, во время выполнения которой работа программы была прервана (IntervalsBuild), значение параметров, с которыми эта функция была вызвана (в нашем случае — один параметр «y» со значением 979), имя файла и номер строки исходного текста, соответствующего прерванному фрагменту кода и саму строку кода. Очень часто даже этой информации достаточно для того, чтобы вызвать озарение у программиста, ищущего ошибку. Но gdb способен показать нам гораздо больше. Мы можем просмотреть значения переменных, доступных нам в данном контексте (как минимум, это переменные x, nWidth, pLine, и BlackMask). Для просмотра значений переменных воспользуемся командой print:
(gdb) print x
$1 = 681
(gdb) print pLine[x]
$2 = 0 '\0'
Таким образом мы узнаем, что на момент прерывания переменная x содержала значение 681, а элемент массива pLine[681] — символ '\0' . Если мы забыли или не знали, какой тип имеет переменная pLine, мы можем узнать это, не заглядывая в исходники:
(gdb) ptype pLine
type = unsigned char *
Но и это не все. Мы можем изменить значение переменной:
(gdb) set x=0
а затем возобновить выполнение программы с помощью команды continue.
Поскольку современные микропроцессоры работают очень быстро, комбинация клавиш Ctrl-C далеко не всегда останавливает программу именно там, где нам нужно. Как правило мы хотим, чтобы выполнение программы было прервано в определенном, заранее известном месте. Для этого и служат точки останова. Для начала скомандуем:
(gdb)break copy_text
Эта команда создает точку останова в начале функции copy_text (ленивые могут набирать просто «b copy_text»). В ответ на ввод команды отладчик скажет:
Breakpoint 1 at 0xb7f57f35: file /home/andrei/bazaar/cuneiform-multilang/cuneiform_src/Kern/puma/c/spcheck.cpp, line 85.
Это значит, что отладчик нашел интересующую нас функцию и строчку кода, соответствующую точке останова. Теперь мы снова запускаем программу cuneiform на выполнение с помощью команды run. Когда процессор достигнет точки останова (если это случится) выполнение программы будет прервано и на экране появится следующее:
Breakpoint 1, copy_text (word=0x9452338)
at /home/andrei/bazaar/cuneiform-multilang/cuneiform_src/Kern/puma/c/spcheck.cpp:85
85 word->text = (Word8 *) malloc((word->wlen + 1)*sizeof(Word8));
Current language: auto; currently c++ Мы снова видим имя функции, выполнение которой было приостановлено, имя и значение параметра, переданного этой функции и строку исходного текста, которая будет выполнена далее. Команда
(gdb) ptype word
сообщает нам нам следующее:
type = struct _SPWord {
Bool32 is_latin;
CSTR_rast begin;
CSTR_rast end;
Word8 *text; int wlen;
} *
Мы видим, что переменная word представляет собой указатель на экземпляр структуры _SPWord. Для того чтобы узнать значения полей структуры, командуем:
(gdb) output * word
Команда output делает то же, что и команда print, но при этом не утруждает себя излишним форматированием. Результат выполнения команды выглядит так:
{is_latin = 1, begin = 0x93e7008, end = 0x93c2a20, text = 0x1cc08f3f , wlen = 6}
Обратите внимание, что отладчик любезно сообщил нам о том, что поле text указывает на не выделенную область памяти. Эта информация может очень пригодиться при поиске ошибок, но не в данном случае. Поле text просто еще не инициализировано (это произойдет в следующей строке). Если вам хочется изучить работу программы подробнее, начиная с этого места, к вашим услугам команды step и next. Эти команды выполняют операции, соответствующие одной строке исходного текста (когда это возможно) и снова приостанавливают выполнение программы. Серия вызовов step или next позволяет вам выполнять отладку построчно. В нашем примере в результате выполнения команды
(gdb) step
мы переходим к следующей строке:
86 CSTR_rast rast = word->begin;
разница между командами step и next заключается в том, что step «заходит» в каждую функцию, вызов которой встречается у нее на пути и проходит выполнение функции построчно, тогда как next «перепрыгивает» вызов функции, выполняя его за один шаг. Команда step по смыслу соответствует командам trace into/step into интегрированных отладчиков Borland и Microsoft, тогда как команда next соответствует командам step over этих отладчиков. Запуск приостановленной программы осуществляется командой continue. Вместо полных имен команд step, next и continue сможно использовать их однобуквенные псевдонимы — s, n и c соответственно. Если вы хотите, чтобы программа сделала сразу 5 шагов, можете просто скомандовать
(gdb) s 5
Если построчной отладки вам недостаточно, вы можете перейти на уровень отслеживания выполнения отдельных инструкций процессора с помощью команд stepi и nexti. Точки останова не обязательно расставлять в начале функций. Например, если мы скомандуем
(gdb) break 97
следующая точка останова будет создана в строке 97 файла /home/andrei/bazaar/cuneiform-multilang/cuneiform_src/Kern/puma/c/spcheck.cpp (этот файл является текущим). Если вы хотите создать точку останова на заданной строке в другом исходном файле, команда должна выглядеть так:
(gdb) break имя_файла:номер_строки
Можно также указывать для точки останова определенный адрес. Следует учесть, однако, что не каждой строке текста программы соответствуют операции микропроцессора. Если мы попытаемся создать точку останова в строке, которой отладчик не может сопоставить исполнимый код, точка останова будет создана на одной из следующих строк, иногда в довольно неожиданном месте. Например, если в исходном тексте
151 if (!strchr(",.():;!?\" «»%",
nxt->vers->Alt[0].Code[0])) {
152 rast->vers->Alt[0].Code[0] = ' ';
153 rast->vers->Alt[0].Code[1] = 0;
154 rast = CSTR_GetPrev(rast);
155 continue; 156 }
else{
157 i++ ...
}
мы попытаемся создать точку останова в строке 155, отладчик создаст точку останова в строке 157. Дело в том, что строке 155 (как и строке 156) нельзя сопоставить машинный код. Первая строка после строки 155, для которой это можно сделать, - строка 157, но она находится в другом логическом блоке. Очевидно, что это совсем не то, чего мы хотели. Самое неприятное заключается в том, что отладчик не покажет нам номер строки, на которой на самом деле была создана точка останова, до тех пор, пока эта точка не будет вызвана. Правильным решением может быть создание новой точки останова в строке 154, но в этом случае программа будет приостановлена до вызова функции CSTR_GetPrev(). Если нам нужно отследить результат выполнения CSTR_GetPrev(), точку останова следует создавать внутри этой функции (можно конечно остановиться до вызова CSTR_GetPrev() а затем пройтись по функции серией команд step).
Помимо локаций команда break позволяет задать условие для точки останова. Условием может быть любое выражение, возвращающее значение, приводимое к типу int и валидное в контексте данного кадра отладки. Вы можете создать несколько точек останова в одной строке (это имеет смысл для условных точек останова).
Если вы не хотите больше останавливаться на текущей точке останова, вы можете очистить ее с помощью команды clear. Любую точку останова можно удалить с помощью команды delete, указав в качестве аргумента номер точки останова (эти номера присваиваются автоматически при выполнении команды break). Если вы заранее уверены, что точка останова понадобится вам только один раз, создайте однократную точку останова с помощью команды tbreak (синтаксис этой команды такой же, как и у break).
С некоторыми командами, позволяющими заглянуть в содержимое переменных отлаживаемой программы, мы уже знакомы. Когда выполнение программы приостановлено, мы можем просмотреть содержимое любой переменной, доступной в данном контексте. Очень часто в процессе отладки бывает необходимо отследить, как меняется значение некоторой переменной по ходу выполнения программы. Чтобы не вызывать каждый раз команду print, мы можем создать контрольную точку доступа к данным. Рассмотрим практический пример. В программе cuneiform есть функция make_tokens, которая в ходе своей работы перебирает элементы некоего связного списка. Указатель на текущий элемент списка хранится в локальной переменной rast. В ходе отладки программы мне требовалось посмотреть, как меняются значения rast, для чего я решил создать контрольную точку доступа к данным. Контрольные точки доступа к данным осуществляют мониторинг обращения к определенному адресу памяти на аппаратном уровне. Это означает, между прочим, что контрольную точку доступа нельзя создать до тех пор, пока наблюдаемая переменная не будет создана, то есть, в нашем примере, - до тех пор, пока не будет вызвана функция make_tokens. Таким образом, прежде чем устанавливать контрольную точку доступа мне необходимо остановить программу в начале вызова make_tokens:
(gdb) break make_tokens
(gdb) c Breakpoint 16, make_tokens (line=0x957a108, words=0xbf867b98) at /home/andrei/bazaar/cuneiform-multilang/cuneiform_src/Kern/puma/c/spcheck.cpp:140
140 CSTR_rast rast=CSTR_GetFirstRaster(line);
Теперь можно установить контрольную точку доступа к переменной rast:
(gdb) watch rast
Hardware watchpoint 17: rast
Обратите внимание, что значение rast изменяется в той самой строке (140), на которой было приостановлено выполнение программы. Это значит, что уже следующий вызов next приведет к изменению содержимого переменной:
(gdb) n
Hardware watchpoint 17: rast
Old value = (CSTR_rast) 0x0
New value = (CSTR_rast) 0x957a19c
Отладчик сообщает нам, что значение rast изменилось с 0x0 на 0x957a19c . Ниже показан дальнейший результат пошагового выполнения функции:
142 bool wb = true; (gdb) n
143 int wc = 0; (gdb) n
144 for(rast = CSTR_GetNext(rast);rast;rast=CSTR_GetNext(rast))
(gdb) n
Hardware watchpoint 17: rast
Old value = (CSTR_rast) 0x957a19c
New value = (CSTR_rast) 0x95776d0
Мы видим, что при входе в цикл for значение переменной rast (которая является управляющей переменной этого цикла) тоже изменилось. Если бы значение rast было случайно перезаписано где-то между двумя явными обращениями к переменной, отладчик сообщил бы нам об этом.
Установленная нами контрольная точка существует столько же, сколько существует переменная rast, то есть, до выхода из функции make_tokens и при следующем вызове make_tokens контрольную точку придется создавать снова. Для удаления контрольных точек доступа служит уже известная нам команда delete.
Отслеживать изменение содержимого переменной с помощью комбинации команд watch и step/next не очень-то удобно. У отладчика GNU есть другой, лучший способ. Команда awatch будет останавливать выполнение программы всякий раз, когда значение заданной переменной считывается или записывается. Так же, как и команда watch, команда awatch стремится использовать аппаратные ловушки для выявления обращений к конкретному адресу и может отлавливать неявные обращения к нему.
Иногда программисты тоже пытаются найти ответ на этот сакраментальный вопрос. Если в программе во время отладки происходит нечто, требующее вашего внимания, вам может понадобится не только информация о функции, при вызове которой случилось страшное, но и сведения о полном состоянии стека вызовов. Команда backtrace позволяет узнать, какими путями программа попала в данную точку. Посмотрим результат выполнения команды backtrace для многострадальной функции copy_text (предполагается что выполнение программы cuneiform приостановлено в начале выполнения copy_text):
(gdb) backtrace
#0 copy_text (word=0x983d348) at /home/andrei/bazaar/cuneiform-multilang/cuneiform_src/Kern/puma/c/spcheck.cpp:85
#1 0xb7f213ff in make_tokens (line=0x9789278, words=0xbff78a88) at /home/andrei/bazaar/cuneiform-multilang/cuneiform_src/Kern/puma/c/spcheck.cpp:172
#2 0xb7f2158e in mix_lines (ruseng=0x9789278, local=0x97af118, rus=0x9794578) at
/home/andrei/bazaar/cuneiform-multilang/cuneiform_src/Kern/puma/c/spcheck.cpp:369
#3 0xb7f1eeaf in MultilangRecognizeStringsPass1 () at /home/andrei/bazaar/cuneiform-multilang/cuneiform_src/Kern/puma/c/partrecog.cpp:425
#4 0xb7f1fb55 in Recognize () at /home/andrei/bazaar/cuneiform-multilang/cuneiform_src/Kern/puma/c/partrecog.cpp:798
#5 0xb7f226f7 in PUMA_XFinalRecognition () at /home/andrei/bazaar/cuneiform-multilang/cuneiform_src/Kern/puma/main/puma.cpp:600
#6 0x0804ad18 in ?? ()
#7 0xb7463685 in __libc_start_main () from /lib/tls/i686/cmov/libc.so.6
#8 0x08049fb1 in ?? ()
В переводе на русский сязык казанное отладчиком звучит так: Функция PUMA_XFinalRecognition (), вызванная из функции верхнего уровня, вызвала функцию Recognize (), которая вызвала функцию MultilangRecognizeStringsPass1 (), которая вызвала функцию mix_lines (), которая вызвала функцию make_tokens (), которая и вызвала функцию copy_text(). Но это не все. Благодаря команде backtrace мы можем узнать не только адреса функций и значения их параметров (они приведены в скобках), но и локации вызовов в исходных текстах программы (функция copy_text вызывается в нескольких разных местах функции make_tokens() и нам, разумеется, полезно знать, из какого именно места она была вызвана на этот раз). Не могу не отметить, что команда backtrace помогает не только при отладке кода, но и при изучении работы программы, написанной другими людьми. Фактически у нас перед глазами «живая» цепочка вызовов функций в сложной программе, снабженная значениями их аргументов и другими полезными сведениями.
Из всех графических отладчиков, использующих gdb, мы рассмотрим отладчик DDD. Такая честь выпала DDD потому, что этот отладчик наиболее прозрачно интегрирован с gdb. Основные команды графической оболочки соответствуют командам gdb, а в нижней части главного окна DDD (рис. 1) мы видим фрагмент консоли gdb, в которой отображаются вывод отладчика GNU и его командная строка. Таким образом, если возможностей DDD вам не хватает, вы всегда можете обратиться к отладчику gdb напрямую. Главное преимущество DDD по сравнению с «чистым» gdb — возможность легко ориентироваться в исходных текстах программы.
Понравилась статья? Нажми:
Статья впервые опубликована в журнале Linux Format
© 2008 Андрей Боровский <anb@symmetrica.net>
На главную