Вопросы, касающиеся реализации простого эмулятора ЦП

Исходная информация: В конечном счете, я хотел бы написать эмулятор реальной машины, такой как оригинальная Nintendo или Gameboy. Однако я решил, что мне нужно начать с чего-то гораздо более простого. Мой консультант/профессор информатики предложил мне спецификации очень простого воображаемого процессора, который он создал для эмуляции в первую очередь. Есть один регистр (аккумулятор) и 16 опкодов. Каждая инструкция состоит из 16 бит, первые 4 из которых содержат код операции, а остальные — операнд. Инструкции представлены в виде строк в двоичном формате, например, «0101 0101 0000 1111».

Мой вопрос: как в C++ лучше всего анализировать инструкции для обработки? Пожалуйста, помните о моей конечной цели. Вот некоторые моменты, которые я рассмотрел:

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

  2. Хотя я мог бы разобрать код операции как строку и обработать его, бывают случаи, когда инструкция в целом должна восприниматься как число. Код операции приращения, например, может изменить даже раздел кода операции инструкции.

  3. Если бы мне нужно было преобразовать инструкции в целые числа, я не уверен, как я мог бы анализировать только раздел кода операции или операнда в int. Даже если бы я перекомпилировал каждую инструкцию на три части, всю инструкцию как int, код операции как int и операнд как int, это все равно не решило бы проблему, так как мне, возможно, пришлось бы увеличивать целую инструкцию. а затем проанализируйте затронутый код операции или операнд. Более того, мне нужно написать функцию для выполнения этого преобразования, или есть какая-то библиотека для С++, в которой есть функция преобразования строки в «двоичном формате» в целое число (например, Integer.parseInt(str1, 2) в Java)?

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

Спасибо за любую помощь или совет, который вы можете предложить!


person Community    schedule 02.03.2010    source источник


Ответы (4)


Разберите исходный код в массив целых чисел. Этот массив является памятью вашего компьютера.

Используйте побитовые операции для извлечения различных полей. Например, это:

unsigned int x = 0xfeed;
unsigned int opcode = (x >> 12) & 0xf;

извлечет верхние четыре бита (здесь 0xf) из 16-битного значения, хранящегося в unsigned int. Затем вы можете использовать, например. switch(), чтобы проверить код операции и предпринять соответствующие действия:

enum { ADD = 0 };

unsigned int execute(int *memory, unsigned int pc)
{
  const unsigned int opcode = (memory[pc++] >> 12) & 0xf;

  switch(opcode)
  {
  case OP_ADD:
    /* Do whatever the ADD instruction's definition mandates. */
    return pc;
  default:
    fprintf(stderr, "** Non-implemented opcode %x found in location %x\n", opcode, pc - 1);
  }
  return pc;
}

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

person unwind    schedule 02.03.2010
comment
Я надеялся, что кто-нибудь упомянет подобную концепцию. Я никогда не использовал его раньше, поэтому мне придется провести больше исследований. Спасибо! - person ; 02.03.2010
comment
+1. Это основной подход, который вы должны использовать. Ключевым моментом здесь, Брэндон, относительно вашего вопроса является то, что для того, чтобы подойти к этому нормально, вам нужно получить машинный код, который будет массивом байтов в массиве, представляющем адресное пространство вашего виртуального компьютера. Затем, если инструкции редактируют память (код), вы не делаете ничего особенного, вы просто следуете инструкциям, и они должны делать правильные вещи внутри вашего большого массива виртуальной памяти. IOW, вам нужен как ассемблер (инструмент, который переводит текстовые строки в байты инструкций), так и эмулятор, то, что выполняет - person Ben Zotto; 02.03.2010
comment
Хм ... Почему вы и код операции с 0xf после сдвига? Мне кажется, что он вернет одно и то же значение с ним или без него. - person ; 03.03.2010
comment
Я реализовал вышеуказанный подход, и он отлично работает. Спасибо! (Однако я все еще не уверен в смысле & 0xf) - person ; 03.03.2010
comment
@Brandon: Маскировка (побитовая и ) не является строго необходимой, но добавляет определенную ясность. Это также защищает вас от вещей, которые могут произойти, если, например, были биты, установленные выше 16-ти младших битов в целочисленном значении памяти. Вы можете выжать эти вещи, например. использование неподписанных шорт и так далее, но проще перестраховаться. - person unwind; 03.03.2010

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

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

  2. Поскольку вы конвертируете инструкции в целые числа, эта проблема неактуальна.

  3. Чтобы разобрать разделы кода операции и операнда, вам нужно будет использовать сдвиг битов и маскирование. Например, чтобы получить код операции, вы маскируете верхние 4 бита и выполняете сдвиг вниз на 12 бит (instruction >> 12). Вы также можете использовать маску для получения операнда.

  4. Вы имеете в виду, что в вашей машине есть инструкции, которые сдвигают биты? Это не должно влиять на то, как вы храните операнды. Когда вы приступите к выполнению одной из этих инструкций, вы можете просто использовать операторы битового сдвига C++ << и >>.

person Nick Meyer    schedule 02.03.2010
comment
Относительно 1: в этом случае мне все равно придется перекомпилировать набор инструкций перед их выполнением, а не выполнять их по мере их чтения, верно? Это не проблема, мне просто интересно, может ли быть хороший способ реализовать метод без перекомпиляции. // Относительно 4: Верно, это я и имел в виду. Я думал, что это повлияет на то, как я буду хранить части инструкций, так как мне придется рассматривать каждую часть отдельно. Но эта битовая математика кажется, что она может достичь этого. // То, что вы упомянули, концептуально имеет смысл. Попробую сегодня, когда буду дома. Спасибо! - person ; 02.03.2010
comment
Я не знаю, что вы подразумеваете под перекомпилированием здесь. (Простой) ЦП ничего не перекомпилирует: у него есть биты, и он делает то, что они говорят. Можете пояснить, что вы имеете в виду? - person Ken; 02.03.2010
comment
Насколько я понимаю, эмуляторы часто работают, перекомпилируя инструкции в другой формат, который может быть легче обработан машиной эмулятора. Я использую этот термин вольно здесь, но я думаю, что это та же самая идея. Мне просто интересно, есть ли эффективный способ выполнить инструкции без необходимости хранить инструкции где-то еще в памяти в другой форме (например, как беззнаковые целые числа). - person ; 02.03.2010
comment
Я спросил о выполнении инструкций без их сохранения в массиве, потому что программы (хотя и не конкретно для этой машины) могут оказаться довольно большими, и их хранение потребует много памяти. // Ник, я тоже не уверен, что понимаю смысл & 0xF000 здесь. Вроде без него работает. - person ; 03.03.2010
comment
@ Брэндон, ты прав, 0xF000 не нужен. Что он делает, так это маскирует младшие 12 бит, устанавливая их все в 0, но тогда сдвиг все равно отбрасывает их. Виноват. - person Nick Meyer; 03.03.2010

На всякий случай, вот последний эмулятор процессора, который я написал на C++. На самом деле, это единственный эмулятор, который я написал на C++.

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

http://www.boundvariable.org/um-spec.txt

Вот мой (несколько переработанный) код, который должен дать вам некоторые идеи. Например, он показывает, как реализовать математические операторы в выражении Giant Switch в um.cpp:

http://www.eschatonic.org/misc/um.zip

Возможно, вы сможете найти другие реализации для сравнения с веб-поиском, так как в конкурсе участвовало много людей (я не был одним из них: я сделал это намного позже). Хотя, я думаю, не так много на С++.

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

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

Чтобы выбрать между ними, нужно посмотреть код операции, который используется для изменения программы. Подается ли ему новая инструкция как целое число или как строка? Что бы это ни было, проще всего начать с сохранения программы в этом формате. Вы всегда можете изменить позже, как только он заработает.

В случае описанной выше единой системы обмена сообщениями машина определяется в терминах «пластин» с пространством для 32 бит. Ясно, что они могут быть представлены в C++ как 32-битные целые числа, что и делает моя реализация.

person Steve Jessop    schedule 02.03.2010
comment
Прежде чем я поговорил со своим профессором, я искал что-то простое для подражания и нашел это соревнование. Это было все еще слишком сложно для меня, чтобы начать, но это может быть моим следующим шагом. Я просмотрю ваш код для идей. Спасибо! - person ; 03.03.2010

Я создал эмулятор для собственного криптографического процессора. Я использовал полиморфизм C++, создав дерево базовых классов:

struct Instruction  // Contains common methods & data to all instructions.
{
    virtual void execute(void) = 0;
    virtual size_t get_instruction_size(void) const = 0;
    virtual unsigned int get_opcode(void) const = 0;
    virtual const std::string& get_instruction_name(void) = 0;
};

class Math_Instruction
:  public Instruction
{
  // Operations common to all math instructions;
};

class Branch_Instruction
:  public Instruction
{
  // Operations common to all branch instructions;
};

class Add_Instruction
:  public Math_Instruction
{
};

У меня также было несколько заводов. Было бы полезно как минимум два:

  1. Фабрика для создания инструкции из текста.
  2. Фабрика для создания инструкции из кода операции

Классы инструкций должны иметь методы для загрузки своих данных из источника ввода (например, std::istream) или текста (std::string). Также должны поддерживаться сопутствующие методы вывода (например, имя инструкции и код операции).

Я заставил приложение создавать объекты из входного файла и помещать их в вектор Instruction. Метод executor будет запускать метод 'execute()` каждой инструкции в массиве. Это действие просочилось к объекту-листу инструкции, который выполнил детальное выполнение.

Существуют и другие глобальные объекты, которые также могут нуждаться в эмуляции. В моем случае некоторые включали шину данных, регистры, АЛУ и ячейки памяти.

Пожалуйста, потратьте больше времени на проектирование и обдумывание проекта, прежде чем писать код. Я нашел это довольно сложной задачей, особенно реализовать отладчик с поддержкой single-step и графический интерфейс.

Удачи!

person Thomas Matthews    schedule 02.03.2010
comment
Кстати, в моей альма-матер один из профессоров поручил своим студентам написать функции для эмуляции аппаратных компонентов для класса Проектирование микропроцессоров. У нас было несколько хороших дискуссий об эмуляции процессора. :-) - person Thomas Matthews; 02.03.2010