§ MOV r, imm

Сегодня разберем инструкции, которые занимаются, собственно, перемещением данных. Начну я, пожалуй, с самого интересного, это базовых инструкции загрузки непосредственного операнда в регистры 8 или 16 битные. Причем интересный факт: существуют также копии таких инструкции, но выполнены они в другом виде, в более компактном и не занимающим место в слотах операндов. О них будет рассказано позже. А сейчас код.
// MOV r16, imm16
case 0xB8: case 0xB9: case 0xBA: case 0xBB:
case 0xBC: case 0xBD: case 0xBE: case 0xBF:

    regs16[ opcode_id & 7 ] = fetch(2);
    break;
Как видим, код простой до невозможности. Просто читаются следующие 2 байта и засылается в необходимый нам регистр, который содержится в 3-х младших бита опкода.
Поскольку последовательность такая AL, CL, DL, BL, AH, CH, DH, BH, а регистры хранятся в таком виде в памяти AL, AH, CL, CH, DL, DH, BL, BH, то придется сделать иначе с 8-битным представлением.
case 0xB0: case 0xB1: case 0xB2: case 0xB3:
case 0xB4: case 0xB5: case 0xB6: case 0xB7:

    // 100 -> 001 | 011 --> 110 Меняются местами
    regs[ ((opcode_id & 4) >> 2) | ((opcode_id & 3) << 1) ] = fetch(1);
    break;
Эта хитрость направлена на то, чтобы записать в правильную ячейку памяти regs, поскольку там память организована иначе. Дело в том что 2-й бит opcode_id отвечает за либо старший байт, либо за младший байт, а старший и младший байт адресуются битом 0, поэтому бит 2 двигается в бит 0. А биты 1:0 двигаются в биты 2:1, тем самым приводя всё в порядок.

§ MOV rm, imm

Код для этих инструкции простой
// MOV rm, i8/16
case 0xC6: put_rm(0, fetch(1)); break;
case 0xC7: put_rm(1, fetch(2)); break;
Для MOV rm8, imm8 считывается 1 байт, и записывается в rm-часть, которая указана была в байте modrm. Запись может происходить не только в память, но еще и в регистр. Так же работает инструкция MOV rm16, imm16, просто с другой разрядностью (16 бит).

§ MOV rm | rm

Следующая группа инструкции занимается тем, что пересылает из памяти в регистр или наоборот
case 0x88: put_rm(0, regs[REG8(i_reg)]); break;
case 0x89: put_rm(1, regs16[i_reg]); break;
case 0x8A: regs[REG8(i_reg)] = get_rm(0); break;
case 0x8B: regs16[i_reg] = get_rm(1); break;
Здесь используются функции put_rm и get_rm, которые записывают или читают данные в соответствии с тем, что находится в байте modrm. В принципе тут все очевидно и даже не требуется дополнительных комментариев.

§ MOV a, m16

Есть специальные 4 инструкции, которые работают с регистром AL/AX и памятью. Эти инструкции избыточны, поскольку могут быть легко заменены на MOV rm|imm, но, по всей видимости, так захотела INTEL. А SALC эта Intel до сих пор не документировала!
case 0xA0: regs[REG_AL]   = rd(SEGREG(segment_id, fetch(2)), 1); break;
case 0xA1: regs16[REG_AX] = rd(SEGREG(segment_id, fetch(2)), 2); break;
case 0xA2: wr(SEGREG(segment_id, fetch(2)), regs[REG_AL],    1); break;
case 0xA3: wr(SEGREG(segment_id, fetch(2)), regs16[REG_AX],  2); break;
А теперь разберем что тут происходит. Как видим, тут добавлен для удобства новый макрос SEGREG, который позволяет сэкономить на месте и удобочитаемости.
#define SEGREG(a,b) (16*regs16[a] + b)
В любом случае будет прочитаны следующие 2 байта, которые будут указывать на адрес в соответствии с выбранным текущим сегментом. По умолчанию он DS, но может быть заменен через префиксы. Остальная логика вполне очевидна. Либо читает, либо пишет AL и AX из памяти или в память.

§ MOV sreg, LEA

Ну и завершая статью, приведу еще 3 инструкции
// MOV rm16, seg
case 0x8C: put_rm(1, regs16[REG_ES + (i_reg & 3)]); break;

// LEA rm16, [address]
case 0x8D: regs16[i_reg] = i_ea; break;

// MOV seg, rm16
case 0x8E: regs16[REG_ES + (i_reg & 3)] = get_rm(1); break;
Первая инструкция извлекает сегмент, который указывается в reg-части байта modrm, и записывается в rm-часть, это либо в память, либо в 16-разрядный регистр.
LEA записывает вычисленный эффективный адрес RM в регистровую часть. Эта инструкция зачастую выступает в качестве быстрого вычислителя.
MOV seg, rm16 записывает в сегмент из RM-операнда.
Ссылка на код.
Следующий материал
29 сен, 2020
© 2007-2022 Гнал лажовый север