Учебные материалы: механизм вместо тезисов, блоки «Факты для карточек», Ловушки (Claude Code) + diag/cards_src.tsv
This commit is contained in:
+125
-12
@@ -3,21 +3,134 @@
|
||||
В `crash.c` лежит программа, которая падает на части входов и портит память.
|
||||
Задача — не переписать её с нуля, а **найти дефекты отладчиком и починить минимально**.
|
||||
|
||||
Ожидаемое поведение после починки:
|
||||
## Ожидаемое поведение после починки
|
||||
|
||||
- `./crash` (без аргумента) печатает `len=5` и завершается с кодом 0;
|
||||
- `./crash <слово>` печатает `len=<длина слова>` и завершается с кодом 0;
|
||||
- `./crash ""` печатает `len=0` и завершается с кодом 0;
|
||||
- сборка с `-fsanitize=address,undefined` не даёт ни одного сообщения об ошибке.
|
||||
|
||||
Что сделать:
|
||||
1. Собрать с отладочной информацией и санитайзерами:
|
||||
`gcc -g -O0 -fsanitize=address,undefined crash.c -o crash`
|
||||
2. Разобраться, что именно портит память, а что приводит к падению при выходе.
|
||||
Полезно: `gdb ./crash`, `run`, `bt`, `frame`, `info locals`, `watch`.
|
||||
3. Исправить `crash.c` (минимальные правки, стиль сохранить).
|
||||
4. Заполнить `answer.txt`: сколько дефектов нашёл, какие именно, какими командами
|
||||
отладчика это подтвердил (по шагам), почему падало именно так.
|
||||
**Факты для карточек**
|
||||
- base | Что печатает `./crash` без аргументов после починки? — `len=5`, код возврата 0
|
||||
- base | Что печатает `./crash ""` после починки? — `len=0`, код возврата 0
|
||||
- core | Какие два санитайзера должны не давать сообщений после починки? — ASan и UBSan (`-fsanitize=address,undefined`)
|
||||
|
||||
Проверка: `python3 grade.py 09` — компилирует `crash.c` (компилятор C, gcc) с ASAN/UBSAN
|
||||
и прогоняет 5 входов.
|
||||
`answer.txt` проверяется ревью (это часть оценки: важен ход разбора, а не только результат).
|
||||
Почему дальше: чтобы вообще видеть номера строк и имена переменных в отладчике, а не голый
|
||||
ассемблер, программу нужно собрать определённым образом — с этого и начинается разбор.
|
||||
|
||||
## Сборка с отладочной информацией и санитайзерами
|
||||
|
||||
```
|
||||
gcc -g -O0 -fsanitize=address,undefined crash.c -o crash
|
||||
```
|
||||
|
||||
`-g` встраивает в бинарник отладочную информацию в формате DWARF — таблицу соответствия
|
||||
машинных адресов номерам строк исходника, именам переменных, их типам и границам функций.
|
||||
Именно из DWARF gdb берёт всё, что показывает человеку: `bt` печатает имена функций и строки
|
||||
вместо голых адресов, `print x` знает тип `x` и умеет напечатать его по правилам этого типа
|
||||
(структуру — по полям, массив — по элементам), `list` показывает исходный код вокруг текущей
|
||||
точки. Без `-g` DWARF-секций в бинарнике нет, и gdb видит только адреса и ассемблер.
|
||||
|
||||
`-O0` отключает оптимизации компилятора: оптимизатор переставляет и удаляет инструкции,
|
||||
инлайнит функции и переиспользует один регистр под несколько переменных с непересекающимся
|
||||
временем жизни — из-за этого отладочная информация от `-O2` часто не соответствует
|
||||
однозначно исходному коду (строки «прыгают», переменная в `print` показывает не то значение,
|
||||
потому что регистр уже переиспользован под другую переменную). Отсюда правило: для отладки
|
||||
всегда пересобирают отдельно с `-g -O0`, а не отлаживают прод-сборку с оптимизациями.
|
||||
|
||||
`-fsanitize=address,undefined` — это ASan и UBSan вместе, инструментирование на этапе
|
||||
компиляции, а не отдельный инструмент поверх готового бинарника:
|
||||
- **ASan** окружает каждый выделенный блок памяти недоступными «красными зонами» и ведёт
|
||||
теневую карту состояния памяти — ловит выход за границы буфера, use-after-free и двойное
|
||||
освобождение прямо в момент обращения, с точным стеком, а не когда испорченные данные
|
||||
позже вызовут крах в случайном другом месте;
|
||||
- **UBSan** инструментирует места, где поведение по стандарту C не определено — знаковое
|
||||
переполнение, сдвиг за пределы разрядности типа, разыменование `NULL` с невалидным типом —
|
||||
и печатает конкретную строку кода нарушения.
|
||||
|
||||
**Ловушки**
|
||||
- Собрать без `-g` и пытаться понять крах по голому `bt` с адресами вместо строк → отладка
|
||||
вслепую по ассемблеру, не соответствует задаче «найти минимальными правками».
|
||||
- Отлаживать сборку с `-O2` → строки в `bt`/`print` не совпадают с реальным местом бага
|
||||
из-за инлайнинга и переиспользования регистров → ложный след при поиске дефекта.
|
||||
- Пропустить пересборку с `-fsanitize=address,undefined` перед финальной проверкой → баг,
|
||||
который не падает явным креша при обычном запуске (например, чтение за границей внутри
|
||||
тем же выделенного блока), останется незамеченным до `grade.py`.
|
||||
|
||||
**Факты для карточек**
|
||||
- base | Что даёт флаг `-g` при сборке для gdb? — отладочную информацию (DWARF): соответствие адресов строкам, именам и типам переменных
|
||||
- core | Почему для отладки собирают с `-O0`, а не с `-O2`? — оптимизатор переставляет/удаляет инструкции и переиспользует регистры, из-за чего строки и значения в отладчике перестают однозначно соответствовать исходнику
|
||||
- core | Что ловит ASan из перечисленного: выход за границы, use-after-free, двойное освобождение? — все три, в момент обращения к памяти, а не постфактум
|
||||
- deep | Что именно ловит UBSan, в отличие от ASan? — неопределённое поведение по стандарту C (знаковое переполнение, сдвиг за пределы разрядности), а не ошибки работы с памятью
|
||||
|
||||
Почему дальше: раз отладочная информация уже в бинарнике, нужно знать конкретные команды
|
||||
gdb, которыми по ней ходят при разборе краша.
|
||||
|
||||
## Команды gdb для разбора краша
|
||||
|
||||
Рабочий цикл: `gdb ./crash`, затем `run [аргумент]` — передаёт управление процессу; если
|
||||
процесс получает сигнал (обычно `SIGSEGV` от порчи памяти или abort от санитайзера), gdb сам
|
||||
останавливается на инструкции, вызвавшей сбой.
|
||||
|
||||
- `bt` — стек вызовов, цепочка кадров от текущей функции до `main`; первое, на что смотрят
|
||||
после остановки — показывает, в какой функции и через какие вызовы дошли до краша.
|
||||
- `frame N` — переключает контекст `print`/`list` на конкретный кадр стека из `bt`, чтобы
|
||||
посмотреть локальные переменные вызывающей функции, а не только текущей.
|
||||
- `info locals` — печатает все локальные переменные текущего кадра с их значениями по
|
||||
информации из DWARF.
|
||||
- `watch var` — аппаратная точка наблюдения: останавливает выполнение при каждом изменении
|
||||
значения переменной. Нужна, когда переменная меняется как будто сама по себе из другого
|
||||
места кода (типичный симптом переполнения буфера, которое затирает соседнюю память) — без
|
||||
`watch` пришлось бы расставлять точки останова по подозрению вручную.
|
||||
- `print x` — печатает текущее значение `x`, по типу из DWARF (структуру — по полям).
|
||||
|
||||
Санитайзер добавляет отдельный слой: при нарушении (ASan/UBSan) программа останавливается
|
||||
сама, печатая стек и описание нарушения ещё до того, как повреждение памяти успело привести
|
||||
к крашу в другом, не связанном с причиной месте — это часто быстрее, чем ловить последствия
|
||||
вручную в gdb по голому `SIGSEGV`.
|
||||
|
||||
**Факты для карточек**
|
||||
- base | Какая команда gdb показывает цепочку вызовов до краша? — `bt`
|
||||
- core | Чем `watch var` отличается от обычного `break`? — останавливает выполнение при каждом изменении значения переменной, а не в заданной точке кода
|
||||
- core | Зачем нужен `frame N` после `bt`? — переключает контекст `print`/`info locals` на конкретный кадр стека, чтобы смотреть переменные не только текущей функции
|
||||
|
||||
## Отчёт и проверка
|
||||
|
||||
Исправить `crash.c` (минимальные правки, стиль сохранить). Заполнить `answer.txt`: сколько
|
||||
дефектов нашёл, какие именно, какими командами отладчика это подтвердил (по шагам), почему
|
||||
падало именно так.
|
||||
|
||||
Проверка: `python3 grade.py 09` — компилирует `crash.c` (компилятор C, gcc) с ASAN/UBSAN и
|
||||
прогоняет 5 входов. `answer.txt` проверяется ревью (это часть оценки: важен ход разбора, а
|
||||
не только результат).
|
||||
|
||||
**Факты для карточек**
|
||||
- base | Сколько входов прогоняет `grade.py 09`? — 5
|
||||
- base | Каким компилятором собирается `crash.c` на проверке? — gcc (компилятор C)
|
||||
|
||||
<details>
|
||||
<summary>Проверь себя</summary>
|
||||
|
||||
1. Почему для отладки нельзя использовать тот же бинарник, что уже собран с `-O2` для
|
||||
финальной проверки производительности?
|
||||
<details><summary>Ответ</summary>Оптимизатор переставляет и удаляет инструкции, инлайнит
|
||||
вызовы и переиспользует регистры под разные переменные — отладочная информация от такой
|
||||
сборки не соответствует однозначно исходным строкам, `bt`/`print` покажут «прыгающие» или
|
||||
неверные значения; для отладки нужна отдельная сборка `-g -O0`.</details>
|
||||
|
||||
2. Программа падает с `SIGSEGV` без санитайзеров, но при сборке с ASan вместо краша печатается
|
||||
сообщение о heap-buffer-overflow раньше, в другом месте кода. Почему это не противоречие?
|
||||
<details><summary>Ответ</summary>ASan останавливает программу в момент фактического выхода
|
||||
за границу буфера — в точке причины; без ASan повреждённая память могла не привести к
|
||||
немедленному краху, а вызвать `SIGSEGV` значительно позже, когда испорченные данные
|
||||
использовались уже в другом месте — это типичная картина «краш далеко от причины».</details>
|
||||
|
||||
3. Чем `watch var` полезнее обычной точки останова (`break`) для поиска места, где переменная
|
||||
портится неожиданно?
|
||||
<details><summary>Ответ</summary>`break` останавливает выполнение в заданной строке кода,
|
||||
но не знает, где именно переменная меняется; `watch var` — аппаратная точка наблюдения,
|
||||
которая остановит выполнение на любой инструкции, изменившей значение этой переменной, даже
|
||||
если изменение происходит из неожиданного места (например, из-за записи за границей другого
|
||||
буфера, затирающей соседнюю память).</details>
|
||||
|
||||
</details>
|
||||
|
||||
Reference in New Issue
Block a user