Как реализуется относительный 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 байт) для каждой инструкции. Операнд еще не известен, потому что это прыжок в еще неизвестное место. Поэтому я реализовал механизм "перезаписи", который перезаписывает операнды в правильном месте в памяти, если известно местоположение прыжка, а остальное заполняет NOPs. Это несколько серьезная проблема в узких кругах.

теперь моя проблема заключается в следующей ситуации:

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 байт порога прыжка первоначально. Поскольку кандидаты все прыжки многих байтов, они менее вероятно будут в критическом состоянии ситуации цикла, поэтому вы, скорее всего, обнаружите, что дальнейшие проходы предлагают мало или вообще не приносят пользы по сравнению с решением двух проходов.