Справочная информация: В конечном счете я хотел бы записать эмулятор реальной машины, такой как исходная Nintendo или Gameboy. Однако я решил, что должен запустить где-нибудь очень, намного более простой. Мой советник/преподаватель по вопросам информатики предложил мне спецификации для очень простого мнимого процессора, который он создал для эмуляции сначала. Существует один регистр (аккумулятор) и 16 кодов операций. Каждая инструкция состоит из 16 битов, первые 4 из которых содержат код операции, остальная часть, которая является операндом. Инструкции даны как строки в двоичном формате, например, "0101 0101 0000 1111".
Мой Вопрос: В C++, что лучший способ состоит в том, чтобы проанализировать инструкции для обработки? Помните о моей конечной цели. Вот некоторые вопросы, которые я рассмотрел:
Я не могу только обработать и выполнить инструкции, когда я считал их, потому что код самоизменяет: инструкция может изменить более позднюю инструкцию. Единственный путь I видит для двигений, это должно было бы сохранить все изменения и для каждой инструкции проверить, должно ли изменение быть применено. Это могло привести к значительные суммы сравнений с выполнением каждой инструкции, которая не хороша. И так, я думаю, что должен перекомпилировать инструкции в другом формате.
Хотя я мог проанализировать код операции как строку и обработать его, существуют экземпляры, где инструкция в целом должна быть взята в качестве числа. Инкрементный код операции, например, мог изменить даже раздел кода операции инструкции.
Если я должен был преобразовать инструкции в целые числа, я не уверен затем, как я мог проанализировать просто код операции или раздел операнда интервала, Даже если бы я должен был перекомпилировать каждую инструкцию в три части, целую инструкцию как интервал, код операции как интервал и операнд как интервал, который все еще не решит проблему, поскольку мне, возможно, придется увеличить всю инструкцию и более поздний синтаксический анализ затронутый код операции или операнд. Кроме того, я должен был бы записать функцию для выполнения этого преобразования или являюсь там некоторой библиотекой для C++, который имеет функциональное преобразование строка в "двоичном формате" к целому числу (как Integer.parseInt (str1, 2) в Java)?
Кроме того, я хотел бы смочь выполнить операции, такие как смещающиеся биты. Я не уверен, как это может быть достигнуто, но это могло бы влиять, как я реализую эту перекомпиляцию.
Спасибо за любую справку или совет можно предложить!
Разберите исходный код на массив целых чисел. Этот массив - память вашего компьютера.
Используйте побитовые операции для извлечения различных полей. Например, это:
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;
}
Модификация памяти - это просто запись в ваш массив целых чисел, возможно, с использованием побитовой математики, если это необходимо.
На случай, если это поможет, вот последний эмулятор ЦП, который я написал на C ++. Собственно, это единственный эмулятор, который я написал на C ++.
Язык спецификации немного своеобразен, но это вполне респектабельное, простое описание виртуальной машины, возможно, очень похожее на виртуальную машину вашего профессора:
http://www.boundvariable.org/um-spec.txt
Вот мой (несколько перестроенный) код, который должен дать вам некоторые идеи. Например, он показывает, как реализовать математические операторы, в инструкции Giant Switch в um.cpp:
http://www.eschatonic.org/misc/um.zip
Вы можете найти другие реализации для сравнения с веб-поиск, поскольку в конкурсе приняло участие множество людей (я не был одним из них: я сделал это намного позже). Хотя, наверное, не так много в C ++.
На вашем месте я бы для начала сохранял инструкции только в виде строк, если это способ, которым ваша спецификация виртуальной машины определяет операции над ними. Затем преобразуйте их в целые числа по мере необходимости, каждый раз, когда вы хотите их выполнить. Это будет медленно, ну и что? У вас не настоящая виртуальная машина, которую вы собираетесь использовать для запуска критичных по времени программ, и медленный интерпретатор все еще иллюстрирует важные моменты, которые вам нужно знать на этом этапе.
Однако возможно, что виртуальная машина фактически определяет все в терминах целых чисел, а строки служат только для описания программы, когда она загружается в машину. В этом случае сначала преобразуйте программу в целые числа. Если виртуальная машина хранит программы и данные вместе с одинаковыми операциями, действующими на обоих, то это правильный путь.
Способ выбора между ними - это посмотреть на код операции, который используется для изменения программы. Предоставляется ли новая инструкция в виде целого числа или в виде строки? Что бы это ни было, проще всего начать с того, чтобы сохранить программу в этом формате. Вы всегда можете изменить его позже, когда он заработает.
В случае UM, описанного выше, машина определяется в терминах «пластин» с пространством для 32 битов. Ясно, что они могут быть представлены в C ++ как 32-битные целые числа, что и делает моя реализация.
Я создал эмулятор для специального криптографического процессора. Я использовал полиморфизм 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
{
};
У меня также было несколько фабрик. Было бы полезно по крайней мере два:
Классы инструкций должны иметь методы для загрузки своих данных из источника ввода (например, std :: istream
) или текста ( std :: string
). Методы вывода следствия также должны поддерживаться (например, имя инструкции и код операции).
Я попросил приложение создать объекты из входного файла и поместить их в вектор инструкции
. Метод исполнителя будет запускать метод execute () для каждой инструкции в массиве. Это действие перешло к объекту листа инструкции, который выполнил подробное выполнение.
Есть и другие глобальные объекты, которые также могут нуждаться в эмуляции. В моем случае некоторые из них включали шину данных, регистры, ALU и ячейки памяти.
Пожалуйста, потратьте больше времени на разработку и обдумывание проекта, прежде чем писать его. Мне это показалось довольно сложной задачей, особенно реализация одношагового
отладчика и графического интерфейса пользователя.
Удачи!
Я думаю, что лучший подход - это прочитать инструкции, преобразовать их в целые числа без знака и сохранить их в памяти, а затем выполнить их из памяти.
Как только вы разобрали инструкции и сохранили их в памяти, самомодификация намного проще, чем хранение списка изменений для каждой инструкции. Вы можете просто изменить память в этом месте (предполагая, что вам никогда не понадобится знать, какой была старая инструкция).
Поскольку вы преобразуете инструкции в целые числа, эта проблема отпадает.
Чтобы разобрать секции опкода и операнда, необходимо использовать сдвиг битов и маскирование. Например, чтобы получить операционный код, нужно замаскировать верхние 4 бита и сдвинуть вниз на 12 бит (instruction >> 12
). Маску можно использовать и для получения операнда.
Вы имеете в виду, что в вашей машине есть инструкции, которые сдвигают биты? Это не должно влиять на то, как вы храните операнды. Когда вы перейдете к выполнению одной из таких инструкций, вы можете просто использовать операторы сдвига битов C++ <<
и >>
.