Экспозиция - пытаюсь прямо ответить на вопрос
Если вы читаете исходный код для эмулятора, а он не читает определенные биты двоичного (исполняемого) файла и все еще добросовестно выполняет код, то возможны три результата:
- Вы ошибаетесь, считая, что эмулятор не читает каждый бит файла, а на самом деле это делает, и вы просто ошибаетесь.
- Вы правы, и эмулятор не читает каждый бит, потому что он может предположить определенные факты о поведении программы, которую он эмулирует, чтобы не нужно было читать каждый бит, чтобы знать, что ему нужно делать (возможно, потому что он ожидает запуска определенного игрового движка, или определенного типа графического API, или определенного типа звукового API и т. д.).
- Вы правы, и эмулятор не читает каждый бит, потому что есть определенные биты исполняемого файла, которые просто не нужны для правильного выполнения программы. Это могут быть устаревшие "меткие" данные, или метаданные, или что-то еще, что является просто лишним пухом, который на самом деле не состоит из функциональности программы.
- Вы правы, и эмулятор не читает каждый бит, потому что эмулятор преобразует определенные операции в коде в операции более высокого уровня и полностью игнорирует низкоуровневые, специфичные для процессора / аппаратного обеспечения инструкции. Например, если вас попросят точно имитировать то, что человек делает на видеозаписи этого человека, выполняющего сложную операцию, и они скажут: "Теперь просверлите отверстие в боковой части коробки", у вас будет искушение остановиться. смотреть видео и использовать имеющийся у вас опыт того, как сверлить дыры в вещах, вместо того, чтобы следовать буквальным движениям парня в видео (при условии, что вы обладаете надлежащим упражнением и, как правило, имеете опыт в жизни). Точно так же, если эмулятор может сделать вывод, что программа просит нарисовать изображение 32x32 на экране с заданным набором координат, он может прекратить чтение кода, как только он поймет, что это за изображение, в каком формате это изображение, и где его нарисовать - не нужно видеть, как его рисует эмулируемая система.
Как работают эмуляторы
Эмулятор, который выполняет код для другой платформы и / или процессора (например, Wine), выполняет различные действия. Некоторые этапы абсолютно необходимы для работы эмулятора; другие этапы являются необязательными и представляют возможности для оптимизации производительности.
Обязательно: "Разбор" исполняемого кода (машинный код, MSIL, байт-код Java и т.д.) Разбор состоит из:
- Чтение каждого бита исполняемого кода.
- Понимание достаточного количества макета / формата (синтаксиса) и цели (семантики) каждого бита / байта (или любой другой дискретной единицы измерения информации, которую вы хотите использовать) нативного кода, чтобы понять, что он делает.
- Чтобы понять, что говорит программа, эмулятор должен понимать синтаксис двоичного формата и семантику. Синтаксис состоит из таких вещей, как «мы выражаем 32-битные целые числа со знаком в формате Least Signed Bit»; семантика состоит из таких вещей, как «когда нативный код содержит код операции 52, это означает вызов функции».
- Мнемоника (помогает вам вспомнить, почему это необходимо): если я посвятил себя следованию рецепту, если я полностью игнорирую этот рецепт и даже не читаю его, невозможно, чтобы я когда-либо следовал этому рецепту, если только я случайно попробуй кучу вещей и удачи в том, чтобы предпринять те же шаги, что и рецепт. Точно так же, если у вас нет рандомизированной симуляции Монте-Карло, которая выполняет случайные инструкции ЦП до тех пор, пока она не удастся выполнить те же функции, что и программа, любой эмулятор должен будет понять, что говорит программа.
Требуется: "Преобразование" разобранного кода (обычно какая-то абстрактная модель данных, конечный автомат, абстрактное синтаксическое дерево или что-то в этом роде) в команды высокого уровня (например, операторы в C или Java) или команды низкого уровня (например, инструкции процессора для процессора x86). Команды высокого уровня имеют тенденцию быть более оптимальными. Например, если вы анализируете поток кода длинной последовательности инструкций процессора и на высоком уровне определяете, что он запрашивает воспроизведение определенного файла MP3 с диска, вы можете пропустить всю эмуляцию уровня инструкции и просто использовать свой собственный MP3-декодер платформы (который может быть оптимизирован для вашего процессора) для воспроизведения того же файла MP3. С другой стороны, если бы вы "отслеживали" выполнение эмулируемой программы в буквальном смысле, насколько это возможно, это было бы медленнее и менее оптимальным, потому что вы бы отказались от большей части оптимизации, от которой вы выигрываете, выполняя инструкции самостоятельно.
Необязательно: "Оптимизация" и анализ потока кода большой полосы эмулируемого программного кода или всей программы, чтобы определить полную последовательность выполнения, и построение очень подробной и сложной модели того, как ваш эмулятор будет эмулировать это поведение с возможностями родной платформы. Wine делает это в некоторой степени, но ему помогает тот факт, что код, который он переводит, имеет формат от x86 до x86 (это означает, что в обоих случаях процессор имеет одинаковый набор инструкций, поэтому все, что вам нужно сделать, это подключить Windows) код во внешнюю среду на основе UNIX и пусть он работает "изначально").
Торт по аналогии
Рассматривая производительность эмулятора, подумайте о том, сколько листов бумаги вам понадобится, чтобы записать инструкции для себя, если вы смотрели, как кто-то на видео (со звуком) выпекает торт, в следующих сценариях:
Если вы никогда в своей жизни не двигали руками или не тренировали мышцы тела; (подсказка: вам понадобятся тысячи листов бумаги для документирования подробных шагов движения руки, координации рук и глаз, наклона, скорости, положения, базовых техник, таких как захват, хранение посуды, разминание и т. д.)
Если у вас есть базовое управление двигателем (вы можете ходить и кормить себя), но никогда в своей жизни не готовили никакой пищи; (подсказка: вам понадобятся десятки листов бумаги для документирования отдельных шагов, и вам, вероятно, понадобится много практики, чтобы освоить такие вещи, как разминание и хранение незнакомой посуды, но вы сможете документировать это гораздо реже время, чем в предыдущем случае)
Если вы никогда в своей жизни не пекли пирог, но раньше вы готовили пищу; (подсказка: вам понадобится пара листов бумаги, но не более 10; вы уже знакомы с измерением ингредиентов, перемешиванием и т. д.)
Если вы уже много раз пекли пирог и хорошо знакомы с процессом, но не знаете, как испечь этот конкретный сорт / аромат торта (подсказка: вам может понадобиться половина листа бумаги, чтобы записать основные ингредиенты и сколько нужно времени в духовке, и все тут).
По сути, на этих возрастающих уровнях "компетенции эмулятора" эмулятор может делать более высокоуровневые вещи "изначально" (используя уже известные ему подпрограммы и процедуры) и должен выполнять меньше "трассировки" (используя подпрограммы и процедуры, которыми он является). следуя буквально из эмулируемой программы).
Чтобы представить эту аналогию в терминах компьютера, вы можете представить себе эмулятор, который эмулирует реальное оборудование , на котором будет работать эмулируемая программа, и точно отслеживает поведение этого оборудования, возможно, даже до аппаратного (схемного) уровня; это было бы очень медленно по сравнению с эмулятором, который анализирует программу до такого уровня сложности, который он понимает, когда пытается воспроизвести звуковой файл, и может "воспроизводить" этот звуковой файл "без изменений", не отслеживая инструкции эмулируемой программы. так.
О "отслеживании" (он же rote mimicry) против "нативного исполнения"
И последнее: трассировка идет медленно, главным образом потому, что вам приходится использовать много памяти для "репликации" очень подробных, запутанных компонентов того, что вы эмулируете, и вместо того, чтобы просто выполнять инструкции на вашем центральном процессоре, вы должны выполнять инструкции которые выполняют инструкции (см. уровень косвенности?), что приводит к неэффективности. Если бы вы пошли на все, и эмулировали физическое оборудование компьютерной системы, а также программу, вы бы эмулировали процессор, материнскую плату, звуковую карту и т.д., Что, в свою очередь, "отслеживало бы" выполнение программы как эмулятор "отслеживает" выполнение процессора, и при таком количестве уровней трассировки все это будет чрезвычайно медленным и громоздким.
Вот подробный пример того, где эмулятору не нужно читать каждый бит / байт программы ввода, чтобы эмулировать его.
Допустим, нам известен API, написанный на C или C++ (подробности не важны) для эмулируемой программной среды, где этот API имеет функцию void playSound(string fileName)
. Допустим, мы знаем, что семантика этой функции заключается в том, чтобы открыть файл на диске, прочитать его содержимое, выяснить, в какой кодировке находится файл (MP3? WAV? что-то еще?), чтобы затем воспроизвести его на динамиках с обычной / ожидаемой частотой дискретизации и высотой тона. Если мы прочтем из нативного кода набор инструкций, который говорит: «войдите в процедуру playSound, чтобы начать воспроизведение звука /home/hello/foo.mp3
», мы можем прекратить чтение программного кода прямо здесь и использовать наш собственный ( оптимизированная!) обычная процедура открытия этого звукового файла и его воспроизведения. Нужно ли следовать эмулируемому процессору на уровне инструкций? Нет, на самом деле, нет, если мы верим, что знаем, что делает API.
Дикая разница возникает! (проблема в земле высокого уровня)
Конечно, читая кучу инструкций и "выводя" высокоуровневый план выполнения, как в примере выше, вы рискуете не точно подражать поведению исходной программы, работающей на исходном оборудовании. Скажем, например, исходное оборудование могло иметь аппаратные ограничения, которые позволяли ему воспроизводить только 8 звуковых файлов одновременно. Что ж, если ваш новый компьютер может одновременно воспроизводить 128 звуковых файлов, и вы эмулируете процедуру playSound
на высоком уровне, что может помешать вам воспроизводить более 8 звуковых файлов одновременно? Это может вызвать ... странное поведение (в лучшую или худшую сторону) в эмулированной версии программы. Эти случаи могут быть решены путем тщательного тестирования или, возможно, очень хорошего понимания исходной среды выполнения.
Например, DOSBox имеет функцию, которая позволяет преднамеренно ограничивать скорость выполнения эмулируемой программы DOS, поскольку некоторые программы DOS будут работать некорректно, если им будет разрешено работать на полной скорости; на самом деле они зависели от тактовой частоты процессора для выполнения с ожидаемой скоростью. Этот тип "функции", которая намеренно ограничивает среду выполнения, может быть использован для обеспечения хорошего компромисса между верностью выполнения (то есть обеспечением правильной работы эмулируемой программы) и эффективностью выполнения (то есть созданием представления программы, которая достаточно высокого уровня, чтобы его можно было эффективно эмулировать с минимумом отслеживания).