Еще ни одному ИТ-администратору не удалось избежать аварий, завершающихся «голубым экраном». Обычно они происходят в самое неудачное время и часто приводят к перерывам в обслуживании и остановкам работы, связанным с финансовыми потерями. Работая в составе группы разработки Windows, мне ежедневно приходится выполнять диагностику таких сбоев и составлять план действий, чтобы предотвратить их повторение. Это очень напоминает популярное криминальное телешоу CSI, в котором следователи на месте преступления применяют самые хитроумные методы, чтобы «вычислить» преступника. В некоторых моих диагностических расследованиях уликой оказывается единственный бит, переключенный в 1, тогда как программа ожидает 0. Бывает, что «преступник» давно скрылся, и единственный оставленный им след — повреждение памяти. Поэтому драйвер с некорректным поведением обнаружить очень трудно.
В настоящей статье мы рассмотрим инструменты, используемые группой технической поддержки Microsoft для диагностики повреждений памяти ядра, обычно вызванных ошибкой какого-нибудь драйвера. Из-за сложности диспетчера памяти не всегда понятно, почему группа технической поддержки рекомендует именно эти инструменты. Я расскажу об этом в ближайших статьях, которые будут особенно полезны для специалистов, ответственных за подготовку плана действий для менеджеров. Существует много других причин для исследования аварий, но в данной статье основное внимание уделяется инструментарию, используемому группой технической поддержки Microsoft при диагностике проблем, вызванных нарушениями памяти ядра.
Архитектура пулов памяти
Прежде чем углубиться в подробности, кратко рассмотрим высокоуровневую архитектуру пулов памяти. Это упростит понимание описания инструментов. Основная часть памяти, используемая драйверами, выделяется из пулов, предоставляемых операционной системой: выгружаемого пула (paged pool) и невыгружаемого пула (nonpaged pool). Из этого правила существуют исключения, но мы здесь их рассматривать не будем. Как видно из названий, память в невыгружаемом пуле всегда гарантированно находится в физической памяти, а части выгружаемого пула в любой момент могут быть перенесены в файл подкачки.
Для лучшего понимания результатов работы диагностических инструментов важно отметить, что пул делится на меньшие единицы, именуемые страницами. Страницы могут быть малыми и большими. Размер малой страницы — 4 Кбайт, а большой — 2 Мбайт на платформе x64 или 4 Мбайт в компьютерах x86. Однако, если в системе x86 включен режим расширения физического адреса (PAE), размер больших страниц уменьшается до 2 Мбайт. У страниц каждого типа есть свои преимущества, но в данной статье предполагается, что используются малые страницы по 4 Кбайт.
Далее страница дробится на участки памяти, выделяемые различным драйверам, запрашивающим память. На экране показан образец страницы невыгружаемой памяти на 4 Кбайт.
Я воспользовался командой !pool для отображения памяти в отладчике Microsoft (windbg.exe), который можно загрузить в составе пакета Microsoft Debugging Tools из сайта загрузок Microsoft. Дополнительные сведения об использовании отладчика Microsoft можно найти в статье «Диагностика неисправностей: рекомендации администраторам», опубликованной в Windows IT Pro/RE № 8 за 2009 г. Windbg — основной инструмент диагностики, используемый группой Microsoft Global Escalation Services. В одной из следующих статей предполагается рассмотреть различные методы отладки, применяемые для диагностики такого типа сбоев с помощью Windbg.
В данном случае представлена одна страница невыгружаемой памяти на 4 Кбайт. Каждая строка — блок памяти, выделенный для страницы или освобожденный из нее. Большинство блоков в данном примере выделены, и владельца блока можно определить по тегу, приведенному справа от каждого выделенного участка памяти. В поступающем от драйвера запросе на выделение памяти из пула содержится размер запрошенного блока и четырехсимвольный идентификатор, именуемый тегом. Тег и размер запрошенного выделения сохраняются в учетной структуре, именуемой заголовком пула, которая находится в верхней части каждого выделенного участка памяти. В этой структуре также хранятся текущий размер и предшествующий размер блока. На основе этих сведений диспетчер памяти анализирует содержимое страницы при выполнении задач обслуживания, например объединяя смежные освобожденные блоки в один большой свободный блок. Следующая за заголовком область — вероятно, самая важная при выделении памяти драйверам, так как именно в ней хранятся данные страницы памяти.
Место преступления: следы виновника
Учитывая метод организации пула диспетчером памяти, рассмотрим, какое воздействие ошибка в программе оказывает на среду, что приводит к сбоям с «голубым экраном». Одна из наиболее распространенных ошибок драйверов — запись за пределы выделенного участка памяти с переходом в следующий выделенный участок. При этом перезаписываются данные, не принадлежащие драйверу. Как отмечалось выше, в каждом выделенном участке собственно области данных всегда предшествует заголовок пула. Поэтому при записи данных в следующий выделенный участок портится его заголовок пула. Если впоследствии диспетчер памяти попытается прочесть испорченный заголовок пула, вероятно, произойдет сбой с ошибкой Bug Check 0x19: BAD_POOL_HEADER или Bug Check 0xC2: BAD_POOL_CALLER. Параметры этих ошибок неплохо документированы в MSDN. Первые два параметра обычно указывают на поврежденное состояние; однако с их помощью нельзя определить некорректный драйвер.
Расширим ситуацию, прежде чем рассмотреть инструменты, используемые для обнаружения «виновных» драйверов. Используя результаты вывода, показанные на экране, предположим, что драйвер, использующий пул VadS с виртуальным адресом fffffadcda813c50, выполнил запись далее заголовка пула выделенного участка памяти Irp по адресу fffffadcda813c90 и продолжил запись в область данных. В этом случае испорчен не только заголовок пула, но и данные драйвера. Если драйвер, которому принадлежит пул Irp, использует искаженные данные, велика вероятность нарушения стабильности системы. Ситуация усугубляется тем, что владелец пула Irp выглядит виновником, так как этот драйвер может оказаться на вершине стека в случае системного сбоя.
Код ошибки зависит от того, как владелец пула Irp использовал испорченные данные. Это может быть STOP 0x0000001e, если драйвер пула Irp пытается использовать испорченное значение как указатель, и соответствующий адрес недоступен. А если он доступен и драйвер записал данные по этому произвольному адресу, повреждение переходит в другой пул или даже критическую структуру ядра. Эти примеры иллюстрируют, как трудно отследить драйвер, когда нет очевидного способа выяснения виновника.
В данном случае легко указать на пул VadS и быстро обнаружить причину неполадок. Иное дело, если пул VadS был освобожден и из него выделен блок другому драйверу, но после того, как VadS повредил пул Irp. Владелец VadS давно сменился, следствие зашло в тупик.
Поможет Special Pool
Special Pool был реализован в Windows NT SP4 для обнаружения в реальном времени драйверов, которые портят память. Вокруг выделенного участка памяти формируются охранные страницы, и идея состоит в том, чтобы перехватить драйвер, выполняющий запись вне выделенного ему участка памяти. Попытка записи в охранную зону приводит к немедленному системному сбою, и виновник находится на вершине стека. Преступник схвачен с дымящимся пистолетом! В следующей статье будет подробно рассмотрен механизм Special Pool и особенности его применения.
Рон Сток (ronsto@microsoft.com) — инженер по разработке в группе Global Escalation Services компании Microsoft. Специализируется на диагностике сложных ошибок и проблемах, влияющих на производительность Windows