Оглавление
§ Инструкция NOP и каркас
Пришло время для хотя бы какой-нибудь реализации инструкции. Сегодня будут разобраны следующие инструкции: NOP; XCHG ax, r16; CBW; CWD; FWAIT; SAHF; LAHF; CLC; STC; CLI; STI; CLD; STD; XLAT. Это самые простые инструкции из всех представленных в базовом наборе 8086.
NOP реализуется тривиально:
switch (opcode_id) {
case 0x90: break;
default: ud_opcode(opcode_id);
}
То есть буквально эта инструкция ничего не делает. Но это не совсем так. На самом деле она делает обмен AX с AX, но в итоге это все равно получается NOP (No OPeration). Здесь есть такая ситуация, что при любом другом неопознанном опкоде будет вызываться процедура ud_opcode.
void ud_opcode(int data) {
printf("UNDEFINED OPCODE: %x\n", data);
exit(2);
}
Данная процедура просто завершает исполнение программы, потому что никаких неизвестных опкодов на пути исполнения эмулятора быть не должно. Если это так, то произошла критическая ошибка.
§ XCHG ax, r16
Эта инструкция просто обменивает регистр AX с другим выбранным 16-битным регистром:
case 0x90: case 0x91: case 0x92: case 0x93:
case 0x94: case 0x95: case 0x96: case 0x97:
i_reg = opcode_id & 7;
i_tmp = regs16[REG_AX];
regs16[REG_AX] = regs16[i_reg];
regs16[i_reg] = i_tmp;
break;
Сначала выбирается i_reg = 0..7, поскольку выбранный регистр зависит от самого опкода. Потом в i_tmp помещается старое значение AX, следующий шаг заменяет AX на новый регистр, и последний шаг помещает старое значение AX в выбранный регистр.
§ CBW, CWD, FWAIT
Это очень простая инструкция, которая занимается тем, что выставляет все биты либо в AH регистре в 0 или 1, или в DX. Инструкция CBW устанавливает FFh в AH, если старший бит AL равен 1, и в 00h – если 0. Соответственно так же работает CWD, но старший бит смотрится в регистре AX, а установка идет в DX.
case 0x98: regs [REG_AH] = regs [REG_AL] & 0x80 ? 0xFF : 0x00; break;
case 0x99: regs16[REG_DX] = regs16[REG_AX] & 0x8000 ? 0xFFFF : 0x0000; break;
case 0x9B: break;
FWAIT на самом деле должен ожидать, пока не будут выполнены все прерывания в модуле FPU, но так как во-первых, FPU у нас нет, а во-вторых, это эмулятор, то ждать FPU вообще не надо.
§ SAHF и LAHF
Эти инструкции загружают 8 бит либо из AH во флаги (SAHF), либо наоборот, из флагов в AH (LAHF). Их программная реализация будет выглядеть так:
case 0x9E:
i_tmp = regs[REG_AH];
flags.c = !!(i_tmp & 0x01);
flags.p = !!(i_tmp & 0x04);
flags.a = !!(i_tmp & 0x10);
flags.z = !!(i_tmp & 0x40);
flags.s = !!(i_tmp & 0x80);
break;
Инструкция SAHF просто конвертирует флаги в соответствии с битами в регистре AH.
case 0x9F:
regs[REG_AH] =
(!!flags.c) |
0x02 |
(!!flags.p<<2) |
0 |
(!!flags.a<<4) |
0 |
(!!flags.z<<6) |
(!!flags.s<<7);
break;
Инструкция LAHF преобразует флаги в регистр AH. При этом бит 1 (0x02) всегда установлен. Это особенность архитектуры x86.
§ Установка и сброс флагов
Эти инструкции настолько простые, что не требуется даже описывать их:
case 0xD6: regs[REG_AL] = flags.c ? 0xFF : 0x00; break;
case 0xF4: regs16[REG_IP]--; break;
case 0xF5: flags.c = !flags.c; break;
case 0xF8: flags.c = 0; break;
case 0xF9: flags.c = 1; break;
case 0xFA: flags.i = 0; break;
case 0xFB: flags.i = 1; break;
case 0xFC: flags.d = 0; break;
case 0xFD: flags.d = 1; break;
Здесь стоит только разве что пояснить насчет инструкции HLT. Эта инструкция занимается тем, что останавливает процессор. То есть, процессор просто застревает на этом месте и все. Единственное, что может сдвинуть процессор с "мертвой точки" – это вызов аппаратного прерывания.
А еще есть интересная инструкция SALC. Дело в том, что она недокументированная, однако все её используют, в том числе и компиляторы. Она настолько популярна, что пора бы уже Intel добавить пару строк в документацию. Тем более все процессоры ее поддерживают. Эта инструкция выставляет 0xFF в AL если CF=1 и 0, если CF=0.
§ XLAT
Существует весьма полезная инструкция, которая извлекает данные по адресу DS:BX + AL. Здесь DS можно заменить на другой сегмент с помощью сегментного префикса.
case 0xD7:
regs[REG_AL] = rd(16*regs16[segment_id] + regs16[REG_BX] + regs[REG_AL], 1);
break;
Логика работы инструкции XLAT очень проста. К DS:BX добавляется значение AL, и из полученного адреса извлекается обратно в AL. Эта инструкция позволяет делать различные табличные преобразования (256 байтные). Между прочим интересный факт. На самом деле XLAT может выйти за пределы HIMEM, поскольку если сегмент DS=FFFFh, BX=FFFFh и AL=FFh, то максимальный адрес будет равен FFFF0h + FFFFh + FFh = 1100EEh.