Как ЦП правильно декодирует инструкции переменной длины?

На большинстве архитектур все инструкции имеют фиксированную длину. Это упрощает загрузку и выполнение программы. В x86 / x64 инструкции имеют переменную длину, поэтому дизассемблированная программа может выглядеть так:

File Type: EXECUTABLE IMAGE

  00401000: 8B 04 24           mov         eax,dword ptr [esp]
  00401003: 83 C4 04           add         esp,4
  00401006: FF 64 24 FC        jmp         dword ptr [esp-4]
  0040100A: 55                 push        ebp
  0040100B: E8 F0 FF FF FF     call        00401000
  00401010: 50                 push        eax
  00401011: 68 00 30 40 00     push        403000h
  00401016: E8 0D 00 00 00     call        00401028
  0040101B: 83 C4 08           add         esp,8
  0040101E: 33 C0              xor         eax,eax
  00401020: 5D                 pop         ebp
  00401021: 83 C4 04           add         esp,4
  00401024: FF 64 24 FC        jmp         dword ptr [esp-4]
  00401028: FF 25 00 20 40 00  jmp         dword ptr ds:[00402000h]

  Summary

        1000 .data
        1000 .rdata
        1000 .reloc
        1000 .text

Довольно сложно представить, как ЦП «знает», где заканчивается одна инструкция и начинается следующая. Например, если я добавлю байт 0x90 (NOP) к середине XOR EAX,EAX кодов операций, программа затем дизассемблируется как:

File Type: EXECUTABLE IMAGE

  00401000: 8B 04 24           mov         eax,dword ptr [esp]
  00401003: 83 C4 04           add         esp,4
  00401006: FF 64 24 FC        jmp         dword ptr [esp-4]
  0040100A: 55                 push        ebp
  0040100B: E8 F0 FF FF FF     call        00401000
  00401010: 50                 push        eax
  00401011: 68 00 30 40 00     push        403000h
  00401016: E8 0D 00 00 00     call        00401028
  0040101B: 83 C4 08           add         esp,8
  0040101E: 33 90 C0 5D 83 C4  xor         edx,dword ptr [eax+C4835DC0h]
  00401024: 04 FF              add         al,0FFh
  00401026: 64 24 FC           and         al,0FCh
  00401029: FF
  0040102A: 25
  0040102B: 00 20              add         byte ptr [eax],ah
  0040102D: 40                 inc         eax

  Summary

    1000 .data
    1000 .rdata
    1000 .reloc
    1000 .text

Что, как и ожидалось, вылетает при запуске.

Мне любопытно, что именно видит декодер инструкций с этим дополнительным байтом, который заставляет его думать, что строка 0040101E имеет длину 6 байтов, а строка с исходным номером 00401028 представляет собой четыре отдельные инструкции.


person Govind Parmar    schedule 04.08.2014    source источник
comment
ЦП делает это точно так же, как и ваш дизассемблер.   -  person David Schwartz    schedule 05.08.2014
comment
Да, но я спрашиваю, почему с лишним байтом они оба полностью портят декодирование последующих инструкций   -  person Govind Parmar    schedule 05.08.2014
comment
По той же причине, если вы измените 10 32 5 на 10132 5, 10 и 32 изменится, просто изменив пробел.   -  person David Schwartz    schedule 05.08.2014
comment
Что касается 0xFF / 0x25, он, вероятно, в настоящее время не определен. Впрочем, здесь нет ничего плохого - вы видите то, что сейчас кодируют эти байты. Во время выполнения ЦП вызовет ошибку недопустимой инструкции (при условии, что он еще не был AV-обработан на xor) и никогда не выполнит пару add/inc.   -  person 500 - Internal Server Error    schedule 05.08.2014


Ответы (1)


При получении инструкции ЦП сначала анализирует свой первый байт (код операции). Иногда достаточно знать общую длину инструкции. Иногда он указывает процессору проанализировать последующие байты, чтобы определить длину. Но в целом кодировка однозначная.

Да, командный поток сбивается, если вы волей-неволей вставляете случайные байты в середину. Этого следовало ожидать; не каждая последовательность байтов составляет допустимый машинный код.

Теперь о вашем конкретном примере. Исходная команда была XOR EAX, EAX (33 C0). Кодирование XOR является одним из тех, которые зависят от второго байта. Первый байт - 33 - означает XOR. Второй байт - это байт ModR / M. Он кодирует операнды - будь то пара регистров, регистр и ячейка памяти и т. Д. Начальное значение C0 в 32-битном режиме соответствует операндам EAX, EAX. Введенное вами значение 90 соответствует операндам EDX, [EAX + offset], и это означает, что за байтом ModR / M следует 32 бита смещения. Следующие четыре байта командного потока больше не интерпретируются как команды - это смещение в искаженной команде XOR.

Итак, изменив второй байт, вы превратили 2-байтовую команду в 6-байтовую.

Затем ЦП (и дизассемблер) возобновляет загрузку после этих четырех. Он находится в середине инструкции ADD ESP, 4, но ЦП не имеет возможности узнать об этом. Он начинается с байта 04, третьего в кодировке ADD. Первые несколько байтов в этот момент по-прежнему имеют смысл как команды, но, поскольку вы оказались в середине, исходная последовательность команд полностью потеряна.

person Seva Alekseyev    schedule 05.08.2014
comment
Интересный факт: можно создать запутанный машинный код, который возвращается в середину ранее выполненной инструкции. Декодирование с этой точки дает разные инструкции, которые не повторяют этот прыжок. Это сбивает с толку многие дизассемблеры, но ЦП просто декодирует инструкции, как всегда. Ключевым моментом является то, что для декодирования инструкций необходим начальный адрес, поскольку инструкции x86 не имеют требований к выравниванию и могут иметь длину от 1 до 15 байтов (включая байты префикса: первый байт даже не обязательно должен быть кодом операции). - person Peter Cordes; 06.04.2016