Этот байт идет за опкодом, сразу же. Он присутствует не во всех опкодах, а в тех, в котором требуются указать операнды. К примеру, он нужен для инструкции ADD, SUB и так далее. Такие инструкции получают на вход 2 операнда – destination и source (обычно пишется слева dst и справа src), над ними делаются вычисления и записывается в операнд destination. При этом операнд может указывать в том числе на память, два операнда могут быть регистрами. Есть исключение в том, что оба операнда не могут указывать на память.
Пример: ADD ax, bx. Здесь указываем destination = ax, source = bx, и получим AX = AX + BX.
static const unsigned char opcodemap_modrm[512] = {
1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0,
1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0,
1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0,
1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1,
1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
};
Здесь число 1 означает, что modrm байт присутствует.
Они будут нужны для дальнейшей работы. В i_modrm будет сам байт modrm, i_mod будет равен 0..2 (MOD-часть), i_reg и i_mem равны 0..7 и отвечают за определенные биты в modrm-байте.
Можно считать, что эта процедура – основная для разбора эффективного адреса. Именно эффективный адрес, а также 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 делается выбор того, что надо сделать далее, а именно какое смещение выбрать (или не выбирать его совсем):
После того, как байт modrm был успешно разобран, необходимо будет получать оттуда операнды. Поскольку то, откуда операнды будут получены – из памяти или из регистра, надо будет написать процедуру, которая также учитывает еще и разрядность получаемых операндов (8 или 16 бит).
В этом коде мы прямо указываем разрядность i_w. Если i_w=0, то будет получено 8 битное число, если i_w=1, то 16 бит (или 32, 64, это от старших архитектур зависит). Если i_mod = 3, то значит, извлекать эти операнды будут из регистров. Если нет – то из памяти. Для этого как раз и потребуется сегмент segment_id, который был вычислен при разборе modrm, а также смещение i_ea.
Также стоит отметить, что при сохранении 8-битного числа, поскольку порядок следования байтов другой, то надо будет преобразовывать его с помощью следующего макроса:
Здесь все так же, меняется лишь только направление, то есть, будет запись либо 8, либо 16 битного числа.
Немного не в тему, но очень нужная вещь, без которой потом ничего работать не будет
В этом коде делается попытка открыть bios.rom через функцию open, и если такого файла не найдено, то выходит с ошибкой. Если все хорошо, то читается файл bios.rom а адресу 0xF0100, где обычно BIOS и располагается. Это не обычный биос, а именно для этого эмулятора. Создание кода для BIOS рассматривается в другом разделе.