Лисья Нора

Оглавление


§ Что это такое

Этот байт идет за опкодом, сразу же. Он присутствует не во всех опкодах, а в тех, в котором требуются указать операнды. К примеру, он нужен для инструкции ADD, SUB и так далее. Такие инструкции получают на вход 2 операнда – destination и source (обычно пишется слева dst и справа src), над ними делаются вычисления и записывается в операнд destination. При этом операнд может указывать в том числе на память, два операнда могут быть регистрами. Есть исключение в том, что оба операнда не могут указывать на память.
Пример: ADD ax, bx. Здесь указываем destination = ax, source = bx, и получим AX = AX + BX.
Общий формат байта modrm:
Приведу массив, в котором будут указаны те опкоды, в которых есть байт modrm:
// Карта наличия байта MODRM
static const unsigned char opcodemap_modrm[512] = {
 
// Базовый опкод
1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, /* 00 */
1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, /* 10 */
1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, /* 20 */
1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, /* 30 */
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 40 */
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 50 */
0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, /* 60 */
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 70 */
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 80 */
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 90 */
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* A0 */
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* B0 */
1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, /* C0 */
1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, /* D0 */
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* E0 */
0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, /* F0 */
 
// Расширенный опкод
1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, /* 100 */
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 110 */
1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, /* 120 */
0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, /* 130 */
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 140 */
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 150 */
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 160 */
1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1, /* 170 */
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 180 */
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 190 */
0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, /* 1A0 */
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 1B0 */
1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, /* 1C0 */
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 1D0 */
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 1E0 */
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, /* 1F0 */
};
Здесь число 1 означает, что modrm байт присутствует.

§ Код разбора байта modrm

В первую очередь, необходимо определить новые переменные:
unsigned short i_ea;
int i_modrm, i_mod, i_reg, i_rm;
Они будут нужны для дальнейшей работы. В i_modrm будет сам байт modrm, i_mod будет равен 0..2 (MOD-часть), i_reg и i_mem равны 0..7 и отвечают за определенные биты в modrm-байте.
// Прочитать эффективный адрес i_ea и параметры modrm
void get_modrm() {
 
i_modrm = fetch(1);
i_mod = i_modrm >> 6;
i_reg = (i_modrm >> 3) & 7;
i_rm = i_modrm & 7;
i_ea = 0;
 
// Расчет индекса
switch (i_rm) {
 
case 0: i_ea = (regs16[REG_BX] + regs16[REG_SI]); break;
case 1: i_ea = (regs16[REG_BX] + regs16[REG_DI]); break;
case 2: i_ea = (regs16[REG_BP] + regs16[REG_SI]); break;
case 3: i_ea = (regs16[REG_BP] + regs16[REG_DI]); break;
case 4: i_ea = regs16[REG_SI]; break;
case 5: i_ea = regs16[REG_DI]; break;
case 6: i_ea = regs16[REG_BP]; break;
case 7: i_ea = regs16[REG_BX]; break;
}
 
// В случае если не segment override
if (!segment_over_en) {
 
if ((i_rm == 6 && i_mod) || (i_rm == 2) || (i_rm == 3))
segment_id = REG_SS;
}
 
// Модифицирующие биты modrm
switch (i_mod) {
 
case 0: if (i_rm == 6) i_ea = fetch(2); break;
case 1: i_ea += (signed char) fetch(1); break;
case 2: i_ea += fetch(2); break;
case 3: i_ea = 0; break;
}
}
Можно считать, что эта процедура – основная для разбора эффективного адреса. Именно эффективный адрес, а также segment_id здесь и высчитываются.
Первым делом, считывается сам байт modrm и разбирается по частям. После этого берется значение i_rm и рассчитывается базовый 16-битный адрес на его основе. В случае если нет segment prefix (segment_over_en == 0), то тогда префикс сегмента будет равен SS тогда если i_mod != 0 и i_rm == 6 или i_rm равны 2 или 3. Почему так? Потому что там указывается регистр BP, который вместо того, чтобы указывать на DS-сегмент, будет указывать на SS. Естественно, при перегрузке префиксом будет указываться именно тот префикс, который ранее и был при чтении опкода инструкции.
Потом, на основе i_mod делается выбор того, что надо сделать далее, а именно какое смещение выбрать (или не выбирать его совсем):

§ Чтение и запись из/в R/M

После того, как байт modrm был успешно разобран, необходимо будет получать оттуда операнды. Поскольку то, откуда операнды будут получены – из памяти или из регистра, надо будет написать процедуру, которая также учитывает еще и разрядность получаемых операндов (8 или 16 бит).
// Получение R/M части; i_w = 1 (word), i_w = 0 (byte)
unsigned int get_rm(int i_w) {
 
if (i_mod == 3) {
return i_w ? regs16[i_rm] : regs[REG8(i_rm)];
} else {
return rd(16*regs16[segment_id] + i_ea, i_w + 1);
}
}
В этом коде мы прямо указываем разрядность i_w. Если i_w=0, то будет получено 8 битное число, если i_w=1, то 16 бит (или 32, 64, это от старших архитектур зависит). Если i_mod = 3, то значит, извлекать эти операнды будут из регистров. Если нет – то из памяти. Для этого как раз и потребуется сегмент segment_id, который был вычислен при разборе modrm, а также смещение i_ea.
Также стоит отметить, что при сохранении 8-битного числа, поскольку порядок следования байтов другой, то надо будет преобразовывать его с помощью следующего макроса:
#define REG8(x) ((x & 4) >> 2) | ((x & 3) << 1)
Процедура записи аналогична процедуре чтения
// Сохранение данных в R/M
void put_rm(int i_w, unsigned short data) {
 
if (i_mod == 3) {
if (i_w) regs16[i_rm] = data;
else regs[REG8(i_rm)] = data;
} else {
wr(16*regs16[segment_id] + i_ea, data, i_w + 1);
}
}
Здесь все так же, меняется лишь только направление, то есть, будет запись либо 8, либо 16 битного числа.

§ Загрузка BIOS в память

Немного не в тему, но очень нужная вещь, без которой потом ничего работать не будет
// Загрузка bios в память
int bios_rom = open("bios.rom", 32898);
if (bios_rom < 0) { printf("No bios.rom present"); return 1; }
(void) read(bios_rom, RAM + 0xF0100, 0xFF00);
В этом коде делается попытка открыть bios.rom через функцию open, и если такого файла не найдено, то выходит с ошибкой. Если все хорошо, то читается файл bios.rom а адресу 0xF0100, где обычно BIOS и располагается. Это не обычный биос, а именно для этого эмулятора. Создание кода для BIOS рассматривается в другом разделе.