illumium.org

Главная › Блоги › Блог kayo

Полезные особенности GCC/GDB, о которых необходимо знать

kayo — Чт, 02/07/2015 - 23:31

Многие, как и я, уже очень давно пользуются набором инструментов разработчика GCC и отладчиком GDB, однако, далеко не все полезные возможности этого инструментария широко известны и активно применяются нами. Постараемся восполнить пробелы. Пусть эта статья будет чем-то вроде памятки.

Расширенная отладочная информация

По-умолчанию, опция -g или -ggdb включает сборку с уровнем отладочной информации 2. С этим уровнем GDB ничего не узнает о макроопределениях, что иногда создаёт неудобства. Например, при программировании под микроконтроллеры, для доступа к регистрам периферии используется набор макросов, которые связывают адреса регистров с именами. Эти имена не будут доступны в отладчике, если использовалась опция -g или -ggdb при сборке.

Уровень 3 включает макроопределения в отладочную информацию. Достаточно просто передать -g3 или -ggdb3, чтобы просматривать и вычислять макросы во время отладки. Также становятся доступны команды работы с макросами:

# Узнаём текущее значение в регистре данных АЦП
(gdb) p ADC_DR(ADC1)
$7 = 1495
# Узнаём адрес регистра данных АЦП
(gdb) p &ADC_DR(ADC1)
$8 = (volatile uint32_t *) 0x4001244c
# Смотрим, как был посчитан этот адрес
(gdb) macro expand ADC_DR(ADC1)
expands to: (*(volatile uint32_t *)((((0x40000000U) + 0x10000) + 0x2400) + 0x4c))

Исследовалась прошивка для ARM микроконтроллера на ядре Cortex-M3, работающая с АЦП через библиотеку libopencm3. Поскольку все регистры периферии в этой библиотеке определены с использованием макросов, то мы не смогли бы так просто узнать текущее измеренное значение в регистре данных преобразователя, если бы собрали с отладочной информацией уровня меньше третьего.

Язык сценариев GDB

Продвинутый отладчик GDB имеет встроенный язык программирования, который поддерживает переменные, выражения и управляющие конструкции. Это даёт возможность исследовать сложные моменты функционирования наших программ и устройств, на которых они работают.

Давайте на примере рассмотрим работу с переменными и наиболее часто используемые управляющие конструкции if-else-end и while-end:

# Создаём переменную и присваиваем ей значение
(gdb) set $i=0
# Смотрим значение переменной командой print
(gdb) p $i
$1 = 0
# Увеличиваем значение переменной на единицу
(gdb) set $i=$i+1
# Смотрим новое значение переменной командой print
(gdb) p $i
$2 = 1
# Пробуем цикл с инкрементом и выводом значения
(gdb) while $i<10
set $i=$i+1
p $i
end
$4 = 2
$5 = 3
$6 = 4
$7 = 5
$8 = 6
$9 = 7
$10 = 8
$11 = 9
$12 = 10
# Пробуем условие
(gdb) if $i >= 10
p $i
else
p 0
end
$13 = 10

Примеры бессмысленные, поскольку в них не участвую команды отладки, такие как: step (s), next (n), continue (c). Но если их таки задействовать, можно разрабатывать довольно витиеватые отладочные алгоритмы.

Надо отметить, что с выбором имён переменных надо быть осторожным: GDB экспортирует имена аппаратных регистров в виде $<reg> переменных. Вот как можно просмотреть информацию о регистрах:

(gdb) info registers
r0             0x200005cc	536872396
r1             0x5e7	1511
r2             0x9ae	2478
r3             0x200007e8	536872936
r4             0x800c3b4	134267828
r5             0x8000151	134218065
r6             0x20001e5a	536878682
r7             0x20001ef8	536878840
r8             0x20001e58	536878680
r9             0x20001e34	536878644
r10            0x20001e5a	536878682
r11            0x2	2
r12            0xffffffff	-1
sp             0x20002000	0x20002000
lr             0xffffffff	-1
pc             0x800a548	0x800a548 <reset_handler>
xPSR           0x1000000	16777216

В сущности же наши переменные $<var> и есть регистры GDB, только не аппаратные, а виртуальные. Отладчик и сам всякий раз при вычислении печатаемых значений создаёт виртуальные регистры под номерами $1 ... $N.

Условная остановка

Команда break (b) одна из наиболее полезных и часто используемых при отладке. Она расставляет точки останова в нашем коде, позволяя контролировать состояние программы в них. Иногда возникают ситуации, что нам необходимо отладить горячий код, поставив точку останова в таком месте программы, которое выполняется очень часто. Так отлаживать программу было бы сущим кошмаром, если бы у команды break не было возможности условной остановки. Обычно у нас имеются догадки относительно того, при каких условиях исследуемая проблема имеет место быть. А значит мы можем попросить отладчик останавливать выполнение только при достижении этих условий.

Предположим, что странности в поведении некого итеративного кода начинаются только с тысячной итерации:

(gdb) break step_func if iteration > 1000

Теперь только когда счётчик итераций перевалит за тысячу, произойдёт остановка, и мы сможем исследовать состояние нашей программы. В условии можно задействовать любые переменные, встречающиеся в коде, а также переменные, созданные командой set.

Точки останова по шаблону

Точки останова можно расставить сразу на целую группу функций, используя регулярные выражения в качестве шаблонов. Это особенно полезно в объектно-ориентированном программировании для отладки некоторого класса типов, когда с ходу локализовать проблему не получается. Для использования регулярных выражений существует специальная форма команды break с префиксом r. Я разрабатывал токенайзер, вытаскивающий распознаваемые токены из потока. Каждый токен разбирается соответствующей функцией, начинающейся на tokenize_ и оканцивающейся на _token. Вот как можно поставить точки останова на все подобные функции:

(gdb) rbreak tokenize_.*_token
Breakpoint 1 at 0x400a4f: file tokenizer.c, line 178.
static void tokenize_false_token(tokenize_runtime *);
Breakpoint 2 at 0x400a12: file tokenizer.c, line 168.
static void tokenize_true_token(tokenize_runtime *);
Breakpoint 3 at 0x4009d5: file tokenizer.c, line 158.
static void tokenize_null_token(tokenize_runtime *);
...

Маска .* совпадает с любым количеством любых символов в том числе и с отсутствием каких-либо символов, поэтому надо быть осторожным и в некоторых случаях использовать менее фривольные шаблоны. Отладчик находит все функции, имена которых совпадают с регулярным выражением, и ставит точки останова на них.

Автовыполнение команд

Итак, продолжим исследовать нашу гипотетическую программу. На этот раз выяснилось, что некие переменные меняются странным образом на протяжении многих итераций, и переменных этих также довольно много. GDB позволяет назначить команды, которые выполняются всякий раз, когда происходит остановка. Воспользуемся этим, например так:

# Описываем набор команд для точки останова с номером 1
(gdb) command 1
Type commands for when breakpoint 1 is hit, one per line.
End with a line saying just "end".
>p var1
>p var2
>p varN
>end

Теперь при каждой остановке мы будем видеть значения всех важных для нас переменных.

Примеры посложнее

А теперь усложним задачу. Предположим, что итеративная функция из первого примера не имеет счётчика итераций в программе, но нам необходимо исследовать значение некоторых переменных внутри неё с тысячной по тысяча сотую итерацию:

# Устанавливаем виртуальный счётчик в 0
(gdb) set $i=0
# Устанавливаем точку останова где-то внутри функции
(gdb) b main.c:233 if $i >= 1000 && $i < 1100
# Добавляем команды вывода значений переменных
(gdb) commands 1
Type commands for when breakpoint 1 is hit, one per line.
End with a line saying just "end".
>p var1
>p var2
>p varN
>set $i=$i+1
>end
# Продолжаем выполнение программы
(gdb) c

Логгирование вывода в файл

Иногда нам необходимо собрать значения переменных в процессе работы программы для последующего анализа. В простейшем случае можно перенаправить вывод GDB в файл, но это не всегда удобно. Например, когда мы хотим выборочно сохранить некоторые значения. В GDB поддерживается логгирование. Рассмотрим на практическом примере, как оно устроено:

#!/usr/bin/env arm-none-eabi-gdb --nh --nx -x adc.gdb --batch-silent

# Отлаживаем прошивку под ARM Cortex-M
# (в linux такой шэбанг работать не будет,
# поэтому запускаем вручную)

# Подключаемся к серверу OpenOCD
target remote localhost:3333
# Загружаем прошивку с отладочной информацией
file firmware.elf
# Выключаем логгирование
set logging off
# Перенаправляем лог в файл
set logging file adc.dat
# Запрещаем перезапись файла
# Данные будут добавляться в конце
set logging overwrite off
# Включаем перенаправление в файл
# чтобы не нагружать попусту stdout
set logging redirect on
# Делаем программный сброс цели
monitor reset halt
# Устанавливаем точку останова в функции
# преобразования измеренных значений АЦП
b adc_convert
# Устанавливаем счётчик
set $i=100
# Делаем цикл до нуля
while $i>0
  # Уменьшаем значение счётчика
  set $i=$i-1
  # Продолжаем выполнение до точки останова
  c
  # Устанавливаем начальный индекс элемента массива
  set $n=0
  # Проходим до конца массива
  while $n<sizeof(adc_buffer)/sizeof(adc_buffer[0])
    # Делаем ссылку на текущий элемент
    set $e=adc_buffer[$n]
    # Увеличиваем индекс на единицу
    set $n=$n+1
    # Включаем логгирование
    set logging on
    # Выводим значения АЦП буффера
    printf "%d %d %d %d %d %d\n", $e.Tmcu, $e.Vref, $e.Tback, $e.Vref1, $e.Tfront, $e.Vref2
    # Выключаем логгирование
    set logging off
  end
end
EOF

Здесь засветилась ещё одна приятная команда printf, это привычный программистам на C форматированный вывод. Мы использовали здесь эту функцию, чтобы сформировать данные, пригодные для построения графиков средствами GNUPlot.

Другие возможности GDB

Осталось ещё немного важных вещей, которые все обязаны знать.

Запуск отладки программы с аргументами:

sh:~$ gdb --args program --arg1=val1 --arg2

Удобная, надо сказать, особенность.

Вывод значений

Команда print (p) может отображать числовые значения в разных форматах:

# Напечатаем число в двоичном представлении
(gdb) p/t 10
$1 = 1010
# Выведем в восьмеричной системе счисления
(gdb) p/o 10
$2 = 012
# Ну и конечно можно вывести в шестнадцатеричной
(gdb) p/x 10
$3 = 0xa

Вот весь список возможных представлений:

  • o(octal)
  • x(hex)
  • d(decimal)
  • u(unsigned decimal)
  • t(binary)
  • f(float)
  • a(address)
  • i(instruction)
  • c(char)
  • s(string)
  • z(hex, zero padded on the left).

Команда print удобна для просмотра как атомарных значений, так и массивов и структур, поскольку она печатает значение так, как оно могло бы быть проинициализировано в исходном коде. Но эта команда подходит не для всего, ведь зачастую нам нужно просмотреть содержимое произвольного участка памяти. Для решения этой задачи существует команда x. Эта команда помимо формата представления принимает размер ячейки и общее число ячеек, содержимое которых необходимо показать:

# Отобразим одно слово (32 бита) в 16-ричном виде 
(gdb) x/1wx 0x08000000
0x8000000 <vector_table>:	0x20002000
# Отобразим три слова с указанного адреса
(gdb) x/3wx 0x08000000
0x8000000 <vector_table>:	0x20002000	0x0800a549	0x0800a547
# Отобразим одно слово в двоичном представлении
(gdb) x/1wt 0x08000000
0x8000000 <vector_table>:	00100000000000000010000000000000
# Отобразим один байт в двоичном представлении
(gdb) x/1bt 0x08000000
0x8000000 <vector_table>:	00000000

Вот полный список поддерживаемых аргументов размера:

  • b(byte)
  • h(halfword)
  • w(word)
  • g(giant, 8 bytes)

Надо отметить, что команда x в отличии от команды p запоминает последние использовавшиеся размер ячейки и формат представления, но не количество ячеек. Это удобно при работе с отладчиком из командной строки. Если не указать число выводимых элементов, то будет выведен один.

Заключение

На этом, пожалуй, всё. Подробности об этих и других возможностях хорошо описаны в документации. Удачной отладки.

  • Бортовой журнал Иллюмиума

Отправить комментарий

Содержимое этого поля является приватным и не будет отображаться публично.
  • Доступные HTML теги: <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd>
  • Syntax highlight code surrounded by the {syntaxhighlighter SPEC}...{/syntaxhighlighter} tags, where SPEC is a Syntaxhighlighter options string or "class="OPTIONS" title="the title".

Подробнее о форматировании

CAPTCHA
Этот вопрос задается для того, чтобы выяснить, являетесь ли Вы человеком или представляете из себя автоматическую спам-рассылку.
   ____    ____   _   _   _   _           _   _ 
/ ___| / ___| | \ | | | | | | __ _ | | | |
| | _ | | | \| | | |_| | / _` | | | | |
| |_| | | |___ | |\ | | _ | | (_| | | |_| |
\____| \____| |_| \_| |_| |_| \__, | \___/
|___/
Введите код, изображенный в стиле ASCII-арт.
RSS-материал

Навигация

  • Подшивки
  • Фотоальбомы

«Иллюмиум» на якоре.

Работает на Drupal, система с открытым исходным кодом.

(L) 2010, Illumium.Org. All rights reversed ^_~