Дело о случайном сбое IE

Иногда мне удается выкроить время для описания случаев диагностики ошибок, с которыми столкнулся лично. В процессе решения этих проблем я часто придумываю новые методики, которыми могу поделиться с вами в своих презентациях и статьях из серии «Дело о необъяснимом …». На днях я успешно справился с ошибкой, которая заключалась в сбое работы Internet Explorer (IE) при просмотре в нем веб-страницы:

clip_image001

Каждый раз, когда я сталкиваюсь со сбоями – вне зависимости от того, является причиной этого сбоя сама система или приложение – я всегда пытаюсь разобраться, почему это произошло. И во многих случаях мне хватает всего нескольких минут, чтобы обнаружить подсказки, указывающие на то, что причиной ошибки является надстройка, после чего я уже могу исправить ошибку, либо обойти ее. В большинстве случаев, когда речь идет о сбое приложения, процесс, вызвавший этот сбой, очевиден, и я просто запускаю Windbg (из пакета Debugging Tools for Windows, который поставляется с Windows SDK и Windows DDK), прикрепляю его к этому процессу и начинаю расследование.

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

clip_image002

Как обычно, у меня было открыто несколько вкладок, так что я должен был выяснить, какой из четырех запущенных процессов IE (в дополнение к родительскому экземпляру процесса) вызвал сбой. Я мог бы попытаться решить эту задачу «в лоб», проследив за каждым процессом и найдя ошибочный поток, но есть более простой и короткий путь для идентификации нужного процесса.

Когда происходит сбой процесса, служба Windows Error Reporting (WER) запускает свой собственный процесс, называемый WerFault, в сеансе ошибочного процесса для того, чтобы отобразить диалоговое окно с сообщением об ошибке для пользователя, запустившего сеанс, и для генерации файла аварийного дампа. Для того чтобы WerFault знал, какой именно процесс претерпел сбой, служба WER передает идентификатор нужного процесс (PID) в строку команды WerFault. Вы можете легко просмотреть эту строку команды с помощью Process Explorer. Поскольку у меня всегда запущен Process Explorer и соответствующий значок отображается в системном трее на панели задач, я щелкнул по нему для открытия утилиты и нашел процесс WER в дереве процессов:

clip_image003

Я дважды щелкнул на нем для открытия диалогового окна свойств процесса, и строка команды содержала ID проблемного процесса IE:

clip_image004

Теперь, когда я узнал, что мне нужен процесс 4440, я запустил Windbg, нажал F6 для открытия диалогового окна выбора процесса и дважды щелкнул на процессе 4440 Iexplore.exe. Следующим шагом стало нахождение потока, который вызвал сбой, чтобы я мог исследовать его стек на наличие проблемной надстройки. В некоторых случаях для решения этой задачи можно обратиться к встроенной в Windbg функции эвристического анализа, которая вызывается с помощью команды !analyze, но на этот раз она не помогла. Однако, процесс нахождения проблемного потока все-таки довольно прямолинеен.

Во-первых, перейдите в меню View в Windbg и откройте диалоговые окна Processes and Threads и Call Stack, расположив их рядом друг с другом. Целью является нахождение потока, у которого есть функции, в именах которых присутствуют слова fault, exception, или unhandled. Вы можете быстро сделать это, выбирая каждый поток в окне Processes and Threads, нажимая Enter и затем просматривая стек, который появляется в окне Call Stack. После того, как я просмотрел первые несколько потоков, я нашел искомый, в названиях функций которого по всему стеку встречались указанные строки:

clip_image005

К сожалению, перейдя к надстройке, я оказался в тупике: все DLL, показанные в стеке вызовов, принадлежали Microsoft. Одна строчка указывала на то, что это могла быть надстройка, не присутствующая в списке - в ней говорилось, что Windbg не смог найти символы для нескольких кадров стека и был вынужден сослаться на размещение стека и показал адрес, который не входил ни в одну DLL:

clip_image006

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

У Windbg есть команда, которую можно использовать в таких случаях для поиска пропущенных кадров с адресами функций – команда Display Words and Symbols. Если вы производите отладку 32-разрядного процесса, то используйте dds-версию этой команды, а если 64-разрядного процесса – то dqs. Вы также можете использовать команду dps (Display Pointer Symbols), которая интерпретирует адреса функций в соответствующие размеры для 32-разрядных и 64-разрядных процессов. В качестве адреса, предоставляемого команде в качестве стартовой точки, нужно брать адрес фрейма стека, который расположен прямо над потерянным Windbg адресом. Чтобы увидеть этот адрес, нажмите кнопку Addrs в диалоговом окне стека вызовов:

clip_image007

Адрес рассматриваемого фрейма – 2cbc5c8:

clip_image008

Я передал его в качестве аргумента для команды dds и нажал Enter:

clip_image009

Первая страница с результатами содержала только одну функцию, KiUserException. Я снова нажал кнопку Enter, не введя никакой новой команды, поскольку для команд работы с адресами, таких как dds, подобное действие заставляет Windbg повторить последнюю команду, начиная с того адреса, на котором она остановилась. Вторая страница выглядела уже интереснее и содержала имена DLL, с которыми я был не знаком:

clip_image010

Чтобы просмотреть информацию о версии модуля прямо из Windbg, можно воспользоваться командой lm (List Modules). Результат выполнения этой команды показал, что Yt.dll (именем DLL является текст слева от знака «!») являлся частью Yahoo Toolbar:

clip_image011

Это стало неожиданностью для меня, поскольку система, на которой произошел сбой, являлась мой домашней игровой системой, компьютером, который я приобрел всего несколько недель назад. Единственным программным обеспечением, которое я устанавливаю на моих игровых системах, является Microsoft Office и игры. Я не использую панели инструментов браузеров, а если бы я все-таки сделал это, то выбрал бы Bing, а не Yahoo. Более того, дата создания DLL указывала на то, что ей более двух лет. Я очень внимательно просматриваю опции установщиков программ, так что, вероятно, эта панель инструментов появилась на моей системе через установку одной из утилит тестирования и отслеживания температуры видеокарты, которые я использовал для разгона системы. Я нахожу практику навязывание пользователям дополнительных опций весьма раздражающей, тем более, когда пользователям даже не дают право выбора, как в данном случае. Пара минут работы с панелью задач, и моя система освободилась от нежелательной и устаревшей панели задач.

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