ИПР 2026/27
Дебаг систем
в UNIX
01
План лекции
02
К концу занятия: выбрать наблюдение, объяснить вывод и проверить исправление.
Раздел
Результат
Симптом и падение
Гипотеза, сигнал, живой процесс и core dump
Отладчик и граница ОС
GDB, символы, LLDB/pdb, strace и логи
Ошибки памяти
ASan, MSan, TSan, UBSan и границы проверок
Скорость и масштаб
perf, статический анализ, профилирование и GWP-ASan
Главный вопрос расследования
03
Симптом → гипотеза → наблюдение → причина → проверка исправления.
Какое наблюдение отличит
мою гипотезу от других?
Сначала фиксируем версию, вход, окружение и воспроизводимость.
Один симптом — разные первые шаги
04
Название инструмента следует из вопроса, который нужно проверить.
Симптом
Первое наблюдение
Инструмент
Падение
Сигнал, версия, стек
GDB и core dump
Файл не найден
Путь, каталог, errno
strace на Linux
Память или гонка
Исполненный сценарий
Санитайзер
Медленно
Нагрузка и метрика
Профилировщик
Статус в shell и сигнал
05
Лучше записать имя сигнала, чем один числовой статус.
Завершение командой
./port_demo
echo $?
$? — статус последней команды в shell.
Завершение сигналом
SIGABRT · SIGSEGV
kill PID → SIGTERM
SIGKILL нельзя перехватить; он не создаёт core dump.
Core dump: снимок момента остановки
06
Сообщение «core dumped» не гарантирует файл ./core.
01
Регистры, потоки и части памяти на момент завершения
02
Наличие и путь зависят от лимитов и конфигурации
03
Дамп не хранит историю и не исполняется
ulimit -c unlimited
cat /proc/sys/kernel/core_pattern
Живой процесс и дамп
07
Если дамп хранит systemd: coredumpctl list / info / debug.
Живой процесс
Core dump
Стек и значения
Изучаем текущий момент
Изучаем сохранённый момент
Шаг и продолжение
Возможны
Невозможны
След от события
Можно поймать заново
Только то, что сохранилось
Что не так?
08
Сначала показываем validate_port, значение из main раскрываем позже.
#include <cstdlib>
int validate_port(int port) {
const int max_port = 65535;
if (port < 1 || port > max_port) {
std::abort();
}
return port;
}
Сборка для понятного показа
09
Ожидаем SIGABRT; это ещё не ответ на вопрос «почему».
int main() {
return validate_port(70000);
}
g++ -std=c++17 -g -O0 -fno-omit-frame-pointer \
port_demo.cpp -o port_demo
./port_demo
GDB
10
После break и run остановка будет в validate_port.
gdb -q ./port_demo
(gdb) break validate_port
(gdb) run
(gdb) next
(gdb) print port
(gdb) print max_port
Данные в выбранном кадре
11
После next: port = 70000, max_port = 65535.
Значения
port = 70000
max_port = 65535
Недопустимый порт проходит условие остановки.
Причинная цепочка
main передал 70000 → validate_port проверил границы → вызван abort.
Стек: как мы сюда пришли
12
Верхний кадр после SIGABRT может принадлежать библиотеке C.
(gdb) backtrace
(gdb) continue
(gdb) backtrace
(gdb) up
(gdb) list
Проявление: abort. Условие: плохой порт. Источник: вызов из main.
Шаг, кадр и многопоточность
13
gdb -p PID останавливает процесс на время работы отладчика.
Вопрос
Команды
Следующая строка / внутрь вызова
next / step
Выбранный кадр и значения
frame N, up, down, info locals
Все потоки и стеки
info threads; thread apply all bt
Посмертный разбор в GDB
14
Тот же бинарник и подходящие debug symbols критичны для чтения стека.
gdb -q ./port_demo ./core
(gdb) bt
(gdb) info threads
(gdb) frame N
(gdb) info locals
Символы связывают адрес и исходник
15
Случайный абсолютный адрес нельзя всегда напрямую подать symbolizer.
01
Адрес в процессе
02
Модуль и смещение
03
Функция и строка в нужной сборке
llvm-symbolizer --obj=... адрес
LLDB и pdb: тот же ход мысли
16
Пауза → стек → кадр → значения → проверка гипотезы.
LLDB · macOS
breakpoint set --name validate_port
run
thread backtrace
Точки останова, шаги и кадры.
pdb · Python
Остановка на строке Python, стек, значения и исключение. Нативные расширения — отдельный уровень диагностики.
Какой путь программа передала ОС?
17
strace показывает системные вызовы и сигналы, а не строки исходника.
«No such file or directory»
Нужно увидеть операцию, путь и результат вызова.
strace: сохранить и отфильтровать
18
ls завершится с ошибкой: для этого примера она ожидаема.
strace -f -e trace=%file -o trace.log \
ls /tmp/ipr-debug-missing
grep 'ipr-debug-missing' trace.log
ENOENT: читать справа налево
19
Имя системного вызова может отличаться между версиями системы.
newfstatat(AT_FDCWD,
"/tmp/ipr-debug-missing",
..., 0) = -1 ENOENT
01
Результат: ENOENT
02
Путь: /tmp/ipr-debug-missing
03
Операция: запрос сведений о файле
Что трасса доказывает и чего не доказывает
20
На macOS для этой задачи нужен инструмент платформы.
Наблюдение
Следующий вопрос
Много ENOENT
Какой путь связан с жалобой?
Нет новых системных вызовов
Что делают потоки в user space?
Ожидание I/O
Как долго и какой вызов ждёт?
Логи восстанавливают последовательность
21
Контекст помогает, но секреты и персональные данные логировать нельзя.
Было: failed
2026-09-29T08:10:02Z ERROR
component=loader request_id=abc
file=/data/input.csv error=ENOENT
Время · компонент · операция · путь · код ошибки · запрос
Санитайзер проверяет исполненный путь
22
Отсутствие отчёта не доказывает корректность всей программы.
01
Сборка с инструментированием
02
Тест или нагрузка проходит проблемный код
03
Отчёт ближе к ошибочному действию
Какие ошибки искать каким инструментом
23
ASan + UBSan можно сочетать; ASan, MSan и TSan обычно — разные сборки.
Инструмент
Типичная находка
Граница
ASan
Границы и время жизни памяти
Не любое use-after-move
MSan
Неинициализированное значение
Инструментировать зависимости
TSan
Гонка данных
Не каждый deadlock
UBSan
Поддерживаемые виды UB
Не весь UB
Use-after-free в четырёх строках
24
Попросите аудиторию назвать опасную строку до запуска.
int main() {
int* value = new int[1];
value[0] = 42;
delete[] value;
return value[0]; // ошибка
}
ASan: получить отчёт
25
Исправление — убрать доступ после delete[] и прояснить владение памятью.
g++ -g -O0 \
-fsanitize=address,undefined uaf.cpp -o uaf
./uaf
Ожидаемый заголовок: heap-use-after-free
Три места в отчёте ASan
26
Вернуться к исходному входу после исправления.
01
Где выполнен ошибочный доступ
02
Где память была освобождена
03
Где память выделили
Границы проверок
27
Исполненный путь, версия сборки и режим проверки имеют значение.
Отчёт есть
Сопоставляем доступ, освобождение, выделение и исправляем причину.
Отчёта нет
Путь мог не исполниться; класс ошибки может не входить в возможности инструмента.
«Программа корректна» — но медленная
28
Правильный ответ не означает приемлемую задержку.
Что именно стало медленным?
Выберите метрику до профиля
29
Один просмотр исходника не показывает реальные затраты.
Жалоба
Измерение
Вероятный следующий шаг
CPU вырос
CPU под похожей нагрузкой
Профиль
Ответ стал дольше
Задержка по запросам
Разделить CPU и ожидания сторонних событий
Редкие пики
Время, версия и частота
Сравнить интервалы и сборки
perf: от образцов к месту затрат
30
perf.data содержит образцы, не готовое изображение.
perf record -g -- ./hot
perf report
Найдите busy_work в профиле и проверьте характер нагрузки.
Flame graph — агрегированные стеки
31
Ширина блока не равна времени одного вызова.
01
Ширина блока: доля образцов с этим кадром
02
Высота: глубина стека вызовов
03
Сравнивайте профили при похожей нагрузке
CPU и ожидание — разные причины задержки
32
Высокая доля в профиле — гипотеза, проверяемая на сценарии.
CPU занят
Ищем горячие функции при характерной нагрузке.
Процесс ждёт
Измеряем I/O, блокировки и время ожидания — другой вид наблюдений.
До запуска: clang-tidy
33
clang-format форматирует код и сам по себе не ищет ошибки.
01
Анализ исходника с параметрами сборки
02
Пример: bugprone-use-after-move
03
Предупреждение требует проверки
На многих машинах: непрерывный профиль
34
Профиль распределяет наблюдённые затраты; он не объясняет любой сбой.
Сравнить версии и время регрессии
01
Реальный трафик
02
Группа машин
03
Распределение затрат
GWP-ASan: выборочная защита heap
35
Название не означает сочетание профилировщика с ASan.
Что ловит
Некоторые ошибочные обращения к защищённым heap-выделениям.
Чего не обещает
Полного покрытия всех выделений и всех путей исполнения.
Дампы в большом парке машин
36
Доступ к памяти дампа ограничиваем: в ней могут быть секреты.
01
Дамп и версия бинарника
02
Подходящие символы
03
Контроль доступа и срок хранения
Проверка понимания
37
01
Ночной SIGABRT · не найден файл
02
Случайный use-after-free · гонка двух потоков
03
После релиза CPU вырос на 30%
Маршрут после занятия
38
01
Зафиксируйте точный симптом
02
Выберите наблюдение под гипотезу
03
Повторите исходный сценарий после исправления