Это широко открытый вопрос, не уверен, куда вы хотите пойти с ним. Википедия охватывает общую тему с общим ответом. Собственный код, который эмулируется или виртуализируется, заменяется собственным кодом. Чем больше кода выполняется, тем больше заменяется.
Я думаю, что вам нужно сделать несколько вещей, сначала решите, говорите ли вы об эмуляции или виртуальной машине, такой как vmware или virtualbox. Эмуляция процессора и аппаратного обеспечения эмулируется с помощью программного обеспечения, поэтому эмулятор читает следующую инструкцию, код операции разбирается на части по коду, и вы сами определяете, что с ним делать. Я занимался эмуляцией 6502 и статическим двоичным переводом, который представляет собой динамическую перекомпиляцию, но предварительно обработанную вместо реального времени. Таким образом, ваш эмулятор может взять LDA # 10, загрузить с немедленной, эмулятор видит загрузку немедленной инструкции, знает, что он должен прочитать следующий байт, который является непосредственным, эмулятор имеет переменную в коде для регистра A и помещает непосредственное значение в этой переменной. Перед выполнением инструкции эмулятор должен обновить флаги, в этом случае нулевой флаг очищен, N-флаг очищен, C и V не тронуты. Но что, если следующая инструкция была немедленной загрузкой X? Ничего страшного, верно? Что ж, load x также изменит флаги z и n, поэтому в следующий раз, когда вы выполните команду load, вы можете выяснить, что вам не нужно вычислять флаги, потому что они будут уничтожены, это мертвый код в эмуляции. Вы можете продолжить такое мышление, скажем, вы видите код, который копирует регистр x в регистр a, затем помещает регистр в стек, затем копирует регистр y в регистр и помещает в стек, вы можете заменить этот блок просто нажав регистры x и y в стеке. Или вы можете увидеть пару операций добавления с переносами, соединенных вместе для выполнения 16-разрядного добавления и сохранения результата в смежных ячейках памяти. По сути, поиск операций, которые не может выполнять эмулируемый процессор, но легко выполняется в эмуляции. Статическая двоичная трансляция, которую я предлагаю вам изучить перед динамической перекомпиляцией, выполняет этот анализ и трансляцию статическим образом, как, например, перед запуском кода. Вместо того, чтобы эмулировать, вы, например, переводите коды операций в C и удаляете столько мертвого кода, сколько можете (приятная особенность - компилятор C может удалить больше мертвого кода за вас).
Как только концепция эмуляции и перевода понятна, вы можете попытаться сделать это динамически, это, конечно, не тривиально. Я бы предложил попробовать снова выполнить статический перевод двоичного кода в машинный код целевого процессора, что является хорошим упражнением. Я не буду пытаться динамически оптимизировать время выполнения, пока мне не удастся выполнить их статически по отношению к / двоичному файлу.
Виртуализация - это отдельная история, вы говорите о запуске одного и того же процессора на одном процессоре. Так х86 на х86 например. прелесть здесь в том, что, используя не старые процессоры x86, вы можете взять виртуализированную программу и запускать действительные коды операций на реальном процессоре, без эмуляции. Вы настраиваете ловушки, встроенные в процессор, для захвата вещей, поэтому загрузка значений в AX и добавление BX и т. Д. Все это происходит в реальном времени на процессоре, когда AX хочет читать или записывать память, это зависит от вашего механизма прерываний, если адреса находятся в пределах пространство памяти виртуальных машин, ловушек нет, но, скажем, программа записывает на адрес, который является виртуализированным UART, у вас есть ловушка процессора, которая затем vmware или любой другой декодер, который записывает и эмулирует его, взаимодействуя с реальным последовательным портом. Хотя эта инструкция не была в реальном времени, ее выполнение заняло много времени. Что бы вы могли сделать, если бы выбрали это, замените эту инструкцию или набор инструкций, которые записывают значение в виртуализированный последовательный порт и, возможно, затем записывают его на другой адрес, который может быть реальным последовательным портом или каким-либо другим местоположением, которое не будет вызвать ошибку, приводящую к тому, что vm manager должен эмулировать инструкцию. Или добавьте некоторый код в пространство виртуальной памяти, которое выполняет запись в uart без прерывания, и вместо этого добавьте этот код в эту процедуру записи uart. В следующий раз, когда вы нажмете этот кусок кода, он будет запущен в реальном времени.
Еще одна вещь, которую вы можете сделать, это, например, эмулировать и, по ходу дела, переводить в виртуальный промежуточный байт-код, такой как llvm. Оттуда вы можете перевести с промежуточного компьютера на собственный компьютер, в конечном итоге заменяя большие разделы программы, если не все. Вам все равно придется иметь дело с периферией и вводом / выводом.