§ Префиксы
Вот и настал тот день и час, когда мы начнем декодирование инструкции 8086! Для начала теория, которую кто-то прекрасно изложил в книгах, но я скажу кратко. Инструкция x86 состоит из следующих частей:- Префиксы или REX (от 0 до 15 байт)
- Опкод (1 байт)
- Байт ModRM (1 байт)
- Байт SIB - если это 32х битный адрес (1 байт)
- Смещение (от 0 до 8 байт)
- Непосредственные операнды (от 0 до 8 байт)
Префиксов существует не так много, а именно:
- 0Fh - Расширенный опкод
- 26h - Префикс сегмента ES:
- 2Eh - Префикс сегмента CS:
- 36h - Префикс сегмента SS:
- 3Eh - Префикс сегмента DS:
- 64h - Префикс сегмента FS: только для 386
- 65h - Префикс сегмента GS: только для 386
- 66h - Размер операнда (16/32 бит) 386+
- 67h - Размер адреса (16/32 бит) 386+
- F0h - LOCK: для эксклюзивной блокировки шины (без понятия зачем это нужно)
- F2h - REPNZ (либо REP) префикс для повтора строковых инструкции
- F3h - REPZ (либо REP) аналогично, но повтор до тех пор пока будет Z
§ Инициализация
Так что же, приступим писать код. Для начала надо создать регистры и сегментные регистры. Регистров у нас 8 штук, сегментных регистров только 4, остальные 2 (fs: gs:) рассматривать не будем.unsigned char regs[32]; unsigned short* regs16;Не забудем про флаги. Для флагов вообще структуру другую сделаю
struct flags_struct { unsigned char o; // 11 overflow unsigned char d; // 10 direction unsigned char i; // 9 interrupt unsigned char t; // 8 trap unsigned char s; // 7 sign unsigned char z; // 6 zero unsigned char a; // 4 aux unsigned char p; // 2 parity unsigned char c; // 0 carry }; struct flags_struct flags;Также сделаю обозначения для указания регистров в памяти, это хоть и не так важно, но будет удобно
enum regs_name { // 16 bit REG_AX = 0, REG_CX = 1, REG_DX = 2, REG_BX = 3, REG_SP = 4, REG_BP = 5, REG_SI = 6, REG_DI = 7, // 8 bit REG_AL = 0, REG_CL = 2, REG_DL = 4, REG_BL = 6, REG_AH = 1, REG_CH = 3, REG_DH = 5, REG_BH = 7, // Segment REG_ES = 8, REG_CS = 9, REG_SS = 10, REG_DS = 11, // System REG_IP = 12 };А теперь важный момент. Перед запуском машины, поскольку я буду использовать BIOS от другого эмулятора, то надо чтобы были заполнены следующие поля:
-
DL = 00h
(FD) или 80h (HD) -
CX:AX
= размер диска в секторах (по 512 байт) -
CS = F000h
-
IP = 100h
1] = AH regs[0] = AL regs[3] = CH regs[2] = CL regs[5] = DH regs[4] = DL regs[7] = BH regs[6] = BL regs[ 9: 8] = SP regs[11:10] = BP regs[13:12] = SI regs[15:14] = DI regs[17:16] = ES regs[19:18] = CS regs[21:20] = SS regs[23:22] = DS regs[25:24] = IPregs[Чтобы regs16 указывал на regs, это надо в программе написать:
unsigned short*) & regs; flags.t = 0; regs16[REG_CS] = 0xF000; // CS = 0xF000 regs16[REG_IP] = 0x0100; // IP = 0x0100 regs [REG_DL] = 0x00; // Загружаем с FDregs16 = (Тут на всякий случай также выставляется флаг TF=0, чтобы он не срабатывал.
§ Считывание опкода
Для начала надо написать вспомогательную функцию, которая извлекает следующий байт (или слово) из потока по адресу CS:IP// Считывание очередного byte/word из CS:IP unsigned int fetch(unsigned char wsize) { int address = regs16[REG_CS]*16 + regs16[REG_IP]; regs16[REG_IP] += wsize; return rd(address, wsize); }Рассмотрю процедуру считывания опкода с префиксами
// Считывание опкода 000h - 1FFh unsigned int fetch_opcode() { i_size = 0; i_rep = 0; segment_over_en = 0; segment_id = REG_DS; while (i_size < 16) { uint8_t data = fetch(1); switch (data) { // Получен расширенный опкод case 0x0F: return 0x100 + fetch(1); // Сегментные префиксы case 0x26: segment_over_en = 1; segment_id = REG_ES; break; case 0x2E: segment_over_en = 1; segment_id = REG_CS; break; case 0x36: segment_over_en = 1; segment_id = REG_SS; break; case 0x3E: segment_over_en = 1; segment_id = REG_DS; break; case 0x64: case 0x65: case 0x66: case 0x67: /* undefined opcode */ break; case 0xF0: break; // lock: case 0xF2: i_rep = REPNZ; break; case 0xF3: i_rep = REPZ; break; default: return data; } } /* undefind opcode */ return 0; }На самом деле тут все просто. Необходимо всего лишь считать префиксы сегментов и префиксы REP, остальные не понадобятся. При необходимости там где сейчас undefined code, можно вызывать GP(0) к примеру (GP = General Protection).
Итак, считываем байт. Если это байт префикса расширения опкода (0Fh), то дочитываем следующий байт, который и будет опкодом, и тут же выходим из функции. Если это префикс сегмента, то указывается какой именно сегмент будет замещен и установлен флаг segment_over_en. Если это REPNZ, REPZ, то в переменную i_rep будет указан либо REPNZ(1), либо REPZ(2).
Все переменные требуется, конечно же, указать:
int i_size; int i_rep; int opcode_id; int segment_over_en; int segment_id; enum rep_prefix { REPNZ = 1, REPZ = 2, };Поскольку статья у нас только про декодирование опкода, то этого будет пока что достаточно:
// Выполнение инструкции void step() { opcode_id = fetch_opcode(); }В этой процедуре происходит выборка префиксов и опкода. И на этом пока что все.
Коды, как обычно, прикреплены в файле.
Следующая статья "Декодирование операндов байта modrm"