![]() |
В системной архитектуре отображение вирт.памяти на физическую носит таинственный характер. Предлагая материал на эту тему, 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_Type1.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). Чтобы просмотреть его содержимое, можно потянуть за расширение Код:
!filecache1.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Пробежавшись по макушкам терминов посмотрим на схему ниже, где представлена логическая связь между основными структурами VMM. Ясно, что этот рисунок не отражает всей палитры, ведь полностью охватить хозяйство вирт.памяти в одном скрине просто нереально. Поэтому MSDN и подкидывает нам инфу жалкими крапалями. Однако общую картину зрительно уже можно будет сформировать: https://forum.antichat.xyz/attachmen...507289e698.png Значит у каждого процесса своё древо VAD, где хранятся адреса его регионов памяти. Далее страницы попадают в "котёл" WorkingSet для фильтрации их по назначению. Когда декодер-инструкций процессора обнаруживает запрос к ОЗУ, он передаёт адрес в блок MMU, чтобы транслятор преобразовал его в физический. Вирт.адреса одинаковы у всех процессов, но благодаря базе PFN они указывают на разные фреймы памяти. Чтобы процесс(А) не считал данные процесса(В), диспетчеру нужно сменить каталог-страниц процесса "PageDirectoryTable", внутри которого имеются записи PDTE – этим занимается планировщик Scheduler, при переключении с одного потока на другой. Адрес каталога-страниц верхнего уровня РDТ каждого из процессов, система запоминает в его ядерной структуре Код:
_KPROCESSТеперь сфокусируем своё внимание на системных таблицах и посмотрим, информацию какого рода хранит в них диспетчер. Пробираться по тёмным переулкам памяти будем таким маршрутом, как это указано на схеме выше, т.е. сверху вниз. 2.Древо VAD процесса– Virtual Address Descriptor В отличие от планировщика, который выбирает потоки Thread для исполнения и пропускает между ног процессы, диспетчер наоборот полностью концентрируется на процессах и не подозревает о существовании потоков, ведь именно процессы (а не потоки) владеют адресным пространством. Когда программа запускается на исполнение, загрузчик образов в Ntdll.dll считывает её РЕ-заголовок (сколько секций, какого размера и т.д.), и на основании этого выделяет процессу вирт.память. При этом диспетчер создаёт сразу несколько дескрипторов VAD, в которых прописаны диапазоны отображаемых адресов. Таким образом, адресное пространство процесса полностью определяется списком его VAD. В каждой структуре VAD хранится вирт.адрес первой и последней страницы в данном регионе памяти (Start и EndVPN), а если в нём отображается какой-нибудь файл, то и полный путь до него. Чтобы поиск отдельных VAD в списке был эффективным, все они выстраиваются в виде бинарного AVL-древа, которое имеет корень "VadRoot" и разветвляющиеся вниз узлы "VadNode". Формат деревьев AVL такой, что слева от узла всегда будут находиться VAD с меньшим от родителя стартовым адресом, а справа – большим. Такой подход позволяет с лёгкостью сортировать страницы по возрастанию или убыванию. Графическое представление древа типа AVL представлено ниже: https://forum.antichat.xyz/attachmen...b86751009e.png Обратите внимание на поле StartVPN (VirtualPageNumber)в структурах VAD. Во-первых, значение в левом узле всегда будет меньше чем у родителя, а в правом больше. Во-вторых, поскольку регионы памяти выравниваются на границу 4К (размер одной страницы), то для экономии в VAD указываются только старшие 20-бит адреса, а младшие 12 отброшены, т.к. зарезервированы под смещение внутри выбранного пейджа (2^12=4096). То-есть чтобы получить полный вирт.адрес, нужно дополнять значения всех адресов тремя hex-нулями справа. Тогда получается, что корневой VAD описывает регион из трёх страниц с адресами от 0x00400000 до 0x00403000, и при инициализации система назначила ему атрибуты Exe+Write+Copy (полный доступ).Позже, в записях РТЕ ненужные атрибуты для конкретных страниц снимаются. В нёдрах ядра NT структура VAD числится как Код:
_MMVADКод:
!process 0 0Код: Код:
lkd> !process 0 0 ModuleInfo.exeВ последней строке лога видим линк на корень древа VadRoot, и теперь можно просмотреть его структуру: Код: Код:
lkd> dt _MMVAD 86688b20Значит в каждой структуре VAD представлен уже знакомый нам диапазон памяти Start\EndVPN, а так-же указатели на Left\Right узлы дочернего уровня. Что касается атрибутов защиты данного региона, они спрятаны во-вложенных безымянных структурах Код:
u..u5Код: Код:
lkd> dt _MMVAD 86688b20 u.А вот и атрибуты-защиты "Protection" моего корневого VAD (0y0 является двоичным представлением). Не знаю, может и прописаны значения этих флагов где-нибудь в сишных хидерах, но мне было проще нагуглить. "CommitCharge" указывает число зафиксированных страниц в регионе, т.е. уже привязанных к физ.адресам. Если CommitCharge=0, значит память только зарезервирована, но не связана с фреймами. VAD может описывать как память процесса, так и выделенную, например, девайсам память – вот перечисления типов VAD: Вышеизложенный подход просмотра VAD представляет практический интерес, чтобы обозначить расположение и состав структур при программировании драйверов (кстати VadRoot хранится в структуре EPROCESS). Он абсолютно не пригоден для визуального просмотра всего древа с высоты птичьего полёта. Для этого WinDbg имеет спец.расширение Код:
!vadКод: Код:
lkd> !vad 86688b20Здесь видно, что в древе моего процесса имеется всего 21-структура VAD, с макс.уровнем(4) и средним(2). В первом столбце лежит адрес структуры, а во-втором её уровень Level в глобальном древе. Нуль – это корень VadRoot, а дальше отладчик отсортировал древо по возрастанию адресов StartVPN. Например на левом узле уровня(1) занял позицию Код:
VAD=891a0eb8Код:
VAD=89a9e398Для программного доступа к структурам VAD нужен драйвер, поскольку корень древа лежит в вирт.пространстве ядра выше 0х80000000 (верхняя половина х32). Однако кое-что в режиме ReadOnly можно получить и из пользовательского уровня через VirtualQuery(). Вот её прототип: C-подобный: Код:
DWORD VirtualQueryПосмотрите на поля структуры – ничего не напоминает? Всё тот-же "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() для перечисления флагов всех страниц в наборе. В практической части приводится пример их вызова. https://forum.antichat.xyz/attachmen...c646c4fcba.png Если процесс пытается обратиться к странице, которой нет на данный момент в его наборе (и соответственно в VAD), блок MMU генерит аппаратное исключение #PF, и диспетчер подкачивает отсутствующую страницу с диска. Если-же процесс освобождает пейджи при помощи VirtualFree(), менеджер убирает их из списка WorkingSet, а если страница была изменена посредством записи, помещает её в "ModifiedPageList", и далее в отстойник "Standby". Страницы ExecuteRead, как правило, относятся к классу немодифицируемых, так-что после освобождения, они из набора прямиком отправляются в отстойник. Менеджер ведёт ещё 2 списка: свободных страниц "FreePage", и пустых "ZeroPage". В список свободных помещаются пейджи, которые освободились после окончания процесса, а в лист пустых сбрасываются страницы, которые забил нулями специальный поток менеджера "ZeroPageThread" при помощи функции RtlZeroMemory(). В системном диспетчере-задач TaskMan (Ctrl+Alt+Del), на вкладке "Быстродействие" есть информация о рабочем наборе ОС: • Доступно – это сумма объёмов: отстойника + пустых + свободных страниц (вход в набор, см.рис.выше); • Кэшированно – сумма: отстойника + рабочих страниц. Отладчик WinDbg имеет расширение Код:
!memusageКод: Код:
lkd> !memusageКак видим, MMPTE является контейнером для 8-ми вложенных структур – для нас интересны только три из них: • Hard – описывает типичный фрейм в физ.памяти, • Proto – прототип PFN для расшаренных фреймов, • Soft – фрейм выгружен в файл-подкачки на диск. Чтобы определить тип записи PTE, диспетчер проверяет в каждой из этих структур бит в позиции нуль "Valid". Он может быть выставлен в единицу только в одной из структур, а в остальных будет сброшен – так диспетчер понимает, с фреймом какого типа имеет дело: Код: Код:
lkd> dt _MMPTE u.Hard.Обратите внимание на лог команды Код:
!ptehttps://forum.antichat.xyz/attachmen...cd2751e80a.png Здесь видно, что из-за ограниченного кол-ва бит, ни о каких 1GB-страницах не может быть и речи. Зато если расширить Offset до 22-бит, можно охватить 4МБ фрейм. При этом макс.адресом в системе будет по-прежнему 4GB. Но тогда как удаётся в режиме РАЕ адресовать память 2^36=64GB? Здесь инженеры нашли оригинальное решение – они просто расширили сами записи РТЕ в таблице PageTable до 64-бит, хотя в режиме без РАЕ эти записи имеют размер 32-бит. В арсенале WinDbg есть расширение Код:
!vtop1. Запрашиваем дамп активных процессов системы Код:
!process 0 0Код:
0x5f5af400Код:
0x5f5afd002. Теперь передаём команде Код:
!vtopКод:
0х00401100Значит система для тестов у меня Win7-x32 с включённым РАЕ, а потому в логе пестрят напоминания об этом. Модель транслятора 3-х уровневая. Записи Entry на всех уровнях имеют размер 64-бит, что позволяет адресовать в режиме РАЕ пространство свыше 4Gb (в режиме без РАЕ записи размером 4-байт). Вирт.адресу Код:
0х00401100Код:
0x3202e100Код:
0x39565100Код:
1000hОбратите внимание на значение РТЕ моего лога = Код:
0х800000003202e947Младшие 12-бит Код:
947hКод:
0x3202e100Расширение отладчика Код:
!dcКод:
0х00401100Код:
!vtopКод:
0х004000004.2. База данных PFN Теперь проведём инвентаризацию базы PFN. Мы не согрешим против истины заявив, что "база страничных фреймов" является ключевой фигурой во-всём механизме трансляции! Если подвести черту под вышесказанным, то процент участия MMU в этом деле стремится к нулю – аппаратный транслятор определяет лишь план действий диспетчеру вирт.памяти, который подстраиваясь под MMU должен создать соответствующее число каталогов и заполнить таблицу-трансляции, записями РТЕ. То-есть без привлечения средств диспетчера, транслятор в MMU ничего из себя не представляет. При включении машины, диспетчер запрашивает у BIOS объём реально установленной физ.памяти ОЗУ, и разделив это значение на 4096-байт, получает общее кол-во фреймов в системе. Теперь, для каждого из них диспетчер создаёт индивидуальную запись – в ядре она числится как структура Код:
_MMPFNКод:
?poiКод: Код:
lkd> ?poi nt!MmPfnDatabaseВ следующем логе MMPFN я убрал всё лишнее, и оставил только интересующие нас поля. Обратите внимание на расшифровку "CacheAttribute" и "PageLocation" – как видим они совпадают с выхлопом расширения Код:
!pfnРеальные эксперименты в отладчике позволяют толковать спецификацию с практической точки зрения, ведь только пощупав объект руками можно сделать о нём выводы. На данный момент мы знаем, что размер одной структуры MMPFN может быть равен 24, 28 или 48-байт. Кол-во структур напрямую зависит от размера установленной в системе физ.памяти ОЗУ. Выходит, что простой арифметикой можно вычислить размер всей базы PFN на текущей машине, что демонстрирует код ниже: C-подобный: Код:
format pe console5.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• METHOD_IN_DIRECT + OUT_DIRECT Здесь линк на буфер лежит так-же в Код:
Irp–>AssociatedIrp.SystemBufferКод:
Irp–>MdlAddress• METHOD_NEITHER Диспетчер ввода-вывода не предоставляет в ядре никаких системных буферов! В Код:
Irp–>UserBufferКод:
Parameters.DeviceIoControl.Type3InputBufferТеперь посмотрим на структуру MDL, она имеет размер 28-байт (на системах х32) и всего 8 элементов: Код: Код:
lkd> dt -v _MDL1. 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 всегда описывает только один буфер ввода-вывода. Вот как это выглядит графически: https://forum.antichat.xyz/attachmen...504869d0b6.png Здесь, в вирт.памяти представлено всего три страницы, а буфер 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В скрепке лежит инклуд Kernel32.inc с описанием всех используемых здесь структур. Как оказалось, в штатной поставке FASM'а имеется только оставшийся нам в наследство от Win-XP старый набор, поэтому я обновил его и советую заменить им устаревший инклуд по адресу: fasm\include\equates. 7. Постскриптум. Такой вот получился "бутафорский ман" с мозговым штурмом.. В статье планировал лишь коротко рассмотреть основные моменты, но в работе диспетчера-памяти всё переплетено в клубок так, что если потянешь за одну нить, то автоматом всплывает другая, без объяснения которой в первой теряется смысл. Здесь вспоминается критика к фильму "Выживщий" с Лео в главной роли: -"Выживщим является тот, кто досмотрел фильм до конца". В скрепку ложу два исполняемым файла для тестов, инклуд Kernel32.inc, а так-же лист из 1000+ поддерживаемых Win7 кодов IOCTL/FSCTL. Всем удачи, пока! |
Браво.
|
Замечательная статья! Браво!
Тимур, можно я твои статьи на WASM.IN перенесу? После технических неполадок на античат стало как-то боязно... |
Спасибо за статью!
|
Цитата:
|
Благодарю за просвящение. ты делаешь этот мир айти лучше
|
еще немного света упала на меня
|
| Время: 18:45 |