1 of 38

ИПР 2026/27

Дебаг систем

в UNIX

01

2 of 38

План лекции

02

К концу занятия: выбрать наблюдение, объяснить вывод и проверить исправление.

Раздел

Результат

Симптом и падение

Гипотеза, сигнал, живой процесс и core dump

Отладчик и граница ОС

GDB, символы, LLDB/pdb, strace и логи

Ошибки памяти

ASan, MSan, TSan, UBSan и границы проверок

Скорость и масштаб

perf, статический анализ, профилирование и GWP-ASan

3 of 38

Главный вопрос расследования

03

Симптом → гипотеза → наблюдение → причина → проверка исправления.

Какое наблюдение отличит

мою гипотезу от других?

Сначала фиксируем версию, вход, окружение и воспроизводимость.

4 of 38

Один симптом — разные первые шаги

04

Название инструмента следует из вопроса, который нужно проверить.

Симптом

Первое наблюдение

Инструмент

Падение

Сигнал, версия, стек

GDB и core dump

Файл не найден

Путь, каталог, errno

strace на Linux

Память или гонка

Исполненный сценарий

Санитайзер

Медленно

Нагрузка и метрика

Профилировщик

5 of 38

Статус в shell и сигнал

05

Лучше записать имя сигнала, чем один числовой статус.

Завершение командой

./port_demo

echo $?

$? — статус последней команды в shell.

Завершение сигналом

SIGABRT · SIGSEGV

kill PID → SIGTERM

SIGKILL нельзя перехватить; он не создаёт core dump.

6 of 38

Core dump: снимок момента остановки

06

Сообщение «core dumped» не гарантирует файл ./core.

01

Регистры, потоки и части памяти на момент завершения

02

Наличие и путь зависят от лимитов и конфигурации

03

Дамп не хранит историю и не исполняется

ulimit -c unlimited

cat /proc/sys/kernel/core_pattern

7 of 38

Живой процесс и дамп

07

Если дамп хранит systemd: coredumpctl list / info / debug.

Живой процесс

Core dump

Стек и значения

Изучаем текущий момент

Изучаем сохранённый момент

Шаг и продолжение

Возможны

Невозможны

След от события

Можно поймать заново

Только то, что сохранилось

8 of 38

Что не так?

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;

}

9 of 38

Сборка для понятного показа

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

10 of 38

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 of 38

Данные в выбранном кадре

11

После next: port = 70000, max_port = 65535.

Значения

port = 70000

max_port = 65535

Недопустимый порт проходит условие остановки.

Причинная цепочка

main передал 70000 → validate_port проверил границы → вызван abort.

12 of 38

Стек: как мы сюда пришли

12

Верхний кадр после SIGABRT может принадлежать библиотеке C.

(gdb) backtrace

(gdb) continue

(gdb) backtrace

(gdb) up

(gdb) list

Проявление: abort. Условие: плохой порт. Источник: вызов из main.

13 of 38

Шаг, кадр и многопоточность

13

gdb -p PID останавливает процесс на время работы отладчика.

Вопрос

Команды

Следующая строка / внутрь вызова

next / step

Выбранный кадр и значения

frame N, up, down, info locals

Все потоки и стеки

info threads; thread apply all bt

14 of 38

Посмертный разбор в GDB

14

Тот же бинарник и подходящие debug symbols критичны для чтения стека.

gdb -q ./port_demo ./core

(gdb) bt

(gdb) info threads

(gdb) frame N

(gdb) info locals

15 of 38

Символы связывают адрес и исходник

15

Случайный абсолютный адрес нельзя всегда напрямую подать symbolizer.

01

Адрес в процессе

02

Модуль и смещение

03

Функция и строка в нужной сборке

llvm-symbolizer --obj=... адрес

16 of 38

LLDB и pdb: тот же ход мысли

16

Пауза → стек → кадр → значения → проверка гипотезы.

LLDB · macOS

breakpoint set --name validate_port

run

thread backtrace

Точки останова, шаги и кадры.

pdb · Python

Остановка на строке Python, стек, значения и исключение. Нативные расширения — отдельный уровень диагностики.

17 of 38

Какой путь программа передала ОС?

17

strace показывает системные вызовы и сигналы, а не строки исходника.

«No such file or directory»

Нужно увидеть операцию, путь и результат вызова.

18 of 38

strace: сохранить и отфильтровать

18

ls завершится с ошибкой: для этого примера она ожидаема.

strace -f -e trace=%file -o trace.log \

ls /tmp/ipr-debug-missing

grep 'ipr-debug-missing' trace.log

19 of 38

ENOENT: читать справа налево

19

Имя системного вызова может отличаться между версиями системы.

newfstatat(AT_FDCWD,

"/tmp/ipr-debug-missing",

..., 0) = -1 ENOENT

01

Результат: ENOENT

02

Путь: /tmp/ipr-debug-missing

03

Операция: запрос сведений о файле

20 of 38

Что трасса доказывает и чего не доказывает

20

На macOS для этой задачи нужен инструмент платформы.

Наблюдение

Следующий вопрос

Много ENOENT

Какой путь связан с жалобой?

Нет новых системных вызовов

Что делают потоки в user space?

Ожидание I/O

Как долго и какой вызов ждёт?

21 of 38

Логи восстанавливают последовательность

21

Контекст помогает, но секреты и персональные данные логировать нельзя.

Было: failed

2026-09-29T08:10:02Z ERROR

component=loader request_id=abc

file=/data/input.csv error=ENOENT

Время · компонент · операция · путь · код ошибки · запрос

22 of 38

Санитайзер проверяет исполненный путь

22

Отсутствие отчёта не доказывает корректность всей программы.

01

Сборка с инструментированием

02

Тест или нагрузка проходит проблемный код

03

Отчёт ближе к ошибочному действию

23 of 38

Какие ошибки искать каким инструментом

23

ASan + UBSan можно сочетать; ASan, MSan и TSan обычно — разные сборки.

Инструмент

Типичная находка

Граница

ASan

Границы и время жизни памяти

Не любое use-after-move

MSan

Неинициализированное значение

Инструментировать зависимости

TSan

Гонка данных

Не каждый deadlock

UBSan

Поддерживаемые виды UB

Не весь UB

24 of 38

Use-after-free в четырёх строках

24

Попросите аудиторию назвать опасную строку до запуска.

int main() {

int* value = new int[1];

value[0] = 42;

delete[] value;

return value[0]; // ошибка

}

25 of 38

ASan: получить отчёт

25

Исправление — убрать доступ после delete[] и прояснить владение памятью.

g++ -g -O0 \

-fsanitize=address,undefined uaf.cpp -o uaf

./uaf

Ожидаемый заголовок: heap-use-after-free

26 of 38

Три места в отчёте ASan

26

Вернуться к исходному входу после исправления.

01

Где выполнен ошибочный доступ

02

Где память была освобождена

03

Где память выделили

27 of 38

Границы проверок

27

Исполненный путь, версия сборки и режим проверки имеют значение.

Отчёт есть

Сопоставляем доступ, освобождение, выделение и исправляем причину.

Отчёта нет

Путь мог не исполниться; класс ошибки может не входить в возможности инструмента.

28 of 38

«Программа корректна» — но медленная

28

Правильный ответ не означает приемлемую задержку.

Что именно стало медленным?

29 of 38

Выберите метрику до профиля

29

Один просмотр исходника не показывает реальные затраты.

Жалоба

Измерение

Вероятный следующий шаг

CPU вырос

CPU под похожей нагрузкой

Профиль

Ответ стал дольше

Задержка по запросам

Разделить CPU и ожидания сторонних событий

Редкие пики

Время, версия и частота

Сравнить интервалы и сборки

30 of 38

perf: от образцов к месту затрат

30

perf.data содержит образцы, не готовое изображение.

perf record -g -- ./hot

perf report

Найдите busy_work в профиле и проверьте характер нагрузки.

31 of 38

Flame graph — агрегированные стеки

31

Ширина блока не равна времени одного вызова.

01

Ширина блока: доля образцов с этим кадром

02

Высота: глубина стека вызовов

03

Сравнивайте профили при похожей нагрузке

32 of 38

CPU и ожидание — разные причины задержки

32

Высокая доля в профиле — гипотеза, проверяемая на сценарии.

CPU занят

Ищем горячие функции при характерной нагрузке.

Процесс ждёт

Измеряем I/O, блокировки и время ожидания — другой вид наблюдений.

33 of 38

До запуска: clang-tidy

33

clang-format форматирует код и сам по себе не ищет ошибки.

01

Анализ исходника с параметрами сборки

02

Пример: bugprone-use-after-move

03

Предупреждение требует проверки

34 of 38

На многих машинах: непрерывный профиль

34

Профиль распределяет наблюдённые затраты; он не объясняет любой сбой.

Сравнить версии и время регрессии

01

Реальный трафик

02

Группа машин

03

Распределение затрат

35 of 38

GWP-ASan: выборочная защита heap

35

Название не означает сочетание профилировщика с ASan.

Что ловит

Некоторые ошибочные обращения к защищённым heap-выделениям.

Чего не обещает

Полного покрытия всех выделений и всех путей исполнения.

36 of 38

Дампы в большом парке машин

36

Доступ к памяти дампа ограничиваем: в ней могут быть секреты.

01

Дамп и версия бинарника

02

Подходящие символы

03

Контроль доступа и срок хранения

37 of 38

Проверка понимания

37

01

Ночной SIGABRT · не найден файл

02

Случайный use-after-free · гонка двух потоков

03

После релиза CPU вырос на 30%

38 of 38

Маршрут после занятия

38

01

Зафиксируйте точный симптом

02

Выберите наблюдение под гипотезу

03

Повторите исходный сценарий после исправления