Как виртуальные машины (x86) обычно обрабатывают флаги?

Для побочного проекта я пытаюсь написать полупрограммируемую виртуальную машину x86.

Я понимаю форматы, поэтому большая часть дизайна относительно проста, но после выполнения инструкции с ее операндами флаги часто меняются. Было бы очень неэффективно проверять каждый потенциальный бит, поэтому я подумал о том, чтобы вытолкнуть регистр флага в виртуальную машину, выполнить операцию И, а затем установить регистр флага виртуальной машины. Однако это по-прежнему много накладных расходов.

Это на грани самоуверенности, но есть ли что-то, что я упускаю?


person Lupe    schedule 27.10.2016    source источник
comment
Непонятно, что ты делаешь.   -  person Jester    schedule 27.10.2016
comment
Хм, обычно гипервизоры не эмулируют шаг выполнения инструкций, а позволяют гостевому коду работать (в основном) без изменений, задерживаясь в ядре гипервизора, когда оборудование обнаруживает привилегированную инструкцию. Конечно, на практике это намного сложнее, но дело в том, что обычно вам нужно беспокоиться о состоянии регистра флагов только при ловушке в гипервизоре, а не после каждой инструкции.   -  person Matteo Italia    schedule 27.10.2016
comment
@Jester Я думаю, что он создает эмулятор.   -  person fuz    schedule 27.10.2016
comment
Я не понимаю, что ты делаешь. Если это виртуальная машина, вы обычно позволяете коду выполняться и делать свое дело, пока не дойдете до vmcall, и в основном не заботитесь о флагах. Если это наивный эмулятор, и вы повторно реализуете набор инструкций x86 на языке более высокого уровня, то вы можете быть умнее и в большинстве случаев избегать вычисления флагов (либо откладывая это на потом, либо заранее проверяя, какие флаги могут иметь значение) .   -  person zneak    schedule 27.10.2016
comment
В любом случае, выталкивание флагового регистра в виртуальную машину — необычный способ формулировки вещей, потому что обычно вы выталкиваете вещи из стека. Мне также неясно, что может потребовать ANDing; Инструкции x86 могут либо установить (или очистить флаг, либо оставить его без изменений, либо сделать его неопределенным, поэтому операция должна выглядеть примерно так: vm_flags = output_flags | (vm_flags & unmodified_flags_mask).   -  person zneak    schedule 27.10.2016
comment
Если вы просто эмулируете инструкции (это означает, что вы никогда не запускаете инструкции изначально на ЦП, используя VT-X или нет), отредактируйте свой вопрос, чтобы указать, что вы создаете эмулятор.   -  person Jonathon Reinhart    schedule 27.10.2016


Ответы (2)


Если вы хотите, чтобы ваш эмулятор имитировал процессор как есть, то да, вам нужно точно эмулировать флаги.

Это означает очистку битов, которые необходимо очистить (с помощью И), установку битов, которые необходимо установить (с помощью ИЛИ), и копирование/вычисление битов при необходимости (т. е. флаг Z требует проверки, равен ли результат нулю, перенос требует знать, есть ли у вас переполнение и т. д.)

Нет никакого способа обойти это.

Это похоже на декодирование байта R/M mod. У вас нет возможности загрузить этот байт, проверить режим, чтобы определить, является ли это регистром или доступом к памяти, и применить их соответственно...

И на самом деле это означает, что ваш эмулятор будет «намного медленнее» (если только вы не эмулируете старый процессор 10 МГц с современным процессором 3 ГГц, когда у вас все равно есть время выполнить 300 циклов инструкций... так что все должно быть в порядке. )

Если вам интересно, я написал эмулятор 6502 и протестировал его с ПЗУ Apple 2. Мне пришлось добавить спящие режимы, чтобы он не работал на частоте 100 МГц или выше... (этот процессор изначально работал на частоте 1 МГц...)

person Alexis Wilke    schedule 27.10.2016
comment
Есть обходной путь; это называется ленивой оценкой: сохраните достаточно информации для расчета флагов, но пока не делайте этого. x86 чаще записывает флаги, чем читает их. - person Peter Cordes; 27.10.2016
comment
@PeterCordes Да, я думаю, вы могли бы сохранить последний результат в переменной и использовать его, если вам нужно оценить Z, C, V и, возможно, некоторые другие флаги. Это может быть быстрее, чем каждый раз оценивать флаги. Однако в некоторых ситуациях это может быть сложно, например, при конфликтах при вычислении C между инструкциями ADC и ROL. - person Alexis Wilke; 28.10.2016

Похоже, вы спрашиваете об эмуляции x86, а не о виртуализации. Поскольку современное оборудование x86 поддерживает виртуализацию, при которой ЦП изначально запускает гостевой код и перехватывает только гипервизор для некоторых привилегированных инструкций это то, что обычно означает термин «виртуализация».


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

Это означает, что на самом деле вам не нужно вычислять PF и AF каждый раз, когда они записываются (почти каждая инструкция), только каждый раз, когда они читаются (в основном только PUSHF или прерывания, едва ли какой-либо код когда-либо считывает PF (за исключением ветвей FP). где это означает NaN)). Вычисление PF после каждой целочисленной инструкции в чистом C является дорогостоящим, поскольку требует подсчета всплывающих окон для младших 8 бит результатов. (И я думаю, что компиляторы C, как правило, не могут распознать этот шаблон и сами используют setp, не говоря уже о pushf или lahf для хранения нескольких флагов, если компилируют эмулятор x86 для запуска на хосте x86. Иногда они распознают подсчет населения. шаблоны и выдают popcnt инструкции, однако, при нацеливании на хост-процессоры, которые имеют эту функцию (например, -march=nehalem)).

BOCHS использует этот метод и подробно описывает реализацию в разделе "Отложенные флаги" этого короткого файла PDF: Как работает Bochs изнутри, 2-е издание. Они сохраняют результат, чтобы получить ZF, SF и PF, а также перенос из двух старших битов для CF и OF и из бита 3 для AF. Благодаря этому им никогда не нужно повторять инструкцию для вычисления результатов ее флага.

Есть дополнительные сложности из-за того, что некоторые инструкции не записывают все флаги (т. е. частичное обновление флагов) и, предположительно, из-за таких инструкций, как BSF, которые устанавливают ZF на основе входных, а не выходных данных.


Дополнительная литература:

В этом документе на emulators.com содержится много подробностей о том, как эффективно сохранять достаточное количество состояния восстановить флаги. Он имеет «Флаги ленивой арифметики 2.1 для эмуляции ЦП».

Одним из авторов является Darek Mihocka (долгое время пишет эмуляторы, сейчас, по-видимому, работает в Intel). Он написал много интересного о том, как ускорить работу не-JIT-эмуляторов, и о производительности ЦП в целом, большая часть из которых размещена на его сайте http://www.emulators.com/. Например. эта статья о том, как избежать неправильного предсказания перехода в цикле интерпретатора эмулятора, который отправляет функции, реализующие каждый код операции довольно интересен. Дарек также является соавтором статьи о внутреннем устройстве BOCHS, на которую я ссылался ранее.

Также может быть актуален поиск в Google для оценки отложенного флага: https://silviocesare.wordpress.com/2009/03/08/lazy-eflags-evaluation-and-other-emulator-optimisations/

В последний раз, когда возникла эмуляция x86-подобных флагов, обсуждение в комментариях в моем ответе с ленивыми флагами было кое-что интересное: например. @Raymond Chen предложил эту ссылку на документ Mihocka & Troeger, а @amdn указал, что динамический перевод JIT может обеспечить более быструю эмуляцию, чем интерпретация.

person Peter Cordes    schedule 27.10.2016