null

GDB бояться - код не дебажить

Многие *nix программисты опасаются всяческих видов дебага сишного кода и переходят в состояние паники реагируя на слово "GDB".

Скорее всего, причиной этому послужило отсутствие VisualStudio с её замечательными функциями по дебагу кода.
Порой, решения, предлагаемые такими программистами сводятся к выводу на поток ошибок служебной информации, но это не позволяет полностью овладеть кодом и приводит к переписыванию оного, чтобы смоделировать различные ситуации.
Предлагаю в этой статье рассмотреть, чего же такого страшного нету в gdb, чего боятся упомянутые программисты.
 
Рассмотрим подробнее процедуру дебага в стандартном (командном) режиме gdb (для тех, кто не в курсе).
Итак, начнём с вот-такого кода на С:
 
#include <stdio.h>

int f(int arg) {
   if (arg) {
      return -1;
   }
   return 0;
}

int main(int argc, char *argv[]) {   
   int i;
   for (i=0; i<2; i++) {
      if (f(i) < 0) {
         fprintf(stderr, "Negative %d\n", i);
      } else {
         fprintf(stderr, "Positive %d\n", i);
      }
   }

   return 0;
}
 
Тут представлен простой цикл, с вызовом нескольких функций. Сразу оговорюсь, что тестовый стенд представляет собой систему на amd64-процессоре.
Скомпилируем код с использованием gnu c compiler (или gnu compiler collection):
# gcc -Wall -g -omain main.c
Стоит обратить особое внимание на ключ "-g". Этот параметр указывает компилятору включить в код конечной программы символы для дебага.
Мало того, по умолчанию компилятор будет добавлять информацию для дебага специально для использования с GDB (и, как заверяют мануалы, генерировать код, который может приводить к падениям при работе другими дебагерами). Эта фича позволяет использовать -О (оптимизацию) совместно с дебагом.
После компилиции, наиболее простой способ запустить код в дебаг-режиме это
# gdb main
GNU gdb 6.1.1 [FreeBSD]
Copyright 2004 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 "amd64-marcel-freebsd"...
(gdb) 

Тут gdb готов к работе и вывел свое приглашение на терминал.

Для читателей, мало знакомых с особенностями работы в отладчиках уровня gdb, необходимо раскрыть самые основы с максимально подробным объяснением каждого используемого действия, чтобы приведенные в данной статье примеры стали понятны и легко читаемы. Предполагается, что пользователь знаком с процедурой дебага (по крайней мере, данная статья не ставит своей целью раскрыть процесс выполнения кода и описать принципы работа отладчика и методики разбора кода).
 
Для начала, положим, у нас имеется бинарный исполняемый файл, готовый к отладке и наша цель - пройти программу пошагово.
После открытия этого исполняемого файла отладчиком
# gdb file
программа будет находиться в остановленном состоянии и мы не сможем выполнить операцию step (выполнить один шаг).
Чтобы перевести программу в состояние исполнения, необходимо воспользоваться командой
(gdb) run 
Эта команда запустит программу на исполнение. Программа выполнит код до конца и снова остановится.
Поэтому перед запуском программы необходимо установить точку останова на вход в функцию main (главную функцию для программ на libc):
(gdb) break main
Breakpoint 1 at 0x4005ab: file 1.c, line 7.
(gdb) run 
 
Дальнейшее изучение gdb заключается в изучении предоставляемого набора команд и возможностей.
Приведу краткий список полезных команд:
list [func|linenum] -- вывести листинг
break <func|linenum> -- установить точку останова
tbreak <func|linenum> -- установить одноразовую точку останова
clear <func|linenum> -- удалить конкретную точку останова
delete -- удалить все точки останова
disable <breakpoint> -- выключить точку останова
enable <breakpoint> -- включить точку останова
run [argv] -- запустить программу на выполнение с начала
continue -- продолжить выполнение до следующей точки останова
finish -- продолжить выполнение до конца функции (выполнения инструкции ret)
kill -- остановить выполнение программы
quit -- выйти из gdb
step [n] -- выполнить следующую строку кода - входит в функции
next [n] -- выполнить следующую строку кода - не входит в функции
until [linenum] -- выполнять до указанного номера строки или до входа в функцию
stepi и nexti -- то же, что и step/next, но выполнять не по строкам, а по ассемблерным инструкциям
 
Самые вкусные команды по получению и изменению информации:
help -- выводит справку
show -- выводит переменные дебаггера
info <object> -- вывести информацию об объекте
set <variable> <value> -- установить значение переменной (возможно как присвоение значений переменным дебагера, так и изменение значений переменных в текущем контексте)
disas <func|addr> -- вывести дизассемблерный листинг
print <variable> -- вывести значение переменной
display -- включает авто-показ значений выражений перед приглашением дебагера
undisplay -- выключить авто-показ
 
В дебагере работает автодополнение по Tab и авторасширение комманд.
Например, команда br эквивалентна команде break.
А команда info b<Tab> автоматически раскроется в info breakpoints.
Из семейства info наиболее полезными являются:
info address <object> -- выводит информацию об адресе объекта
info all-registers -- выводит состояние регистров
info args -- выводит argc и argv
info breakpoints -- выводит список точек останова
info files -- выводит информацию об адресах сегментов кода и файлах, откуда они загружены. Очень полезно и является единственным адекватным способом анализировать код, скомпилированный без символов для отладки.
info locals -- список локальных переменных
info catch -- выводит список ловушек вызовов функций
 
Базовые способы получения значения перменных:
print argc -- выводит количество аргументов функции main (переменную argc)
print *argv -- выводит нулевой аргумент (значение по адресу)
print argv[0]@argc -- выводит массив аргументов функции
print $rsp -- выводит регистр rsp
 
Теперь, когда вы научились основам gdb, попробуем упростить с ним работу с помощью libcurses.
Такой функционал заложен в программу разработчиками и называется TUI - terminal user interface.
Включается он сочетанием клавиш: <C-x>A (привет, емакс!)
В этом режиме стрелки выполняют скроллинг активного окна.
Переключать схемы окон можно либо сочетаниями <C-x>1, <C-x>2, либо командой layout:
layout next -- следующая схема
layout prev -- предыдущая схема
layout src -- показать исходный код
layout asm -- показать дизассемблерный листинг
layout split -- разделить экран между кодом и дизассемблерным листингом
layout regs -- показать окно с регистрами
Активное окно меняется сочетанием <C-x>o
Перерисовать экран можно стандартным сочетанием <C-L>
 
Теперь, когда вы научились основам дебага с помощью gdb, напомню, что ввод пустой команды повторяет предыдущую и посмотрим, как будет выглядеть дебаг приведённого выше кода:
(gdb) run
Starting program: /tmp/main XX
Positive 0
Negative 1

Program exited normally.
(gdb) tbreak main
Breakpoint 1 at 0x40066f: file main.c, line 12.
(gdb) run XX
Starting program: /tmp/main XX
main (argc=2, argv=0x7fffffffd960) at main.c:12
12    for (i=0; i<2; i++) {
(gdb) next
13       if (f(i) < 0) {
(gdb) 
16          fprintf(stderr, "Positive %d\n", i);
(gdb) 
Positive 0
12    for (i=0; i<2; i++) {
(gdb) print i
$1 = 0
(gdb) next
13       if (f(i) < 0) {
(gdb) print i
$2 = 1
(gdb) continue 
Continuing.
Negative 1

Program exited normally.
(gdb) print $2 * 4
$3 = 4
Последней командой было показано, что gdb не только сохраняет выводимые значения в буфере, но и может с ними оперировать уже после завершения программы.
Видно также, что команда n (next) не входила в функции. Если бы мы исполняли код с помощью команды step, на одном из шагов было бы видно:
(gdb) step
f (arg=0) at main.c:4
4     if (arg) {
 
Ещё один интересный момент представляет дебаг оптимизированного кода.
В случае, если скомпилировать приведённый выше код вот так:
# gcc -g -Wall -O2 -omain main.c
то при попытке обратиться к значению переменной i во время выполнения функции main, увидим вот это:
i = <value optimized out>
Это нам говорит, что компилятор оптимизировал работу с этой переменной используя для её хранения какой-то РОН (регистр общего назначения).
Производить дебаг такой переменной удобнее всего через рассмотренный ранее TUI с ассемблерным листингом и пошаговым выполнением с помощью команды si (stepi).
 
Всем приятного дебага!
P.S. для тех же, кто всё-еще боится gdb, существует графический front-end под названием ddd, в котором интерфейс довольно дружелюбный и позволяет выпонять указанные выше действия с помощью мыши.
korg

 

Коротко о себе

Работаю в компании Tune-IT, администрирую инфраструктуру компании и вычислительную сеть кафедры Вычислительной ТехникиСПбНИУ ИТМО.

Интересы: администрирование UNIX и UNIX-like систем и активного сетевого оборудования, написание shell- и perl-скриптов, изучение технологий глобальных сетей.
Люблю собирать GNU/Linux и FreeBSD, использовать тайлинговые оконные менеджеры и писать системный софт.

Ничего не найдено. n is 0