Аппаратная виртуализация на практике: переход к практике
В прошлой статье я кратко познакомил тебя с архитектурой AMD-V. В этой мы продолжим разбираться с нелегкой темой. Я намерено упустил множество важных деталей относительно механизма работы гипервизора, дабы с первого раза не перегружать твой мозг большими объемами информации. Будем заполнять этот пробел. Я буду приводить примеры и форматы регистров в Long Mode по ходу, поэтому сразу следует сказать (или напомнить), что Long Mode — это режим работы процессора, в котором работают все 64-битные ОС-и. Инструкция VMRUNКак ты помнишь из предыдущей статьи (ведь еще помнишь? :)) — инструкция VMRUN запускает виртуальную машину. Состояние хоста сохраняется в специальной области памяти, указатель на которую хранится в регистре VM_HSAVE_PA. А состояние запускаемого гостя — загружается из VMCB (на которую указывает регистр rax). Сегодня мы будем разбираться с этой чрезвычайно важной инструкцией, чтобы успешно заполнить управляющий блок виртуальной машины и запустить гостевую систему... А верна ли VMCB?VMRUN производит кучу проверок правильности контрольного блока виртуальной машины (а то мало ли что мы ей подсунуть хотим). Если обнаруживается что-то подозрительное и недопустимое, то нас посылают с кодом VMEXIT_INVALID (кстати, некоторые коды VMEXIT я уже упоминал в предыдущей статье). Итак, в каких же случаях VMRUN откажется запускать гостя? Буду перечислять эти ситуации по пунктам, а тебе при прочтении рекомендую иметь перед глазами структуру VMCB. Заголовочный файл, содержащий VMCB: http://cvs.opensolaris.org/source/xref/xen-gate/xvm-3.4+xen.hg/xen/include/asm-x86/hvm/svm/vmcb.h 1. Флаг в SVME в регистре EFER равен 0. Аппаратная виртуализация в госте должна быть включена. 2. Сброшен бит CR0.CD и одновременно установлен CR0.NW. Первый бит расшифровывается как Cache Disable — когда он установлен, инструкции и данные не помещаются во внутренние кэши. А вот второй бит (по крайней мере, так написано в мануале AMD) — вообще игнорируется. Странно, что VMRUN его проверяет при сброшенном бите CD. 3. Старшие 32 бита CR0 не равны 0. Если ты посмотришь на формат регистра CR0 (в long mode), то увидишь, что эти биты должны быть равны 0. 4. В регистрах CR3, CR4, DR6, DR7, EFER не равны нулю биты, отмеченные как MBZ (Must Be Zero). 5. ASID равен 0. ASID — это идентификатор адресного пространства, позволяющий отличать элементы хоста от гостевых в ассоциативном буфере трансляции (TLB). То есть поле ASID должно быть обязательно проинициализировано. На всякий случай напомню, что TLB используется для ускорения преобразования виртуальных адресов в физические. Наличие ASID-идентификатора позволяет избежать сброса TLB при каждом входе и выходе из гостя. Что, конечно, положительно сказывается на производительности. 6. Ошибочная инъекция события. Инжектированное событие — это прерывание или исключение, выполняемое перед первой инструкцией гостя.
Вообще, возможных типов инжектированных событий (поле type) всего 4:
Если бит ev (Error code valid) установлен, то код ошибки errorcode будет "затолкнут" в стек. В каких случаях инъекция события неверна? Например, если гость в 64-битном режиме, а мы пытаемся инжектировать исключение #BR (вызывается командой bound), невозможное в этом режиме. Мы также получим VMEXIT_INVALID, если будем использовать зарезервированные значения поля type (1,5,6 или 7). Или если мы укажем тип исключения, а номер вектора — 2, что соответствует NMI (это немаскируемое прерывание, а не исключение!). 7. Биты EFER.LMA (Long Mode Active) или LME (Long Mode Enable), отвечающие за активацию Long Mode, установлены, а процессор не поддерживает Long Mode. Вполне логично, что такое сочетание будет признано невалидным. 8. Одновременная установка битов EFER. LME и CR0.PG (флаг включения страничного преобразования адресов) при сброшенном бите CR4.PAE (или CR0.PE) — недопустимая комбинация. 9. Флаги EFER.LME, CR0.PG, CR4.PAE (бит расширения физического адреса), CS.L и CS.D одновременно установлены. CS — сегмент кода. Биты L и D содержатся в дескрипторе сегмента. В 32-битном защищенном режиме бит D использовался для указания размера операнда и адреса (32 или 16 бит), а бит L — это бит, указывающий, что размер адреса и операнда у нас 64 бита. Теперь, я думаю, тебе понятно, почему одновременная установка этих двух битов является ошибкой (то есть мы указываем, что у нас одновременно по умолчанию установлен размер операнда 64 и 32 бита). 10. Бит перехвата инструкции VMRUN (в области управления VMCB) сброшен — эта инструкция должна перехватываться в обязательном порядке. Да, ты все верно подумал. Действительно, можно вызывать VMRUN, уже находясь в режиме гостя. Вот пример, связанный с Голубой пилюлей. Некоторым людям удавалось запускать более 20 (!) вложенных пилюль. Главное — правильно сделать перехват этой инструкции. В VMCB бит перехвата VMRUN располагается в двойном слове по смещению 10h от начала управляющего блока виртуальной машины и перехватить VMRUN можно так:
Вообще, поле general2_intercepts помимо бита перехвата VMRUN содержит флаги перехвата других инструкций из svm-расширения: VMMCALL, VMLOAD, VMSAVE, STGI, CLGI и SKINIT, но перехватывать эти инструкции уже необязательно. 11. Физические адреса карты разрешения MSR (MSRPM) или ввода-вывода (IOPM) равны или больше максимального поддерживаемого физического адреса. А иначе им (картам) просто не хватит места! Карта разрешения MSR (как и ввода-вывода) должна быть выровнена по границе 4 килобайта. И VMRUN игнорируются младшие 12 бит адреса MSRPM и IOPM. О картах, кстати, я упоминал в предыдущей статье. Если VMCB верная, то можно продолжать. Теперь ты знаешь, чего делать нельзя. Дальше поговорим о том, что можно и нужно :). VMRUN после проверок и сохранения состояния хоста загружает следующую информацию из контрольного блока виртуальной машины. Первое, что обрабатывается VMRUN — это область состояния гостя (она же State Save Area): 1. CS и rip — определяют, откуда начнет выполняться гость. CS — сегмент кода, а rip — указатель инструкции в long mode (когда мы имели дело с 32-битами, у нас был регистр eip). 2. Регистры rflags,rax... 3. SS (сегмент стека) и rsp — стек гостя. В 32-битном режиме был не rsp, a esp :). 4. CR0, CR2 (в этом регистре содержится виртуальный адрес ошибки страницы — page fault), CR3, CR4 и EFER — эти регистры отвечают за страничное преобразование адресов в гостевой системе. 5. IDTR, GDTR(база и размер таблиц дескрипторов GDT и IDT),ES и DS, DR7 и DR6. 6. V_TPR — виртуальный регистр приоритета задачи (TPR). Значение поля v_tpr записывается в регистр CR8 гостя. Регистр приоритета задачи используется в случаях, если, у нас, например, пришло какое-то "не очень важное" прерывание, а выполняется какая-то задача, которую ну никак нельзя прервать. Тогда мы записываем приоритет прерывания (например, 7) в регистр CR8 и все прерывания с приоритетом меньше 7(включительно) будут игнорироваться. 7. V_IRQ. Этот флаг определяет, будет ли виртуальное прерывание отложено. 8. CPL или текущий уровень привилегий. Если гостевая система загружается в реальный режим — CPL = 0, если в режим виртуального 8086, то 3. CPL определяет кольцо защиты: 0 — нулевое, 3, соответственно, третье. После этого VMRUN переходит к обработке области флагов (Control Area). Здесь можно отметить так называемый TSC_OFFSET. Значение TSC_OFFSET добавляется к счетчику меток реального времени (в мануалах — TSC) в госте, когда тот решит получить его значение. То есть, когда мы выполним в госте команду rdtsc, то к значению счетчика будет автоматически прибавлено значение TSC_OFFSET. С этим полем связан известный баг (Erratum 140), когда при чтении TSC MSR через rdmsr, а не rdtsc значение TSC_OFFSET не прибавлялось к счетчику. Конечно, в Control Area мы можем указать множество различных событий или инструкций, которые хотим перехватывать. Так же считывается уже упоминавшийся идентификатор ASID и т.д. В нашей структуре VMCB каждый сегмент описывается структурой segment_register (код взят из Xen), содержащей части дескриптора сегмента и селектор на этот дескриптор:
К атрибутам сегментов в VMCB предъявляются определенные требования. Подробнее: 1. Не разрешен нулевой сегмент кода, поэтому воспринимаются аппаратурой только некоторые биты в дескрипторе (D, L, R). 2. Регистр TR может иметь только тип TSS (напоминаю, что тип сегмента определяется в дескрипторе сегмента). 3. У LDTR из атрибутов не игнорируется только бит присутствия сегмента (P). Когда сработает какое-либо перехватываемое событие, управление получит инструкция после VMRUN (впрочем, я об этом уже говорил). После VMRUN необходимо разместить код проверки и обработки различных условий #VMEXIT. Ядро гипервизора составляет цикл из VMRUN и обработки VMEXIT:
Для полного понимания сказанного тебе придется подтянуть знания защищенного режима работы процессора (если ты не знаком с этой темой). Создаем VMCBНу что, твоих знаний еще недостаточно для создания полноценного гипервизора? Однако продолжаю потихоньку вводить тебя в курс дела :). Что касается выделения блока памяти под VMCB и Host Save Area — это можно сделать ядерной функцией MmAllocateContiguousMemorySpecifyCache. Ее прототип:
В прошлой статье я упоминал, что для понимания кода понадобится опыт разработки дров под Винду (ну или хотя бы минимальные знания, чтобы понимать сорс). Примерный код для выделения памяти под VMCB:
Аналогичным образом память выделяется и для HSA, и для карт разрешения MSR и IOIO. ЗаключениеВот и все на сегодня. Информации много, и, чтобы ее полностью переварить (если тема для тебя новая), потребуется время. Помни, дорогу осилит идущий. А со мной по-прежнему можно связаться по e-mail и написать свои предложения или вопросы по содержанию статей (или просто пообщаться на тему аппаратной виртуализации). WWWBroken Sword на wasm.ru, посвященный защищенному режиму: http://wasm.ru/publist.php?list=24 Систематизировать знания (или пробелы заполнить) по Long Mode тебе помогут следующие статьи. Архитектура AMD64 (EM64T): viva64.com/content/articles/64-bit-development/?f=amd64_em64t_rus.html&lang=ru&content=64-bitdevelopment Статья Криса Касперски "Архитектура x86-64 под скальпелем ассемблерщика": insidepro.com/kk/072/072r.shtml Статья "An Introduction to Hardware-Assisted Virtual Machine (HVM) Rootkits" (в том числе, упоминается баг с TSC_OFFSET): megasecurity.org/papers/hvmrootkits.pdf "Revision Guide for AMD NPT Family 0Fh Processors" (перечисляются erratum-ы): support.amd.com/us/Processor_TechDocs/33610.pdf
|