Форум Эму-Россия http://forum.emu-russia.net/ |
|
[АРХИВ] Интерпретатор http://forum.emu-russia.net/viewtopic.php?f=13&t=65 |
Страница 1 из 1 |
Автор: | Keyman [ 15 авг 2007, 16:11 ] |
Заголовок сообщения: | [АРХИВ] Интерпретатор |
Автор: 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 строчек) + декодирование. Это является основной причиной такой медлительности эмуляторов. |
Автор: | Keyman [ 15 авг 2007, 16:17 ] |
Заголовок сообщения: | Re: Интерпретатор [АРХИВ] |
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 - это внешнее устройство, флешка с последовательным доступом, например. Со стороны системы её обслуживает контроллер. Его и надо эмулировать (как - зависит от устройства контроллера). |
Автор: | Keyman [ 15 авг 2007, 16:23 ] |
Заголовок сообщения: | Re: Интерпретатор [АРХИВ] |
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, ты чего это пост удалил ? =) Да как ты мог заметить я его не в тот топик направил, так что просто перенес в память. |
Автор: | Keyman [ 15 авг 2007, 16:27 ] |
Заголовок сообщения: | Re: Интерпретатор [АРХИВ] |
Serke Прошу прощения за эксгумацию темы, но хочу заметить, что обработку опкодов можно организовать при помощи таблиц с адресами переходов, а не таблиц с адресами вызываемых функций, как было показано в первом примере. Теоретически это должно приводить к генерированию более эффективного кода, однако практически, в чем можно убедиться, просматривая сгенерированные компилятором ассемблерные листинги, выигрыш не велик по причине того, что современные компиляторы с грамотно подобранными параметрами оптимизации и в первом случае генерируют весьма эффективный код – например, удаляют неиспользуемые указатели на стековый фрейм, в результате чего скелет функции, как и в случае, если бы код писался на ассемблере, выглядит как [call function_name] function_name: … ret Впрочем, в случае с таблицами, содержащими адреса переходов, листинг эмулятора, на мой взгляд, смотрится “правильнее”, поскольку выглядит более по-ассемблерному ![]() Код: #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 (господа гусары от структурного программирования, молчать! ![]() 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. |
Страница 1 из 1 | Часовой пояс: UTC + 3 часа [ Летнее время ] |
Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group http://www.phpbb.com/ |