Прелести отладки

Процесс отладки, насколько я могу судить, наполовину состоит невнятного мычания, пожимания плечами и чесания в затылке. Процентов двадцать занимает нудное «копание» в отладчике или поиски крупиц полезной информации в логах. Живое воображение с элементами садизма: «а вот мы его еще так попробуем, сверху трейсером придавим, а снизу KD подопрем», берет на себя еще десяток. Чистая удача, хорошая память «где-то я это уже видел» и занудство «а вот почему ту запятая не на месте?» вместе составят – еще десять. Причем на удачу из них приходится ровно 0.42%. Западло, а также строгое выполнение всех законов Мерфи, - и вот еще 9%. На заветное «Эврика! Нашел!» практически ничего не остается. :-(

Последние два дня мне не давала жить одна проблема. Одно из приложений начало падать в нашей ветке и продолжало нормально работать в родительской ветке, блокируя интеграцию изменений из нашей ветки наверх. Чисто чтобы добавить драматизма, приложение это уже год как не поддерживается авторами, о чем прямо заявлено на сайте разработчиков. Разобраться что не так, тем не менее, всё равно пришлось, так как любая проблема в OS обычно затрагивает множество приложений.

Ну, хорошо, ставлю OS на тестовую машину, устанавливаю приложение, запускаю – работает. Гм. Пробую и так и эдак – работает. Уточняю repro steps. Оказывается, нужно запускать с помощью ярлыка на рабочем столе. Замечательно. Запускаю через ярлык. Работает. Откатываю систему до чистого состояния с помощью System Restore, снова ставлю приложение, запускаю – падает. Ага. Запускаю второй раз – работает. Понятно, значит, наверное, какой-нибудь отсутствующий ключ в реестре создается при первом запуске. Для проверки запускаю в третий раз. Падает. Замечательно.

Ладно, смотрю причину падения. Поврежден exception chain и при попытке бросить исключение происходит передача управления по некорректному адресу. Exception record выглядит набором мусора, но весь «мусор» честно передается в вызов RaiseException. Так что либо стек портится в самом приложении, либо это не «мусор». Проверяю работающий вариант. Там бросается точно такое же исключение с аналогичными параметрами. Получается, что это C++ исключение или собственная надстройка над SEH. Самое главное, что к моменту вызова RaiseException в «нерабочем» случае exception chain уже испорчен.

Пробую зайти с другой стороны – делаю лог обращений к реестру в обоих случаях. И в «рабочем» и в «нерабочем» логе есть подозрительные ACCESS DENIED. Проблема, однако, что набор операций завершившихся ACCESS DENIED в «рабочем» случае отличался от запуска к запуску.

Спустя N перезапусков выясняется, что незадолго до RaiseException вызывается RegisterTypeLib, которая, что характерно, возвращает разный результат для «рабочего» и «нерабочего» случаях. Оно? Не тут-то было. По ходу дела выясняется, что в «сбойном» случае RaiseException зовется минимум дважды, и только последний вызов приводит к падению. А первый – тот, что зовется после RegisterTypeLib, отрабатывается нормально. Также выясняется, что в некоторых «успешных» запусках RegisterTypeLib возвращает точно такую же ошибку, как и в «сбойном». Значит не оно.

В конце концов, натыкаюсь на вызов RegCreateKeyEx, который возвращает разный результат, не смотря на идентичные параметры вызова в обоих случаях. Выяснилось, что в «нерабочем» варианте этот RegCreateKeyEx вполне успешно создавал требуемый ключ. В результате программа продолжала выполнение и в конце концов падала после вызова RaiseException. В «рабочем» же варианте ACCESS DENIED, возвращённый из RegCreateKeyEx, приводил к тому, что RaiseException второй раз не вызывался. Exception chain же был в обоих случаях одинаково поврежден к моменту вызова RegCreateKeyEx. Баг ушел владельцам реестра – разбираться кто прав, а кто виноват.

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

comments powered by Disqus