• XSS.stack #1 – первый литературный журнал от юзеров форума

Пейджинг в режиме х64

Roller

RAID-массив
Пользователь
Регистрация
26.05.2020
Сообщения
71
Решения
1
Реакции
54
Всем привет!
В своей ОС пытаюсь организовать страничную память в защищённом режиме х64, и застрял на структуре каталога страниц PML4E.
На данный момент вырисовывается такая картина, в которой я сомневаюсь.

1. Имеем один каталог PML4 на который указывает CR3. В нём 512 записей/указателей, т.к. в лин.адресе выделяется 9 ст.бит.
2. Если каждая запись PML4E указывает на сл.каталог PDPT, значит всего имеем 512 каталогов PDPT, и в каждом из них тоже по 512 записей. Итого 512*512=256К записей.
3. На сл.уровне PDT аналогично, только получается, что общее число каталогов PDT уже 256К, и в каждом по 512 записей чтоли? Итого 256К*512=128М.
4. Наконец 128М записей в PDT указывают на один из 128М каталогов PT, и в каждом по 512 указателей на физ.фреймы памяти ОЗУ.
5. Итого 128М*512=64.000 М записей PTE, которые адресуют 4-Кбайтные фреймы: итого 64000М*4096=256 ТБ доступной памяти в режиме х64.
6. Если размер одной записи в таблицах =8 байт, тогда выходит, что весь каталог PML4 займёт 64000М*8=512 Гб в памяти.

nSmBuR9P.png


Ясно, что это связь каталогов при макс.объёме физ.памяти, а на практике нужно создавать "PageTable" при каждой загрузке ОС динамически, по размеру ОЗУ на текущий момент. Вот здесь и непонятно, какое кол-во каталогов на каждом уровне мне нужно выделить? Читал "Intel SDM" и несколько страниц в инете, но вопросы всё-же остались. Сейчас провожу тесты своего загрузчика в QEMU с 1024 Мб памяти так:

1. Запросить полный объём ОЗУ в байтах (можно взять из таблицы smbios).
2. ОЗУ /4096 байт, чтобы получить общее число физ.фреймов PFN.
3. PFN = всего записей в последней таблице PTE.
4. PFN /512 = записей в предпоследней таблице PDT.
5. Если имеется остаток, то отправить его в таблицу предыдущего уровня PDPT.
6. Повторить пункт 5, и остаток отправить в PML4.

Как результат, для 1 ГБ получаю следующую схему.. На правильном я пути или нет?

pml4.png
 
That insane calculated size is exactly why we don't do it that way. The tables traversal is top down. Instead of "how many total entries", think of it like: "how many full tbls do I need at each level". A single required entry (eg 1 PDPTE) still requies an entire parent tbl (1 PDPT): hence a top down continuous chain.
 
Instead of "how many total entries", think of it like: "how many full tbls do I need at each level".
Как я понимаю, чтобы определить кол-во таблиц, нужно знать, сколько записей в каждой из них.
У меня сейчас в логе указано число именно записей "PageTable Entries", и если теперь перевести их в таблицы, то получится:

PML4=0, PDPT=1, PDT=1, PT=512

В каждой таблице по 512 указателей, и она должна быть выровнена на границу 4КБ (12 мл.бит в указателях = атрибуты).
При этом в данном случае, в PDPT/PDT по 511 записей остаются не заполненными, т.е. действительна только первая.

А так мои рассчёты вроде правильные.
Например расширение !cmkd для WinDBG тоже говорит, что под таблицу страниц резервируется 512GB вирт.памяти:

Код:
0: kd> !cmkd.kvas     ;<------- Kernel Virtual Address Space

### Start             End                                  Length  Type
000 ffff080000000000  fffff67fffffffff    ee8000000000  ( 238 TB)  SystemSpace
001 fffff68000000000  fffff6ffffffffff      8000000000  ( 512 GB)  PageTables    <---------------
002 fffff70000000000  fffff77fffffffff      8000000000  ( 512 GB)  HyperSpace
003 fffff78000000000  fffff78000000fff            1000  (   4 KB)  SharedSystemPage
004 fffff78000001000  fffff7ffffffffff      7ffffff000  ( 511 GB)  CacheWorkingSet
005 fffff80000000000  fffff87fffffffff      8000000000  ( 512 GB)  LoaderMappings
006 fffff88000000000  fffff89fffffffff      2000000000  ( 128 GB)  SystemPTEs
007 fffff8a000000000  fffff8bfffffffff      2000000000  ( 128 GB)  PagedPool
008 fffff90000000000  fffff97fffffffff      8000000000  ( 512 GB)  SessionSpace
009 fffff98000000000  fffffa7fffffffff     10000000000  (   1 TB)  DynamicKernelVa
010 fffffa8000000000  fffffa80038fffff         3900000  (  57 MB)  PfnDatabase
011 fffffa8003800000  fffffa80c01fffff        bca00000  (   2 GB)  NonPagedPool
012 ffffffffffc00000  ffffffffffffffff          400000  (   4 MB)  HalReserved

0: kd>

Но здесь непонятно другое..
Видимо потому, что адрес в ядре "Каннонический" (старшие биты равны 1, а не 0),
записи в PML4 должны следовать не 0..511, а наоборот 511..0. Вот, например, как отображает схему каталогов всё тот-же !cmkd:

Код:
0: kd> lm m di*
start              end                 module name
fffff880`01433000  fffff880`01448000   disk       <----- Пусть адресом в ядре будет база "disk.sys"
fffff880`03ff0000  fffff880`03fff000   discache

0: kd> !cmkd.ptelist -v fffff880`01433000
VA=FFFFF88001433000
  PXE Idx=1F1  Va=FFFFF6FB7DBEDF88  Contents=0000000127A44863  Hard Pfn=00127A44  Attr=---DA--KWEV
  PPE Idx=000  Va=FFFFF6FB7DBF1000  Contents=0000000127A43863  Hard Pfn=00127A43  Attr=---DA--KWEV
  PDE Idx=00A  Va=FFFFF6FB7E200050  Contents=00000000034DA863  Hard Pfn=000034DA  Attr=---DA--KWEV
  PTE Idx=033  Va=FFFFF6FC4000A198  Contents=8000000003FA8963  Hard Pfn=00003FA8  Attr=-G-DA--KW-V

0: kd>

То есть при макс.значении 200h (512), индекс PXE = 1F1. Это тоже нужно учитывать чтоли?
Блин сколько не находил примеров в сети, все они только для первого мегабайта памяти, т.е. три старших каталога вообще не используются.
 
PML4=0,
You mean PML4=1?))

в PDPT/PDT по 511 записей остаются не заполненными
Not for the PDT though: 1GB requires 1024MB/2MB per entry = 512.

001 fffff68000000000 fffff6ffffffffff 8000000000 ( 512 GB) PageTables <---------------
self-ref

То есть при макс.значении 200h (512), индекс PXE = 1F1. Это тоже нужно учитывать чтоли?
Yes, it's a direct consequence. This rule requires bit 47=1 placing the 9bit PML4 idx (47-39) into the [256, 511].
 

shrekushka,​

по всей вероятности вы разбираетесь в теме - ссылка 👍.
Подскажите ещё.. Нужно-ли мне создавать полное число каталогов от PML4 до PT, или только с учётом кол-ва фреймов PFN физ.памяти на текущей машине? В первом случае это израсходует всю доступную память (т.к. размер получится 512ГБ), а если выбрать вариант(2), тогда как мне вести список вирт.адресов, которые CPU уже запрашивал? Например если в системе 8 ГБ ОЗУ (и я прописал всю её в записях своих каталогов), а процессор обращается по вирт.адресу 16 ГБ, где я должен буду хранить этот вирт.адрес, чтобы ему соответствовал выделенный мною PFN из пула свободных? Этот адрес 16 ГБ разве не должен указывать на запись в каталоге, которой в варианте(2) у меня нет? Или как вообще работает эта схема?

Знаю, что помимо каталогов страниц "PageTable", в Win/Linux имеется ещё и глобальная "Pfn-Database" - это массив структур _MMPFN. Каждая структура размером 48 байт (30h), и описывает характеристики одного физ.фрейма PFN. Таким образом, на каждый 4К фрейм приходится 48 байт служебной инфы, а размер всей базы напрямую зависит от размера ОЗУ.

Так вот сначала я думал, что связь вирт.адресов с физическими указывается именно в структурах _MMPFN (тогда не нужно создавать полное число каталогов), но в этих структурах нет поля с вирт.адресом, а только номера PFN. Вот лог отладчика:

Код:
0: kd> ?? sizeof(nt!_MmPfn)
          unsigned int64 0x30         ;<---- Размер одной структуры

0: kd> x nt!MmPfnDataBase             ;<---- Переменная с адресом базы
fffff800`02d05280  nt!MmPfnDatabase

0: kd> dt _MmPfn fffff800`02d05280 -r1    ;<---- Первая структура в базе
nt!_MMPFN
   +0x000 u1               : <unnamed-tag>
      +0x000 Flink            : 0xfffffa80`00000000
      +0x000 WsIndex          : 0
      +0x000 Event            : 0xfffffa80`00000000 _KEVENT
      +0x000 Next             : 0xfffffa80`00000000 Void
      +0x000 VolatileNext     : 0xfffffa80`00000000 Void
      +0x000 KernelStackOwner : 0xfffffa80`00000000 _KTHREAD
      +0x000 NextStackPfn     : _SINGLE_LIST_ENTRY

   +0x008 u2               : <unnamed-tag>
      +0x000 Blink            : 0xa7c4
      +0x000 ImageProtoPte    : 0x00000000`0000a7c4 _MMPTE
      +0x000 ShareCount       : 0xa7c4

   +0x010 PteAddress       : 0xfffffa80`03901c50 _MMPTE
      +0x000 u                : <unnamed-tag>

   +0x010 VolatilePteAddr  : 0xfffffa80`03901c50 Void
   +0x010 Lock             : 0n59776080
   +0x010 PteLong          : 0xfffffa80`03901c50

   +0x018 u3               : <unnamed-tag>
      +0x000 ReferenceCount   : 0x10
      +0x002 e1               : _MMPFNENTRY
      +0x000 e2               : <unnamed-tag>

   +0x01c UsedPageTableEntries : 0x40
   +0x01e VaType           : 0 ''
   +0x01f ViewCount        : 0 ''

   +0x020 OriginalPte      : _MMPTE
      +0x000 u                : <unnamed-tag>
   +0x020 AweReferenceCount : 0n500

   +0x028 u4               : <unnamed-tag>
      +0x000 PteFrame         : 0x2e00000000
      +0x000 Unused           : 0y000
      +0x000 PfnImageVerified : 0y0
      +0x000 AweAllocation    : 0y0
      +0x000 PrototypePte     : 0y0
      +0x000 PageColor        : 0y000000 (0)
0: kd>
 

Нужно-ли мне создавать полное число каталогов от PML4 до PT, или только с учётом кол-ва фреймов PFN физ.памяти на текущей машине?
Yes, it's all built on a "fault + fix" model. Start with known -> work backwards.

2.png


describe_phys_addr():
3.png

тогда как мне вести список вирт.адресов, которые CPU уже запрашивал?
You don't maintain a list. You track what a proc is allowed to request using OS specific structs (like VADs / vm_area_struct).

Так вот сначала я думал, что связь вирт.адресов с физическими указывается именно в структурах _MMPFN (тогда не нужно создавать полное число каталогов), но в этих структурах нет поля с вирт.адресом, а только номера PFN. Вот лог отладчика:
Этот адрес 16 ГБ разве не должен указывать на запись в каталоге, которой в варианте(2) у меня нет?
This is by design: PFN database performs the reverse mapping.

4.png
 


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх