2. Пока происходит утечка памяти в пуле, сначала откройте диспетчер задач и перейдите на вкладку Performance (Быстродействие). Вы увидите, как растет показатель Paged Pool (Выгружаемая память). To же самое можно увидеть в окне System Information утилиты Process Explorer. (Выберите Show и System Information.)
3. Чтобы определить метку пула, где происходит утечка, запустите Poolmon и нажмите клавишу b, чтобы сортировать по числу байтов. Дважды нажмите клавишу p для отображения в Poolmon только пула подкачиваемой памяти. Вы должны заметить, что пул с меткой «Leak» поднимается вверх по списку. (Poolmon выделяет строки, где происходят изменения.)
4. Теперь щелкните кнопку Stop Leaking, чтобы не истощить пул подкачиваемой памяти в своей системе.
5. Используя приемы, описанные в предыдущем разделе, запустите Strings (ее можно скачать с wwwsysinternals.com) для поиска двоичных файлов драйвера, содержащих метку пула «Leak»:
Strings windowssystem32drivers*.sys | findstr Leak
Эта команда должна указать на файл Myfault.sys.
Ассоциативные списки
Windows поддерживает механизм быстрого выделения памяти — ассоциативные списки (look-aside lists). Главное различие между пулом и ассоциативным списком в том, что из пула можно выделять блоки памяти различного размера, а из ассоциативного списка — только фиксированные. Хотя пулы обеспечивают более высокую гибкость, ассоциативные списки работают быстрее, так как не используют спин-блокировку и не заставляют систему подбирать подходящую область свободной памяти, в которой мог бы уместиться текущий выделяемый блок.
Функции ExInitializeNPagedLookasideList и ExInitializePagedLookasideList (документированные в DDK) позволяют компонентам исполнительной системы и драйверам устройств создавать ассоциативные списки, размеры которых кратны размерам наиболее часто используемых структур данных. Для минимизации издержек, связанных с синхронизацией в многопроцессорных системах, некоторые компоненты исполнительной системы, в том числе диспетчер ввода-вывода, диспетчер кэша и диспетчер объектов, создают отдельные для каждого процессора ассоциативные списки, из которых выделяется память под часто используемые структуры данных. Сама исполнительная система создает для каждого процессора универсальные ассоциативные списки подкачиваемой и неподкачиваемой памяти с гранулярностью выделения в 256 байтов или менее.
Если ассоциативный список пуст (как это бывает сразу после его создания), система должна выделить память из подкачиваемого или неподкачиваемого пула. Ho если в списке уже присутствует освобожденная структура, то занимаемая ею память выделяется очень быстро. (Список разрастается по мере возврата в него структур.) Процедуры выделения памяти из пула автоматически настраивают число освобожденных буферов, хранящихся в ассоциативном списке, в зависимости от частоты выделения памяти из этого списка драйвером или компонентом исполнительной системы. Чем чаще они выделяют память из списка, тем больше буферов в списке. Размер ассоциативных списков автоматически уменьшается, если память из них не выделяется. (Эта проверка выполняется раз в секунду, когда системный поток диспетчера настройки баланса пробуждается и вызывает функцию KiAdjustLookasideDepth.)
ЭКСПЕРИМЕНТ: просмотр системных ассоциативных списков
Содержимое и размер различных ассоциативных списков в системе можно просмотреть командой !lookaside отладчика ядра. Вот фрагмент вывода этой команды.
Утилита Driver Verifier
Driver Verifier представляет собой механизм, который можно использовать для поиска и локализации наиболее распространенных ошибок в драйверах устройств и другом системном коде режима ядра. Microsoft проверяет с помощью Driver Verifier свои драйверы и все драйверы, передаваемые производителями оборудования для тестирования на совместимость и включения в список Hardware Compatibility List (HCL). Такое тестирование гарантирует совместимость драйверов, включенных в список HCL, с Windows и отсутствие в них распространенных ошибок. (Существует и парная утилита Application Verifier, позволяющая улучшить качество кода пользовательского режима. Однако в этой книге она не рассматривается.)
Driver Verifier поддерживается несколькими системными компонентами — диспетчером памяти, диспетчером ввода-вывода и HAL, которые предусматривают параметры, включаемые для верификации драйверов. B этом разделе поясняются параметры верификации драйверов на отсутствие ошибок, связанных с управлением памятью (см. также главу 9).
Настройка и инициализация Driver Verifier
Для настройки Driver Verifier и просмотра статистики запустите Driver Verifier Manager (Диспетчер проверки драйверов), файл WindowsSystem32Verifier.exe. После запуска появится окно с несколькими вкладками. Версия окна для Windows 2000 приведена на рис. 7–7. Чтобы указать, какие драйверы устройств вы хотите проверить, и задать типы проверок, используйте вкладку Settings (Параметры).
B Windows XP и Windows Server 2003 этой утилите придали интерфейс в стиле мастера, как показано на рис. 7–8.
Рис. 7–8. Driver Verifier Manager в Windows XP и Windows Server 2003
Включать и отключать Driver Verifier, а также просматривать текущие параметры можно из командной строки этой утилиты. Для вывода списка ключей наберите verifier /?.
Настройки Driver Verifier Manager хранятся в разделе реестра HKLMSYS-TEMCurrentControlSetControlSession ManagerMemory Management. Параметр VerifyDriverLevel содержит битовую маску, представляющую включенные типы проверок. Имена проверяемых драйверов содержатся в параметре VerifyDrivers. (Эти параметры создаются в реестре только после выбора проверяемых драйверов в окне Driver Verifier Manager.) Если вы выберете верификацию всех драйверов, VerifyDrivers будет содержать символ звездочки. B зависимости от выбранных параметров может понадобиться перезагрузка системы.
Ha ранних этапах загрузки диспетчер памяти считывает из реестра значения этих параметров, определяя, какие драйверы следует верифицировать и какие параметры Driver Verifier включены. (Если загрузка происходит в безопасном режиме, все параметры Driver Verifier игнорируются.) Далее, если для проверки выбран хотя бы один драйвер, ядро сравнивает имя каждого загружаемого драйвера с именами драйверов, подлежащих верификации. Если имена совпадают, ядро вызывает функцию MiApplyDriverVerifer, которая заменяет все ссылки драйвера на функции ядра ссылками на эквивалентные функции Driver Verifier. Так, ExAllocatePool заменяется на VerifierAllocatePool. Драйвер подсистемы управления окнами производит аналогичные замены для использования эквивалентных функций Driver Verifier.
Теперь рассмотрим четыре параметра верификации драйверов, относящиеся к использованию памяти: Special Pool, Pool Tracking, Force IRQL Checking и Low Resources Simulation.
Special Pool (Особый пул)
Этот параметр заставляет функции, отвечающие за выделение памяти из пулов, окружать выделяемый блок недействительными страницами, чтобы ссылки за пределы этого блока вызывали нарушение доступа в режиме ядра и последующий крах системы. A это позволяет тут же указать пальцем на сбойный драйвер. Параметр Special Pool также заставляет проводить дополнительные проверки, когда драйвер выделяет или освобождает память.
При включении параметра Special Pool функции пулов выделяют в памяти ядра регион для Driver Verifier, и последний перенаправляет запросы проверяемого драйвера на выделение памяти в особый пул, а не в стандартные пулы. При выделении драйвером памяти из особого пула Driver Verifier округляет размер выделяемого блока до размера, кратного размеру страницы. Поскольку Driver Verifier окружает выделенный блок недействительными страницами, при попытке записи или чтения за пределами этого блока драйвер попадает на недействительную страницу, и диспетчер памяти сообщает о нарушении доступа в режиме ядра.
Ha рис. 7–9 приведен пример блока, выделенного Driver Verifier в особом пуле для проверяемого драйвера устройства.
По умолчанию Driver Verifier распознает ошибки, связанные с попытками обращения за верхнюю границу выделенного блока (overrun errors). Он делает это, помещая используемый драйвером буфер в конец выделенной страницы и заполняя ее начало случайными значениями. Хотя Driver Verifier Manager не предусматривает параметр для включения детекции ошибок, связанных с попытками обращения за нижнюю границу выделенного блока (underrun errors), вы можете активизировать ее вручную, добавив в раздел реестра HKLMSYSTEMCurrentControlSetControlSession ManagerMemoryManagement параметр PoolTagOverruns типа DWORD и присвоив ему значение O (или запустив утилиту Gflags и установив флажок Verify Start вместо установленного по умолчанию Verify End). Тогда Driver Verifier будет размещать буфер драйвера не в конце, а в начале страницы.
Конфигурация, при которой Driver Verifier обнаруживает ошибки типа «overrun», до некоторой степени обеспечивает и распознавание ошибок типа «underrun». Когда драйвер освобождает буфер и возвращает его в Driver Verifier, последний должен убедиться, что содержимое памяти, предшествующее буферу, не изменилось. Иное означает, что драйвер обратился к памяти, расположенной до начала буфера, и что-то записал за пределами этого буфера.