Показать сообщение отдельно

  #3  
Старый 11.01.2008, 16:45
_Great_
Флудер
Регистрация: 27.12.2005
Сообщений: 2,372
Провел на форуме:
5339610

Репутация: 4360


Отправить сообщение для _Great_ с помощью ICQ
По умолчанию

2. Низкоуровневые функции для работы с PDE/PTE и списками физических страниц
Все это, вообщем то, я уже описывал, когда рассказывал про списки страниц, MMPFN и другое, поэтому приведу лишь прототипы функций с кратким описанием их действий:
PFN_NUMBER FASTCALL MiRemoveAnyPage(IN ULONG PageColor) ; // Выделяет физическую страницу заданного цвета (SecondaryColor) из списков свободных, обнуленных или простаивающих страниц.
PFN_NUMBER FASTCALL MiRemoveZeroPage(IN ULONG PageColor) ; // Выделяет физическую страницу заданного цвета (SecondaryColor) из списка свободных страниц.
VOID MiRemovePageByColor (IN PFN_NUMBER Page, IN ULONG Color); // Выделяет указанную страницу, удаляя ее из списка свободных страниц указанного цвета.

3. Функции, предоставляемые драйверам для работы с физической памятью
PMDL
MmAllocatePagesForMdl(
IN PHYSICAL_ADDRESS LowAddress,
IN PHYSICAL_ADDRESS HighAddress,
IN PHYSICAL_ADDRESS SkipBytes,
IN SIZE_T TotalBytes
);

Эта функция выделяет физические страницы (не обязательно идущие подряд, как это делает, например, MmAllocateContiguousMemory), пробуя выделить страницы общим размером TotalBytes, начиная с физического адреса LowAddress и заканчивая HighAddress, "перешагивая" по SkipBytes.
Просматриваются списки обнуленных, затем свободных страниц. Разумеется, страницы неподкачиваемые. Если страниц не хватает, функция старается выделить столько страниц, сколько возможно.
Возвращаемое значение - Memory Descriptor List (MDL), описывающий выделенные страницы. Они должны быть освобождены соответствующим вызовом MmFreePagesFromMdl и ExFreePool для структуры MDL.
Страницы НЕ спроецированы ни на какие виртуальные адреса, об этом должен позаботиться программист с помощью вызова MmMapLockedPages.

PVOID
MmAllocateContiguousMemory(
IN ULONG NumberOfBytes,
IN PHYSICAL_ADDRESS HighestAcceptableAddress
);

Функция выделяет физически непрерывную область физических страниц общим размером NumberOfBytes, не выше HighestAcceptableAddress, так же проецируя их в адресное пространство ядра. Сначала она пытается выделить страницы из неподкачиваемого пула, если его не хватает, она начинает просматривать списки свободных и обнуленных страниц, если и их не хватает, то она просматривает страницы из списка простаивающих страниц. Возвращает базовый адрес выделенного участка памяти. Память должна быть освобождена с помощью вызов MmFreeContiguousMemory.

4. функции, предоставляемые драйверам для работы с пулом
Они подробно описаны в статье Four-F, поэтому останавливаться на этом я не буду.

Дополнительные функции управления памятью режима ядра
Из функций контроля ядерной памяти следует, наверное, упомянуть про MmIsAddressValid и MmIsNonPagedSystemAddressValid.
Функция MmIsAddressValid проверяет страницу памяти на то, возникнет ли ошибка страницы при доступе к этому адресу. То есть, другими словами, она проверяет тот факт, что страница уже сейчас находится в физической памяти. Следует отметить, что состояние transition,paged-out,prototype ее не волнуют, поэтому она может использоваться лишь для контроля адресов при высоких IRQL (>=DPC/Dispatch), поскольку при этих IRQL не разрешены ошибки страниц (а если встретится ошибка страницы, будет выброшен синий экран IRQL_NOT_LESS_OR_EQUAL). Если нужно проверять ядерные адреса на доступ при низком IRQL, то, насколько мне известно, нет документированных способов это сделать. Видимо, считается, что драйвер должен знать, какие у него адреса правильные, а какие нет и не пробовать обращаться по неправильным адресам. В приложении к статье имеется написанная мной функция MmIsAddressValidEx, которая проверяет адрес на корректность доступа при низком IRQL, учитывая, что PTE может находиться в недействительном состоянии, но ошибка страницы не вызовет синего экрана или исключения (в программном смысле). С учетом рассказанных мною структур недействительных PTE, разобраться в ее исходном коде будет нетрудно.
Функция MmIsNonPagedSystemAddressValid, почему-то незаслуженно "выброшенная" разработчиками Windows и обозначенная как obsolete, на самом деле тоже полезна. Она на порядок проще, чем MmIsAddressValid (которую, кстати, и рекомендует использовать Microsoft), и всего лишь проверяет то, что переданный ей адрес принадлежит подкачиваемой или неподкачиваемой областям памяти ядра. Адрес не проверяется на корректность, но результат функции вовсе не эквивалентен MmIsAddressValid (в том смысле, что память может быть в пуле подкачиваемой памяти, но может быть как выгружена на диск так и загружена, поэтому возвращенное значение FALSE еще ничего не говорит о том, можно ли обращаться к этой памяти), поэтому я совершенно не понимаю, почему Microsoft сочли ее "obsolete" и не рекомендуют использовать, подсовывая взамен MmIsAddressValid. Использовать MmIsNonPagedSystemAddressValid мы будем, например, в функции вывода MMPFN в приложении, когда потребуется определить, принадлежит ли адрес подкачиваемому пулу (поля MMPFN, как Вы помните, различаются для подкачиваемого и неподкачиваемого пулов).

Управление пользовательской памятью
Для начала стоит заметить, что для управления пользовательской памятью используется дополнительный механизм - Virtual Address Descriptors (VAD), которые описывают проекции секций, а так же выделения памяти через NtAllocateVirtualMemory (VirtualAlloc в Win32 API). Представлены эти VAD в виде дерева, указатель на вершину содержится в поле EPROCESS->VadRoot. Секции можно создавать и проецировать на пользовательские адреса с помощью NtCreateSection, NtMapViewOfSection (Win32API-аналоги у них: CreateFileMapping, MapViewOfFileEx). Адреса памяти могут резервироваться (reserve) и в последствии память по этим адреса может передаваться (commit) процессу. Этим заведует NtAllocateVirtualMemory.

Функции работы с VAD и пользовательской памятью
Элемент VAD представлен следующей структурой:
Код:
typedef struct _MMVAD_FLAGS {
    ULONG_PTR CommitCharge : COMMIT_SIZE;
    ULONG_PTR PhysicalMapping : 1;
    ULONG_PTR ImageMap : 1;
    ULONG_PTR UserPhysicalPages : 1;
    ULONG_PTR NoChange : 1;
    ULONG_PTR WriteWatch : 1;
    ULONG_PTR Protection : 5;
    ULONG_PTR LargePages : 1;
    ULONG_PTR MemCommit: 1;
    ULONG_PTR PrivateMemory : 1;
} MMVAD_FLAGS;

typedef struct _MMVAD_SHORT {
    ULONG_PTR StartingVpn;
    ULONG_PTR EndingVpn;
    struct _MMVAD *Parent;
    struct _MMVAD *LeftChild;
    struct _MMVAD *RightChild;
    union {
        ULONG_PTR LongFlags;
        MMVAD_FLAGS VadFlags;
    } u;
} MMVAD_SHORT, *PMMVAD_SHORT;
Так же есть структура MMVAD, аналогичная MMVAD_SHORT, но содержащая больше полей и используемая для проецированных файлов (дополнительные поля это PCONTROL_AREA, необходимая для поддержания спроецированных файлов и содержащая такие важные указатели, как PFILE_OBJECT и др; о проекциях файлов, как-нибудь в следующий раз: и так уже 50 килобайт вышло =\), а MMVAD_SHORT используется для пользовательских выделений памяти.
Чтобы отличить, какой именно VAD представлен указателем, используется флаг u.VadFlags.PrivateMemory: если он установлен, то это "частная память", то есть обычное выделение памяти. Если сброшен - проекция файла.
Поля StartingVpn и EndingVpn, соответственно, обозначают начальную и конечную виртуальную страницу (Virtual Page Number) описываемой области (для конвертирования виртуального адреса в номер страницы используется MI_VA_TO_VPN, который просто сдвигает виртуальный адрес на PAGE_SHIFT бит вправо).
Поля Parent, LeftChild, RightChild используются для связи дескрипторов виртуальных адресов в дерево.
u.VadFlags содержит некоторые полезные флаги, а именно:
  • CommitCharge. Это поле содержит количество фактически выделенных и переданных страниц процессу, если VAD описывает переданную память, или 0 если описывается зарезервированная память.
  • PhysicalMapping. Этот флаг показывает, что память на самом деле является проекцией физических страниц, созданной с помощью MmMapLockedPages при AccessMode == UserMode.
  • Флаг ImageMap показывает, что VAD описывает загруженный исполняемый модуль (с помощью LoadLibrary и других, в конечном итоге сводящихся к NtCreateSection с SEC_IMAGE).
  • UserPhysicalPages устанавливается при вызове NtAllocateVirtualMemory с MEM_PHYSICAL|MEM_RESERVE, используемом для выделения окна для физических страниц при испольщовании AWE (Address Windowing Extensions).
  • NoChange установлен, когда запрещено менять атрибуты доступа области, описываемой этим VAD. Чтобы создать такую область, используется флаг SEC_NO_CHANGE у NtCreateSection.
  • WriteWatch устанавливается при выделении памяти с флагом MEM_WRITE_WATCH, при этом создается битовая карта страниц, где впоследствии отмечается, на какие страницы была произведена запись. Эту информацию можно получить в последствии через Win32 API GetWriteWatch() и сбросить карту через ResetWriteWatch()
  • Protection - изначальные атрибуты доступа к памяти.
  • LargePages содержит 1 при использовании больших страниц через MEM_LARGE_PAGES. В Windows XP/2000 не поддерживается.
  • MemCommit содержит 1 если память была передана процессу.
  • PrivateMemory, как уже было сказано, отличает MMVAD от MMVAD_SHORT.

Функция MiAllocateVad выделяет VAD для процесса, резервируя переданные ей адреса, а функция MiCheckForConflictingVad (на самом деле макрос, раскрывающийся в вызов функции MiCheckForConflictingNode) проверяет, существуют ли VAD у процесса такие, что описываемая ими область памяти перекрывается с указанными виртуальными адресами. Если это так, возвращается VAD первой конфликтной области, иначе NULL. Функция используется при передаче памяти процессу в NtAllocateVirtualMemory для поиска VAD, соответствующего указанному адресу.
Функция MiInsertVad добавляет VAD в дерево виртуальных дескрипторов адресов процесса и реорганизует его, если это нужно.
Функция MiRemoveVad, соответственно, удаляет и освобождает VAD. Перейдем теперь к функциям, доступным пользовательскому коду и драйверам устройств для управления памятью.

Функция NtAllocateVirtualMemory производит следующие действия:
1) для резервирования адресов вызывается MiCheckForConflictingVad для проверки, не была ли эта область или какая-либо ее часть зарезервированы или использованы другой функцией для работы с памятью (например, проецированием секции) ранее. Если так - возвращает STATUS_CONFLICTING_ADDRESSES.
Далее выделяется VAD функцией MiAllocateVad, заполняются соответствующие поля и VAD добавляется в дерево с помощью MiInsertVad. Если он описывает AWE-вид или включен WriteWatch, тогда еще вызывается MiPhysicalViewInserter.
2) для передачи адресов вызывается MiCheckForConflictingVad, но уже с целью найти соответствующий VAD, созданный при резервировании. Потом соответствующие страницы выставляются в таблице страниц как обнуляемые по требованию, а так же меняются атрибуты защиты, если это необходимо.
NtFreeVirtualMemory производит обратные действия.

На этом я думаю, наконец-то (!), что статью можно завершить.

В приложении к статье можно найти:
1. программу Working Sets для демонстрации усечения рабочих наборов. http://gr8.cih.ms/uploads/wsets.rar
2. Функции ручной загрузки и выгрузки страницы в файл подкачки. Примечание: очень сырые! Поскольку страница не добавляется в рабочий набор и не удаляется из него, может быть синий экран MEMORY_MANAGEMENT или PFN_LIST_CORRUPT (для выгрузки и загрузки соответственно), поэтому экспериментировать на реальной системе я не советую. Лучше запускать только изучающий и анализирующий код, который не изменяет никаких параметров системы. Это функции MiPageOut и MiLoadBack (префиксы Mi я сам добавил для красоты )
3. Функция вывода в DbgPrint содержимого MMPFN. Это MiPrintPfnForPage.
4. Функция MmIsAddressValidEx для расширенной проверки доступа к адресам при низком IRQL. Возвращает статус проверки - элемент перечисления
Код:
enum VALIDITY_CHECK_STATUS {
	VCS_INVALID = 0,     //  = 0 (FALSE)
	VCS_VALID,           //-|
	VCS_TRANSITION,      // |
	VCS_PAGEDOUT,        // |-   > 0
	VCS_DEMANDZERO,      // |
	VCS_PROTOTYPE,       //-|
};
Так же может трактоваться как BOOLEAN, поскольку статус невалидной страницы 0, а все остальные больше нуля.
5. Комплексный пример драйвера, демонстрирующий все эти функции (ручная загрузка и выгрузка закомментированы). http://gr8.cih.ms/uploads/ptetest.rar

(C)
Great, 2007-2008.
 
Ответить с цитированием