HOME FORUMS MEMBERS RECENT POSTS LOG IN  
× Авторизация
Имя пользователя:
Пароль:
Нет аккаунта? Регистрация
Баннер 1   Баннер 2
НОВЫЕ ТОРГОВАЯ НОВОСТИ ЧАТ
loading...
Скрыть
Вернуться   Форум АНТИЧАТ > БЕЗОПАСНОСТЬ И УЯЗВИМОСТИ > Безопасность и Анонимность > Защита ОС: вирусы, антивирусы, файрволы.
   
 
 
Опции темы Поиск в этой теме Опции просмотра

  #1  
Старый 18.11.2021, 00:27
Marylin
Постоянный
Регистрация: 01.09.2019
Сообщений: 378
Провел на форуме:
145166

Репутация: 0
По умолчанию

В системной архитектуре отображение вирт.памяти на физическую носит таинственный характер. Предлагая материал на эту тему, MSDN забывает о логической нити – по большому счёту у них тонны безсвязных отрывков. В результате сеть заполонили статьи о диспетчерезации памяти, однако инфа в них сильно разнится, поскольку каждый из авторов трактует оригинальные доки по своему. Приняв это во-внимание, в данной статье был выбрал формат "беседа с новичком", чтобы рассмотреть такие вопросы как: задачи MMU и VMM, область применения списков VAD и MDL, назначение базы PFN, состав рабочих страниц WorkingSet, способы трансляции адресов, и многое другое. Из оружий ближнего боя понадобится отладчик WinDbg, и том(3) мануалов Intel"System Programming Guide", как внушающий доверие источник информации.

Оглавление:

1. FAQ – часто задаваемые вопросы;
2. Древо VAD процесса (Virtual Address Descriptor);
3. WorkingSet – набор рабочих страниц;
4. MMU – блок управления физической памятью;
5. База данных PFN (Page Frame Number);
6. Списки MDL (Memory Descriptor List);
7. Практика – сбор информации;
8. Постскриптум.



1. FAQ – Frequently Asked Questions

Чтобы далее не возникало лишних вопросов, начнём с определений часто встречающихся в технической литературе терминов. Практика показывает, что многие рерайтеры не обременяют себя даже чтением официальных доков от Intel и Microsoft, и как результат "сущности" получают совсем другие имена. На входе в тему фундамент очень важен, иначе продвигаясь дальше, заинтересованный читатель будет петлять траекторией пьяного гонщика, то и дело обращаясь к манам в поисках истины. Не зря говорят, как корабль назовёшь, так он и поплывёт. Вот основные моменты, на которые делается ставка в данной статье.


...::: Примечание :::...

Всё пространство физ.памяти ОЗУ почекано на 4-Кбайтные фреймы, которые называют ещё кадрами.

Термин "Frame" ввели, чтобы не путать страницу "Page" вирт.памяти, с физической. Таким образом,

если мы говорим "страничный фрейм", значит речь идёт о физ.памяти, а если просто "страница" – подразумеваем виртуальную.


В идеале фрейм, страница, и кластер жёсткого диска должны быть одинаковых размеров, и в большинстве случаях это 4096-байт.


1.1. Какая разница между блоком
MMUи диспетчером памяти?

Часто MMU обзывают диспетчером, что не соответствует действительности. MMU – это аппаратный блок управления памятью "Memory Management Unit", и находится он внутри процессора. Помимо прочего, содержит в себе транслятор адресов, а так-же небольшой кэш в виде буфера TLB "Translation Lookaside Buffer". Если-же говорить о диспетчере памяти, в доках он числится как VMM "Virtual Memory Manager" (не путать с Virtual Machine Manager) и это не аппаратный, а программный модуль ОС. Функции Kernel-API диспетчера прописаны в файле ядра Ntoskrnl.exe и имеют префиксы Mm\Mi_xx(). Сервисы VMM занимают добрую половину всего функционала ядра, что говорит об особой их важности.

1.2. Зачем нужны регистрыMTRR?

Регистры MTRR процессора входят в состав MSR (Model Specific Registers) и означают "Memory Type Range Registers" – регистры диапазонов типа памяти. Если транслятор в MMU озадачен адресацией страничных фреймов, то 23 регистра MTRR задают атрибуты кэширования этим фреймам. Жонглируя битами MTRR процессор на аппаратном уровне может определить до 96 областей памяти, с одним из пяти типом кэша: UnCacheable, WriteProtect, WriteBack, WriteThrough, WriteCombining. Бит[11] регистра
Код:
IA32_MTRR_Def_Type
системный BIOS использует для вкл/откл этого рульного механизма. Всех, кого интересует данная фишка, копайте доку
Intel том(4). В системе имеется альтернатива аппаратным MTRR под названием РАТ, или "Page Attribute Table". Она не имеет уже ограничений на кол-во подконтрольных блоков памяти, т.к. работает на уровне записей РТЕ каждой из страниц (PageTableEntry).

1.3. Что такое системный кэш?

Не нужно путать кэши L1,2,3 процессора с системным кэшем Win – это два разных субъекта, хотя и придерживаются одной веры. Диспетчер кэша состоит из набора ядерных API, обеспечивающих кэширование файлов NTFS. Такой подход на порядок увеличивает скорость операций ввода-вывода при работе с дисковыми файлами. В дефолте кеш всегда включён, но в избирательном порядке механизм можно усыпить при открытии файловых объектов чз CreateFile() (см.параметр Flags&Attributes). К примеру флаг "NO_BUFFERING" даёт постановку диспетчеру вообще не буферизовать\кэшировать содержимое данного файла в памяти, а флаг "WRITE_THROUGH" предписывает сквозную запись изменённых файлов и в активный кэш, и сразу на диск. Это отнимает больше времени, зато получаем согласованность данных на диске и в памяти.

На моей Win-7 системный кэш загребает у ядра порядка 600 MБ. Он состоит из т.н. "слотов" размером по 256K, каждый слот описывает своя структура VACB (Virtual Address Control Block). Функции диспетчера-кэша имеют префикс Сc_хх() (Cache control). Чтобы просмотреть его содержимое, можно потянуть за расширение
Код:
!filecache
отладчика WinDbg. Во-втором томе издания(6) М.Руссиновича кэшу посвящена целая глава, а мнение MSDN на этот счёт лежит по следующим линкам:
кеширование, и сразубуферизацияфайлов.

1.4. Каково назначение списковMDL?

Memory Descriptor List (или список дескрипторов памяти) представляет собой одноимённую структуру в ядерном пространстве, для отображения фрейма на страницу. Диспетчер заносит в структуру MDL адреса страниц только в двух случаях – когда устройства DMA запрашивают прямой доступ к физ.памяти (Direct Memory Access), или-же функция DeviceIoControl() просит драйвер вернуть ей какие-нибудь данные, передавая этому драйверу вирт.адрес своего приёмного буфера. Позже мы заглянем внутрь этой структуры.

1.5. Какую роль играет древоVAD?

Всякий процесс требует определённого кол-ва страниц вирт.памяти. В ядре имеется структура VAD (Virtual Address Descriptor, дескриптор вирт.адреса), которая описывает один непрерывный регион памяти с одинаковыми атрибутами. Например сотню идентичных по характеру последовательных страниц, описывает всего одна запись VAD. В силу того, что процессу требуется память с различными флагами защиты (чтение, запись, исполнение), то получаем несколько структур VAD. Для комфортного доступа и сортировки адресов, диспетчер собирает все принадлежащие данному процессу VAD'ы, в двоичное древо. Если учесть, что у каждого процесса своя вирт.память, то соответственно и своё древо VAD.

1.6. В чём смысл набора "WorkingSet"?

Прописанные в VAD страницы представляют "рабочий набор" процесса, что инглише звучит как "WorkingSet". Это ещё один клиент диспетчера, поскольку страницы могут находиться в различном состоянии типа: не тронутая с атрибутом READ-ONLY, модифицированная WRITE, только-что выделенная\чистая, зарезервированная, обнулённая после модификации, отсутствующая и т.д. VMM обязан следить за состоянием всех страниц в наборе, и при обнаружении проблем принимать соответствующие меры.

1.7. Что хранится в базе данныхPFN?

PFN берёт начало от "Physical Frame Number", т.е. просто номер физ.фрейма. Специальный поток диспетчера включает свой радар и в непрерывном режиме следит, в каком из состояний находится конкретно взятый фрейм – варианты: занят, свободен, расшаренный, кэшируемый, можно-ли сбрасывать его в файл-подкачки (Paged, Non-Paged) и прочее. Для этого, с каждым фреймом связывается 28-байтная структура
Код:
_MMPFN
, но поскольку фрейм у памяти не один, все структуры собираются в системную "базу PFN". Более того имеются и прототипы PFN (prototype), с помощью которых диспетчер открывает доступ к фреймам всем желающим – это т.н. расшаренные фреймы, например для отображения Ntdll и Kernel32.dll сразу во-все пользовательские процессы.



Пробежавшись по макушкам терминов посмотрим на схему ниже, где представлена логическая связь между основными структурами VMM. Ясно, что этот рисунок не отражает всей палитры, ведь полностью охватить хозяйство вирт.памяти в одном скрине просто нереально. Поэтому MSDN и подкидывает нам инфу жалкими крапалями. Однако общую картину зрительно уже можно будет сформировать:






Значит у каждого процесса своё древо VAD, где хранятся адреса его регионов памяти. Далее страницы попадают в "котёл" WorkingSet для фильтрации их по назначению. Когда декодер-инструкций процессора обнаруживает запрос к ОЗУ, он передаёт адрес в блок MMU, чтобы транслятор преобразовал его в физический. Вирт.адреса одинаковы у всех процессов, но благодаря базе PFN они указывают на разные фреймы памяти.

Чтобы процесс(А) не считал данные процесса(В), диспетчеру нужно сменить каталог-страниц процесса "PageDirectoryTable", внутри которого имеются записи PDTE – этим занимается планировщик Scheduler, при переключении с одного потока на другой. Адрес каталога-страниц верхнего уровня РDТ каждого из процессов, система запоминает в его ядерной структуре
Код:
_KPROCESS
, от куда он считывается шедулером в регистр CR3 (PDBR, Page Directory Base Register).

Теперь сфокусируем своё внимание на системных таблицах и посмотрим, информацию какого рода хранит в них диспетчер. Пробираться по тёмным переулкам памяти будем таким маршрутом, как это указано на схеме выше, т.е. сверху вниз.

2.Древо VAD процесса– Virtual Address Descriptor

В отличие от планировщика, который выбирает потоки Thread для исполнения и пропускает между ног процессы, диспетчер наоборот полностью концентрируется на процессах и не подозревает о существовании потоков, ведь именно процессы (а не потоки) владеют адресным пространством. Когда программа запускается на исполнение, загрузчик образов в Ntdll.dll считывает её РЕ-заголовок (сколько секций, какого размера и т.д.), и на основании этого выделяет процессу вирт.память. При этом диспетчер создаёт сразу несколько дескрипторов VAD, в которых прописаны диапазоны отображаемых адресов. Таким образом, адресное пространство процесса полностью определяется списком его VAD.

В каждой структуре VAD хранится вирт.адрес первой и последней страницы в данном регионе памяти (Start и EndVPN), а если в нём отображается какой-нибудь файл, то и полный путь до него. Чтобы поиск отдельных VAD в списке был эффективным, все они выстраиваются в виде бинарного AVL-древа, которое имеет корень "VadRoot" и разветвляющиеся вниз узлы "VadNode". Формат деревьев AVL такой, что слева от узла всегда будут находиться VAD с меньшим от родителя стартовым адресом, а справа – большим. Такой подход позволяет с лёгкостью сортировать страницы по возрастанию или убыванию. Графическое представление древа типа AVL представлено ниже:




Обратите внимание на поле StartVPN (VirtualPageNumber)в структурах VAD.

Во-первых, значение в левом узле всегда будет меньше чем у родителя, а в правом больше. Во-вторых, поскольку регионы памяти выравниваются на границу 4К (размер одной страницы), то для экономии в VAD указываются только старшие 20-бит адреса, а младшие 12 отброшены, т.к. зарезервированы под смещение внутри выбранного пейджа (2^12=4096). То-есть чтобы получить полный вирт.адрес, нужно дополнять значения всех адресов тремя hex-нулями справа. Тогда получается, что корневой VAD описывает регион из трёх страниц с адресами от 0x00400000 до 0x00403000, и при инициализации система назначила ему атрибуты Exe+Write+Copy (полный доступ).Позже, в записях РТЕ ненужные атрибуты для конкретных страниц снимаются.

В нёдрах ядра NT структура VAD числится как
Код:
_MMVAD
, так-что запустив отладчик WinDbg можно просмотреть её содержимое. Только для начала нужно узнать, по какому адресу лежит корень древа конкретно нашего процесса. Для этого, запустим какую-нибудь свою прожку и не закрывая её, в отладчике потянем за расширение
Код:
!process 0 0
, чтобы он показал нам карту нашего процесса (у меня это ModuleInfo.exe):


Код:


Код:
lkd> !process 0 0 ModuleInfo.exe
;//------------------------------
PROCESS 89102348  SessionId: 1  Cid: 0ed4    Peb: 7ffd3000  ParentCid: 0da0
    DirBase: 5f590d00   ObjectTable: b2f14598  HandleCount: 7
    Image: ModuleInfo.EXE
--> VadRoot 86688b20.  Vads 21. Clone 0. Private 60. Modified 50. Locked 0.


В последней строке лога видим линк на корень древа VadRoot, и теперь можно просмотреть его структуру:


Код:


Код:
lkd> dt _MMVAD 86688b20
;//----------------------
nt!_MMVAD
   +0x000 u1                : 
   +0x004 LeftChild         : 0x891a0eb8 _MMVAD
   +0x008 RightChild        : 0x89a9e398 _MMVAD
   +0x00c StartingVpn       : 0x400
   +0x010 EndingVpn         : 0x403
   +0x014 u                 : 
   +0x018 PushLock          : _EX_PUSH_LOCK
   +0x01c u5                : 
   +0x020 u2                : 
   +0x024 Subsection        : 0x891d09d8 _SUBSECTION
   +0x024 MappedSubsection  : 0x891d09d8 _MSUBSECTION
   +0x028 FirstPrototypePte : 0x93ee1840 _MMPTE
   +0x02c LastContiguousPte : 0xfffffffc _MMPTE
   +0x030 ViewLinks         : _LIST_ENTRY [ 0x891d09d0 - 0x891d09d0 ]
   +0x038 VadsProcess       : 0x89102348 _EPROCESS


Значит в каждой структуре VAD представлен уже знакомый нам диапазон памяти Start\EndVPN, а так-же указатели на Left\Right узлы дочернего уровня. Что касается атрибутов защиты данного региона, они спрятаны во-вложенных безымянных структурах
Код:
u..u5
(union). Чтобы раскрыть их, достаточно указать имя поля в структуре, и поставить в конце точку:


Код:


Код:
lkd> dt _MMVAD 86688b20 u.
;//------------------------
nt!_MMVAD
   +0x000 u1 :
      +0x000 Balance    : 0y00
      +0x000 Parent     : 0x891025c0 _MMVAD  ;// dt _MMVAD 86688b20 u.VadFlags.
;//--------------------------------
   +0x000 u1 :
   +0x014 u  :
      +0x000 VadFlags   :
         +0x000 CommitCharge  : 0y010   (0x2)
         +0x000 NoChange      : 0y0
         +0x000 VadType       : 0y010   (0x2)
         +0x000 MemCommit     : 0y0
         +0x000 Protection    : 0y00111 (0x7)
         +0x000 Spare         : 0y00
         +0x000 PrivateMemory : 0y0


А вот и атрибуты-защиты "Protection" моего корневого VAD (0y0 является двоичным представлением).

Не знаю, может и прописаны значения этих флагов где-нибудь в сишных хидерах, но мне было проще нагуглить.

"CommitCharge" указывает число зафиксированных страниц в регионе, т.е. уже привязанных к физ.адресам. Если CommitCharge=0, значит память только зарезервирована, но не связана с фреймами. VAD может описывать как память процесса, так и выделенную, например, девайсам память – вот перечисления типов VAD:




Вышеизложенный подход просмотра VAD представляет практический интерес, чтобы обозначить расположение и состав структур при программировании драйверов (кстати VadRoot хранится в структуре EPROCESS). Он абсолютно не пригоден для визуального просмотра всего древа с высоты птичьего полёта. Для этого WinDbg имеет спец.расширение
Код:
!vad
, которое выводит лог в более приглядном виде. Достаточно взять адрес корня "VadRoot" и вскормить его отладчику:


Код:


Код:
lkd> !vad 86688b20
;//---------------------
VAD      level    start      end   commit
891b7478  ( 3)       10       1f        0 Mapped       READWRITE          Pagefile-backed section
9411b640  ( 4)       20       2f        0 Mapped       READWRITE          Pagefile-backed section
89a43408  ( 2)       30       6f        3 Private      READWRITE         
891d17a8  ( 3)       70       73        0 Mapped       READONLY           Pagefile-backed section
891a0eb8  ( 1)       80       80        1 Private      READWRITE         
867ae7b8  ( 4)       90       f6        0 Mapped       READONLY           \Windows\System32\locale.nls
89aaa2f0  ( 3)      100      1ff       14 Private      READWRITE         
89afdbc8  ( 4)      200      203        4 Private      EXECUTE_READ     
8a17f5e0  ( 2)      230      23f        6 Private      READWRITE         
a8632ea8  ( 3)      3e0      3ef        5 Private      READWRITE         
86688b20  ( 0)      400      403        2 Mapped  Exe  EXECUTE_WRITECOPY  \TEMP\ASM\CODE\ModuleInfo.EXE
89ac7608  ( 3)    759a0    759ea        3 Mapped  Exe  EXECUTE_WRITECOPY  \Windows\System32\KernelBase.dll
89acc8b0  ( 2)    761f0    7629b        8 Mapped  Exe  EXECUTE_WRITECOPY  \Windows\System32\msvcrt.dll
891821e8  ( 3)    774a0    77574        2 Mapped  Exe  EXECUTE_WRITECOPY  \Windows\System32\kernel32.dll
89a9e398  ( 1)    77830    77971       10 Mapped  Exe  EXECUTE_WRITECOPY  \Windows\System32\ntdll.dll
890f18b8  ( 4)    77a00    77a04        2 Mapped  Exe  EXECUTE_WRITECOPY  \Windows\System32\psapi.dll
89b0b870  ( 3)    77a90    77a90        0 Mapped  Exe  EXECUTE_WRITECOPY  \Windows\System32\apisetschema.dll
86785f88  ( 4)    7f6f0    7f7ef        0 Mapped       READONLY           Pagefile-backed section
941854c8  ( 2)    7ffb0    7ffd2        0 Mapped       READONLY           Pagefile-backed section
867c1600  ( 3)    7ffd3    7ffd3        1 Private      READWRITE         
9403d420  ( 4)    7ffdf    7ffdf        1 Private      READWRITE         

Total VADs: 21    Average level: 2    Maximum depth: 4


Здесь видно, что в древе моего процесса имеется всего 21-структура VAD, с макс.уровнем(4) и средним(2).

В первом столбце лежит адрес структуры, а во-втором её уровень Level в глобальном древе. Нуль – это корень VadRoot, а дальше отладчик отсортировал древо по возрастанию адресов StartVPN. Например на левом узле уровня(1) занял позицию
Код:
VAD=891a0eb8
(диапазон памяти с меньшими адресами, в данном случае одна страница 0x80000), а на правом уровня(1)
Код:
VAD=89a9e398
. Не забыл отладчик и про атрибуты, которые завершают картину. Если-же мы хотим просмотреть детали конкретного взятого VAD из всего\этого древа, можно указать его адрес с аргументом(1) (см.справку WinDbg, команда ".hh !vad" в окне отладчика):




Для программного доступа к структурам VAD нужен драйвер, поскольку корень древа лежит в вирт.пространстве ядра выше 0х80000000 (верхняя половина х32). Однако кое-что в режиме ReadOnly можно получить и из пользовательского уровня через VirtualQuery(). Вот её прототип:


C-подобный:


Код:
DWORD VirtualQuery
  lpAddress       dd
0
;
// In.  линк на переменную с базой запрашиваемых страниц
lpBuffer        dd
0
;
// Out. линк на структуру "MEMORY_BASIC_INFORMATION"
dwLength        dd
0
;
// In.  sizeof.MEMORY_BASIC_INFORMATION
;
//---------------------------------------------------------------
struct MEMORY_BASIC_INFORMATION
  BaseAddress     dd
0
;
// База страницы внутри региона
AllocationBase  dd
0
;
// База региона
AllocaProtect   dd
0
;
// Флаг защиты при выделении региона (PAGE_xx)
RegionSize      dd
0
;
// Размер региона, в котором все страницы имеют одинаковые атрибуты
State           dd
0
;
// Состояние памяти (MEM_COMMIT\FREE\RESERVE)
Protect         dd
0
;
// Защита доступа (см.AllocaProtect)
Type            dd
0
;
// Тип памяти в регионе (MEM_MAPPED\PRIVATE\IMAGE)
ends


Посмотрите на поля структуры – ничего не напоминает? Всё тот-же "StartVPN" из VAD (AllocationBase), размер региона и атрибуты защиты. Чтобы обойти всё древо, эту функцию нужно вызывать в цикле, на каждой итерации которого прибавлять к аргументу "lpAddress" значение поля "RegionSize". Цикл продолжаем до тех пор, пока не упрёмся в потолок вирт.памяти, который возвращает GetNativeSystemInfo().


...:: Примечание ::...

Во-всей линейке Win система всегда резервирует снизу и сверху по 64К для отлова неверных указателей,

поэтому на х32 мин.адресом процесса будет 0х00010000, а максимальным 0х7FFF0000. Буферы 64К оставлены на случай,

когда мы забываем передать аргумент какой-нибудь функции Win-API, и система подставляет вместо него Null-указатель.

В результате запрос попадает в зону сигнального буфера и получаем ошибку AccessViolation = 0xC0000005.


Благодаря VAD, выделение даже больших объёмов памяти не представляет проблему для диспетчера. Например та-же VirtualAlloc() просто добавляет ещё одну запись в древо VAD процесса, а реверсивная ей операция VirtualFree() тупо удаляет определённый узел из него.

3.WorkingSet– набор рабочих страниц процесса

Память – это разделяемый системный ресурс, а потому требует чёткого управления. Надзор ложится на плечи диспетчера вирт.памяти VMM. Он должен следить за тем, какие регионы свободны, выделять их процессам и освобождать, когда процесс завершает свою работу. Под определение WorkingSet попадают не регионы памяти, а набор отдельных страниц, которые в настоящий момент видны процессу во-фреймах памяти. Такие страницы называют ещё резидентными и доступны они приложению, не вызывая исключения PageFault (#PF, ошибка страницы). Когда система испытывает нехватку ОЗУ, кол-во пейджей в наборе влияет на процесс сброса их в файл-подкачки Pagefile.sys – эта процедура известна как обрезка набора, или "WorkingSet trim".

Диспетчер ведёт несколько списков-состояния страниц (см.рис.ниже), которые используют пользовательские процессы и сама ОС. Здесь диапазон "WorkingSetSize" представляет текущий размер набора, а Peak (пик) – макс.возможный для данной системы. В составе Kernel32.dll имеется функция K32GetProcessMemoryInfo() для вывода инфы о размере раб.набора указанного в аргументе процесса, а так-же K32QueryWorkingSet() для перечисления флагов всех страниц в наборе. В практической части приводится пример их вызова.






Если процесс пытается обратиться к странице, которой нет на данный момент в его наборе (и соответственно в VAD), блок MMU генерит аппаратное исключение #PF, и диспетчер подкачивает отсутствующую страницу с диска. Если-же процесс освобождает пейджи при помощи VirtualFree(), менеджер убирает их из списка WorkingSet, а если страница была изменена посредством записи, помещает её в "ModifiedPageList", и далее в отстойник "Standby". Страницы ExecuteRead, как правило, относятся к классу немодифицируемых, так-что после освобождения, они из набора прямиком отправляются в отстойник.

Менеджер ведёт ещё 2 списка: свободных страниц "FreePage", и пустых "ZeroPage". В список свободных помещаются пейджи, которые освободились после окончания процесса, а в лист пустых сбрасываются страницы, которые забил нулями специальный поток менеджера "ZeroPageThread" при помощи функции RtlZeroMemory(). В системном диспетчере-задач TaskMan (Ctrl+Alt+Del), на вкладке "Быстродействие" есть информация о рабочем наборе ОС:

• Доступно – это сумма объёмов: отстойника + пустых + свободных страниц (вход в набор, см.рис.выше);
• Кэшированно – сумма: отстойника + рабочих страниц.


Отладчик WinDbg имеет расширение
Код:
!memusage
для вывода дампа рабочих страниц всей системы, и каждого приложения в отдельности. Правда для последнего случая нужно подцепить к дебагеру клиентскую ОС (например на вир.машине), а в локальном режиме(lkd) он выводит только общий лист, как показано в примере ниже:


Код:


Код:
lkd> !memusage
;//------------------
loading PFN database
loading (100% complete)..
Compiling memory usage data (99% Complete).
             Zeroed:      0 (      0 kb)
               Free:  21654 (  86616 kb)
            Standby: 133179 ( 532716 kb)
           Modified:  21004 (  84016 kb)
    ModifiedNoWrite:      6 (     24 kb)
       Active/Valid: 214243 ( 856972 kb)
         Transition:    395 (   1580 kb)
                Bad:    237 (    948 kb)
            Unknown:      0 (      0 kb)
              TOTAL: 390481 (1561924 kb)
Building kernel map
Finished building kernel map
Unable to get control area: pfn 8ebc7d88 83c03b64 ;// dt -v _MMPTE u.
;//---------------------------------
struct _MMPTE, 1 elements, 0x8 bytes
   +0x000 u    : union ,                 10 elements, 0x8 bytes
      +0x000 Long         : Uint8B
      +0x000 VolatileLong : Uint8B
      +0x000 HighLow      : struct _MMPTE_HIGHLOW,     2 elements, 0x8 bytes
      +0x000 Hard         : struct _MMPTE_HARDWARE,   14 elements, 0x8 bytes
      +0x000 Proto        : struct _MMPTE_PROTOTYPE,   8 elements, 0x8 bytes
      +0x000 Soft         : struct _MMPTE_SOFTWARE,   10 elements, 0x8 bytes
      +0x000 TimeStamp    : struct _MMPTE_TIMESTAMP,   9 elements, 0x8 bytes
      +0x000 Trans        : struct _MMPTE_TRANSITION, 10 elements, 0x8 bytes
      +0x000 Subsect      : struct _MMPTE_SUBSECTION,  7 elements, 0x8 bytes
      +0x000 List         : struct _MMPTE_LIST,        9 elements, 0x8 bytes


Как видим, MMPTE является контейнером для 8-ми вложенных структур – для нас интересны только три из них:

• Hard – описывает типичный фрейм в физ.памяти,
• Proto – прототип PFN для расшаренных фреймов,
• Soft – фрейм выгружен в файл-подкачки на диск.


Чтобы определить тип записи PTE, диспетчер проверяет в каждой из этих структур бит в позиции нуль "Valid". Он может быть выставлен в единицу только в одной из структур, а в остальных будет сброшен – так диспетчер понимает, с фреймом какого типа имеет дело:


Код:


Код:
lkd> dt _MMPTE u.Hard.
;//-------------------
   +0x000 u  :
      +0x000 Hard :
         +0x000 Valid        : Pos 0,  1 Bit  ;// dt _MMPTE u.Soft.
nt!_MMPTE
   +0x000 u  :
      +0x000 Soft :
         +0x000 Valid        : Pos 0,  1 Bit
         +0x000 Unused0      : Pos 1,  3 Bits
         +0x000 SwizzleBit   : Pos 4,  1 Bit
         +0x000 Protection   : Pos 5,  5 Bits
         +0x000 Prototype    : Pos 10, 1 Bit
         +0x000 Transition   : Pos 11, 1 Bit
         +0x000 PageFileLow  : Pos 12, 4 Bits    ;// !pte 10000
      VA 00010000
      PDE at C0600000             PTE at C0000080
      contains 0000000017D3E867   contains 8000000024492947
      pfn 17d3e     ---DA--UWEV   pfn 24492     -G-D---UW-V

;//---------------------------------------------------------
;//------------- Флаги защиты [ ---DA—UWEV ] ---------------
;//---------------------------------------------------------
0x200  C  -  Copy on Write. 
0x100  G  -  Global. 
0x080  L  -  Large page (большой фрейм, флаг только в PDE) 
0x040  D  -  Dirty (изменённый)
0x020  A  -  Accessed (был доступ)
0x010  N  -  Cache disabled (некэшируемый)
0x008  T  -  Write-through (сквозная запись на диск)
0x004  U  K  Owner (владелец, user\kernel) 
0x002  W  R  Writeable или Read-only
0x001  V  -  Valid (вилидная запись)
       E  -  Execute page. Для платформ без аппаратного бита NX, всегда отображается буква E.


Обратите внимание на лог команды
Код:
!pte
. Можно сделать вывод, что транслятор моего "Dual-Core E5200" работает в 2-уровневом режиме: PageDirectory(L2) и PageTable(L1). Такой модели придерживаются 32-битные системы без расширения РАЕ (см.рис.ниже), а если РАЕ включён битом[5] в регистре CR4, то в модель подключается ещё и уровень PageDirectoryPointer(L3). Вот как выглядит х32 транслятор 4-Кбайтных страниц без РАЕ:






Здесь видно, что из-за ограниченного кол-ва бит, ни о каких 1GB-страницах не может быть и речи. Зато если расширить Offset до 22-бит, можно охватить 4МБ фрейм. При этом макс.адресом в системе будет по-прежнему 4GB. Но тогда как удаётся в режиме РАЕ адресовать память 2^36=64GB? Здесь инженеры нашли оригинальное решение – они просто расширили сами записи РТЕ в таблице PageTable до 64-бит, хотя в режиме без РАЕ эти записи имеют размер 32-бит.

В арсенале WinDbg есть расширение
Код:
!vtop
(VirtualToPhysical). Если вскормить ему вирт.адрес, получим значения записей(Entry) всех уровней транслятора, а так-же связанный с виртуальным, физический адрес. Проведём небольшой эксперимент по такому алго..

1. Запрашиваем дамп активных процессов системы
Код:
!process 0 0
, и возьмём из них два произвольных. В поле "DirBase" будет лежать указатель на корневой каталог транслятора – в моём случае это
Код:
0x5f5af400
для процесса ModuleInfo.exe, и
Код:
0x5f5afd00
для FoxitReader.exe:




2. Теперь передаём команде
Код:
!vtop
полученные на этапе(1) значения "DirBase", и во-втором аргументе любой вирт.адрес, например
Код:
0х00401100
:




Значит система для тестов у меня Win7-x32 с включённым РАЕ, а потому в логе пестрят напоминания об этом. Модель транслятора 3-х уровневая. Записи Entry на всех уровнях имеют размер 64-бит, что позволяет адресовать в режиме РАЕ пространство свыше 4Gb (в режиме без РАЕ записи размером 4-байт).

Вирт.адресу
Код:
0х00401100
моего процесса соответствует физ.адрес
Код:
0x3202e100
, а к такому-же адресу процесса Foxit привязан уже другой физ.адрес
Код:
0x39565100
. По этой причине, процесс(А) не может прочитать данные процесса(В). Физ.адрес получаем из записи РТЕ последнего уровня(L1), и если разделить его на
Код:
1000h
(размер 4К фрейма), получим PFN или порядковый номер страничного фрейма "Physical Frame Number".

Обратите внимание на значение РТЕ моего лога =
Код:
0х800000003202e947
.

Младшие 12-бит
Код:
947h
являются здесь атрибутами фрейма (см.формат записи РТЕ в табл.выше), поэтому диспетчер запоминает и сбрасывает их в нуль, получая таким образом базу 4К-фрейма в физ.памяти. Теперь из вирт.адреса берётся 12-битный офсет (в данном случае 100h), и складывается с базой. После такой арифметики, получаем физ.адрес
Код:
0x3202e100
.

Расширение отладчика
Код:
!dc
показывает дамп памяти, ожидая на входе физ.адрес (обычный dc требует вирт.адрес). Так сложились звёзды, что
Код:
0х00401100
указывает в моей прожке на секцию-данных, где имеется массив текстовых строк – вот он собственной персоной (см.код в практической части ниже). Если-бы я передал в
Код:
!vtop
адрес
Код:
0х00400000
, получил-бы дамп РЕ-заголовка, с сигнатурой "MZ":




4.2. База данных PFN

Теперь проведём инвентаризацию базы PFN.

Мы не согрешим против истины заявив, что "база страничных фреймов" является ключевой фигурой во-всём механизме трансляции! Если подвести черту под вышесказанным, то процент участия MMU в этом деле стремится к нулю – аппаратный транслятор определяет лишь план действий диспетчеру вирт.памяти, который подстраиваясь под MMU должен создать соответствующее число каталогов и заполнить таблицу-трансляции, записями РТЕ. То-есть без привлечения средств диспетчера, транслятор в MMU ничего из себя не представляет.

При включении машины, диспетчер запрашивает у BIOS объём реально установленной физ.памяти ОЗУ, и разделив это значение на 4096-байт, получает общее кол-во фреймов в системе. Теперь, для каждого из них диспетчер создаёт индивидуальную запись – в ядре она числится как структура
Код:
_MMPFN
. Её размер зависит от режима работы процессора: на системах х32 без РАЕ это 24-байта, для х32.РАЕ = 28-байт, а на х64 все 48-байт. Таким образом, чем больше физ.ОЗУ, тем больше имеем структур, которые собираются в глобальную базу PFN. Указатель на базу лежит в переменной ядра nt!MmPfnDatabase, прочитать её можно командой отладчика
Код:
?poi
(pointer value):


Код:


Код:
lkd> ?poi nt!MmPfnDatabase
;//-------------------------
    Evaluate expression: -2084569088 = 0x83c00000  !pfn 22e4
;//-----------------------------------
    PFN 000022E4 at address 83C3D0F0
    Flink           89A999E8   Share count    00000008   PteAddress C0603018
    Reference count     0001   Сolor                 0   Priority          0
    Restore pte 200000000080   Containing page  0022E4   Active            M       
    Cached  Modified


В следующем логе MMPFN я убрал всё лишнее, и оставил только интересующие нас поля.

Обратите внимание на расшифровку "CacheAttribute" и "PageLocation" – как видим они совпадают с выхлопом расширения
Код:
!pfn
:




Реальные эксперименты в отладчике позволяют толковать спецификацию с практической точки зрения, ведь только пощупав объект руками можно сделать о нём выводы. На данный момент мы знаем, что размер одной структуры MMPFN может быть равен 24, 28 или 48-байт. Кол-во структур напрямую зависит от размера установленной в системе физ.памяти ОЗУ. Выходит, что простой арифметикой можно вычислить размер всей базы PFN на текущей машине, что демонстрирует код ниже:


C-подобный:


Код:
format   pe console
include
'win32ax.inc'
entry    start
;
//----------
.
data
sysInfo   SYSTEM_INFO
;
// макрос переводит из байт в M\Kbyte
macro     FpuDiv
[
pAddr
,
pSize
]
{
fild    qword
[
esp
]
;
// ST0 = аргументы из стека
fidiv
[
pSize
]
;
// разделить на аргумент М или Кбайт
fst
[
pAddr
]
;
// сохранить в переменной
add     esp
,
8
}
;
// очистить аргументы
align
16
kByte      dd
1024
mByte      dd
1024
*
1024
fpuRes1    dq
0
fpuRes2    dq
0
isWow      dd
0
pageSize   dd
0
pfnSize    dd
48
;
// x64=48, x32PAE=28, x32=24
x86_64     db
'x86.64'
,
0
x86_32pae  db
'x86.32 PAE'
,
0
x86_32     db
'x86.32 Non PAE'
,
0
buff       db
0
;
//----------
.
code
start
:
invoke  SetConsoleTitle
,

;
//---- Получить размер страницы\фрейма
invoke  GetNativeSystemInfo
,
sysInfo
        mov     eax
,
[
sysInfo
.
dwPageSize
]
shr     eax
,
10
mov
[
pageSize
]
,
eax
;
// в КБ
;
//---- Проверить систему на 64-бит (WOW64)
invoke  GetCurrentProcess
        invoke  IsWow64Process
,
eax
,
isWow
        mov     esi
,
x86_64
        cmp
[
isWow
]
,
1
jz      @next
;
//---- Проверить на режим РАЕ (только х32)
invoke  IsProcessorFeaturePresent
,
PF_PAE_ENABLED
;
// константа =9
or      eax
,
eax
        jz      @f
        mov
[
pfnSize
]
,
28
;
// размер структуры _MMPFN при РАЕ
mov     esi
,
x86_32pae
        jmp     @next
@@
:
mov
[
pfnSize
]
,
24
mov     esi
,
x86_32
@next
:
cinvoke  printf
,

,
esi
;
//---- Запросить реальный размер установленной ОЗУ
invoke  GetPhysicallyInstalledSystemMemory
,
buff
        push    dword
[
buff
+
4
]
dword
[
buff
]
FpuDiv  fpuRes1
,
kByte
        finit
       cinvoke  printf
,

,
\
                        dword
[
fpuRes1
]
,
dword
[
fpuRes1
+
4
]
,
[
sysInfo
.
dwPageSize
]
;
//---- Имеем размер памяти ОЗУ и размер страницы.
;
//---- Вычисляем общее кол-во фреймов PFN
push    dword
[
buff
+
4
]
dword
[
buff
]
FpuDiv  fpuRes1
,
pageSize
        fistp
[
fpuRes2
]
cinvoke  printf
,

,
\
                        dword
[
fpuRes1
]
,
dword
[
fpuRes1
+
4
]
,
\
                        dword
[
fpuRes2
]
,
dword
[
fpuRes2
+
4
]
,
[
pfnSize
]
;
//---- Всего PFN * размер одной структуры = Размер базы PFN
fld     qword
[
fpuRes1
]
fimul
[
pfnSize
]
fidiv
[
mByte
]
fstp
[
fpuRes2
]
cinvoke  printf
,

,
\
                        dword
[
fpuRes2
]
,
dword
[
fpuRes2
+
4
]
cinvoke  _getch
       cinvoke  exit
,
0
;
//---------------
section
'.idata'
import data readable
library   msvcrt
,
'msvcrt.dll'
,
kernel32
,
'kernel32.dll'
,
user32
,
'user32.dll'
include
'api\msvcrt.inc'
include
'api\kernel32.inc'
include
'api\user32.inc'




5.MDL – Memory DescriptorList

Структура MDL используется ядром исключительно при операциях прямого обращения к памяти DMA (Direct Memory Access, чтение/запись без участия процессора). Как-правило, каналы DMA используют только физ.устройства типа: накопители ATA/ATAPI (харды и DVD-ROM), девайсы USB, Audio/Video, LAN и прочие, т.е. все высокоскоростные. Для их поддержки, ещё во-времена динозавров в чипсет был включён спец.процессор с ограниченными возможностями DMAC, который называют ещё "Slave DMA Controller".

Ведомым Slave его обозвали потому, что в наше время DMA-контролёры уже встраиваются непосредственно в сами устройства так, что они могут захватывать шину-памяти по своей инициативе, не привлекая к этому делу вечно перегруженный DMAC – этот механизм известен как "BusMastering". Однако нужно учитывать, что шина у памяти одна, а потому в любой момент доступ к ней будет иметь кто-то один: или CPU (благодаря своему кэшу он редко обращается к памяти), или устройство DMA, ..но не оба сразу.

Чтобы организовать запрос на операцию DMA, пользовательское приложение должно сначала получить дескриптор нужного устройства, а потом передать драйверу этого устройства, адрес промежуточного буфера. Доступные на чтение/запись девайсы относятся к файловым объектам системы, так-что дескрипторы получаем через CreateFile(), а взаимодействуем с их драйверами через DeviceIoControl():


C-подобный:


Код:
BOOL
DeviceIoControl
(
)
In
.
HANDLE        hDevice
;
// дескриптор устройства
In
.
DWORD         dwIoControlCode
;
// IOCTL = код операции
In
.
LPVOID        lpInBuffer
;
// dt -v -b _IRP
;//--------------------------------
struct _IRP, 21 elements, 0x70 bytes
   +0x000 Type             : Int2B
   +0x002 Size             : Uint2B
   +0x004 MdlAddress       : Ptr32 to     ;//AssociatedIrp.SystemBuffer
. Буфф является общим для входа и выхода – дров принимает данные из этого буфера, а затем передаёт в него-же.[/FONT]

• METHOD_IN_DIRECT + OUT_DIRECT
Здесь линк на буфер лежит так-же в
Код:
Irp–>AssociatedIrp.SystemBuffer
, но прицепом имеется ещё и указатель на структуру MDL, в поле
Код:
Irp–>MdlAddress
. Буферы для приёма и передачи раздельны. Ниже мы положим их под скальпель и рассмотрим в деталях.


• METHOD_NEITHER
Диспетчер ввода-вывода не предоставляет в ядре никаких системных буферов! В
Код:
Irp–>UserBuffer
лежит вирт.адрес выходного буфера пользователя (напомню, что разговор идёт от лица драйвера), а в поле
Код:
Parameters.DeviceIoControl.Type3InputBuffer
структуры IO_STACK_LOCATION – адрес входного, которые были указаны при вызове DeviceIoControl().



Теперь посмотрим на структуру MDL, она имеет размер 28-байт (на системах х32) и всего 8 элементов:


Код:


Код:
lkd> dt -v _MDL
;//--------------------------------
struct _MDL, 8 elements, 0x1c bytes
   +0x000 Next             : Ptr32 to struct _MDL,        8 elements, 0x1c bytes
   +0x004 Size             : Int2B
   +0x006 MdlFlags         : Int2B
   +0x008 Process          : Ptr32 to struct _EPROCESS, 144 elements, 0x2e0 bytes
   +0x00c MappedSystemVa   : Ptr32 to Void
   +0x010 StartVa          : Ptr32 to Void
   +0x014 ByteCount        : Uint4B
   +0x018 ByteOffset       : Uint4B


1. Next – это линк на сл.структуру MDL, если они связываются в цепочку (используется редко);

2. Size – размер этой структуры (чтобы различать х32 от х64);

3. MdlFlags – флаги листа (см.ниже);

4. Process – указатель на процесс, которому принадлежит данная структура MDL;

5. MappedSystemVa – линк на буфер, если он отображается в виртуальное (не физ) пространство ядра;

6. StartVa – база вирт.страницы, которая выделена пользователем под буфер;

7. ByteCount – размер отображаемого в MDL буфера;

8. ByteOffset – смещение буфера от начала (базы) вирт.страницы StartVA.

Структуру MDL нужно рассматривать как заголовок, сразу после которого следует массив PFN физ.памяти. Если пользовательский буфер меньше 4К-страницы, после заголовка будет всего один указатель на PFN, для отображения этого буфера в свободный фрейм памяти. Поскольку вирт.память всегда линейна, а выделенные для неё фреймы могут идти в разнобой, то когда буфер юзера больше одной страницы, диспетчеру-памяти приходится выделять несколько разбросанных по физ.памяти фреймов, и связывать их в цепочку. В этом случае после заголовка MDL будут лежать уже несколько линков на выделенные PFN. Важно понять, что всякий лист MDL всегда описывает только один буфер ввода-вывода. Вот как это выглядит графически:






Здесь, в вирт.памяти представлено всего три страницы, а буфер DMA находится внутри второй. Его смещение от начала страницы указывается в поле "ByteOffset", а размер в "ByteCount". Пунктирные линии будут действительны только при размере буфа больше 4К-страницы.

Ещё одним\важным моментом является то, как VMM выделяет физ.фреймы для буфера. Ключевым событием в этом алго является вызов функции MmProbeAndLockPages(), которая намертво блокирует выделенные фреймы так, что они становятся не выгружаемыми в файл-подкачки (NonPagedPool). При этом в MDL взводится флаг "PAGES_LOCKED". Фреймы остаются закреплёнными вплоть до окончания операции прямого обращения к памяти DMA, после чего драйвер должен освободить их посредством MmUnlockPages() + ExFreePool().

6. Практика – сбор информации

На финишной прямой соберём основные моменты статьи в приложение, которое будет использовать сл.функции Win32-API:

• GetNativeSystemInfo() – возвращает в структуру "SYSTEM_INFO" размер страницы и пр.инфу;

• GetPerformanceInfo() – структура "PERFORMANCE_INFORMATION", где можно найти счётчики использования памяти;

• K32GetModuleInformation() – в структуру "MODULEINFO" сбрасывает инфу о РЕ-заголовке (база/размер образа, и точка-входа ЕР);

• GetPhysicallyInstalledSystemMemory() – появилась начиная с Win7 и возвращает QWORD с реальным размером DDR-SDRAM в КБ;

• GlobalMemoryStatusEx() – в структуре "MEMORYSTATUS_EX" можно будет найти инфу о вирт.памяти процесса;

• GetProcessWorkingSetSize() – в переменных инфа о макс/мин рабочего набора процесса WorkingSet;

• K32GetProcessMemoryInfo() – в структуре "PROCESS_MEMORY_COUNTERS_EX" лежат различные квоты памяти;

• VirtualQuery() – в цикле позволит создать всю карту-памяти процесса MemoryMap (обходит древо VAD).

Последняя функция из этого списка возвращает двоичные "флаги состояния" регионов памяти и атрибутов их защиты. Чтобы вывести их в более дружелюбном нам текстовом виде, я создал таблицу соответствий. Поскольку приложение х32, а в коде имеются 64-бит поля, то удобно использовать макросы с операциями FPU. Вот пример:


C-подобный:


Код:
format   pe console
include
'win32ax.inc'
entry   start
;
//----------
.
data
memTable  dd
001
h
,
pNA
,
002
h
,
pRO
,
004
h
,
pRW
,
008
h
,
pWC
,
010
h
,
pEx
,
020
h
,
pER
          dd
040
h
,
pERW
,
080
h
,
pEWC
,
104
h
,
pG
,
200
h
,
pNC
,
400
h
,
pWCN
          dd
0
,
mRes
,
1000
h
,
mC
,
2000
h
,
mRes
,
4000
h
,
mDec
,
8000
h
,
mRel
,
10000
h
,
mFree
          dd
20000
h
,
mPriv
,
30000
h
,
mCr
,
40000
h
,
mMap
,
80000
h
,
mRst
,
100000
h
,
mTd
,
1000000
h
,
mIm
tblSize
=
(
$
-
memTable
)
/
8
;
// Page access/protect flags
pNA       db
'NO_ACCESS'
,
0
pRO       db
'READONLY'
,
0
pRW       db
'READWRITE'
,
0
pWC       db
'WRITECOPY'
,
0
pEx       db
'EXECUTE'
,
0
pER       db
'EXECUTE_READ'
,
0
pERW      db
'EXECUTE_READWRITE'
,
0
pEWC      db
'EXECUTE_WRITECOPY'
,
0
pG        db
'PAGE_GUARD'
,
0
pNC       db
'PAGE_NOCACHE'
,
0
pWCN      db
'WRITECOMBINE'
,
0
;
// Memory allocation flags
mC        db
'COMMIT'
,
0
mRes      db
'RESERVED'
,
0
mDec      db
'DECOMMIT'
,
0
mRel      db
'RELEASE'
,
0
mFree     db
'FREE'
,
0
mPriv     db
'PRIVATE'
,
0
mMap      db
'MAPPED'
,
0
mRst      db
'RESET'
,
0
mTd       db
'TOP_DOWN'
,
0
mIm       db
'IMAGE'
,
0
mCr       db
'COMMIT + RESERVE'
,
0
Unk       db
'Combine'
,
0
;
//-------------------------
align
16
perfInfo  PERFORMANCE_INFORMATION
sysInfo   SYSTEM_INFO
mStat     MEMORYSTATUS_EX
mInfo     MODULEINFO
mBasic    MEMORY_BASIC_INFORMATION
mCount    PROCESS_MEMORY_COUNTERS_EX
;
// Макрос переводит из байт в M\Kbyte
macro     FpuDiv
[
pAddr
,
pSize
]
{
fild    qword
[
esp
]
fidiv
[
pSize
]
fstp
[
pAddr
]
add     esp
,
8
}
;
// Переводит из страниц в Kbyte
macro     FpuMul
[
pAddr1
,
pSize1
]
{
fild    qword
[
esp
]
fimul
[
pSize1
]
fstp
[
pAddr1
]
add     esp
,
8
}
;
// Переводит из Kb в Mb
macro     FpuK2M
[
pAddr2
]
{
fld
[
pAddr2
]
fidiv
[
kByte
]
fstp
[
pAddr2
]
}
;
// Возвращает в ESI указатель на строку для VirtualAlloc()
macro     GetAttr
[
Attr
,
pTable
,
pSize
]
{
local   @found
          mov     esi
,
pTable
          mov     ecx
,
pSize
@@
:
lodsd
          cmp     eax
,
Attr
          je      @found
          add     esi
,
4
loop    @b
          mov     esi
,
Unk
          jmp     @f
@found
:
mov     esi
,
[
esi
]
@@
:
}
workMin   dd
0
workMax   dd
0
kByte     dd
1024
mByte     dd
1024
*
1024
pageSize  dd
4096
/
1024
align
16
fpuRes1   dq
0
fpuRes2   dq
0
fpuRes3   dq
0
fpuRes4   dq
0
fpuRes5   dq
0
fpuRes6   dq
0
fpuRes7   dq
0
fpuRes8   dq
0
pAddress  dd
10000
h
hProcess  dd
0
hModule   dd
0
buff      db
0
;
//----------
.
code
start
:
invoke  SetConsoleTitle
,

;
//---- Получить дескрипторы и заполнить структуры
invoke  GetModuleHandle
,
0
mov
[
hModule
]
,
eax
        invoke  OpenProcess
,
PROCESS_QUERY_INFORMATION
,
0
,
\
                            invoke GetCurrentProcessId
        mov
[
hProcess
]
,
eax
        invoke  GetNativeSystemInfo
,
sysInfo
        invoke  GetPerformanceInfo
,
perfInfo
,
sizeof
.
PERFORMANCE_INFORMATION
;
//---- Собираем инфу..
invoke  GetModuleFileName
,
0
,
buff
,
256
cinvoke  printf
,

,
buff
        invoke  K32GetModuleInformation
,
-
1
,
[
hModule
]
,
mInfo
,
sizeof
.
MODULEINFO
       cinvoke  printf
,

,
\
[
mInfo
.
lpBaseOfDll
]
,
[
mInfo
.
EntryPoint
]
,
[
mInfo
.
SizeOfImage
]
;
//--------------------------------
invoke  GetPhysicallyInstalledSystemMemory
,
buff
        push    dword
[
buff
+
4
]
dword
[
buff
]
FpuDiv  fpuRes1
,
kByte
       cinvoke  printf
,

,
\
                        dword
[
fpuRes1
]
,
dword
[
fpuRes1
+
4
]
;
//--------------------------------
invoke  GlobalMemoryStatusEx
,
mStat

        push    dword
[
mStat
.
dqTotalPhys
+
4
]
dword
[
mStat
.
dqTotalPhys
]
FpuDiv  fpuRes1
,
mByte
        push    dword
[
mStat
.
dqAvailPhys
+
4
]
dword
[
mStat
.
dqAvailPhys
]
FpuDiv  fpuRes2
,
mByte
        push    dword
[
mStat
.
dqTotalPageFile
+
4
]
dword
[
mStat
.
dqTotalPageFile
]
FpuDiv  fpuRes3
,
mByte
        push    dword
[
mStat
.
dqAvailPageFile
+
4
]
dword
[
mStat
.
dqAvailPageFile
]
FpuDiv  fpuRes4
,
mByte
        push    dword
[
mStat
.
dqTotalVirtual
+
4
]
dword
[
mStat
.
dqTotalVirtual
]
FpuDiv  fpuRes5
,
mByte
        push    dword
[
mStat
.
dqAvailVirtual
+
4
]
dword
[
mStat
.
dqAvailVirtual
]
FpuDiv  fpuRes6
,
mByte
       cinvoke  printf
,

,
\
                        dword
[
fpuRes1
]
,
dword
[
fpuRes1
+
4
]
,
dword
[
fpuRes2
]
,
dword
[
fpuRes2
+
4
]
,
\
[
mStat
.
dwMemoryLoad
]
,
\
                        dword
[
fpuRes3
]
,
dword
[
fpuRes3
+
4
]
,
dword
[
fpuRes4
]
,
dword
[
fpuRes4
+
4
]
,
\
                        dword
[
fpuRes5
]
,
dword
[
fpuRes5
+
4
]
,
dword
[
fpuRes6
]
,
dword
[
fpuRes6
+
4
]
,
\
[
sysInfo
.
dwPageSize
]
;
//--------------------------------
push
0
[
perfInfo
.
KernelTotal
]
FpuMul  fpuRes1
,
pageSize
        FpuK2M  fpuRes1
        push
0
[
perfInfo
.
KernelPaged
]
FpuMul  fpuRes2
,
pageSize
        FpuK2M  fpuRes2
        push
0
[
perfInfo
.
KernelNonpaged
]
FpuMul  fpuRes3
,
pageSize
        FpuK2M  fpuRes3
        push
0
[
perfInfo
.
SystemCache
]
FpuMul  fpuRes4
,
pageSize
        FpuK2M  fpuRes4
       cinvoke  printf
,

,
\
                        dword
[
fpuRes1
]
,
dword
[
fpuRes1
+
4
]
,
[
perfInfo
.
KernelTotal
]
,
\
                        dword
[
fpuRes2
]
,
dword
[
fpuRes2
+
4
]
,
[
perfInfo
.
KernelPaged
]
,
\
                        dword
[
fpuRes3
]
,
dword
[
fpuRes3
+
4
]
,
[
perfInfo
.
KernelNonpaged
]
,
\
                        dword
[
fpuRes4
]
,
dword
[
fpuRes4
+
4
]
,
[
perfInfo
.
SystemCache
]
;
//--------------------------------
invoke  GetProcessWorkingSetSize
,
[
hProcess
]
,
workMin
,
workMax
        shr
[
workMin
]
,
10
shr
[
workMax
]
,
10
mov     eax
,
[
workMin
]
mov     ebx
,
[
workMax
]
shr     eax
,
2
shr     ebx
,
2
cinvoke  printf
,

,
\
[
workMin
]
,
eax
,
[
workMax
]
,
ebx
;
//--------------------------------
invoke  K32GetProcessMemoryInfo
,
-
1
,
mCount
,
sizeof
.
PROCESS_MEMORY_COUNTERS_EX
        push
0
[
mCount
.
PeakWorkingSetSize
]
FpuDiv  fpuRes1
,
kByte
        push
0
[
mCount
.
WorkingSetSize
]
FpuDiv  fpuRes2
,
kByte
        push
0
[
mCount
.
QuotaPeakPagedPoolUsage
]
FpuDiv  fpuRes3
,
kByte
        push
0
[
mCount
.
QuotaPagedPoolUsage
]
FpuDiv  fpuRes4
,
kByte
        push
0
[
mCount
.
QuotaPeakNonPagedPoolUsage
]
FpuDiv  fpuRes5
,
kByte
        push
0
[
mCount
.
QuotaNonPagedPoolUsage
]
FpuDiv  fpuRes6
,
kByte
        push
0
[
mCount
.
PagefileUsage
]
FpuDiv  fpuRes7
,
kByte
        mov     eax
,
[
mCount
.
PeakWorkingSetSize
]
mov     ebx
,
[
mCount
.
WorkingSetSize
]
shr     eax
,
12
shr     ebx
,
12
cinvoke  printf
,

,
\
                        dword
[
fpuRes1
]
,
dword
[
fpuRes1
+
4
]
,
eax
,
\
                        dword
[
fpuRes2
]
,
dword
[
fpuRes2
+
4
]
,
ebx
,
\
                        dword
[
fpuRes3
]
,
dword
[
fpuRes3
+
4
]
,
dword
[
fpuRes4
]
,
dword
[
fpuRes4
+
4
]
,
\
                        dword
[
fpuRes5
]
,
dword
[
fpuRes5
+
4
]
,
dword
[
fpuRes6
]
,
dword
[
fpuRes6
+
4
]
,
\
                        dword
[
fpuRes7
]
,
dword
[
fpuRes7
+
4
]
,
[
mCount
.
PageFaultCount
]
;
//---- Карта памяти процесса --------------
cinvoke  printf
,

@map
:
invoke  VirtualQuery
,
[
pAddress
]
,
mBasic
,
sizeof
.
MEMORY_BASIC_INFORMATION
        or      eax
,
eax
        je      @err

        mov     ebx
,
[
mBasic
.
Protect
]
or      ebx
,
ebx
        jnz     @f
        mov     ebx
,
[
mBasic
.
AllocaProtect
]
@@
:
GetAttr ebx
,
memTable
,
tblSize
        push    esi

        mov     ebx
,
[
mBasic
.
State
]
GetAttr ebx
,
memTable
,
tblSize
        push    esi

        pop     eax ebx
        mov     edx
,
[
pAddress
]
mov     ecx
,
[
mBasic
.
RegionSize
]
add     edx
,
ecx
        shr     ecx
,
10
mov     esi
,
ecx
        shr     esi
,
2
cinvoke  printf
,

,
\
[
pAddress
]
,
edx
,
eax
,
ebx
,
ecx
,
esi

@err
:
mov     eax
,
[
mBasic
.
RegionSize
]
add
[
pAddress
]
,
eax
;
//<---- Переход к сл.региону!
cmp
[
pAddress
]
,
0x7fff0000
;
//<---- Проверить потолок юзера
jb      @map

       cinvoke  _getch
       cinvoke  exit
,
0
;
//---------------
section
'.idata'
import data readable
library  msvcrt
,
'msvcrt.dll'
,
kernel32
,
'kernel32.dll'
,
\
         user32
,
'user32.dll'
,
psapi
,
'psapi.dll'
include
'api\msvcrt.inc'
include
'api\kernel32.inc'
include
'api\user32.inc'
include
'api\psapi.inc'




В скрепке лежит инклуд Kernel32.inc с описанием всех используемых здесь структур. Как оказалось, в штатной поставке FASM'а имеется только оставшийся нам в наследство от Win-XP старый набор, поэтому я обновил его и советую заменить им устаревший инклуд по адресу: fasm\include\equates.

7. Постскриптум.

Такой вот получился "бутафорский ман" с мозговым штурмом..

В статье планировал лишь коротко рассмотреть основные моменты, но в работе диспетчера-памяти всё переплетено в клубок так, что если потянешь за одну нить, то автоматом всплывает другая, без объяснения которой в первой теряется смысл. Здесь вспоминается критика к фильму "Выживщий" с Лео в главной роли: -"Выживщим является тот, кто досмотрел фильм до конца".

В скрепку ложу два исполняемым файла для тестов, инклуд Kernel32.inc, а так-же лист из 1000+ поддерживаемых Win7 кодов IOCTL/FSCTL. Всем удачи, пока!
 
Ответить с цитированием
 





Здесь присутствуют: 1 (пользователей: 0 , гостей: 1)
 


Быстрый переход




ANTICHAT ™ © 2001- Antichat Kft.