Сообщения без ответов | Активные темы Текущее время: 28 апр 2024, 08:15



Ответить на тему  [ Сообщений: 4 ] 
 [АРХИВ] Интерпретатор 
Автор Сообщение
Сообщение 15 авг 2007, 16:11
Профиль
Аватара пользователя

Зарегистрирован:
06 авг 2007, 07:46
Сообщения: 105
Автор: Wind - 26 Dec 2004.

Эмуляцию любой системы принято начинать с эмуляции процессора.
Эмуляция процессора – в чем же она заключается?
Каждый человек, знакомый с языком ассемблер, знает, что процессор имеет набор строго определенных команд. Эти команды разные для разных процессоров (fixme). Так вот для того, чтобы
«заэмулировать» процессор необходимо каждую команду эмулируемого процессора исполнить с помощью команд процессора, для которого предназначен эмулятор.
(lynx: дополнить чётким примером, аля X1 = Y1 && Y2, а X2 = Y3).

Эмуляция разбивается на две стадии: декодирование команд и их исполнение.

Декодирование
Прежде чем эмулировать саму команду, необходимо узнать, а что это за команда. Каждая команда представляет собой некую бинарную (двоичную) последовательность, декодировав которую мы получим саму команду.
Рассмотрим процесс декодирования команд на примере Hitachi SH4 (этот процессор используется в SEGA DREAMCAST).
SH4 имеет постоянную длину команды в памяти, а именно 16 бит (2 байта).

Например:
MOVI закодирована, как 0хЕ000 (E0 00 - два байта, записанных в шестнадцатиричном формате). Эта команда загружает в регистр «некое» значение, где первые 4 бита (пол байта) означают, что это - команда MOVI:
E0 hex -> 11100000 bin, следовательно команда обозначается последовательностью 1110

MOV закодирована, как 0х6003 (эта команда загружает в регистр-приемник значение регистра-источника),
где первые 4 бита (0х6) и последние 4 бита (0х3) означают, что это команда MOV

MULL закодирована, как 0х0007 (эта команда перемножает два регистра и помещает нижние 32бита результата в регистр MACL),
где первые 4 бита (0х0) и последние 4 бита (0х7) означают, что это команда MULL

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


Поэтому для декодирования команд удобно использовать таблицы перехода (look up tables). Код:

void (*OpcodeTableAll[16]) () =
{
Opcode0, MOVLS4, Opcode2, Opcode3,
Opcode4, MOVLL4, Opcode6, ADDI,
Opcode8, MOVWI, BRA, BSR,
OpcodeC, MOVLI, MOVI, OpcodeF
};



Вот так выглядит таблица для первых 4бит

Код:

for(;; )
{
sh4.code = memRead16(sh4.pc);
OpcodeTableAll[sh4.code >> 12 & 0xf]();
}



А это - цикл, в котором происходит чтение из памяти очередных 16 бит, где переменная sh4.pc содержит «адрес» чтения очередной порции данных.
В случае, если значение равно 0хЕ000, то переход произойдет на инструкцию MOVI,
а в случает 0х6003 на функцию Opcode6, которая в свою очередь, используя очередную таблицу, произведет переход на инструкцию MOV. Так происходит и со всеми остальными инструкциями.

Исполнение
После декодирования инструкции мы попадаем в функцию, которая и будет её эмулировать.

Рассмотрим на примере:
Встретив последовательность 0хЕ000 мы попадем в функцию MOVI.
В этой команде осталась еще три 4 битовых поля (полтора байта, 12 байт), о которых не было упомянуто.

Итак:
Вторые 4 бита (0х0) означают номер регистра, в который нужно произвести загрузку значения. В данном случае в R0.
Остальные биты содержат значение, которое будет загружено в R0, предварительно SIGN-расширено до 32бит т.е. если установлен лидирующий бит (0х80) то будет загружено 0xffffff80, в обратном случае (0х00) то будет загружено 0х00000000.

Пример этой команды на языке C. Код:

reg[sh4.code >> 8 & 0xf] = (long) sh4.code & 0xff;



Пример этой команды на ASM х86. Код:

mov eax, sh4.code
movsx edx, al
shr eax, 8
and eax,0xf
mov reg[eax*4], edx



где reg - массив 32-битных элементов, кол-во элементов соответствует кол-ву регистров.

В конце функциии увеличиваем sh4.pc на 2 (байта, ширина команды, как описано выше) и возвращаемся в цикл, где декодируем/исполняем следующую команду...

Как можно понять из примера - на простейшую команду понадобилось 5 команд (5 ASM строчек) + декодирование. Это является основной причиной такой медлительности эмуляторов.

_________________
ARE YOU LIVING IN THE REAL WORLD?...


Последний раз редактировалось Keyman 15 авг 2007, 22:57, всего редактировалось 1 раз.



Сообщение 15 авг 2007, 16:17
Профиль
Аватара пользователя

Зарегистрирован:
06 авг 2007, 07:46
Сообщения: 105
Admin
Полагаю, надо привести так же пример эмуляции пары опкодов 6502, да таких, которые напрямую можно замапить на x86.
Т.к. сказать почувствуйте разницу - сложность архитектур. Плюс рассказать о кол-ве опкодов в разных процессорах и их сложности.

Думаю, org/RomikB пособили бы в описании GB/PSX/GC.

Admin
Подредактировал, великлопено!
Меня вот интересует вопрос - в конце sh4.pcp была опечаткой?

Wind
Да канешно это опечатка, там их наверно много просто сразу не заметишь все

Admin
Я пофиксил всё что мог :)
Оч. хорошо получилось, до полноценной статьи недалеко, надо разжеать сильней - это я сделаю, когда время появится. И показать разницу со старыми системами и их простой комманд.

Wind
Я еще могу попробовать написать статью про динамическую рекомпиляцию, раз уж сам никакого эмулятора не написал, попробую помочь другим это сделать.
Только тебе снова придется ее долго и усердно редактировать. Так, что надо или нет?

Admin
Цитата:
Wind писал:
Я еще могу попробовать написать статью про динамическую рекомпиляцию, раз уж сам никакого эмулятора не написал, попробую помочь другим это сделать.
Только тебе снова придется ее долго и усердно редактировать. Так, что надо или нет?


Не вопрос, главное положить начало, а там мы дальше наворотим :)
Добрый, но колючий.

Shiru
Несколько замечаний на скорую руку.

"Эти команды разные для разных процессоров (fixme)."

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

Далее. Не всегда, не на всех процессорах возможно декодировать команду по битовым полям. Скажем, у того-же Z80 есть команды, которые выпадают из общей сетки.

Ещё можно упомянуть о недокументированных командах - незапланированных разработчиком CPU, они почти в каждом процессоре есть.

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

Потом поконкретнее и повнятнее напишу, сейчас голова никакая.

simplexe
ну так че продолжение с углублением будет?
АМ2 FX 62/2x2048 Mb DDR2 800/nF-590SLI/2xn7950GTX 512 Mb/2x250 Gb SATA-2 16Mb хехе...

Wind
А по конкретней, о чем бы еще ты хотел почитать, я просто не знаю о чем еще написать.

simplexe
ну например интерпретатор состоит не только из декодирования и исполнения.... весь бы процесс в целом посмотреть....кстати офтоп а про мемори карты написать можешь?
АМ2 FX 62/2x2048 Mb DDR2 800/nF-590SLI/2xn7950GTX 512 Mb/2x250 Gb SATA-2 16Mb хехе...

Wind
А что там кроме декодирования и исполнения?
или ты хочешь узнать весь процесс эмуляции по шагам, ну хорошо:

1) Инициализируем память (считываем содержимое БИОСа, инициализируем таблицы)
2) Инициализируем процессор (присваиваем начальные значения регистрам)
3) Передаем управление на некую ф-ию Execute() (собственно с этого момента начинается эмуляция процессора, т.е. выше описанный процесс)
4) Вызов инструкций, которые оперируют с памятью, приводит нас в блок эмуляции памяти (по средствам вызова memWrite(), memRead()), далее проверяется, что за адрес на входе, если это оперативная память происходит присваивание/чтение, а если адрес на входе соответствует например адресам DMA для эмулируемой системы то эмулируется работа DMA, и так для каждого блока системы.
5) Вызов инструкций перехода обычно приводит к вызову некой ф-ии Cputest(), которая проверяет не произошло прерывание, и не пора ли нарисовать очередной кадр.

Вообщем это в двух словах, но этого достаточно для понимания принципа построения эмулятора, а вообще взгляни на сырцы PCXS/PCSX2 очень толково написаны.

про мемори карты – это ты о тех в которых савы хранятся или об адресах памяти?

simplexe
про сэйвы...
АМ2 FX 62/2x2048 Mb DDR2 800/nF-590SLI/2xn7950GTX 512 Mb/2x250 Gb SATA-2 16Mb хехе...

Wind
А чо они про них написать-то, я признаюсь честно никогда не эмулировал их, но там должно быть все довольно просто - это всего-лишь кусок памяти. Если адрес на входе memWrite()/memRead() попадает в диапазон адресов отвечающих за "мемори карт" то происходит просто запись/чтение.
Может быть и что-то более сложное, вобщем все зависит от системы, а тебе оно вобще зачем, "сэйвы" эмулируют в последнюю очередь если вобще эмулируют.

Shiru

Не, не надо путать тёплое с мягким. Есть сейв-стейты. Это сохранённое состояние системы - вся память, все регистры процессора, и регистры/память прочих устройств.

А сейвы, которые мемори-карты - типа memory card от Playstation - это внешнее устройство, флешка с последовательным доступом, например. Со стороны системы её обслуживает контроллер. Его и надо эмулировать (как - зависит от устройства контроллера).

_________________
ARE YOU LIVING IN THE REAL WORLD?...


Сообщение 15 авг 2007, 16:23
Профиль
Аватара пользователя

Зарегистрирован:
06 авг 2007, 07:46
Сообщения: 105
Wind
А я ничего не путаю я же написал, что в общем случе это просто чтение/запись, но в конечном счете это зависит от конкретной сиситемы, вплоть до контролера о котором ты говоришь.

simplexe
ну сейв стейты понятно, а мемори я так понял флэш память (у ЗЫЧ2 по моему SD), вот про нее собственно и вопрос был...вот на примере PCSX2 у них значит файлы мемори карт сами не создаются (нужно создавать пустые файлы) но при эмуляции биос их форматит и превращает в рабочие карты (точно не известно т.к. неначем тестить)...так вот хотелось бы узнать структуру файла...смотрел сорцы PCSX2 и не нашел работу с мемори картой
АМ2 FX 62/2x2048 Mb DDR2 800/nF-590SLI/2xn7950GTX 512 Mb/2x250 Gb SATA-2 16Mb хехе...

Wind
За карты отвечает SIO.c.

simplexe
ок..посмотрю...
то что надо спасибо =)

Admin
У Sony не может быть SD.
Там своя память.

simplexe
своя, но какая?
АМ2 FX 62/2x2048 Mb DDR2 800/nF-590SLI/2xn7950GTX 512 Mb/2x250 Gb SATA-2 16Mb хехе...

Admin
"Sony" :)

simplexe
блин..не знаю...но ИМХО глупо разрабатывать свой формат если есть куча других =)...и как интересно китайские девелоперы получили лицензию на разработку =)?
АМ2 FX 62/2x2048 Mb DDR2 800/nF-590SLI/2xn7950GTX 512 Mb/2x250 Gb SATA-2 16Mb хехе...

R4kk00n
А какие микросхемы стоят в палках Memory Stick? Почему бы в картах памяти не быть таким же? Сделаны-то они, один хрен, Samsung'ом, Hynix'ом, AMD или Infineon'ом...
Фукинские драконы!

Shiru

Цитата:
simplexe писал:
блин..не знаю...но ИМХО глупо разрабатывать свой формат если есть куча других =)...и как интересно китайские девелоперы получили лицензию на разработку =)?


Ты чего?:))) Глупо как раз НЕ разрабаотать свой формат - денег дополнительных не получить, а за использование ещё могут их и попросить. А так - сделал из стандартных чипов нестандартный девайс, и продаёшь втридорога ;)

simplexe
неа глупо че не гри...скорее всего нету там своего формата блин а карту надо разобрать какю нить....
АМ2 FX 62/2x2048 Mb DDR2 800/nF-590SLI/2xn7950GTX 512 Mb/2x250 Gb SATA-2 16Mb хехе...

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

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

НАЧАЛО
ВЫБОРКА ИНСТРУКЦИИ ПО АДРЕСУ СЧЕТЧИКА КОМАНД
ДЕКОДИРОВАНИЕ
ИСПОЛНЕНИЕ

ПРОВЕРКА ИСКЛЮЧЕНИЙ
ИЗМЕНЕНИЕ СЧЕТЧИКА КОМАНД

ПЕРЕЙТИ НА НАЧАЛО



Если во время исполнения инструкции возникли какие-то исключения центрального процессора, то управление передается обработчику исключения.

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

Далее у Wind всё нормально, но пропустил этап выборки инструкции:

Выборка. Процессор: на данном этапе центральный процессор считывает данные инструкции из памяти. В некоторых процессорах размер инструкции постоянный (RISC), а в некоторых - переменный (x86). Поэтому из памяти считывается максимально возможное количество байт, которые могут потребоваться на этапе декодирования. Интерпретатор: используя модуль эмуляции памяти, считываем необходимое количество байт. Иногда требуется перевернуть порядок байт, так как разные процессоры по разному определяют "байт номер 0". К примеру на Intel слово 0xAABB будет записано в памяти по байтам как: [BB] [AA], а в PowerPC как: [AA] [BB].

Можно как-нибудь объединить всё это с описанием Wind'а..
0 error(s), 0 warning(s)

Organic
Хм, что-то я не вижу, где здесь байты у value переворачиваются. Пример: в памяти находится строка 'Wind'. Если твоим методом её прочитать на Intel, то получится 'dniW', если на PowerPC, то 'Wind'..
И метод доступа тоже странный.. Зачем memory указывает на конец памяти, и почему бы не использовать : *(uX *)(&memory[mem]) = value ?

добавлено спустя 3 минуты:

опа.. Wind, ты чего это пост удалил ? =)
0 error(s), 0 warning(s)

Wind
Цитата:
Цитата:
Хм, что-то я не вижу, где здесь байты у value переворачиваются. Пример: в памяти находится строка 'Wind'. Если твоим методом её прочитать на Intel, то получится 'dniW', если на PowerPC, то 'Wind'..
И метод доступа тоже странный.. Зачем memory указывает на конец памяти, и почему бы не использовать : *(uX *)(&memory[mem]) = value ?



Весь смысл в том, что вся память первернута, начало в конце, а конец в начале, я тоже когда впервые, когда это увидел, не сразу понял, но потом убедился, что это работает и работает хорошо, поробуй поэскперементировать и убедишся
Цитата:
Цитата:
опа.. Wind, ты чего это пост удалил ? =)



Да как ты мог заметить я его не в тот топик направил, так что просто перенес в память.

_________________
ARE YOU LIVING IN THE REAL WORLD?...


Сообщение 15 авг 2007, 16:27
Профиль
Аватара пользователя

Зарегистрирован:
06 авг 2007, 07:46
Сообщения: 105
Serke
Прошу прощения за эксгумацию темы, но хочу заметить, что обработку опкодов можно организовать при помощи таблиц с адресами переходов, а не таблиц с адресами вызываемых функций, как было показано в первом примере. Теоретически это должно приводить к генерированию более эффективного кода, однако практически, в чем можно убедиться, просматривая сгенерированные компилятором ассемблерные листинги, выигрыш не велик по причине того, что современные компиляторы с грамотно подобранными параметрами оптимизации и в первом случае генерируют весьма эффективный код – например, удаляют неиспользуемые указатели на стековый фрейм, в результате чего скелет функции, как и в случае, если бы код писался на ассемблере, выглядит как

[call function_name]

function_name:

ret

Впрочем, в случае с таблицами, содержащими адреса переходов, листинг эмулятора, на мой взгляд, смотрится “правильнее”, поскольку выглядит более по-ассемблерному :). Минус вышеназванного подхода – меньшая портабельность кода (насколько мне известно, возможность переходов по адресам меток реализована исключительно GCC-компиляторами (DJGPP, GCC, MinGW, Cygwin и иже с ними). Пример, иллюстрирующий вышесказанное (аналогичный примеру в первом посте):
Код:

#define NEXT_INSTRUCTION goto next_iteration;
typedef unsigned char BYTE;
typedef unsigned short WORD;

WORD memRead16(BYTE *linear_addr);
struct sh4_record {
WORD code;
BYTE *pc;
//blah-blah-blah :)
} sh4;
long reg[16];

int main()
{
static void *OpcodeTableAll[16] =
{
&&OpcodeDummy, &&OpcodeDummy, &&OpcodeDummy, &&OpcodeDummy,
&&OpcodeDummy, &&OpcodeDummy, &&OpcodeDummy, &&OpcodeDummy,
&&OpcodeDummy, &&OpcodeDummy, &&OpcodeDummy, &&OpcodeDummy,
&&OpcodeDummy, &&OpcodeDummy, &&MOVI, &&OpcodeDummy
};

for(;;)
{
sh4.code = memRead16(sh4.pc);
sh4.pc += 2;
goto *OpcodeTableAll[ sh4.code >> 12 & 0xf ];
next_iteration:;
}
return;

MOVI:
reg[sh4.code >> 8 & 0xf] = (long) sh4.code & 0xff;
NEXT_INSTRUCTION

OpcodeDummy:
NEXT_INSTRUCTION
}

WORD memRead16(BYTE *linear_addr)
{
return *(WORD *)linear_addr;
}



Как видно из листинга, переход на обработчики опкодов осуществляется командой goto (господа гусары от структурного программирования, молчать! :), адрес метки берется унарным оператором &&. Значение имеет тип void * и является константой.

Wind
Ты сам сказал, что современые компиляторы генерируют для описаного мной способа весьма эффективный, а как говорится от добра добра не ищут. Не в моих традициях отходить от ANSI C, особенно когда этого не требует ситуация.

Я уж не говорю, что GCC впринципе не способен скомпилировать эфективного кода.

Pablo
pcsx2 скомпиленный в MS visual C++ медленнее того же скомпиленного на GCC
фильм The Perfect World:
http://perfectworld.h15.ru

Wind
Все зависит от опций компиляции, при правильных опциях MS visual C++ ver 7.1 и выше выигрывает всегда у GCC, как говорится за явным примуществом.
В сырцах pcsx2 встречаются асемблерные вставки написаные на AT&T(например Vif.c), они компилируются только при использовании GCC в качестве компилятора.

Serke
Вопрос спорный. Если верить бенчмаркам с http://www.coyotegulch.com/reviews/inte ... 209.tar.gz в сравнении с MinGW, входящим в комплект Dev-Cpp 4.9.9.2 (GCC версии 3.4.2, если не ошибаюсь) микрософтовский компилятор C из бесплатного пакета Microsoft Visual C++ Toolkit 2003 очень сильно проигрывает (цифры могу привести). А вот (судя опять же, по бенчмаркам) качество кода, получаемого компилятором C++ в случае с GCC действительно оставляет желать лучшего. Конечно, сравнение GCC 3.4.2 с микрософтовским тулкитом 2003 это не слишком актуально, но зато по крайней мере ясно, чего можно ожидать от бесплатных компиляторов…

Хочу задать “отцам” эмуляции давно интересующий меня вопрос. С технической стороной эмуляции более или менее понятно, а вот как получить детальную информацию по потрохам системы? Представим себе ситуацию, с которой сталкиваются пионеры-первопроходцы эмуляции той или иной системы: из доступной документации только более-менее подробный мануал по процессору, информации по всему прочему – ноль или около того. Как я себе это представляю, дизассемблирование приложений для данной системы практически ничего не дает – нужно как минимум знать организацию памяти системы (т.е. грубо говоря, где что в ней расположено и что с чем связано). Лично меня весьма сильно интересует вопрос, из какой волшебной шляпы эмуляторщики-первопроходцы вытаскивают необходимую для написания хоть сколько-нибудь работоспособного эмулятора информацию? Возможно ли вообще создание эмулятора “с нуля” хотя бы какой-либо относительно несложной 8-битной системы без использования, например, неизвестно как просочившейся документации для разработчиков?

Shiru
Дизассемблирование, реверс-инженеринг. Наличие реального девайса и знаний электроники может сильно помочь. О том, какой это гемор, думаю, можно не говорить:)

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

RomikB
Код который выше ИМХО генериться нормальными компиляторами при использовании конструкции switch.
Код:

switch(sh4.code >> 12 & 0xf)
{
case 0:
case 1:
case 2:
break;
case 3:
reg[sh4.code >> 8 & 0xf] = (long) sh4.code & 0xff;
break;
case 4:
break;
default:
break;
}

Organic

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

[call function_name]

function_name:

ret


Visual C позволяет генерировать функции без использования стекового фрейма. Для этого нужно в начале функции указать атрибут __declspec(naked):

__declspec(naked) void MOVI(void)
{
...
}

В GCC для этого есть подобный макрос __attribute__((naked)) :

void MOVI(void) __attribute__((naked))
{
...
}
Цитата:
Цитата:
Хочу задать “отцам” эмуляции давно интересующий меня вопрос. С технической стороной эмуляции более или менее понятно, а вот как получить детальную информацию по потрохам системы?


Детальную информацию невозможно получить - это интеллектуальная собственность компании разработчика, соответственно делиться ею она ни с кем не будет. А чтобы догадаться как работает то, или иное железо, достаточно поковырять SDK.

_________________
ARE YOU LIVING IN THE REAL WORLD?...


Показать сообщения за:  Поле сортировки  
Ответить на тему   [ Сообщений: 4 ] 

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 51


Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете добавлять вложения

Найти:
Перейти:  
cron
Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group.
Designed by STSoftware for PTF (mod by Zeru-j).
Русская поддержка phpBB