Физические адреса в win95(98) — Архив WASM.RU
О чем пойдет речь
  Вы никогда не задумывались над тем, в каком именно мегабайте вашего компа выполняется ваша программа ? А в каком уютно разместился кернел ? Нет ? А мне вот стало интересно, и я решил узнать...
Страничная адресация
  Для начала немного теории.
  Несколько слов о том, как процессоры 386+ осуществляют страничную адресацию. Информация взята из книги Е.Бердышева "Технология MMX. Возможности процессоров P5 и P6".
  Пара определений. Физическим адресом назовем реальный номер байта в памяти. Линейным адресом назовем адреса, которые используют выполняющиеся программы.
  Допустим, для доступа к сегменту данных в win32 приложении может встретиться такая инструкция:
  mov byte ptr ds:[eax],2h
  Как же процессор может вычислять физический адрес требуемой ячейки памяти ?
  Если не используется страничная адресация, то процессор обратится к дескриптору памяти, задаваемому значением селектора в регистре ds, возьмет из него базовый адрес сегмента памяти, являющемся именно физическим адресом, прибавит к нему смещение, задаваемое регистром eax:
  Физический адрес = база из дескриптора + eax.
  Если включено страничное преобразование, то все происходит иначе. Рассмотрим случай размера страницы в 4 килобайта. Процессор выделяет из значения смещения в eax три части:
  Номера битов смещения/Предназначение
  22..31 Index in Page Directory
  12..21 Index in Page Table
  0..11 Index in Page
  Взяв "Index in Page Directory", процессор обращается к так называемой "Page Directory" - каталогу страниц. Это область памяти с физическими адресами таблиц страниц. Физический адрес этого каталога находится в регистре CR3(только старшие 20 бит CR3 !), а число элементов нетрудно получить из количества бит, отводимых под индекс в каталоге - 10 бит дают 1024 элемента. Таким образом, сначала процессор извлекает элемент из каталога страниц:
  Элемент каталога = [ CR3 + Index in Page Directory * 4 ],
  который является(не совсем весь, только старшие 20 бит) физическим адресом начала одной из таблиц страниц. Таблицы страниц (Page Table) являются, в свою очередь, набором физических адресов(опять только старшие 20 бит) начала самих страниц в памяти. Для выборки конкретного элемента из Page Table процессор использует адрес ее начала (Элемент каталога) и "Index in Page Table" из смещения команды:
  Адрес начала страницы = [ Элемент каталога + Index in Page Table * 4]
  Окончательный физический адрес элемента памяти вычисляется по адресу начала страницы и индексу элемента страницы из смещения в команде:
  Физический адрес = Адрес начала страницы + Index in Page. Схематично это можно представить таким образом:
Код (Text):
Смещение в команде ______________ [31..22] [21..12] [11..0] ------------>| Физ. адрес | | | ______________ | Page | | x 4 |- x 4 ->| Начало Page |->|______________| | ______________ | | | | | | Page Table | |->|Начало P.Table|->|______________| | | |Page Directory| CR3--->|______________|Управление страничной адресацией
  Теперь подробнее о том, как задается страничная адресация.
  За включение страничной адресации ответственнен бит PG(Paging Flag) (31 бит) регистра CR0 процессора. Если он 1, то страничное преобразование разрешено.
  Тип страничной адресации задается битами PAE(Physical Address Extention) (5 бит), PSE(Page Size Extention) (4 бит) регистра CR4 процессора, а также битом PS(Page Size) (7 бит) в выбранном элементе Page Directory.
  Следует отметить, что регистр CR4 доступен только в процессорах Pentium, и в общем случае необходимо проверять тип процессора командой CPUID и только затем - биты в регистре CR4.
  Если бит PAE=1, то разрешен 36-ти разрядный физический адрес, иначе - "обычный" 32-х разрядный. Если бит PSE=0, то размер страницы 4 Килобайта, иначе - может быть 2 или 4 Мегабайта.
  Экспериментально тип страничного преобразования можно проверить, например, так:
Итак, если бит PG=1, а биты PSE=PAE=0, то у нас "обычное" страничное преобразование с 4-х киобайтным размером страницы и 32-х битным адресом.Код (Text):
.586p ; Pentium Processor PG equ 1 shl 31 PAE equ 1 shl 5 PSE equ 1 shl 4 ; ... mov eax,CR0 test eax,PG jz @@NoPageRegim mov eax,CR4 test eax,PAE jnz @@PhysAddr_36bit ; ... @@PhysAddr_36bit: ; ... @@NoPageRegim:  Значения этих битов в windows98 показывают, что эта ОС использует как раз именно такой тип страничного преобразования.
Как получить физический адрес по линейному адресу
  Решим следующую небольшую задачку: напишем процедуру, которая будет возвращать win32-приложению по заданному линейному адресу физический адрес.
  Код процедруры разместим в динамическом VxD, поскольку для его реализации необходимо использование привелигерованных инструкций типа "mov eax,CR0", недопустимых в win32-коде. Подробнее о написании динамических VxD можно прочесть, например, в "tutorials by iczelion", размещенных на сайте HI-TECH.
  Условимся также пропустить проверки на тип страничного преобразования, считая, что windows98 использует 4-х килобайтные страницы и 32-х разрядный физический адрес.
  Итак, начнем. Сначала - стандартное начало динамического VxD:
Код (Text):
; Программа чтения физического адреса по линейному. ; Этот динамический VxD можно загружать через DeviceIOControl и получать ; по указателю физический адрес по линейному адресу. ; Coded by Chingachguk. 2002. ; .386p include vmm.inc include vwin32.inc DECLARE_VIRTUAL_DEVICE PHY,1,0, PHY_Control,\ UNDEFINED_DEVICE_ID, UNDEFINED_INIT_ORDER Begin_control_dispatch PHY Control_Dispatch w32_DeviceIoControl, OnDeviceIoControl End_control_dispatch PHY  Определимся с передачей параметров и получением результатов нашего кода. Будем передавать VxD адрес следующей структуры:
Код (Text):
; Структура параметров при вызове CallParams struc LinearAddr dd ? ; Линейный адрес PhysAddr dd ? ; Сюда вернуть физический адрес CallParams ends  Определим в сегменте данных одну переменную - флаг ошибки:
Код (Text):
; Сегмент данных нашего VxD VxD_PAGEABLE_DATA_SEG Result dd ? ; Если будет ошибка, мы будем хранит тут 0FFFFFFFFh (-1). VxD_PAGEABLE_DATA_ENDS  Алгоритм вычисления физического адреса понятен. Остается решить один важный вопрос: как же обращаться к памяти, если у нас есть физический адрес, а мы находимся в режиме страничного преобразования ? Например, мы получили физический адрес элемента в Page Directory(например, сейчас он в eax), но команда вида:
  mov eax,[eax]
  совсем не приведет к чтению элемента каталога страниц, поскольку процессор будет трактовать значение в eax согласно страничному преобразованию !
  Разумным решением было бы вызвать сервис другого VxD, например VMM, с целью получить физический адрес по линейному (в этом случае нам вообще делать будет нечего, даже не надо читать никаких Page Directory, Page Table...). Однако такого сервиса не существует(DDK) ! С другой стороны, существует сервис получения линейного адреса по физическому у VMM. Необходимость его существования следует из необходимости некоторым драйверам адресоваться к конкретной физической памяти, например при работе с BIOS и т.д.:
  Get linear address by physical address(DDK)
  VMMCall _MapPhysToLinear,
  Таким образом, нам придется вызывать данный сервис всякий раз, когда потребуется по физическому адресу извлечь из памяти значение.
  А вот наш сегмент кода:
Код (Text):
VxD_PAGEABLE_CODE_SEG BeginProc OnDeviceIoControl assume esi:ptr DIOCParams .if [esi].dwIoControlCode==DIOC_Open ; Контрольное сообщение ?! xor eax,eax ; Надо отвечать: eax=0. ; А вот это уже серьезно. Это вызов из вин-приложения с конкретным заданием. ; Что это за задание - знаем только мы и тот, кто нас вызвал. .elseif [esi].dwIoControlCode==1 mov dword ptr Result,0FFFFFFFFh ; Установим флаг ошибки pushad ; На всякий случай сохраним все регистры, кроме сегментных - pushad pushfd ; Сохраним флаг направления. Видимо, это перестраховка. mov edi,[esi].lpvInBuffer mov edi,[edi] ; указатель на буфер, который нам передал win32-код ; Получить начальный физический адрес Page Directory mov eax,CR3 and eax,1111111111111111111100000000000b ; Выделить биты 31..12 ; Получит индекс в Page Directory (биты 22..31) mov ecx,[edi].LinearAddr shr ecx,22 shl ecx,2 ; Получить физический адрес элемента в Page Directory add eax,ecx ; Получить линейный адрес по физическому адресу от VMM.vxd call GetLinearAddr_Memory jz @@PageDirectoryErr mov eax,[eax] and eax,1111111111111111111100000000000b ; eax=физический адрес Page Table ; Получит индекс в Page Table (биты 21..12) mov ecx,[edi].LinearAddr shr ecx,12 and ecx,1111111111b shl ecx,2 ; Получить физический адрес элемента в Page Table add eax,ecx ; Получить линейный адрес по физическому адресу от VMM.vxd call GetLinearAddr_Memory jz @@PageDirectoryErr mov eax,[eax] and eax,1111111111111111111100000000000b ; eax=физический адрес Page ; Получит индекс в Page (биты 11..0) mov ecx,[edi].LinearAddr and ecx,111111111111b ; Получить физический адрес ! add eax,ecx ; Вернуть его вызвавшей программе mov [edi].PhysAddr,eax mov dword ptr Result,0h ; Сбросим флаг ошибки - все прошло нормально. @@PageDirectoryErr: popfd ; Восстановим флаги направления и т.д. - перестраховка ?! popad mov eax,dword ptr Result ; Вернем в eax флаг ошибки .endif ret EndProc OnDeviceIoControl GetLinearAddr_Memory proc ; Input: eax=phys addr ; Result: eax=linear addr or ZF is set ; Get linear address by physical address(DDK) ; VMMCall _MapPhysToLinear, <PhysAddr, nBytes, flags> push 0h ; flags push 4h ; 4 bytes push eax ; PhysAddr int 20h ; Call VxD dw 006Ch ; 006Ch map physical address to linear address dw 0001h ; ID VMM add esp,3*4 ; C-call function cmp eax,0FFFFFFFFh ; 0FFFFFFFFh if not addressable ; eax = address of first byte ; Returns the linear address of the first byte in the specified range of ; physical addresses. Uses EAX, ECX, EDX and Flags. ret GetLinearAddr_Memory endp VxD_PAGEABLE_CODE_ENDS end  Для примера приведен фрагмент вызова такого VxD из win32-кода:
Код (Text):
.data CallParams struc LinearAddr dd ? PhysAddr dd ? CallParams ends ; Имя загружаемого VxD, которое передается CreateFile-у VxDName db "\\.\PHY.VXD",0 ; Структура, которой передаются параметры коду VxD InBuffer dd offset MyMem ; Указатель на буфер для чтения параметров .data? hVxD dd ? ; Тут будет храниться хэндл открытого VxD MyMem CallParams ; Начало выполнимого кода .code start: ; Загрузим динамический VxD через CreateFile invoke CreateFile,addr VxDName,0,0,0,0,FILE_FLAG_DELETE_ON_CLOSE,0 .if eax!=INVALID_HANDLE_VALUE ; VxD Успешно загружена ? mov hVxD,eax ; Получить физический адрес какого-нибудь линейного, например, ; текущего указателя EIP: call @@GetOfs @@GetOfs: pop eax mov dword ptr MyMem.LinearAddr,eax invoke DeviceIoControl,hVxD,1,addr InBuffer,sizeof InBuffer,NULL,NULL,NULL,NULL ; Проверка на ошибку. Если ошибка, то eax = 0 test eax,eax jz @@ErrorReadPhys ; Ошибки нет. Покажем физический адрес call Print_PhysAddr ; ... @@ErrorReadPhys:Пример трансляции линейных адресов
  Для примера привожу трансляцию нескольких характерных линейных адресов в физические под windows98, компьютер с 16 Мегабайт памяти:
Код (Text):
Линейный адрес Физический адрес Сегмент кода win32-приложения(EIP) 0040103Bh 0072103Bh (~7 Мегабайт) Kernel32.dll, ф-ция CreateFile BFF77ADFh 00345ADFh (~3 Мегабайт) Стек win32- -приложения(ESP) 0063FE3Ch 00635E3Ch (~6 Мегабайт) Сегмент кода загруженного VxD C188A4E6h 00E3C4E6h (~14 Мегабайт)Итого
  Таким образом, единственный необходимый сервис ОС для получения физического адреса по линейному - это сервис "Получить линейный адрес по физическому". Очевидно, аналогичные сервисы существуют не только windows98(95), а и windows NT и ее наследниках - windows2000 и т.д, что позволит переносить приведенный выше код без особых изменений на эти платформы, используя соответствующие модели драйверов(*.wdm).
  Знание настоящего положения программ в памяти, на мой взгляд, не только любопытно, но и позволяет глубже понять стратегию размещения программ ОС и делать грубые оценки ее работы и эффективности. Например, оказывается что windows98 использует наиболее простой тип страничного преобразования в случае размера памяти компьютера 16 МБайт, в то время как технологии позволяют использовать еще несколько режимов с большими размерами страниц или же смешанным размером страниц. Было бы интересно оценить поведение этой ОС в случае существенного увеличения размера памяти, ведь в наше время не такая уж экзотика компьютер с 128 МБайт памяти и более, а также используемые типы страничного преобразования в новых версиях windows.
  Исходники:
  phy.asm, readphys.asm - код vxd и win32-приложения, его вызывающего.
  (C) Chingachguk /HI-TECH
Физические адреса в win95(98)
Дата публикации 16 сен 2002