Лабораторная работа по Intel VMX

Современные процессоры Intel и AMD под­дер­жи­ва­ют тех­но­ло­гии вир­ту­а­ли­за­ции, по­зво­ля­ю­щие ис­поль­зо­вать од­ну плат­фор­му как не­сколь­ко вир­ту­аль­ных ма­шин, в каж­дой из ко­то­рых ра­бо­та­ет своя опе­ра­ци­он­ная сис­те­ма. Эта ста­тья рас­счи­та­на на под­го­тов­лен­но­го чи­та­те­ля, же­ла­ю­ще­го по­лу­чить прак­ти­че­ский опыт ра­бо­ты с дан­ны­ми тех­но­ло­ги­я­ми, на уров­не на­пи­са­ния соб­ст­вен­но­го мо­ни­то­ра вир­ту­аль­но­го ре­жи­ма. При­во­дит­ся опи­са­ние ис­сле­до­ва­тель­ской ра­бо­ты, вы­пол­нен­ной ав­то­ром и ис­ход­ные тек­с­ты про­г­рам­мы.

Основные определения Intel VMX

Технология Intel VMX оп­ре­де­ля­ет два спе­ци­аль­ных со­сто­я­ния про­цес­со­ра:

  • Состояние VMX Root Operation (режим гипервизора) используется для выполнения программы, со­зда­ю­щей вир­ту­аль­ные машины на базе одного компьютера и осу­щест­вля­ю­щей ко­ор­ди­на­цию их работы. Эта про­г­рам­ма на­зы­ва­ет­ся VMM или Virtual Machine Monitor. Она имеет не­о­гра­ни­чен­ный доступ к ресурсам про­цес­со­ра и плат­фор­мы.
  • Состояние VMX Non-Root Operation (режим «гостя») используется для выполнения программ или опе­ра­ци­он­ных систем пользователя, для каждой из которых создается своя виртуальная ма­ши­на. При этом про­г­рам­ма или ОС, выполняемая на виртуальной машине, предполагает, что вы­пол­ня­ет­ся на обычном ком­пью­те­ре и в иде­а­ле не дол­ж­на знать о существовании гипервизора и дру­г­их виртуальных машин. Оче­вид­но, что для это­го ги­пер­ви­зор дол­жен уметь пе­ре­хва­ты­вать об­ра­ще­ния гостей к аппаратным ресурсам процессора и платформы, разрешать конфликты, воз­ни­ка­ю­щие при одновременном доступе нескольких гостей к одному аппаратному ресурсу, а так­же не допускать ситуаций, при которых сбой в одной виртуальной машине может привести к сбою всей системы.

Для включения режима VMX с пол­но­мо­чи­я­ми гипервизора, программа дол­ж­на вы­пол­нить ин­ст­рук­цию VMXON. Затем, по­с­ле подготовки в оперативной памяти струк­тур, необходимых для запуска вир­ту­аль­ных машин, про­г­рам­ма может за­­пус­­кать гостей выполняя инструкцию VMLAUNCH. Эта опе­ра­ция, при ко­то­рой про­цес­сор пе­ре­клю­ча­ет­ся из ре­жи­ма гипер­ви­зо­ра в режим го­с­тя называется VM En­try, то есть запуск вир­ту­аль­ной машины.

Для включения режима VMX с полномочиями гипервизора, программа должна выполнить инструкцию VMXON, для выключения режима VMX используется инструкция VMXOFF

 

При выполнении гостей пе­ри­о­ди­че­ски воз­ни­ка­ют ситуации, тре­бу­ю­щие вме­ша­тель­ст­ва ги­пер­ви­зо­ра, на­при­мер по­пыт­ка об­ра­ще­ния к определенным сис­тем­ным ре­сур­сам, аппаратное прерывание или ис­те­че­ние ин­тер­ва­ла времени, от­ве­ден­но­го дан­ной вир­ту­аль­ной ма­ши­не. При этом уп­рав­ле­ние возвращается в гипер­ви­зор. Эта опе­ра­ция называется VM Exit. После об­ра­бот­ки ситуации, выполнение гостя можно во­зоб­но­вить ин­ст­рук­ци­ей VMRESUME. Для выключения режима VMX используется ин­ст­ру­к­ция VMXOFF. Подробности вы­пол­не­ния инструкций здесь.

Идентификация ресурсов Intel VMX

Перед включением режима VMX, про­г­рам­ма дол­ж­на проверить наличие его под­дер­ж­ки про­цес­со­ром и про­чи­тать ряд па­ра­мет­ров, ха­рак­те­ри­зу­ю­щих фун­к­ци­о­ни­ро­ва­ние виртуальной машины. Выполнив фун­к­цию 1 ин­ст­рук­ции CPUID, мы можем по­лу­чить в ре­гист­рах EDX и ECX битовую карту, в которой каждый бит ин­ди­ци­ру­ет под­дер­ж­ку про­цес­со­ром оп­ре­де­лен­ной тех­но­ло­гии: 0=не поддерживается, 1=под­дер­жи­ва­ет­ся (CPU Feature Flags). Тех­но­ло­гии VMX со­от­вет­ст­ву­ет бит 5 регистра ECX. Под­роб­но­с­ти в [6].

Если VMX поддерживается, можно пе­ре­хо­дить к чтению параметров вир­ту­аль­ной ма­ши­ны. Для этого су­щест­ву­ет груп­па Model-Specific регистров (MSR), называемая VMX Capability MSR. Эти регистры имеют ста­тус Read Only и до­ступ­ны по ад­ре­сам 480h-490h в пространстве MSR. На­пом­ним, что для чте­ния MSR ис­поль­зу­ет­ся инструкция RDMSR, которая при­ни­ма­ет адрес MSR в ре­гист­ре ECX и возвращает 64-битное зна­че­ние, про­чи­тан­ное из заданного MSR в ре­гист­рах EDX (старшие биты) и EAX (млад­шие биты).

Группа VMX Capability MSR содержит сле­ду­ю­щие регистры:

IA32_VMX_BASIC MSR (адрес 480h) — содержит номер ревизии VMX, значение размера регионов VMXON и VMCS, и другие параметры.

IA32_VMX_PINBASED_CTLS MSR (481h)
IA32_VMX_TRUE_PINBASED_CTLS MSR (адрес 48Dh) — содержат битовые карты, отражающие способность процессора прерывать выполнение гостевых программ и передавать управление программе-ги­пер­ви­зору при возникновении различных асинхронных событий (аппаратных прерываний).

IA32_VMX_PROCBASED_CTLS MSR (482h)
IA32_VMX_TRUE_PROCBASED_CTLS MSR (48Eh)
IA32_VMX_PROCBASED_CTLS2 MSR (48Bh) — содержат битовые карты, отражающие способность процессора прерывать выполнение гостевых программ и передавать управление программе-ги­пер­ви­зору при возникновении различных синхронных событий (выполнение заданных инструкций и обращение к заданным системным ресурсам со стороны гостевой программы).

IA32_VMX_EXIT_CTLS MSR (483h)
IA32_VMX_TRUE_EXIT_CTLS MSR (48Fh) — содержат битовые карты, отражающие поддержку процессором ряда опций, управляющих операцией VM Exit (завершение выполнения гостевой программы и возврат в гипервизор).

IA32_VMX_ENTRY_CTLS MSR (484h)
IA32_VMX_TRUE_ENTRY_CTLS MSR (490h) — содержат битовые карты, отражающие поддержку процессором ряда опций, управляющих операцией VM Entry (передача управления из ги­пер­ви­зора в гостевую программу).

IA32_VMX_MISC MSR (485h) — содержит параметры, отражающие поддержку процессором ряда функций, управляющих контекстом виртуальной машины.

IA32_VMX_CR0_FIXED0 MSR (486h)
IA32_VMX_CR0_FIXED1 MSR (487h) — эти регистры по­бит­но соответствуют системному регистру управления CR0 и отражают ограничения, накладываемые на его содержимое при переходе в режим VMX. Для каждого бита регистра CR0 они определяют допустимые значения этого бита. При этом может быть 3 варианта: бит должен быть в 0, бит должен быть в 1 или оба значения 0 и 1 допустимы.

IA32_VMX_CR4_FIXED0 MSR (488h)
IA32_VMX_CR4_FIXED1 MSR (489h) — эти регистры по­бит­но соответствуют системному регистру управления CR4 и отражают ограничения, накладываемые на его содержимое при переходе в режим VMX. См. комментарий в предыдущем пункте.

IA32_VMX_VMCS_ENUM MSR (48Ah) — содержит максимальное значение индекса, используемое при адресации полей блока VMCS.

IA32_VMX_EPT_VPID_CAP MSR (48Ch) — содержит параметры, отражающие поддержку процессором режимов EPT (Extended Page Tables) и VPID (Virtual Processor Identifier).

Для управления режимом VMX также ис­поль­зу­ют­ся IA32_FEATURE_CONTROL MSR (адрес 3Ah) и бит CR4.VMXE (бит 13).

Регионы VMXON и VMCS, используемые в Intel VMX

Технология Intel VMX определяет сле­ду­ю­щие две специальные области памяти.

VMXON Region — размер этого блока не пре­вы­шает 4KB. Точное значение размера зависит от модели про­цес­со­ра и мо­жет быть про­чи­та­но из битов 44-32 регистра IA32_VMX_BASIC MSR. Ба­зо­вый фи­зи­че­ский ад­рес этого блока на­хо­дит­ся в 64-битном регистре VMXON Pointer, который за­гру­жа­ет­ся при выполнении ин­ст­рук­ции VMXON, вклю­ча­ю­щей ре­жим VMX. Ба­зо­вый адрес должен быть выровнен на 4KB (биты адреса 0-11 должны быть нулевые) и быть в пре­де­лах мак­си­маль­но­го фи­зи­че­ско­го адреса, под­дер­жи­ва­е­мо­го про­цес­со­ром. Первое 32-битное слово этого блока дол­ж­но со­дер­жать 32-битный иден­ти­фи­ка­тор версии VMX (VMCS Revision Identifier). При вы­пол­не­нии ин­ст­рук­ции VMXON про­ве­ря­ет­ся со­вмес­ти­мость этого иден­ти­фи­ка­то­ра с мо­де­лью процессора. Для ус­пеш­но­го прохождения этой про­вер­ки, пе­ред вы­пол­не­ни­ем ин­ст­рук­ции VMXON про­г­рам­ма должна прочитать под­дер­жи­ва­е­мый номер версии VMX из битов 31-0 ре­гист­ра IA32_VMX_BASIC MSR и за­пи­сать его по сме­ще­нию 0 в рассматриваемом блоке. На­зна­че­ние ос­таль­ных полей блока зависит от модели процессора и не до­ку­мен­ти­ро­ва­но Intel. При работе в режиме VMX (в ин­тер­ва­ле времени от вы­пол­не­ния инструкции VMXON до вы­пол­не­ния ин­с­т­рук­ции VMXOFF) про­цес­сор ис­поль­зу­ет этот блок ап­па­рат­но и про­г­рам­ма не дол­ж­на вы­пол­нять его чтение и запись. Опи­сан­ная выше под­го­тов­ка 32-битного иден­ти­фи­ка­то­ра вер­сии пе­ред за­пус­ком ре­жи­ма VMX яв­ля­ет­ся един­ст­вен­ной за­бо­той про­г­рам­мы о данном бло­ке.

VMCS Region (Virtual Machine Control Struc­tu­re) — размер этого блока зависит от мо­де­ли про­цес­сора и всегда ра­вен размеру вы­ше опи­сан­ного блока VMXON Region. Базовый физический адрес блока на­хо­дит­ся в 64-бит­ном регистре VMCS Pointer, ко­то­рый загружается при вы­­пол­­не­­нии ин­ст­рук­ции VMPTRLD. Как и для блока VMXON, базовый адрес блока VMCS дол­жен быть выровнен на 4KB, и быть в пределах мак­си­маль­но­го фи­зи­че­ско­го адреса, под­дер­жи­ва­е­мо­го процессором. Первое 32-битное слово блока VMCS дол­ж­но со­дер­жать но­мер версии VMX. Его за­пи­сы­ва­ет про­г­рам­ма в со­от­вет­ст­вии с битами 31-0 регистра IA32_VMX_BASIC MSR. Это не­об­хо­ди­мо для ус­пеш­но­го вы­пол­не­ния проверки при выполнении инструкции VMPTRLD. Сле­ду­ю­щее 32-битное слово ис­поль­зу­ет­ся как VMX Abort Indicator, процессор за­пи­сы­ва­ет в эту ячейку код ошибки при ава­рий­ном за­вер­ше­нии гостевых программ. Фор­ма­ти­ро­ва­ние ос­таль­ной ча­с­ти блока не до­ку­мен­ти­ро­ва­но и может раз­ли­чать­ся у про­цес­со­ров раз­ных мо­де­лей, но при этом, спи­сок пе­ре­мен­ных, хра­ня­щих­ся в этом бло­ке, до­ку­мен­ти­ро­ван и фик­си­ро­ван. Отметим, что для этих пе­ре­мен­ных пред­ус­мот­ре­ны ин­ст­рук­ции VMREAD и VMWRITE, которые по­зво­ля­ют об­ра­тить­ся к за­дан­ной пе­ре­мен­ной не по адресу, а по ее иден­ти­фи­ка­то­ру. Бла­го­да­ря такому методу, про­г­рам­ма, ис­поль­зу­ю­щая эти ин­ст­рук­ции для ра­бо­ты с бло­ком VMCS, не требует адап­та­ции под кон­крет­ную мо­дель про­цес­со­ра. Пе­ре­мен­ные объединены в 6 ло­ги­че­ских групп:

  1. Guest state area. Область сохранения данных для гостевой задачи. Состояние процессора сохраняется в эту область при операциях VM Exit (переход от виртуальной машины к ги­пер­ви­зору) и загружается из этой области при операциях VM Entry (переход от ги­пер­ви­зора к виртуальной машине).
  2. Host state area. Область сохранения состояния для ги­пер­ви­зора.
  3. VM execution control fields. Эти поля управляют поведением процессора в режиме гостевой задачи (VMX non-root). Они определяют список возможных причин для выполнения операции VM exit, то есть, по каким причинам управление из виртуальной машины может возвращаться в ги­пер­ви­зор.
  4. VM exit control fields. Управляют выполнением операций VM exit (завершение работы виртуальной машины).
  5. VM entry control fields. Управляют выполнением операций VM entry (запуск виртуальной машины).
  6. VM exit information fields. Принимают информацию при VM exit. Описывают причину и тип операции VM exit. Эти поля доступны только для чтения (Read Only).

Совокупность групп 3, 4, 5 иногда называют VM Controls.

Инструкции Intel VMX

Технология Intel VMX определяет следующие процессорные инструкции.

VMXON m64 — включает режим VMX root operation. Программа приобретает полномочия ги­пер­ви­зо­ра вир­ту­аль­но­го режима. Операнд инструкции m64 – 64-битная ячейка памяти, из которой считывается ука­за­тель VMXON Pointer — фи­зи­че­ский адрес региона VMXON. Считывается поле VMCS Revision ID из ре­ги­о­на VMXON и его значение про­ве­ря­ет­ся на совместимость с моделью процессора. После перехода в ре­жим VMX запрещены следующие функции: механизм программного сброса процессора (сигнал INIT), мас­ки­ро­ва­ние адресной линии A20 (сигнал A20M), то есть линия A20 будет разрешена, независимо от со­сто­я­ния это­го сигнала. Инструкция может использоваться только в Protected Mode. Для успешного пе­ре­хо­да в ре­жим VMX, текущий режим работы процессора и состояние битов в регистрах управления CR0 и CR4 дол­ж­ны удовлетворять ряду условий, детально описанных в [3].

VMXOFF — выключает режим VMX root operation, отменяет действие инструкции VMXON. Процессор воз­вра­ща­ет­ся в обыч­ный режим.

VMPTRLD m64 — загружает значение указателя VMCS Pointer из 64-битной ячейки памяти, адрес которой задан операндом m64. Регион VMCS, адресуемый указателем, приобретает статус активного и текущего региона VMCS. Считывается поле VMCS Revision ID из региона VMCS и его значение проверяется на совместимость с моделью процессора. После загрузки указателя VMCS Pointer, программа может начинать монтировать поля управления виртуальной машиной, используя инструкции VMREAD, VMWRITE.

VMPTRST m64 — сохраняет значение указателя VMCS Pointer в 64-битную ячейку памяти, адрес которой задан операндом m64.

VMCLEAR m64 — очищает состояние блока VMCS, адрес которого находится в 64-битной ячейке памяти, адрес которой задан операндом m64. Если указан адрес текущего блока VMCS, то последний приобретает статус недостоверного. Виртуальная машина, описываемая данным блоком, получает статус остановленной. Точный список полей блока VMCS, обновляемых в ходе очистки и записываемые в них данные зависят от модели процессора. Так как данные блока VMCS могут кэ­ши­ро­вать­ся во внутренних буферах процессора, ожидая отложенной записи, в обязанности инструкции VMCLEAR также входит выполнение указанной отложенной записи.

VMREAD r/m64, r64 VMREAD r/m32, r32 — считывает из текущего блока VMCS элемент, код которого указан в операнде-источнике (регистре r64 или r32) и помещает данный элемент в операнд получатель (регистр или ячейку памяти r/m64 или r/m32). Инструкция обращается к элементам блока VMCS, используя их коды, а не адреса ячеек памяти. Коды элементов перечислены в [5]. Отметим, что обращаться к элементам блока VMCS, используя обычные инструкции работы с ячейками памяти, не рекомендуется по двум причинам:

  1. Форматирование региона VMCS зависит от модели процессора и не документировано. При использовании инструкций VMREAD и VMWRITE это учитывается автоматически, так как в данных инструкциях задается не адрес элемента, а его код, а адрес автоматически генерирует процессор по коду. Но если мы читаем элемент блока VMCS инструкцией MOV, используя адрес элемента, потребуется настройка адреса в соответствии с моделью процессора, что крайне неудобно.
  2. Данные региона VMCS кэ­ши­ру­ют­ся во внутренних буферах процессора. Инструкции VMREAD и VMWRITE автоматически учитывают это и гарантируют когерентность состояния блока VMCS в памяти и в процессоре. А при чтении элемента блока VMCS инструкцией MOV (как обычной ячейки памяти), мы можем прочитать устаревшие данные, не обновленные процессором. При записи элемента блока VMCS инструкцией MOV процессор может не заметить изменения состояния перезаписанного элемента, если использует его копию из внутреннего буфера.

VMWRITE r64, r/m64 VMWRITE r32, r/m32 — значение операнда-источника (r/m64 или r/m32) записывается в указанный элемент текущего блока VMCS. Код элемента указан в операнде-получателе (r64 или r32). Инструкция обращается к элементам блока VMCS, используя их коды, а не адреса ячеек памяти. См. пояснение в описании инструкции VMREAD.

VMLAUNCH — запускает виртуальную машину, управляемую текущим блоком VMCS, то есть тем блоком VMCS, адрес которого находится в регистре VMCS Pointer. Предполагается, что исходно данная виртуальная машина остановлена (Launch State = Clear). После выполнения серии проверок, описанной в [5], процессор переключается из режима ги­пер­ви­зора (VMX root operation) в режим гостя (VMX non-root operation) и начинает выполнение гостевой программы.

VMRESUME — запускает виртуальную машину, управляемую текущим блоком VMCS. Эта инструкция отличается от инструкции VMLAUNCH тем, что предназначена для возобновления работы уже запущенной виртуальной машины, которая временно вернула управление в ги­пер­ви­зор, например из-за попытки доступа к системным ресурсам, обращаться к которым гостевая программа не имеет права. VMRESUME применяется, когда текущее состояние Launch State = Launched.

VMCALL — завершает выполнение виртуальной машины и передает управление в ги­пер­ви­зор (VM Monitor). Выполняется VM Exit, то есть переключение процессора из состояния VMX non-root operation в состояние VMX root operation. Эта инструкция позволяет осуществлять явный вызов сервисных процедур ги­пер­ви­зора из гостевой программы.

Суть эксперимента

Предлагаемый пример демонстрирует возможности технологии вир­ту­а­ли­за­ции Intel VMX. В целях монопольного и беспрепятственного взаимодействия программы с аппаратным обеспечением, использована «древняя» технология отладки под DOS. Очевидно, что полученные знания и навыки универсальны, поэтому будут полезны при написании подобных процедур для любой ОС. В одной из наших публикаций детально описана процедура переключения процессора в 64-битный защищенный режим, которая понадобится и в данном примере. Напомним, что для использования технологии Intel VMX процессор должен работать в защищенном режиме.

В рассматриваемом примере, программа-гипервизор запускает программу-гостя, который выполняет вывод байта в порт 80h и завершается инструкцией VMCALL. Если гипервизор разрешил гостю обращение к портам ввода-вывода, вывод данных происходит и управление возвращается в гипервизор при выполнении инструкции VMCALL. Если доступ гостя к портам, запрещен, вывод данных не происходит и управление возвращается в гипервизор при попытке выполнения инструкции OUT. Разрешать и запрещать доступ можно, модифицируя фрагмент, выполняемый на шаге 24 рассмотренного ниже алгоритма. Статусная информация ви­зу­а­ли­зи­ру­ет­ся на шаге 48.

Адрес порта – 80h выбран не случайно. Если экспериментатор располагает POST-картой, вывод байта можно наблюдать на ее индикаторах. Автор использовал устройство IC80 V5.0 производства компании IC Book Labs, описанное в [17]. Оно поддерживает 16-битный порт данных и переназначение адреса диагностического порта, что расширяет возможности для экспериментов. Выбор в качестве объекта вир­ту­а­ли­за­ции порта вывода с индикацией, делает наши опыты простыми и наглядными.

Примечание. Простейшая POST-карта – это 8-битный порт с адресом 80h. Данные, выводимые в этот порт, отображаются на индикаторах. Основное назначение такого устройства – отображение POST-кодов, которые с диагностической целью выводит BIOS при старте платформы.

Структура программы

Прилагаемый каталог WORK содержит следующие файлы:

ASM_TD.BAT – выполняет ас­семб­ли­ро­ва­ние, лин­ков­ку и запуск программы под отладчиком Turbo Debugger. При запуске TASM и TLINK используются опции, обеспечивающие добавление отладочной информации в EXE файл и отладку с просмотром исходного текста программы.

ASM_EXE.BAT – выполняет ас­сем­б­ли­ро­ва­ние и лин­ков­ку. Генерируется EXE файл. Полученный файл запускается на выполнение.

MAKE_VMX.ASM – основной модуль программы. Проверяет условия запуска программы, выполняет подготовку контекста для работы в защищенном режиме со страничной трансляцией и вызова гостевой процедуры с использованием технологии Intel VMX.

CODE32.INC – содержит 32-битный сегмент кода, в котором находится процедура Transit_32, включающая режим IA-32e и вызывающая целевые подпрограммы: 32-битную подпрограмму Pattern_32 в режиме совместимости и 64-битную подпрограмму Pattern_64 в 64-битном режиме. Процедура Transit_32 является связующим звеном между 16-битным защищенным режимом, в котором оказывается процессор после переключения из реального режима и подпрограммами, вызываемыми в режиме IA-32e.

TARGET32.INC – содержит 32-битный сегмент кода, в котором находится процедура Pattern_32, вызываемая из режима IA-32e в 32-битном режиме совместимости. Этот режим не используется в текущей версии программы и поэтому процедура Pattern_32 является заглушкой. Она выполняет вывод в порт 80h кода 32h и завершается.

TARGET64.INC – содержит 64-битный сегмент кода, в котором находится процедура Pattern_64, вызываемая из режима IA-32e в 64-битном режиме. Эта процедура начинается с вывода в порт 80h кода 64h. Она содержит гипервизор виртуального режима, который включает VMX, устанавливает указатели на блоки данных VMXON Region и VMCS Region, подготавливает содержимое блока VMCS Region для запуска гостя, запускает гостя, и после завершения последнего, считывает статусную информацию из блока VMCS Region, выключает VMX и завершается. Архитектура VMX описана в [5], инструкции описаны в [3].

Примечание 1. В нашем примере гипервизор и гость являются 64-битными процедурами.

Примечание 2. Так как транслятор, используемый автором, не поддерживает инструкции VMX, их коды определены с помощью операторов DB.

GUEST32.INC – содержит 32-битный сегмент кода, в котором находится гостевая процедура Guest_Routine_32, выполняющая вывод в порт 80h кода 33h и возврат в гипервизор инструкцией VMCALL. 32-битная гостевая процедура не используется в текущей версии программы.

GUEST64.INC – содержит 64-битный сегмент кода, в котором находится гостевая процедура Guest_Routine_64, выполняющая вывод в порт 80h кода 65h и возврат в гипервизор инструкцией VMCALL.

INTS16.INC – Сегмент для процедур обработки прерываний в защищенном режиме. В текущей версии, в целях упрощения примера, прерывания в защищенном режиме не разрешаются и данный сегмент не используется.

A20.INC – библиотека процедур для управления адресной линией A20.

DOSMSG.INC – процедура вывода текстовых строк с использованием сервиса DOS.

NUMPRINT.INC – библиотека процедур для визуализации шестнадцатеричных и десятичных чисел.

DATA.INC – сегменты данных. Сегмент DATA_16 содержит константы, переменные и текстовые строки. GLOBAL_DESCRIPTOR_TABLE – глобальная де­скрип­тор­ная таблица для работы в защищенном режиме. PAGING_AND_VMCONTROL – динамически создаваемые таблицы страниц и таблицы для поддержки виртуального режима. STACK_16 – сегмент стека.

VMCSMAC.INC – содержит операторы для присвоения численных значений именам управляющих и статусных полей, содержащихся в блоке VMCS Region. Отметим, что эти значения являются не адресами соответствующих полей, а кодами, выбирающими заданное поле в инструкциях VMREAD и VMWRITE.

VMCSLIST.INC – содержит транзитные переменные, используемые при операциях с блоком VMCS Region. При установке состояния виртуальной машины перед запуском гостевой процедуры, содержимое этих переменных копируется в блок VMCS Region инструкциями VMWRITE. При анализе результатов после завершения гостевой процедуры, содержимое VMCS Region переносится в эти переменные инструкциями VMREAD.

Примечание. Поясним, для чего необходимо введение транзитных переменных и почему нельзя обращаться к элементам блока VMCS Region, напрямую, используя обычные инструкции работы с ячейками памяти. На это есть две причины:

  1. Форматирование блока VMCS Region зависит от модели процессора и не документировано. При использовании специальных инструкций VMREAD и VMWRITE это учитывается автоматически, так как в них задается не адрес элемента, а его код, а адрес автоматически генерирует процессор по коду. Но если мы читаем или записываем элемент блока VMCS Region инструкцией MOV, используя адрес элемента, то потребуется настройка адреса в соответствии с моделью процессора, что крайне неудобно.
  2. Данные региона VMCS Region кэ­ши­ру­ют­ся во внутренних буферах процессора. Инструкции VMREAD и VMWRITE автоматически учитывают это и гарантируют когерентность состояния блока VMCS Region в памяти и в процессоре. А при чтении элементов блока VMCS Region инструкцией MOV (как обычных ячеек памяти), мы можем прочитать устаревшие данные, не обновленные процессором. При записи элементов блока VMCS Region инструкцией MOV процессор может не заметить изменения состояния перезаписанных элементов, если использует их копию из внутреннего буфера.

Описание работы программы

Рассмотрим выполнение основного модуля. Нумерация пунктов данного описания соответствует нумерации пунктов комментариев в исходном тексте – в файле WORK\make_vmx.asm.

1) Установка адреса стека, загрузка регистров SS и SP.

2) Очистка экрана, установка текстового видеорежима 80x25, выдача сообщения о начале процедуры проверки конфигурации.

3) Проверка поддержки 32-битной системы команд. Проверяем доступность для записи бита 14 (NT-Nested Task) регистра флагов. Такая проверка нужна для того, чтобы при попытке запуска на компьютерах ниже 80386, программа выдала сообщение об ошибке, а не “зависла”.

4) Проверка поддержки технологии Intel VMX и ряда сопутствующих технологий, необходимых для работы программы. Сначала проверяем поддержку инструкции CPUID, затем, используя функции 00000000h (Vendor ID and largest standard function) и 00000001h (Feature information) инструкции CPUID, проверяем, что процессор Intel и технологии VMX, PAE, PSE, MSR поддерживается. Аналогичным образом, используя функции 80000000h (Largest extended function) и 80000001h (Extended feature information) проверяем поддержку технологии EM64T. Подробности в [6].

5) Проверка режима работы процессора. Программа использует непосредственный доступ к системным ресурсам, поэтому процессор на момент ее запуска должен работать в реальном режиме.

6) Чтение и визуализация версии VMX и размера блока VMCS Region. Данные параметры содержатся в регистре IA32_VMX_BASIC MSR (MSR с адресом 00000480h). Подробности в [5]. Напомним, что для чтения MSR (Model-Specific Register) используется инструкция RDMSR, которая принимает адрес MSR в регистре ECX и возвращает 64-битное значение, прочитанное из заданного MSR в регистрах EDX (старшие биты) и EAX (младшие биты). Подробности в [3].

7) Выдача сообщения о начале процедуры установки регистров управления.

8) Установка регистра управления CR4. Биты CR4.PSE, CR4.PAE, CR4.VMXE устанавливаем в “1”. Подробности в [4].

9) Установка регистра управления CR0. Биты CR0.NE устанавливаем в “1”, это необходимо для корректного переключения в режим VMX. Подробности в [4], [5].

10) Проверка и установка регистра IA32_FEATURE_CONTROL MSR (MSR с адресом 0000003Ah).

11) Выдача сообщения о начале процедуры очистки буферов.

12) Подготовка базовых адресов для размещения в памяти таблицы страниц и блоков VMXON Region, VMCS Region.

13) Обнуление блока памяти, выделенного для таблицы страниц и блоков VMXON Region, VMCS Region.

14) Выдача сообщения о начале процедуры подготовки контекста для работы процессора в защищенном режиме.

15) Установка указателя на корневую таблицу страниц в регистре CR3.

16) Построение таблицы страниц. Так как используемый блок памяти обнулен на шаге 13, здесь мы записываем значения только тех полей, которые должны быть отличны от нуля.

17) Подготовка базового адреса глобальной дескрипторной таблицы (GDT) и запись его в регистр GDTR.

18) Построение глобальной дескрипторной таблицы (GDT).

19) Подготовка указателей для вызова подпрограмм Transit_32, Pattern_32, Pattern_64 в защищенном режиме.

20) Запись значения VMCS Revision ID в первое 32-битное слово блока VMXON Region. Это необходимо для корректного выполнения инструкции VMXON.

21) Запись значения VMCS Revision ID в первое 32-битное слово блока VMCS Region. Это необходимо для корректного выполнения инструкции VMPTRLD.

22) Подготовка к инициализации транзитных переменных VMCS.

23) Инициализация переменой Pin-based VM Execution Controls, управляющей обработкой асинхронных событий в виртуальной машине. Подробности в [5].

24) Инициализация переменой CPU-based VM Execution Controls, управляющей обработкой синхронных событий в виртуальной машине. Бит 24 этой переменной управляет возможностью гостевой программы обращаться к портам. 0=обращение разрешено, доступ к портам выполняется обычным образом, 1=обращение запрещено, при попытке выполнения инструкций IN/OUT, а также строковых инструкций INS/OUTS, доступ к портам не выполняется и управление передается в гипервизор для обработки ситуации. При этом по содержимому статусных переменных блока VMCS Region гипервизор может определить адрес порта и тип операции, вызвавшей завершение гостя. При отладке программы можно рас­ком­мен­ти­ро­вать инструкцию BTS EAX,24. В результате гость не сможет записать в порт 80h код 65h и будет сформирована соответствующая статусная информация. Отметим, что процессор также поддерживает режим “индивидуального” запрета-разрешения для каждого порта, используя битовые карты I/O Bitmaps. Подробности в [5].

25) Инициализация переменой VM Exit Controls, управляющей выполнением возврата из гостевой процедуры в гипервизор.

26) Инициализация переменой VM Entry Controls, управляющей выполнением запуска гостевой процедуры из гипервизора.

27) Инициализация переменных Host CR0, Guest CR0, задающих значения, загружаемые в системный регистр управления CR0 в режиме гипервизора и гостя соответственно.

28) Инициализация переменных Host CR3, Guest CR3, задающих значения, загружаемые в системный регистр управления CR3 в режиме гипервизора и гостя соответственно.

29) Инициализация переменных Host CR4, Guest CR4, задающих значения, загружаемые в системный регистр управления CR4 в режиме гипервизора и гостя соответственно.

30) Инициализация переменных Host GDTR Base, Guest GDTR Base, задающих значения базового адреса глобальной дескрипторной таблицы в режиме гипервизора и гостя соответственно.

31) Инициализация переменных Host RSP, Guest RSP, задающих значения указателя стека в режиме гипервизора и гостя соответственно. 32) Инициализация переменной Host RIP, задающей значение указателя команд в режиме гипервизора. Это адрес точки возврата из гостя в гипервизор.

33) Инициализация переменной Guest RIP, задающей значение указателя команд в режиме гостя Это адрес точки входа в гостевую процедуру.

Примечание 1. Шаги 23-33 устанавливают значения только тех транзитных переменных VMCS, для которых требуется настройка на конфигурацию системы или адрес загрузки программы. Значения остальных заданы в исходном тексте модуля VMCSLIST.INC как константы. Все транзитные переменные копируются в блок VMCS Region при выполнении шага 42. Подробности в [5].

34) Разрешение адресной линии A20. Это необходимо, так как далее последует переключение в защищенный режим.

35) Выдача сообщения о начале процедуры запуска гостя.

36) Сохранение адреса стека для реального режима, регистры SS:SP сохраняются в памяти.

37) Запрет прерываний.

38) Переключение в защищенный режим.

39) Межсегментный переход на следующую инструкцию для загрузки атрибутов сегмента кода.

40) Загрузка селекторов сегментов данных DS и ES.

41) Загрузка селектора сегмента стека SS. Значение SP не перезагружается, так как используется тот же стек, что и в реальном режиме, с той лишь разницей, что в регистре SS находится не базовый адрес, а селектор сегмента стека.

42) Межсегментный вызов подпрограммы Transit_32, находящейся в 32-битном сегменте кода CODE_32, расположенном в файле CODE32.INC исходного текста. Эта подпрограмма корректирует значения регистров SS и RSP в соответствии с правилами 64-битного режима, включает трансляцию страниц и режим IA-32e, допускающий выполнение 32 и 64-битных сегментов кода. В регистр FS загружается селектор для доступа к сегменту DATA_16. Работая в режиме IA-32e, подпрограмма Transit_32 вызывает две подпрограммы: Pattern_32 в 32-битном режиме совместимости и Pattern_64 в 64-битном режиме. Подпрограмма Pattern_32 (см. файл TARGET32.INC) в текущей версии является заглушкой, она зарезервирована для расширения возможностей программы. Подпрограмма Pattern_64 (см. файл TARGET64.INC) содержит гипервизор виртуального режима. Она выполняет следующие действия:

42.1) Контрольная точка, вывод в порт 80h кода 64h.

42.2) Инструкция VMXON включает режим VMX и устанавливает адрес блока VMXON Region.

42.3) Инструкция VMCLEAR выполняет очистку блока VMCS Region.

42.4) Инструкция VMPTRLD выполняет загрузку указателя на блок VMCS Region.

42.5) Ранее подготовленные переменные, копируются в блок VMCS Region. Используется 3 вызова подпрограммы Execute_VMWRITE для 16, 32 и 64-битных переменных. Эта подпрограмма использует инструкцию VMWRITE для записи полей блока VMCS Region.

42.6) Инструкция VMLAUNCH запускает гостевую процедуру Guest_Routine_64 (см. файл GUEST64.INC).

42.7) Гостевая процедура выводит в порт 80h код 65h и возвращает управление в гипервизор инструкцией VMCALL.

42.8) С помощью инструкций VMREAD, статусная информация из блока VMCS Region, копируется в транзитные переменные и может быть использована для анализа результатов.

42.9) Инструкция VMXOFF выключает режим VMX.

43) Управление вернулось из подпрограммы Transit_32. Загружаем в сегментные регистры селекторы 16-битных сегментов, очищаем буфер трансляции страниц и переключаемся в реальный режим.

44) Восстановление контекста реального режима. Выполняем межсегментный переход на следующую инструкцию для загрузки атрибутов сегмента кода. Восстанавливаем значения в регистрах SS и SP.

45) Разрешение прерываний.

46) Восстановление исходного состояния адресной линии A20.

47) Зарезервировано для восстановления состояния системных регистров процессора. Не выполняется в текущей версии программы.

48) Вывод сообщения о возврате управления из гостевой процедуры. Визуализация статусной информации, по которой можно определить причину завершения гостевой процедуры и проверить наличие ошибок, возникших при работе в режиме VMX. Код ошибки VM Instruction Error позволяет проверить ошибки, возникшие при выполнении инструкций VMX. Коды перечислены в [3]. Код Step определяет номер шага, на котором возникла ошибка, формируется программно в переменной VMX_Progress_Status. Поддерживаются следующие значения:

  • 0=выполнение VMXON, переключение в режим VMX.
  • 1=выполнение VMCLEAR, очистка блока VMCS Region.
  • 2=выполнение VMPTRLD, загрузка указателя на блок VMCS Region.
  • 3=копирование 16-битных данных из блока транзитных переменных в блок VMCS Region.
  • 4=копирование 32-битных данных из блока транзитных переменных в блок VMCS Region.
  • 5=копирование 64-битных данных из блока транзитных переменных в блок VMCS Region.
  • 6=запуск гостевой процедуры Guest_Routine_64 инструкцией VMLAUNCH. Коды Exit Reason (причина завершения гостевой процедуры) и Exit Qualification (уточняющая информация о причине завершения) перечислены в [5]. После вывода статусной информации, ожидаем нажатия клавиши. Затем выводим 00h в порт 80h и выходим в DOS с кодом выхода 00h.

49) Сюда передается управление, если программа завершается из-за ошибок, возникших на ранних стадиях выполнения - при проверке конфигурации или установке контекста до перехода в режим VMX. В регистре BP находится адрес строки для вывода сообщения об ошибке. Выводим сообщение об ошибке. Ожидаем нажатия клавиши. Затем выводим 01h в порт 80h и выходим в DOS с кодом выхода 01h.

Условия эксперимента

Использовалась системная плата MicroStar MS-7357 на чипсете Intel G33, процессор DualCore Intel Pentium D 930, 3000MHz. Для установки контрольных точек при отладке программы, а также в качестве предмета виртуализации использовалась POST-карта IC80 V5.0 производства компании IC Book Labs, подробности в [17].

Анализ результатов

Проверим, как программа отображает результаты выполнения.

Если гостевой процедуре разрешен доступ к портам (инструкция BTS EAX,24 на шаге 24 закомментирована) программа должна вывести:
Exit Reason=00000012h, Exit Qualification=00000000h.

Если используется POST-карта, на индикаторе должен быть код 65h, выведенный гостевой процедурой в порт 80h. Согласно [5], Exit Reason=12h=18 означает завершение гостевой процедуры при выполнении инструкции VMCALL.

Если гостевой процедуре запрещен доступ к портам (инструкция BTS EAX,24 на шаге 24 выполнена) программа должна вывести:
Exit Reason=0000001Eh, Exit Qualification=00800040h.

Если используется POST-карта, на индикаторе должен быть код 64h, выведенный в порт 80h перед попыткой запуска гостевой процедуры. Exit Reason=1Eh=30 означает завершение гостевой процедуры при попытке запрещенного обращения к портам ввода-вывода. Exit Qualification=00800040h означает, что причиной завершения гостя была запись байта в порт с адресом 80h. Подробности в [5].

Если намеренно создать некорректный контекст гостевой процедуры, например, установив нулевое значение переменной Guest_RFLAGS в файле VMCSLIST.INC, получим:
Exit Reason=80000021h, Exit Qualification=00000000h.

Согласно [5], Exit Reason=21h=33 означает ошибку Invalid Guest State.

Если намеренно задать несуществующий код элемента для VMCS Region, например, установив Guest_ES_Selector в файле VMCSMAC.INC, равным 000000FFh, получим:

VM Instruction Error =0000000Ch, step=03h

Согласно [3], VM Instruction Error=0Ch=12 означает ошибку unsupported VMCS component, step=3 означает, что ошибка возникла на этапе копирования 16-битных данных из блока транзитных переменных в блок VMCS Region, при выполнении инструкции VMWRITE.

Если намеренно задать некорректное значение управляющих полей, например, за­ком­мен­ти­ро­вать инструкцию mov ds:[Entry_Control_Prim_CPU_Based_VM_Exec][2],eax, выполняемую на шаге 24, получим: VM Instruction Error =00000007h, step=06h Согласно [3], VM Instruction Error =07h означает ошибку VM Entry with invalid control fields, step=6 означает, что ошибка возникла на этапе запуска гостевой процедуры, при выполнении инструкции VMLAUNCH.

Источники информации

Электронные документы, доступные на сайте developer.intel.com

  1. Intel 64 and IA-32 Architectures Software Developer’s Manual. Volume 1: Basic Architecture. Order Number 253665-034US
  2. Intel 64 and IA-32 Architectures Software Developer’s Manual. Volume 2A: Instruction Set Reference, A-M. Order Number 253666-034US
  3. Intel 64 and IA-32 Architectures Software Developer’s Manual. Volume 2B: Instruction Set Reference, N-Z. Order Number 253667-034US
  4. Intel 64 and IA-32 Architectures Software Developer’s Manual. Volume 3A: System Programming Guide, Part 1. Order Number 253668-034US
  5. Intel 64 and IA-32 Architectures Software Developer’s Manual. Volume 3B: System Programming Guide, Part 2. Order Number 253669-034US
  6. Intel Processor Identification and the CPUID Instruction. Application Note 485. Order Number 241618-033

Электронные документы, доступные на сайте developer.amd.com

  1. AMD64 Architecture Programmer’s Manual. Volume 1: Application Programming. Publication No. 24592
  2. AMD64 Architecture Programmer’s Manual. Volume 2: System Programming. Publication No. 24593
  3. AMD64 Architecture Programmer’s Manual. Volume 3: General-Purpose and System Instructions. Publication No. 24594
  4. AMD64 Architecture Programmer’s Manual. Volume 4: 128-Bit Media Instructions. Publication No. 26568
  5. AMD64 Architecture Programmer’s Manual. Volume 5: 64-Bit Media and x87 Floating-Point Instructions. Publication No. 26569
  6. AMD64 Architecture Programmer’s Manual. Volume 6: 128-Bit and 256-Bit XOP, FMA4 and CVT16 Instructions. Publication No. 43479
  7. BIOS and Kernel Developer’s Guide for AMD NPT Family 0Fh Processors. Publication No. 32559
  8. BIOS and Kernel Developer’s Guide (BKDG) for AMD Family 10h Processors. Publication No. 31116
  9. BIOS and Kernel Developer’s Guide (BKDG) for AMD Family 10h Processors. Publication No. 41256
  10. AMD CPUID Specification. Publication No. 25481.

Электронные документы, доступные на сайте icbook.com.ua

  1. IC80 V5.0. Диагностическая плата. Руководство пользователя. Ревизия 1.5. IC Book Labs, © 2006.

Загрузить ZIP-архив 
с примером программирования
на ассемблере
можно здесь.