Издательский дом ООО "Гейм Лэнд"ЖУРНАЛ ХАКЕР 113, МАЙ 2008 г.

Жизнь сервера без BSOD

Крис Касперски

Хакер, номер #113, стр. 113-144-1

Скрытые рычаги управления ядром Windows Server 2003

Синий экран смерти — одна из самых больших неприятностей, которая только может случиться с сервером. Хотелось бы, чтобы она происходила как можно реже, пусть даже ценой некоторого снижения производительности и увеличения потребления оперативной памяти. Говорить будем, главным образом, о Server 2003 Standard Edition. Но все сказанное во многом будет справедливо и для других ОС линейки NT.

Введение

Синий экран смерти вспыхивает всякий раз, когда ядро диагностирует критическую ошибку, которую не в состоянии корректно обработать. Например: обращение по нулевому указателю, попытка освобождения уже освобожденной памяти и т.д. Ядро передает управление процедуре KeBugCheckEx, ответственной за «отрисовку» BSOD и сохранение дампа памяти (если администратор действительно хочет его сохранить). Некоторые отладчики уровня ядра (например, Soft-Ice) перехватывают вызов KeBugCheckEx, позволяя оператору «разрулить» ситуацию самостоятельно и вернуть систему к жизни. Это требует достаточно высокой квалификации, так что на этом вопросе подробно останавливаться не будем.

При всем моем уважении к NT, следует сказать, что у нее бездарное, кривое и недописанное (!) ядро. Еще со времен NT 4.x (если не раньше) была предусмотрена возможность вызова call-back'ов (функций обратного вызова) из KeBugCheckEx, позволяющих, в частности, сбросить дисковые буферы, чтобы не погубить дисковый том. Но на дворе уже Server 2008, а практическая реализация call-back'ов даже не обещается (хотя в NTFS драйвере все готовое для этого есть, странно, не правда ли?!).

Другая неприятная черта NT – нежелание разбираться с источниками критических ошибок. При возникновении исключения в загружаемом модуле ядра и Linux, и xBSD просто выгружают модуль, продолжая нормальную работу системы. Только в действительно критических ситуациях система впадает в панику («kernel panic» – аналог BSOD). Казалось бы, у разработчиков NT в наличии все необходимые ингредиенты: имеется список модулей (то есть драйверов); у драйверов есть процедура, ответственная за выгрузку драйвера (а даже если ее нет, система имеет возможность выгружать драйвера в аварийном режиме); функция KeBugCheckEx в большинстве случаев определяет имя драйвера-виновника. Ну что же мешает его выгрузить его? Ладно, не будем о грустном, а сразу перейдем к делу.

Основными источниками BSOD являются: дефекты железа, кривые драйвера и rootkit'ы. Некоторые API-функции прикладного уровня за счет ошибок проектирования также могут приводить к синим экранам (как и ошибки в самом ядре системе), но их доля в общем зачете невелика. Итак, по большому счету остаются только железо и драйвера/rootkit'ы.

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

Таким образом, в списке остались лишь драйвера/rootkit'ы. Ну, rootkit'ы удаляем сразу («][акер» неоднократно писал, как), а вот с драйверами проблема. Хороших системных программистов очень мало, а фирм, разрабатывающих железо – великое множество, вот и приходится нанимать «мальчиков по объявлению», клепающих драйверы в визуальных средах проектирования типа DriverStudio, а потом удивляющихся, почему они падают. Исправление ошибок в драйвере – дело посильное (достаточно дружить с дизассемблером и отладчиком). Нетрудно, конечно, скачать более свежую версию (в надежде, что там хотя бы часть ошибок исправлена), но лучше и надежнее переконфигурировать ядро операционной системы, сделав его менее чувствительным к ошибкам в драйверах. Чем мы сейчас, собственно говоря, и займемся.

Рычаги управления ядром

Ядро поддерживает множество рычагов управления. Ниже перечислены только основные из них.

  • boot.ini

Файл boot.ini, который находится в корневом каталоге системного диска, принимает большое количество параметров, управляющих конфигурацией ядра. Параметры подробно описаны на MSDN: support.microsoft.com/kb/833721/ru (только документированные ключи) и в статье Марка Руссиновича «Boot.ini options reference» на сайте www.ingenieroguzman.com.ar.

  • глобальные флаги

Ядро NT поддерживает так называемые «глобальные флаги», управляющие его поведением. Задаются они с помощью утилиты gflags.exe, входящей в комплект поставки «Support Tools», и в DDK, который можно бесплатно скачать с сайта Microsoft.

  • реестр

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

Наиболее популярные BSOD'ы и способы их устранения

Появление многоядерных процессоров поставило драйверы «в позу», к чему большинство из них оказалось совершенно не готово. Синие экраны начали вспыхивать с некоторой вероятностью, зависящей от частоты поступления прерываний от аппаратных устройств. Рассмотрим вполне типичную ситуацию: поток A выполняется на ядре I с IRQL=PASSIVE_LEVEL, в то время как поток B выполняется на ядре II с тем же IRQL. Устройство Device 1 посылает ядру I сигнал прерывания, которое ловит ядро операционки, повышает IRQL процессорного ядра I до DIRQL и передает управление на обработку прерывания устройства Device 1. Обработчик выполняет первичную обработку ситуации и ставит отложенную процедуру DpcForIsr() в очередь для дальнейшей обработки. При этом функция добавляется в очередь того процессорного ядра, на котором запущен обработчик (в данном случае - это ядро I).

Устройство Device 1 вновь генерирует прерывание, которое на этот раз посылается ядру II, поскольку ядро I еще не успело выйти из обработчика прерывания и понизить IRQL. Ось повышает IRQL ядра II до DIRQL и передает управление обработчику прерываний устройства Device 1, который ставит еще одну отложенную процедуру DpcForIsr() в очередь на выполнение ядра II.

Наконец, обработчики прерываний на обоих ядрах завершаются, ось понижает IRQL, и начинается выполнение отложенных процедур, стоящих в очередях ядра I и ядра II. В результате, одна и та же процедура DpcForIsr() на обоих ядрах исполняется одновременно, обрабатывая сразу два различных прерывания! Маленькая небрежность кодирования приводит к ошибкам синхронизации, вызывающим каскад вторичных ошибок и способным генерировать добрую половину всех существующих синих экранов.

Выход?

Использовать только одно ядро, игнорируя все остальные, для чего достаточно указать ключ /ONECPU или /NUMPROC=1 в файле boot.ini. Конечно, это снизит производительность, но если кривой драйвер при интенсивном поступлении прерываний не справляется с синхронизацией и обрушивает систему в BSOD, то лучше поступиться производительностью, чем надежностью.

А кому не знаком BSOD с противным названием IRQL_LESS_OR_EQUAL, выпрыгивающий в самый неподходящий момент? Что это такое, и почему он возникает? Операционные системы семейства NT используют особую систему приоритетов прерываний Interrupt Request Levels (или сокращенно IRQ), оперирующую целыми числами от 0 до 31. Уровень 0 имеет минимальный приоритет, 31 — максимальный. Нормальное выполнение потока происходит на пассивном уровне (PASSIVE LEVEL == 0), и его может прерывать любое асинхронное событие, возникающее в системе. При этом ось повышает текущий IRQL до уровня возникшего прерывания и передает управление его ISR (Interrupt Service Routine — процедура обработки прерывания), причем, подкачка страниц с диска работает только на уровне 2, а прерывания, генерируемые устройствами, начинаются с уровня 3. И потому первичные обработчики прерываний не могут обращаться к памяти ядра, вытесняемой на диск! Увы, как показывает практика, они к ней все-таки обращаются. Если запрещенная страница находится в памяти, то все ОК, а вот если она вытеснена на диск, тогда-то и возникает указанный BSOD.

Как его предотвратить? Решение первое – увеличить количество оперативной памяти, чтобы шансы на вытеснение запрашиваемых страниц были минимальны. Решение второе – запретить свопинг ядра на диск, для чего необходимо установить параметр «DisablePagingExecutive» (типа DWORD) в значение 1 (он находится в следующей ветке реестра: HKLMSYSTEMCurrentControlSetControlSessionManagerMemoryManagement). Также следует запустить gflags.exe с флагом dps, запрещающим вытеснение стека ядра на диск. Изменения вступят в силу только после перезагрузки. И хотя потребности в памяти слегка увеличиваются (поскольку ядро уже не может вытеснить бездействующие драйвера), общая надежность системы *значительно* повышается. Кроме того, система становится нечувствительной к определенным типам DoS атак, «съедающим» всю доступную память и вынуждающим ядро активно свопиться на диск в надежде, что в системе обнаружится хоть один кривой драйвер, вызывающий BSOD с пресловутым IRQL_LESS_OR_EQUAL.

Кстати говоря, в Win2k3 SP2 допущена досадная ошибка, связанная с некорректной реализацией SafeSEH и обрушивающая систему в BSOD при вызове такой безобидной функции, как DbgPrint, использующейся в драйверах для отладочной печати. Подробнее об этом можно прочитать в 16h выпуске «Exploits Review», опубликованном в журнале «][акер». Ну а для преодоления BSOD достаточно вызывать gflags.exe с флагом ddp.

Хочется отметить, что основными поставщиками BSOD являются драйвера видео- и звуковых карт, поэтому удаляем драйвер звуковой карты (а зачем серверу звук?) и для форсирования VGA режима в boot.ini прописываем ключик /BASEVIDEO.

Еще одну проблему представляет собой поддержка расширений физических адресов (Physical Address Extensions или сокращенно PAE). Теоретически она позволяет операционной системе использовать свыше 4х Гб физической памяти, практически же Win2k3 Standard Edition этой возможности не поддерживает, вынуждая нас искать Enterprise Edition. Однако при активном механизме DEP (Data Execution Prevention), включенном по умолчанию в Win2k3 SP1, система всегда стартует с поддержкой PAE, независимо от количества физической памяти, имеющейся на борту. Это создает проблемы с некоторыми драйверами, поскольку в режиме PAE на уровне ядра имеются некоторые тонкости работы с памятью, учитываемые далеко не всеми разработчиками. Очевидное решение — добавить ключ /NOPAE в boot.ini и забыть об этой проблеме раз и навсегда.

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

Заключение

Настройку сервера удобнее осуществлять под виртуальной машиной типа VM Ware, выделяя гостевой операционной системе минимум памяти и направляя на нее шторм сетевых пакетов. При наличии «кривых» драйверов BSOD не заставит себя ждать и тут же появится на экране. К сожалению, VM Ware не дает прямого доступа к большинству компонентов материнской платы. Поэтому тестируются совсем не те драйвера, которые работают в реальных условиях. Но даже такой примитивный тестовый стенд выявляет огромное количество ошибок, позволяя подобрать оптимальные ключи реестра и файла boot.ini. Окончательную настройку ядра можно выполнить уже в натурных условиях, естественно, предварительно создав резервную копию boot.ini и реестра с помощью любой утилиты резервирования, работающей до загрузки Win2k3 (поскольку есть шанс, что после наших экспериментов операционка вообще не сможет загрузиться).

DVD

На прилагаемом к журналу диске ты найдешь исошку Windows Server 2003 SP1 DDK.

INFO

Подробное описание всех BSOD'ов вместе с причинами их возникновения и рекомендациями по их предотвращению можно найти как в MSDN, так и в DDK. Однако и то, и другое, в первую очередь, ориентировано на программистов, знающих ассемблер, умеющих работать с отладчиком и готовых часами разбирать дампы памяти для поиска ошибки в подопытном драйвере.

Ядро Win2k3 поддерживает свыше 128 ключей реестра, большинство из которых не документировано и добыто путем дизассемблирования. Вот только некоторые из них: AdditionalCriticalWorkerThreads, AdditionalDelayedWorkerThreads, AdjustDpcThreshold, AllocationPreference, ClearPageFileAtShutdown, CountOperations, CriticalSectionTimeout.

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

WWW

Статья «Boot.ini options reference» Марка Руссиновича: www.ingenieroguzman.com.ar/articulos/informatica/Boot-INI-Reference.htm.

Содержание
ttfb: 289.54291343689 ms