|
Страница 1 из 1
|
[ Сообщений: 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 впринципе не способен скомпилировать эфективного кода. Pablopcsx2 скомпиленный в 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?...
|
|
|
|
Страница 1 из 1
|
[ Сообщений: 4 ] |
|
Кто сейчас на конференции |
Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 51 |
|
Вы не можете начинать темы Вы не можете отвечать на сообщения Вы не можете редактировать свои сообщения Вы не можете удалять свои сообщения Вы не можете добавлять вложения
|
|