Эмуляторы анализируют двоичный код в файлах?

3

Я видел некоторые эмуляторы, которые утверждают, что они выполняются, и, хотя они делают, их исходный код показывает, что они не анализируют напрямую каждые 1 и 0, чтобы определить инструкцию.

Мой вопрос заключается в том, что если эмулятор должен эмулировать точные коды операций, которые были бы у реального процессора, разве не требовалось бы анализировать правильный двоичный формат кода операции в игре, чтобы эмулировать процессор законно (или вообще)?

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

0000 1111

Моя программа должна проверить, что эта инструкция действительно означает (например, «добавить один в регистр A»), но разве ей не нужно проверять каждый ноль и один в текстовом файле, чтобы убедиться в этом?

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

Например, 0000 1111 может означать добавить один к A, но 0000 1110 может означать добавить A с A.

bertieb
источник
Вы ошибочно полагаете, что программы должны работать с отдельными битами. Они этого не делают - об этом уже заботится процессор. Программы видят целые байты и обрабатывают их как числа.
Гравитация
Но «целые байты» имеют различные комбинации значений. Вы должны сравнить это значение, чтобы убедиться, что это правильный код операции. И вы можете сравнить каждый бит в файле ROM, используя fstream с C ++. Вот почему я спросил ... это не так просто, как вы говорите.
@grawity Каждая инструкция эмулятора основана на существующем наборе команд и форматировании кода операции. Я понимаю, что вы можете проанализировать его в шестнадцатеричном виде, так как он относится к двоичному формату (исключая точность реального 1: 1 с переключением битов), но настоящий процессор не знает, что означает «шестнадцатеричный». Реальный ЦП принимает входные данные от тока и генерирует выходные данные, как правило, из микропрограммы, которая изменяет логические элементы в схеме. Парсинг hex пропускает это, так что вы на самом деле не будете эмулировать его реальные операции до этого уровня. Вы также путаете реальный процессор с эмулированным. Пожалуйста, подтвердите информацию.
Каждый код операции в наборе команд оценивается как целое число. 8-битный компьютер будет иметь инструкции с числовым значением от 0 до 255. Очевидно, что 16-битный компьютер может иметь больший диапазон кодов операций, чем 8-битный. Все, что нужно сделать эмулятору, - это сравнить число со списком «известных» инструкций для процессора, который он эмулирует. Для этого он будет использовать функции сравнения любого языка, на котором он написан (обычно это c или c ++). Там нет необходимости разбирать биты.
Ксавье Дж
1
Вы слишком усложняете проблему, которая на самом деле довольно проста. Эмулятору не нужно анализировать инструкции «в шестнадцатеричном» или «двоичном», это просто способы отображения чисел для чтения людьми. Хотя инструкции в памяти хранятся в виде последовательности битов, программное обеспечение (включая эмуляторы) сможет сравнивать всю инструкцию как число. Проверка того, что символ "x == 0x0F" по сути такой же, как проверка каждого бита числа. Эмулятору не нужно проверять каждый бит, потому что центральный процессор предоставляет инструкции для этого.
Джереми Стурдивант

Ответы:

6

Экспозиция - пытаюсь прямо ответить на вопрос

Если вы читаете исходный код для эмулятора, а он не читает определенные биты двоичного (исполняемого) файла и все еще добросовестно выполняет код, то возможны три результата:

  1. Вы ошибаетесь, думая, что эмулятор не читает каждый бит файла, а на самом деле это делает , и вы просто ошибаетесь.
  2. Вы правы , и эмулятор не читает каждый бит, потому что он может предположить определенные факты о поведении программы, которую он эмулирует, чтобы не нужно было читать каждый бит, чтобы знать, что ему нужно делать (возможно, потому что он ожидает запуска определенного игрового движка, или определенного типа графического API, или определенного типа звукового API, и т. д.).
  3. Вы правы , и эмулятор не читает каждый бит, потому что есть определенные биты исполняемого файла, которые просто не нужны для правильного выполнения программы. Это могут быть устаревшие «меткие», или метаданные, или что-то еще, что является просто лишним пухом, который на самом деле не состоит из функциональности программы.
  4. Вы правыи эмулятор не читает каждый бит, потому что эмулятор преобразует определенные операции в коде в операции более высокого уровня и полностью игнорирует низкоуровневые, специфичные для процессора / аппаратного обеспечения инструкции. Например, если вас попросят точно имитировать то, что человек делает на видеозаписи этого человека, выполняющего сложную операцию, и они скажут: «Теперь просверлите отверстие в боковой части коробки», у вас будет искушение остановиться. смотреть видео и использовать имеющийся у вас опыт того, как сверлить дыры в вещах, вместо того, чтобы следовать буквальным движениям парня в видео (при условии, что вы обладаете надлежащим упражнением и, как правило, имеете опыт в жизни). Точно так же, если эмулятор может сделать вывод, что программа просит нарисовать изображение 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 будут работать некорректно, если им будет разрешено работать на полной скорости; на самом деле они зависели от тактовой частоты процессора для выполнения с ожидаемой скоростью. Этот тип «функции», которая намеренно ограничивает среду выполнения, может быть использован для обеспечения хорошего компромисса между верностью выполнения (то есть обеспечением правильной работы эмулируемой программы) и эффективностью выполнения (то есть созданием представления программы, которая достаточно высокого уровня, чтобы его можно было эффективно эмулировать с минимумом отслеживания).

allquixotic
источник
Я чувствую, что должен отметить, что Wine не является эмулятором;)
Джереми Стурдивант
Ах, но это так! Просто очень эффективный, и не эмулятор инструкций x86 , а эмулятор операционной среды Windows . Это, конечно, эмулятор на некотором уровне, но не на том уровне, о котором люди обычно думают, когда слышат термин «эмулятор» (большинство людей думают о выполнении буквальных инструкций ЦП иностранного ЦП ABI, что является лишь одной из возможностей многих типов / уровней эмулятора).
allquixotic
На самом деле вы правы, и на самом деле первоначальное значение названия «wine» было «WINdows Emulator», однако название проекта в настоящее время является рекурсивной аббревиатурой, приведенной выше, независимо от его точности.
Джереми Стурдивант
1

Мой вопрос заключается в том, что если эмулятор должен эмулировать точные коды операций, которые были бы у реального процессора, разве не требовалось бы анализировать правильный двоичный формат кода операции в игре, чтобы эмулировать процессор законно (или вообще)?

«Разбор» означает «просмотреть текст и выяснить, что это значит». Текстовый и языковой синтаксис сложен, потому что не только отдельные «слова» означают вещи, но и значение может меняться в зависимости от их положения и близости к другим словам.

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

Моя программа должна проверить, что эта инструкция действительно означает (например, «добавить один в регистр A»), но разве ей не нужно проверять каждый ноль и один в текстовом файле, чтобы убедиться в этом?

Процессоры на самом деле не проверяют инструкции. Любой ЦП имеет внутренний регистр, который указывает на область памяти в ОЗУ. Процессор читает его, считывает еще пару байтов, если необходимо, и затем пытается выполнить его. Современные процессоры запустят «процесс исключения», если инструкция недопустима.

Старые процессоры, как и старый 8-битный 6502, даже не делали этого - некоторые незаконные инструкции блокировали процессор, другие делали странные, недокументированные вещи. (Некоторый поиск покажет ряд недокументированных x86-инструкций, найденных во всех процессорах в x86-пантеоне - например, CPUIDинструкция существовала на некоторых 486-ти процессорах до того, как Intel официально зарегистрировала ее.)

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

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

LawrenceC
источник
Конечно, из-за неразрешимости проблемы остановки невозможно, чтобы виртуальный процессор выполнял любую последовательность произвольных инструкций, чтобы центральный процессор во всех случаях мог предотвратить его блокировку, потому что хост может ' • определить, остановится ли виртуальный ЦП, если только он сам не отследит код виртуального ЦП; в этом случае хост может не остановиться (в основном он высасывает кроличью нору с бесконечной сингулярностью рядом с виртуальным ЦП как закон теории информации). :)
allquixotic
Вы можете сделать некоторые «хакерские» вещи, такие как указание эмулятору игнорировать исключения процессора, а что нет. Но тогда ваш эмулятор уже не на 100% точен.
LawrenceC