Дело о случайных сбоях Internet Explorer и Windows Media Player
Когда я впервые столкнулся со сбоями Internet Explorer (IE) на моей домашней 64-битной игровой системе, то изначально списал всю вину на повреждение памяти стороннего плагина. Я продолжил свою работу, но через несколько дней я снова столкнулся со сбоем IE, вслед за котором сбои стали случаться и при каждом третьем или четвертом запуске Windows Media Player (WMP).
Сбои в разных приложениях явно указывали на более фундаментальную проблему. Я разгонял свой процессор и поэтому грешным делом решил, что причиной сбоев является перегрев процессора, поэтому я с неохотой вернулся к стандартным настройкам множителя частоты процессора. Однако, к моему удивлению, сбои продолжались. Следующей моей теорией было то, что у меня проблемы с ОЗУ, но Windows Vista Memory Diagnostic не смогла найти каких-либо проблем.
После того, как стало понятным, что аппаратная составляющая не виновата, следующим моим шагом было изучение дампов на предмет наличия возможных зацепок. Но сначала мне надо было найти дамп сбоя. Процесс Application Error Reporting в Windows XP всегда создает дамп-файл перед тем, как открыть диалоговое окно о сбое программы, и вы всегда можете найти местоположение дампа, нажав на ссылке просмотра подробностей, а потом на ссылке по просмотру технических деталей отчета.
Соответствующий диалог в Windows Vista не предоставляет возможности просмотра технических подробностей, а также не создает файлы дампов, если серверы Windows Error Reporting (WER) его не запрашивают - это происходит только при получении информации о серьезном количестве таких сбоев. К счастью, WerFault, процесс, который представляет собой диалог о сбое программы, хранит информацию об сбойном процессе до тех пор, пока вы не нажмете кнопку Close Program, что дает возможность подключиться к процессу с помощью отладчика и изучить процесс. В Process Explorer вы можете увидеть, как WerFault обрабатывает аварийно завершившийся процесс Windows Media Player:
В следующий раз, когда я столкнулся со сбоем, я запустил WinDbg, отладчик Windows из пакета Debugging Tools for Windows, который доступен для бесплатной загрузки с сайта Microsoft. После того, как я убедился, что в диалоговом окне Symbol File Path выставлены настройки на подключение к публичному серверу символов Microsoft (то есть srv*c:\symbols*https://msdl.microsoft.com/download/symbols), я зашел в меню File и выбрал пункт Attach to a Process...
Данное действие открывает диалоговое окно WinDbg по выбору процесса, к которому необходимо подключится. Я пролистал весь список, чтобы найти сбойный процесс. Когда я выбрал нужный процесс, WinDbg открыл его и отобразил его точно также, как при загрузке файла дампа, за исключением того, что в данном случае вы не можете инициировать команду отладчика !analyze, которая запускает эвристический анализ для выяснения причины сбоя. При подключении отладчика анализ покажет то, что вы уже знаете: вы подключены с помощью отладчика.
Поиск потенциальных причин сбоя при подключении с помощью отладчика требует просмотра стека каждого потока в процессе, поэтому в меню View я открыл окна Processes and Threads и Call Stack.
Я начал изучать потоки с выбора первого элемента в окне потоков.
Обычно в такие моменты командное окно WinDbg становится серым и показывает окно с сообщением о том, что программа занята, так как WinDbg получает символы с публичного сервера, после чего в стеке вызовов потока появляется структура функций потока на момент сбоя. В порядке прямой последовательности я изучал каждый поток, передвигаясь между ними с помощью стрелки вниз и кнопки enter, охотясь за стеком, в котором были бы функции со словами exception или fault. Ближе к концу списка я столкнулся с этим:
Я заметил, что верхняя часть списка полна функций с суффиксом Exception. Просмотрев нижнюю часть списка (верхнюю часть стека), я заметил, что функция в Nvappfilter вызвала функцию HeapFree в Kernel32.dll, что и привело к аварийному завершению работы. Исключение в незанятых подпрограммах динамически распределяемой памяти значит, что или вызывающая функция обратилась к неправильному адресу памяти, или память уже была повреждена, когда функция начала исполнятся. Если бы вызывающей библиотекой была Windows DLL, то я об этом я бы узнал чуть позже, но в данном случае виновата была точно стороння библиотека: об этом говорил тот факт, что WinDbg не смог найти информацию для данного символа и, как следствие, не знал имени функций данной библиотеки. Я подтвердил свое предположение, запустив команду lm (list module), чтобы просмотреть информацию о версии.
Теперь основным подозреваемым стал Nvappfilter, но у меня не было доказательств того, что именно данная библиотека виновата в сбоях. Я продолжил пользоваться системой и проделал те же шаги во время следующих нескольких сбоев. Вне зависимости от того, какое приложение завершало свою работу с ошибкой, стек сбоя всегда был одним и тем же - Nvappfilter вызывал функцию HeapFree. Это все еще не решающее доказательство, но отдельные доказательства были уже налицо.
На данном этапе я решил проверить обновления Nvappfilter, но не знал точно, с каким именно приложением ассоциирована данная библиотека. Я ввел данное имя в поисковик и узнал, что это функция пакета nVidia FirstPacket, которая дает приоритет игровому трафику и входит в состав программного обеспечения для материнских плат nForce.
Я зашел на сайт nVidia и загрузил последнюю версию nForce, но пакет не смог обновить Nvappfilter.dll, поэтому сбои продолжались.
В панели управления nVidia я не смог найти способ предотвращающий загрузку Nvappfilter, поэтому мне надо было самому отключить данную функцию. Я не стал использовать функцию FirstPacket, поскольку раньше я и не знал, как ей пользоваться, но для начала мне было нужно понять, как данной библиотеке удалось настроить Windows на свою загрузку. Для этого я запустил программу Autoruns и в меню Winsock Layered Service Provider (LSP) я нашел ссылки на 32- и 64-битные версии Nvappfilter.
Я удалил все упоминания о Nvappfilter и с тех пор не было никаких сбоев. Пока я писал данную статью, я снова проверил обновления nForce на предмет обновленной версии Nvappfilter. В новых версии Nvappfilter или любой другой Winsock LSP отсутствует, поэтому предположение о том, что Nvappfilter был виноват в падениях, оказалось верным.
В ходе расследования этого дела я также был вынужден прибегнуть к функциональности локальных дампов в Vista SP1. Поэтому в будущем я буду автоматически получать файлы дампов для расследования при сбое какого-либо приложения. Если вы создадите ключ под названием HKLM\Software\Microsoft\Windows\Windows Error Reporting\LocalDumps, то WerFault всегда будет сохранять дамп. По умолчанию они сохраняются в папку %LOCALAPPDATA%\Crashdumps, но в реестре вы можете изменить данное значение, а также количество сохраняемых WerFault-дампов.