Лисья Нора

Оглавление


§ Префиксы

Вот и настал тот день и час, когда мы начнем декодирование инструкции 8086! Для начала теория, которую кто-то прекрасно изложил в книгах, но я скажу кратко. Инструкция x86 состоит из следующих частей:
Необходимо сначала декодировать префиксы и опкод. После этого мы можем точно сказать, нужен ли будет байт modrm/sib и непосредственные операнды после них.
Префиксов существует не так много, а именно:
Вот и все префиксы. Однако их комбинации в сочетании с некоторыми опкодами дают разный эффект, но это не касается 8086 процессора, а гораздо старше процессоры, начиная с Pentium.

§ Инициализация

Так что же, приступим писать код. Для начала надо создать регистры и сегментные регистры. Регистров у нас 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 от другого эмулятора, то надо чтобы были заполнены следующие поля:
Вот тут есть один интересный лайфхак. Дело в том, что regs16 указывает на regs, но с другим типом. Сам по себе regs состоит из следующих байт:
regs[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] = IP
Чтобы regs16 указывал на regs, это надо в программе написать:
regs16 = (unsigned short*) & regs;
flags.t = 0;
 
regs16[REG_CS] = 0xF000; // CS = 0xF000
regs16[REG_IP] = 0x0100; // IP = 0x0100
regs [REG_DL] = 0x00; // Загружаем с FD
Тут на всякий случай также выставляется флаг 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();
}
В этой процедуре происходит выборка префиксов и опкода. И на этом пока что все.