【问题标题】:How is a relative JMP (x86) implemented in an Assembler?如何在汇编程序中实现相对 JMP (x86)?
【发布时间】:2011-02-18 08:44:03
【问题描述】:

在为 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 处指令的大小,然后才能计算d 处操作数的ab 之间的相对距离。 JMPc 也是如此:在计算ea 之间的相对距离之前,它需要知道d 的大小。

现有的汇编程序如何解决这个问题,或者您将如何解决?

这就是我想解决问题的方法:

首先将所有指令编码为JMP与其目标之间的操作码,如果此区域包含可变大小的操作码,则使用最大大小,例如5JMP。然后通过选择可能的最小编码大小(3、4 或 5)并计算距离,将相对 JMP 编码到它的目标。如果对任何可变大小的操作码进行编码,则更改之前的所有绝对操作数,以及跳过此编码指令的所有相对指令:当它们的操作数更改以选择可能的最小大小时,它们将被重新编码。此方法保证会结束,因为可变大小的操作码只能缩小(因为它使用它们的最大大小)。

我想知道,也许这是一个过度设计的解决方案,这就是我问这个问题的原因。

【问题讨论】:

  • +1 获取漂亮的 asm 文档链接

标签: encoding x86 instruction-set


【解决方案1】:

在第一遍中,您将非常近似地使用对所有跳转指令使用悲观字节计数的jmp 代码。

在第二遍中,您可以使用选择的悲观操作码填充跳转。然后可以重写很少的跳转以使用更少的一两个字节,只有那些最初非常接近 8/16 位或 16/32 字节跳转阈值的跳转。由于候选对象都是多字节的跳转,因此它们不太可能处于关键循环情况,因此您可能会发现与两遍解决方案相比,进一步的遍历几乎没有好处。

【讨论】:

  • 很好的答案:但为什么 128 字节边界(8/16 位之间)不太可能出现在关键循环情况下?我可以想象一个恰好是 128 字节的关键循环情况,它比使用 16 位跳转运行得更快。还是这是过早的优化?
  • 好吧,我所说的关键循环是指循环的测试和跳转是循环代码的重要部分。对于这样的循环,128 字节非常长,大多数关键循环只有几个字节,任何跳出循环的可能也很小。
【解决方案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);

【讨论】:

  • 整洁!虽然,但你不知道,我的汇编器和编译器是并行运行的,所以有可能在目标和向后相对跳转之间插入了代码。但是循环两次是一个非常好的方法,谢谢。 (我认为,Fixup 循环中的 PC = IP?)
  • 实际上,我只记得这有点复杂:当您返回并调整跳跃大小时,您还必须考虑您将要在此位置上进行的任何短前跳跃扩展,现在无法再达到目标。
猜你喜欢
  • 2019-07-10
  • 2023-03-22
  • 1970-01-01
  • 1970-01-01
  • 2013-12-18
  • 1970-01-01
  • 1970-01-01
  • 2016-08-17
  • 2017-01-13
相关资源
最近更新 更多