Физические адреса в win95(98)

Дата публикации 16 сен 2002

Физические адреса в 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):
  1.  
  2.          Смещение в команде                  ______________
  3.      [31..22] [21..12] [11..0] ------------>| Физ. адрес   |
  4.      |           |         ______________   |    Page      |
  5.      | x 4       |- x 4 ->| Начало Page  |->|______________|
  6.      |   ______________   |              |
  7.      |  |              |  |  Page Table  |
  8.      |->|Начало P.Table|->|______________|
  9.         |              |
  10.         |Page Directory|
  11.  CR3--->|______________|
  12.  

Управление страничной адресацией

  Теперь подробнее о том, как задается страничная адресация.

  За включение страничной адресации ответственнен бит 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 Мегабайта.

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

Код (Text):
  1.  
  2. .586p ; Pentium Processor
  3. PG equ 1 shl 31
  4. PAE equ 1 shl 5
  5. PSE equ 1 shl 4
  6. ; ...
  7.   mov  eax,CR0
  8.   test eax,PG
  9.   jz   @@NoPageRegim
  10.   mov  eax,CR4
  11.   test eax,PAE
  12.   jnz  @@PhysAddr_36bit
  13. ; ...
  14. @@PhysAddr_36bit:
  15. ; ...
  16. @@NoPageRegim:
  17.  
Итак, если бит PG=1, а биты PSE=PAE=0, то у нас "обычное" страничное преобразование с 4-х киобайтным размером страницы и 32-х битным адресом.

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

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

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

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

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

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

Код (Text):
  1.  
  2. ; Программа чтения физического адреса по линейному.
  3. ; Этот динамический VxD можно загружать через DeviceIOControl и получать
  4. ; по указателю физический адрес по линейному адресу.
  5. ; Coded by Chingachguk. 2002.
  6. ;
  7. .386p
  8. include vmm.inc
  9. include vwin32.inc
  10. DECLARE_VIRTUAL_DEVICE PHY,1,0, PHY_Control,\
  11.      UNDEFINED_DEVICE_ID, UNDEFINED_INIT_ORDER
  12. Begin_control_dispatch PHY
  13.     Control_Dispatch w32_DeviceIoControl, OnDeviceIoControl
  14. End_control_dispatch PHY
  15.  

  Определимся с передачей параметров и получением результатов нашего кода. Будем передавать VxD адрес следующей структуры:

Код (Text):
  1.  
  2. ; Структура параметров при вызове
  3. CallParams struc
  4. LinearAddr dd ? ; Линейный адрес
  5. PhysAddr   dd ? ; Сюда вернуть физический адрес
  6. CallParams ends
  7.  

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

Код (Text):
  1.  
  2. ; Сегмент данных нашего VxD
  3. VxD_PAGEABLE_DATA_SEG
  4.   Result dd ? ; Если будет ошибка, мы будем хранит тут 0FFFFFFFFh (-1).
  5. VxD_PAGEABLE_DATA_ENDS
  6.  

  Алгоритм вычисления физического адреса понятен. Остается решить один важный вопрос: как же обращаться к памяти, если у нас есть физический адрес, а мы находимся в режиме страничного преобразования ? Например, мы получили физический адрес элемента в 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):
  1.  
  2. VxD_PAGEABLE_CODE_SEG
  3. BeginProc OnDeviceIoControl
  4.   assume esi:ptr DIOCParams
  5. .if [esi].dwIoControlCode==DIOC_Open ; Контрольное сообщение ?!
  6.   xor  eax,eax ; Надо отвечать: eax=0.
  7. ; А вот это уже серьезно. Это вызов из вин-приложения с конкретным заданием.
  8. ; Что это за задание - знаем только мы и тот, кто нас вызвал.
  9. .elseif [esi].dwIoControlCode==1
  10.   mov dword ptr Result,0FFFFFFFFh ; Установим флаг ошибки
  11.   pushad ; На всякий случай сохраним все регистры, кроме сегментных - pushad
  12.   pushfd ; Сохраним флаг направления. Видимо, это перестраховка.
  13.   mov  edi,[esi].lpvInBuffer
  14.   mov  edi,[edi] ; указатель на буфер, который нам передал win32-код
  15. ; Получить начальный физический адрес Page Directory
  16.   mov  eax,CR3
  17.   and  eax,1111111111111111111100000000000b ; Выделить биты 31..12
  18. ; Получит индекс в Page Directory (биты 22..31)
  19.   mov  ecx,[edi].LinearAddr
  20.   shr  ecx,22    
  21.   shl  ecx,2
  22. ; Получить физический адрес элемента в Page Directory
  23.   add  eax,ecx
  24. ; Получить линейный адрес по физическому адресу от VMM.vxd
  25.   call GetLinearAddr_Memory
  26.   jz   @@PageDirectoryErr
  27.   mov  eax,[eax]
  28.   and  eax,1111111111111111111100000000000b ; eax=физический адрес Page Table
  29. ; Получит индекс в Page Table (биты 21..12)
  30.   mov  ecx,[edi].LinearAddr
  31.   shr  ecx,12    
  32.   and  ecx,1111111111b
  33.   shl  ecx,2
  34. ; Получить физический адрес элемента в Page Table
  35.   add  eax,ecx
  36. ; Получить линейный адрес по физическому адресу от VMM.vxd
  37.   call GetLinearAddr_Memory
  38.   jz   @@PageDirectoryErr
  39.   mov  eax,[eax]
  40.   and  eax,1111111111111111111100000000000b ; eax=физический адрес Page
  41. ; Получит индекс в Page (биты 11..0)
  42.   mov  ecx,[edi].LinearAddr
  43.   and  ecx,111111111111b
  44. ; Получить физический адрес !
  45.   add  eax,ecx
  46. ; Вернуть его вызвавшей программе
  47.   mov  [edi].PhysAddr,eax
  48.   mov  dword ptr Result,0h ; Сбросим флаг ошибки - все прошло нормально.
  49. @@PageDirectoryErr:
  50.   popfd ; Восстановим флаги направления и т.д. - перестраховка ?!
  51.   popad
  52.   mov  eax,dword ptr Result ; Вернем в eax флаг ошибки
  53. .endif
  54.   ret
  55. EndProc OnDeviceIoControl
  56. GetLinearAddr_Memory proc
  57. ; Input: eax=phys addr
  58. ; Result: eax=linear addr or ZF is set
  59. ; Get linear address by physical address(DDK)
  60. ; VMMCall _MapPhysToLinear, <PhysAddr, nBytes, flags>
  61.   push 0h ; flags
  62.   push 4h ; 4 bytes
  63.   push eax ; PhysAddr
  64.   int  20h   ; Call VxD
  65.   dw   006Ch     ; 006Ch map physical address to linear address
  66.   dw   0001h     ; ID VMM
  67.   add  esp,3*4   ; C-call function
  68.   cmp  eax,0FFFFFFFFh ; 0FFFFFFFFh if not addressable
  69. ; eax = address of first byte
  70. ; Returns the linear address of the first byte in the specified range of
  71. ; physical addresses. Uses EAX, ECX, EDX and Flags.
  72.   ret
  73. GetLinearAddr_Memory endp
  74. VxD_PAGEABLE_CODE_ENDS
  75. end
  76.  

  Для примера приведен фрагмент вызова такого VxD из win32-кода:

Код (Text):
  1.  
  2. .data
  3. CallParams struc
  4. LinearAddr dd ?
  5. PhysAddr dd ?
  6. CallParams ends
  7. ; Имя загружаемого VxD, которое передается CreateFile-у
  8. VxDName     db "\\.\PHY.VXD",0
  9. ; Структура, которой передаются параметры коду VxD
  10. InBuffer    dd offset MyMem ; Указатель на буфер для чтения параметров
  11. .data?
  12. hVxD        dd ? ; Тут будет храниться хэндл открытого VxD
  13. MyMem CallParams
  14. ; Начало выполнимого кода
  15. .code
  16. start:
  17. ; Загрузим динамический VxD через CreateFile
  18.   invoke CreateFile,addr VxDName,0,0,0,0,FILE_FLAG_DELETE_ON_CLOSE,0
  19.   .if eax!=INVALID_HANDLE_VALUE ; VxD Успешно загружена ?
  20.     mov hVxD,eax
  21. ; Получить физический адрес какого-нибудь линейного, например,
  22. ; текущего указателя EIP:
  23.     call @@GetOfs
  24. @@GetOfs:
  25.     pop eax                
  26.     mov dword ptr MyMem.LinearAddr,eax
  27.     invoke DeviceIoControl,hVxD,1,addr InBuffer,sizeof InBuffer,NULL,NULL,NULL,NULL
  28. ; Проверка на ошибку. Если ошибка, то eax = 0
  29.     test eax,eax        
  30.     jz @@ErrorReadPhys
  31. ; Ошибки нет. Покажем физический адрес
  32.     call Print_PhysAddr
  33. ; ...
  34. @@ErrorReadPhys:
  35.  

Пример трансляции линейных адресов

  Для примера привожу трансляцию нескольких характерных линейных адресов в физические под windows98, компьютер с 16 Мегабайт памяти:

Код (Text):
  1.  
  2.                       Линейный адрес  Физический адрес
  3.  Сегмент кода
  4. win32-приложения(EIP)  0040103Bh       0072103Bh  (~7 Мегабайт)
  5.  Kernel32.dll, ф-ция
  6. CreateFile             BFF77ADFh       00345ADFh  (~3 Мегабайт)
  7.  Стек win32-
  8. -приложения(ESP)       0063FE3Ch       00635E3Ch  (~6 Мегабайт)
  9.  Сегмент кода
  10. загруженного VxD       C188A4E6h       00E3C4E6h  (~14 Мегабайт)
  11.  

Итого

  Таким образом, единственный необходимый сервис ОС для получения физического адреса по линейному - это сервис "Получить линейный адрес по физическому". Очевидно, аналогичные сервисы существуют не только windows98(95), а и windows NT и ее наследниках - windows2000 и т.д, что позволит переносить приведенный выше код без особых изменений на эти платформы, используя соответствующие модели драйверов(*.wdm).

  Знание настоящего положения программ в памяти, на мой взгляд, не только любопытно, но и позволяет глубже понять стратегию размещения программ ОС и делать грубые оценки ее работы и эффективности. Например, оказывается что windows98 использует наиболее простой тип страничного преобразования в случае размера памяти компьютера 16 МБайт, в то время как технологии позволяют использовать еще несколько режимов с большими размерами страниц или же смешанным размером страниц. Было бы интересно оценить поведение этой ОС в случае существенного увеличения размера памяти, ведь в наше время не такая уж экзотика компьютер с 128 МБайт памяти и более, а также используемые типы страничного преобразования в новых версиях windows.

  Исходники:

  phy.asm, readphys.asm - код vxd и win32-приложения, его вызывающего.

  (C) Chingachguk /HI-TECH

0 929
archive

archive
New Member

Регистрация:
27 фев 2017
Публикаций:
532