суббота, 8 февраля 2014 г.

Страшная сказка программиста

Страшная сказка для детишек программитов: они жили долго и счастливо, пока за ней не пришёл Segmentation Fault...
В перерывах от перепечатывания дневника по второй поездке вокруг Кавказа я внедряю свой модуль по плавлению вещества нижней мантии в наш институский программный код по моделированию планет.
Надеюсь, этот постик кому-нибудь в будущем поможет найти ошибку у себя в программе быстрее, чем это сделал я.

Одно из необходимых условий для полноценного внедрения модели расплава в код, рассчитывающий механику движения вещества - это учёт разницы плотностей твёрдого и жидкого вещества. Функции для этого писались не мной. Причём, самое занятное, этот блок, который активировуется переменной-флагом в файле входных параметров, не использовался в стандартных расчётах (то есть функции были написаны в расчёте на "когда-нибудь у нас будет модель плавления"). 

Программно же это реализовано четырьмя вызывающими друг друга функциями (где-то 200 строк кода суммарно), распиханным по разным модулям в разных исходных файлах. И, для дополнительного удобства, никаких комментариев.

После того, как я вставил свой модуль, программа стала падать с Segmentation Fault. Самое забавное, что программа падала и до внедрения моего модуля. Просто, так как флаг выключен, функции не использовались, и никто об этом не знал. Скорее всего, подпрограмма нормально работала в какой-то предыдущей версии кода, но затем изменились используемые ей функции (это как я сейчас представляю себе возникновение такой ситуации).

Теперь, как я это лечил.

Дебаггер тут абсолютно бесполезен. Код в компилированном виде "бежит" час до ошибки. Понятно, что за время ожидания дебаггера поседеешь.

Есть слегка более "сложный" вариант - вывод на экран сообщения об исполнении каждой строчки кода (поскольку это segfault, то никакой информации, кроме шестнадцатеричных кодов стэка вызовов, получить напрямую нельзя). Но этот вариант тоже не работает в данном случае: при отсылке текста на экран программа продолжает "бежать" дальше, не дожидаясь завершения вывода в консоль. Но при падении с segfault вывод на экран прекращается, даже если текст уже был послан. Т.е. в тот момент, когда на экран печатается строчка, программа уже продвинулась на несколько строк. При этом, когда происходит ошибка, весь текст, который не успел отпечататься, пропадает.
Есть вариант каждый раз ждать ввода (например, нажатия Enter), но с учётом времени исполнения кода, поседеешь раньше.

Есть третий путь. Медитация над кодом. Плевать в потолок и ждать озарения. Я плевал в потолок пару дней. Он оказался самым успешным. Выяснилось, что вызов одной из функций шёл без одного аргумента (просто был пропущен параметр). Так как передаются аргументы из многомерных массивов, потеря бойца была незаметна глазом (слишком много символов и скобочек). 
Ситуация не была и настолько проста. Функция спокойно "смотрела" в чужую область память, её можно было даже распечатать (эффект оказался не воспроизводим). А затем она передавала эту переменную ссылкой ("смотрящей" в чужую память) в другую функцию! 
А вот при попытке изменить значение этой области памяти система уже давала по рукам и приканчивала программу с Segmentation Fault.

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

Я постарался изложить максимально понятно, но, поскольку тема непростая, приношу всем извинения за возможные трудности с пониманием.
Надеюсь, это описание кому-нибудь пригодится.

Ну а я продолжаю желать специального пояса в седьмом круге ада для программистов, которые не пишут комментарии к своему коду.

Технический PS: язык программирования Fortran, компилятор mpif90, ОС Mac OS X 10.7.5.

2 комментария:

  1. Техподдержка Apple. Занятно. У них поди вымерли от старости не только знатоки Fortrana, но и их дети.

    ОтветитьУдалить