Воробьёвы

(-:

О чем пойдет речь

  Вы никогда не задумывались над тем, в каком именно мегабайте вашего компа выполняется ваша программа ? А в каком уютно разместился кернел ? Нет ? А мне вот стало интересно, и я решил узнать...

Страничная адресация

  Для начала немного теории.

  Несколько слов о том, как процессоры 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. Схематично это можно представить таким образом:

         Смещение в команде                  ______________
     [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 Мегабайта.

  Экспериментально тип страничного преобразования можно проверить, например, так:

.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:
Итак, если бит PG=1, а биты PSE=PAE=0, то у нас "обычное" страничное преобразование с 4-х киобайтным размером страницы и 32-х битным адресом.

  Значения этих битов в windows98 показывают, что эта ОС использует как раз именно такой тип страничного преобразования.

Как получить физический адрес по линейному адресу

  Решим следующую небольшую задачку: напишем процедуру, которая будет возвращать win32-приложению по заданному линейному адресу физический адрес.

  Код процедруры разместим в динамическом VxD, поскольку для его реализации необходимо использование привелигерованных инструкций типа "mov eax,CR0", недопустимых в win32-коде. Подробнее о написании динамических VxD можно прочесть, например, в "tutorials by iczelion", размещенных на сайте HI-TECH.

  Условимся также пропустить проверки на тип страничного преобразования, считая, что windows98 использует 4-х килобайтные страницы и 32-х разрядный физический адрес.

  Итак, начнем. Сначала - стандартное начало динамического VxD:

; Программа чтения физического адреса по линейному.
; Этот динамический 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 адрес следующей структуры:

; Структура параметров при вызове
CallParams struc
LinearAddr dd ? ; Линейный адрес 
PhysAddr   dd ? ; Сюда вернуть физический адрес
CallParams ends

  Определим в сегменте данных одну переменную - флаг ошибки:

; Сегмент данных нашего 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,

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

  А вот наш сегмент кода:

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, 
  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-кода:

.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 Мегабайт памяти:

                      Линейный адрес  Физический адрес
 Сегмент кода
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.

 (C) Chingachguk /HI-TECH