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