Pull to refresh

Отладка программ на C для начинающих

Reading time 3 min
Views 32K
… или что делать если «Hello world!» упала.

Всё последующее в основном написано для ОС Linux и консольной отладки, хотя кое-что можно использовать и в других условиях.

Возможно это прозвучит странно, но начинать писать порграмму стоит с установки системы контроля версий (если ещё не установлена) и создания репозитория. Это нужно для того, чтобы в процессе написания не потерять много времени на попытки вспоминить где, что, как, когда и зачем пишущий исправлял/добавлял. На сегодня наиболее популярные — это svn (subversion), git и mercurial. Последний, лично мне, нравится больше остальных, т.к., субъективно, он проще и удобнее, особенно для личного пользования.

Далее нужно убедиться в наличии команд gdb (отладчик) и strace (монитор системных вызовов). Если их нет, то установить. А при компиляции своей программы не забыть включить добавление отладочной информации.

Итак, случилось — она упала. Один из основных моментов, которому меня научили — внимательно читать, что пишет система/программа. Многое можно узнать, воспользовавшись командами:
  • dmesg (информация ядра), lspci (устройства на шине PCI), lsmod (список загруженных драйверов) — при работе с драйверами;
  • tail /var/log/messages — показывает десяток строк в конце системного лога;
  • ps ax — список запущенных процессов (ключи могут быть и другие);


Допустим, что ничего нужного там не нашлось. Тогда стоит выполнить команду «ulimit -c 50000», ulimit (встроенная команда shell, устанавливающая/показывающая ограничения использования ресурсов shell. Подробное описание можно почитать c помощью man или тут), 50000 — взято отбалды, это размер core-файла, который в большинстве случаев создастся после падения программы, запущенной в этой же консоли, и представляет собой дамп памяти упавшей программы. Далее запускаем программу снова, в консоли, где был ulimit. Она опять падает, но уже с созданием коры (обычно). Как вариант, всё это можно проделать и заранее, т.к. если ошибка плавающая, то второй раз может упасть нескоро, бывало люди месяцами ждали.

С помощью отладчика нужно попытаться рассмотреть тёпленькую кору:
  • gdb <программа> core.NNNN

gdb предоставляет интерактивный консольный интерфейс, в котором много чего можно сделать, но тут я описывать всё не стану — в манах и интернете это есть на куче языков. Пока достаточно будет выполнить там команду «bt» (от backtrace), которая покажет стэк вызовов и, если не было прописи памяти (рассмотрим ниже), можно будет увидеть где сломалось. А с помощью команды «frame N», где N — номер вызова (слева), можно увидеть и подробнее. Команда «print <переменная>» поможет увидеть (не всегда) значение переменной. Если ваш «Hello world!» многонитевый, то тут всё сложнее, но можно попытаться воспользоваться командой «thread N» отладчика, чтобы перейти к стэку N-ой нити, правда, как правило, это не сильно помогает.

Если отладчик рисует только кучу вопросиков в стэке вызовов, то это явная пропись памяти и он (отладчик) вам не поможет. В большинстве таких случаев нужно открыть текст программы и
обратить особое внимание на функции memcpy, memset, sprintf и прочие, подобного типа, работающие с блоками данных, способных выйти за пределы массива и писать поверх всего, что идёт в памяти дальше. Скорее всего ошибка где-то там. Как минимум стоит их заменить на более безопасные аналоги (если есть), например, snprintf. Если и это не помогло, то в бой идут старые, проверенные временем методы:
  • комментирование всего и раскомментирование маленькими частями с последующей компиляцией и проверкой на отсутствие ошибки;
  • вставка отладочной печати (зачастую чуть ли не после каждой строки подозрительного участка кода (memset! memcpy!)) с дальнейшим анализом — после какой печати сломалось.

Это помогает как и для однонитевых, так и для многонитевых программ (тут как обычно могут быть танцы с бубном).

Как быть если программа входит в бесконечный цикл и система впадает в ступор (бывает и такое)? Выгружаем оконный менеджер и в текстовом режиме на одна из консолей запускается под суперпользователем (root). Для полной уверенности можно ей немного повысить приоритет с помощью nice/renice, а программу запустить на другой консоли/терминале под обычным пользователем. Тогда её, при зацикливании, можно будет снять в консоли суперпользователя (команда «kill») и таки увидеть что она пишет.

Что делать если ничего не помогает? Ответ только один — садиться и внимательно, очень внимательно, изучать код и думать.

Если в программе ошибок нет (не видно на 1001й взгляд и анализ), то может на разделах закончилось место? (true story)

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

P.S. В исходниках ядра Linux каталог Documentation есть файлик CodingStyle. В нём есть что взять на заметку начинающему C-программисту.

P.P.S. Если бы мне всё это было известно cначала, то мне было бы гораздо проще.
Tags:
Hubs:
+25
Comments 64
Comments Comments 64

Articles