Отладка
Хорошая книжка по отладке: https://en.wikibooks.org/wiki/Linux_Applications_Debugging_Techniques
О структуре исполняемых файлов (коротко)
ELF:
nm для статически собранных объектников
readelf / objdump для динамически собранных объектников
ldd (LD_LIBRARY_PATH=, LD_PRELOAD= — в следующей лекции)
Что нужно для отладки
- Без оптимизации
- Без привязки к исходнику — дизассемблер + имена
Привязка к исходнику — debuginfo
- Поиск исходника
- То же для всех библиотек
-debuginfo-версии пакетов с библиотеками
+ их исходники (обычно содержатся в -debuginfo)
⇒ cc -O0 -g
GDB
- Выполнение
run
continue
next
step
finish
advance
- Точки останова
По номеру строки (оптимизация ) breakpoint
b main + run — это start
По изменению ячейки (=> выражения) watchpoint
По C++-исключению или syscall-у catchpoint
- Просмотр
Ячейки (=> выражения, в т. ч. адресного) print, display, x
куска исходника вокруг точки останова list
Запись в файл dump
- Стек вызовов
Просмотр backtrace
Переход up, down
- …!
Пример на bt с несколькими функциями
b fun2 + run + bt
Пример игры со стеком:
Он ещё и не падает, скотина! И компилируется без ошибок!! (Правда, ключ -Wall спасает, но не от всего).
Падает, если закомментировать вызов tst(&b);
- Как поймать:
Посмотрим адрес b (локальная переменная ⇒ на стеке) p/x &b
Посмотрим состояние стека в tst() с помощью x/8x $sp и мусора под стеком x/20x $sp-0x20 (стек растёт вниз!)
Посмотрим адрес x в tst() и чему оно становится равно
Посмотрим адрес a в tst2(), какой «мусор» (ой ли?) там лежит, и что из всего этого получается!
Такой вот этот ваш Си! Всё можно написать))
Из чего состоит -devel пакет?
Из чего состоит -debuginfo пакет?
- Вместо установки debuginfo-версий пакетов можно указать Debuginfod-сервер
В частности, для ALT: https://debuginfod.altlinux.org/
Пример с libpcre (если успеем)
~/.cache/debuginfod_client
Материалы:
Шпаргалка по GDB: https://sourceware.org/gdb/onlinedocs/refcard.pdf
ptrace_scope и gdb -p
- ALT:
# cat /etc/sysctl.d/99-sysctl.conf kernel.yama.ptrace_scope = 0
- ALT:
- Способ отладки программ, работающих с экраном (например, с curses)
- На сервере:
$ gdbserver localhost:port prog
- На сервере же:
$ gdb prog (gdb) target remote localhost:port …это вместо run …дальше как обычно
- На сервере:
- Способ отладки программ, работающих с экраном (например, с curses)
- Удалённая отладка:
- На свереве:
- Зайти с пробросом порта:
client$ ssh аккаунт@сервер -Lпорт:localhost:порт server$ gdbserver localhost:port prog
- Зайти с пробросом порта:
- На клиенте
получить исходник от prog (например, с помощью sftp / rsync, ssh)
$ gdb (gdb) target remote localhost:port …
debuginfo скачает сам! (или с $DEBUGINFOD_URLS)
- На свереве:
Интерфейсы для gdb
Бонус: GDBFrontend
- Проброс GDBFrontend с сервера практикума:
- Установка GDBFrontend
Стартовый сценарий лежит в ~/.local/bin/; это хорошее место для добавления в $PATH
- Проброс порта www-сервера
[me@myhost ~]$ ssh myacc@linuxprac -L5000:localhost:5000
- Запуск backend-а
- Запуск frontend-а
Загрузить программу / нажать на жука
- Установка GDBFrontend
- Проброс GDBFrontend с сервера практикума:
Сценарии для GDB
обучающая статья — её имеет смысл отщёлкать!
Пример для кода с fun2() (кумулятивный, разберём понемногу)
set pagination off b fun2 if n > 98 command 1 bt cont end run quit
.gdbinit
Пример (перекрашивание, сохранение истории и специальные функции для вывода содержимого QString):
set history save on set style address foreground yellow define printqs5static set $d=$arg0.d printf "(Qt5 QString)0x%x length=%i: \"",&$arg0,$d->size set $i=0 set $ca=(const ushort*)(((const char*)$d)+$d->offset) while $i < $d->size set $c=$ca[$i++] if $c < 32 || $c > 127 printf "\\u%04x", $c else printf "%c" , (char)$c end end printf "\"\n" end define printqs5dynamic set $d=(QStringData*)$arg0.d printf "(Qt5 QString)0x%x length=%i: \"",&$arg0,$d->size set $i=0 while $i < $d->size set $c=$d->data()[$i++] if $c < 32 || $c > 127 printf "\\u%04x", $c else printf "%c" , (char)$c end end printf "\"\n" end
Сценарии для gdb на Питоне (в документации). А что, так можно было?
Д/З
Установить в сборочное окружение -debuginfo версии библиотек. В разных дистрибутивах могут называться по-разному, приезжать вместе с -devel версиями и даже отсутствовать. В ALT называются libчтототам-debuginfo-версия и лежат в отдельной секции репозитория (вот пример sources.list с сервера практикума)
frbrgeorge@linuxprac ~/src $ grep "^[^#]" /etc/apt/sources.list.d/yandex.list rpm [alt] http://mirror.yandex.ru/altlinux Sisyphus/x86_64 classic debuginfo rpm [alt] http://mirror.yandex.ru/altlinux Sisyphus/x86_64-i586 classic rpm [alt] http://mirror.yandex.ru/altlinux Sisyphus/noarch classic
Прощёлкать как минимум make-your-debugging-easier и gdb-scripting
- Задача.
Написать простейшую (или нет, см. далее ☺) программу-генератор арифметической прогрессии range.c, принимающую от одного до трёх параметров по аналогии с питоновским range()
- Без параметров — выводит help
- С одним параметром N — выводит в столбик последовательность [0, 1, … N-1]
- С двумя — M, N — последовательность [M, M+1, … N-1]
- С тремя — M, N, S — последовательность [M, M+S, M+2S, … N-1]
- Написать следующие gdb-сценарии (формат вывода произвольный):
Запустить ./range с параметрами 1 12 и вывести содержимое переменных, отвечающих на начало, конец, шаг и текущее значение элемента прогрессии, только когда этот элемент кратен 5
Запустить ./range с параметрами -100 100 3 и вывести содержимое переменных, отвечающих на начало, конец, шаг и текущее значение элемента прогрессии только для 28, 29, 30, 31, 32, 33, 34, и 35 по счёту элементов прогрессии
Написать Makefile, в котором будут
Компиляция бинарника с ключами -O0 -g
Цель test: — запуск этих gdb-сценариев и сравнение их вывода с эталонным
gdb любит выводить много лишней/динамической информации, например, адреса, которые вполне могут не повторяться при новом запуске.
я использовал в gdb-сценариях связку echo @@@ и print …, после чего grep-ал строки, начинающиеся на @@@, и с эталоном сравнивал только их.
Ещё я использовал --batch и --quiet, а вот logging не использовал
Чтобы вывод утилиты range не мешал, его надо перенаправлять в /dev/null прямо в самой команде run
Цель clean: — удаление всех генератов
Вариант для тех, кому скучно так тупо программировать на Си. Дописать функции и типы данных к этой программе, решить Д/З с ней:
- Заодно и поотлаживаете ☺!
Создать в репозитории с решениями подкаталог 04_Debugging и положить туда все исходники решения этой задачи