Простой способ - просто прочитать один байт, расшифровать его и затем определить, является ли это полная инструкция. Если не прочитан другой байт, при необходимости расшифруйте его, а затем определите, была ли прочитана полная инструкция. Если нет, продолжайте чтение / декодирование байтов, пока не будет прочитана полная инструкция.
Это означает, что если указатель инструкции указывает на заданную последовательность байтов, существует только возможный способ декодировать эту первую инструкцию этой последовательности байтов. Неопределенность возникает только потому, что следующая команда, которая должна быть выполнена, может не располагаться в байтах, которые следуют сразу за первой командой. Это связано с тем, что первая инструкция в последовательности байтов может изменить указатель инструкции, поэтому будет выполнена другая инструкция, кроме следующей.
Инструкция RET (retn
) в вашем примере может быть концом функции. Функции часто заканчиваются инструкцией e RET, но не обязательно так. Для функции возможно иметь несколько инструкций RET, ни одна из которых не находится в конце функции. Вместо этого последняя инструкция будет своего рода JMP-инструкцией, которая возвращается к некоторому месту в функции или к другой функции целиком.
Это означает, что в вашем примере кода без дополнительного контекста невозможно узнать, будет ли когда-либо выполняться любой из байтов, следующих за инструкцией RET, и если да, то какой из байтов будет первой инструкцией следующей функции , Между функциями могут быть данные, или эта инструкция RET может быть концом последней функции в программе.
Набор команд x86, в частности, имеет довольно сложный формат необязательных байтов префикса, одного или нескольких байтов кода операции, одного или двух возможных байтов формы адресации, а затем возможных байтов смещения и непосредственных байтов. Байты префикса могут быть добавлены к любой инструкции. Байты кода операции определяют, сколько существует байтов кода операции и может ли инструкция иметь байты операнда и непосредственные байты. Код операции также может указывать на наличие байтов смещения. Первый байт операнда определяет, есть ли второй байт операнда и есть ли байты смещения.
Руководство разработчика программного обеспечения для архитектуры Intel 64 и IA-32 имеет следующий рисунок, показывающий формат инструкций x86:
Python-подобный псевдокод для декодирования инструкций x86 будет выглядеть примерно так:
# read possible prefixes
prefixes = []
while is_prefix(memory[IP]):
prefixes.append(memory[IP))
IP += 1
# read the opcode
opcode = [memory[IP]]
IP += 1
while not is_opcode_complete(opcode):
opcode.append(memory[IP])
IP += 1
# read addressing form bytes, if any
modrm = None
addressing_form = []
if opcode_has_modrm_byte(opcode):
modrm = memory[IP]
IP += 1
if modrm_has_sib_byte(modrm):
addressing_form = [modrm, memory[IP]]
IP += 1
else:
addressing_form = [modrm]
# read displacement bytes, if any
displacement = []
if (opcode_has_displacement_bytes(opcode)
or modrm_has_displacement_bytes(modrm)):
length = determine_displacement_length(prefixes, opcode, modrm)
displacement = memory[IP : IP + length]
IP += length
# read immediate bytes, if any
immediate = []
if opcode_has_immediate_bytes(opcode):
length = determine_immediate_length(prefixes, opcode)
immediate = memory[IP : IP + length]
IP += length
# the full instruction
instruction = prefixes + opcode + addressing_form + displacement + immediate
Одна важная деталь, оставленная в псевдокоде выше, состоит в том, что длина инструкций ограничена 15 байтами. Можно построить иные действительные инструкции x86 длиной 16 байтов или более, но такая инструкция при выполнении вызовет неопределенное исключение ЦП кода операции. (Есть и другие детали, которые я пропустил, например, как часть кода операции может быть закодирована внутри байта Mod R / M, но я не думаю, что это влияет на длину инструкций.)
Однако процессоры x86 на самом деле не декодируют инструкции, как я описал выше, они только декодируют инструкции, как будто они читают каждый байт по одному за раз. Вместо этого современные процессоры будут считывать целые 15 байтов в буфер, а затем декодировать байты параллельно, обычно за один цикл. Когда он полностью декодирует инструкцию, определяя ее длину, и готов прочитать следующую инструкцию, он сдвигает оставшиеся байты в буфере, которые не были частью инструкции. Затем он считывает больше байтов, чтобы снова заполнить буфер до 15 байтов, и начинает декодирование следующей инструкции.
Еще одна вещь, которую будут делать современные процессоры, это не подразумевается тем, что я написал выше, это умозрительно выполнять инструкции.Это означает, что ЦП будет декодировать инструкции и предварительно попытаться выполнить их даже до того, как завершит выполнение предыдущих инструкций.Это, в свою очередь, означает, что ЦП может в конечном итоге декодировать инструкции, следующие за инструкцией RET, но только если он не может определить, куда будет возвращаться RET.Поскольку из-за попыток декодирования и условного выполнения случайных данных, которые не предназначены для выполнения, могут быть потери производительности, компиляторы обычно не помещают данные между функциями.Хотя они могут заполнить это пространство инструкциями NOP, которые никогда не будут выполняться для выравнивания функций по соображениям производительности.
(Раньше они размещали данные только для чтения между функциями, но это было до того, как процессоры x86, которые умело могли выполнять инструкции, стали обычным явлением.)