Как реализуется относительный JMP (x86) в ассемблере?
при создании моего ассемблера для платформы x86 я столкнулся с некоторыми проблемами с кодированием JMP
инструкция:
OPCODE INSTRUCTION SIZE
EB cb JMP rel8 2
E9 cw JMP rel16 4 (because of 0x66 16-bit prefix)
E9 cd JMP rel32 5
...
(С моего любимого сайта x86 инструкции,http://siyobik.info/index.php?module=x86&id=147)
все относительные прыжки, где размер каждой кодировки (операция + операнд) находится в третьем столбце.
теперь мой оригинал (и, таким образом, вина из-за этого) дизайн зарезервировал максимальное пространство (5 байт) для каждой инструкции. Операнд еще не известен, потому что это прыжок в еще неизвестное место. Поэтому я реализовал механизм "перезаписи", который перезаписывает операнды в правильном месте в памяти, если известно местоположение прыжка, а остальное заполняет NOP
s. Это несколько серьезная проблема в узких кругах.
теперь моя проблема заключается в следующей ситуации:
b: XXX
c: JMP a
e: XXX
...
XXX
d: JMP b
a: XXX (where XXX is any instruction, depending
on the to-be assembled program)
проблема в том, что я хочу минимально возможные кодировки для JMP
инструкции (и нет NOP
заливка).
Я должен знать размер инструкции в c
прежде чем я смогу вычислить относительное расстояние между a
и b
для операнда at d
. То же самое относится и к JMP
at c
: он должен знать размер d
прежде чем он сможет вычислить относительное расстояние между e
и a
.
как существующие ассемблеры решают эту проблему, или как бы ты это сделал?
вот что я думаю, что решает проблему:
сначала Закодируйте все инструкции к opcodes между
JMP
и это цель, если эта область содержит код операции переменного размера, используйте максимальный размер, например5
наJMP
. Тогда Закодируйте относительноеJMP
к его цели, выбрав наименьший возможный размер кодирования (3, 4 или 5) и вычислить расстояние. Если какой-либо код операции переменного размера закодированные, измените все абсолютные операнды раньше и все относительные инструкции, которые пропускают эту закодированную инструкцию: они перекодируются, когда их операнд изменяется, чтобы выбрать наименьший возможный размер. Этот метод гарантированно заканчивается, так как опкоды переменного размера могут только сжиматься (поскольку он использует максимальный размер).
интересно, возможно, это слишком инженерное решение, вот почему я задаю этот вопрос.
2 ответов
вот один подход, который я использовал, который может показаться неэффективным, но оказывается не для большинства реальных кодов (псевдокод):
IP := 0;
do
{
done = true;
while (IP < length)
{
if Instr[IP] is jump
if backwards
{ Target known
Encode short/long as needed }
else
{ Target unknown
if (!marked as needing long encoding) // see below
Encode short
Record location for fixup }
IP++;
}
foreach Fixup do
if Jump > short
Mark Jump location as requiring long encoding
PC := FixupLocation; // restart at instruction that needs size change
done = false;
break; // out of foreach fixup
else
encode jump
} while (!done);
в первом проходе у вас будет очень хорошее приближение к которому jmp
код для использования с использованием пессимистического подсчета байтов для всех инструкций перехода.
на втором проходе вы можете заполнить прыжки с выбранным пессимистическим кодом операции. Очень немногие прыжки могут быть переписаны, чтобы использовать байт или два меньше, только те, которые были очень близки к 8/16 бит или 16/32 байт порога прыжка первоначально. Поскольку кандидаты все прыжки многих байтов, они менее вероятно будут в критическом состоянии ситуации цикла, поэтому вы, скорее всего, обнаружите, что дальнейшие проходы предлагают мало или вообще не приносят пользы по сравнению с решением двух проходов.