汇编中的 LONG 和 FAR 跳转之间有什么区别(如果有的话)?

What is the difference, if any, between LONG and FAR jumps in Assembly?

我正在看一些汇编练习代码,作业基本上是用一个跳转点替换另一个。

原来的jmp是一个SHORT jmp,我需要接近的终点用这条指令无法到达

我现在有三个选择,我要么删除 'SHORT',要么插入 'LONG',要么插入 'FAR'。

如果有说明它们之间差异的文档,我还没有找到。有人可以帮忙吗?

我假设您的问题与 x86 架构有关;你没有在你的问题中指定。

A SHORT 跳转是从当前指令指针地址跳转到特定偏移量。 LONG 跳转可以使用更大的偏移值,因此可以跳离当前指令指针地址更远的地方。这两种跳转类型通常是 relative - 也就是说,操作数是当前指令指针的偏移量(尽管在汇编源代码中,您通常提供目标标签 - 然后是汇编器或链接器计算偏移量)。 都没有跳转到不同的代码段,所以都是'near'跳转.

A FAR 跳转 指定段和偏移量 ,它们都是 absolute 因为它们指定了需要代码段和指令指针,而不是相对于当前代码段/指令指针的偏移量。

总结一下,直接跳转分为三种:shortlong,都是near跳转可以跳转相同代码段的不同相对距离,而far可以跳转到任意绝对地址(段和偏移量)。

(请注意,也可以执行 间接绝对 跳转,您可以在其中指定一个操作数来保存您希望跳转到的绝对地址。在这种情况下jump 可以是 near 也可以是 far - 即它可以包含或不包含所需的代码段)。

如果不指定跳转'distance',则由汇编程序决定是短跳转、长跳转还是远跳转。大多数现代汇编程序都是“两次通过”的,如果可能,将使用短跳转,否则将使用长跳转或远跳转 - 后者仅在需要时使用。

如果您需要帮助理解我所说的 'segment' 的意思,请参阅 wikipedia's entry on x86 memory segmentation

有关可能的 JMP 指令寻址模式的完整详细信息,请参阅 this description of the x86 JMP instruction

一个SHORT跳:

  • 如果是正向跳转,编码使用00h(+0)到7Fh(+127)的相对偏移值,使程序执行能够跳转到另一条指令,中间最大127字节.
  • 如果是向后跳转,编码使用从80h(-128)到FFh(-1)的相对偏移值,这使得程序执行能够跳转到另一条指令,它们之间最多有125个字节.

一个LONG跳转,可以使用较大的偏移量。

一个FAR跳转,跳转到另一个代码段。

TL:DR: short vs. long/near 只是强制选择指令长度。
far 是完全不同的野兽。

  • jmp short foojmp rel8.
  • jmp long foojmp near foojmp rel16/rel32(取决于模式)
  • jmp far [rdi] 是对新 CS:[ER]IP 的跳转/调用。你很少想要这个。

请参阅 Intel 手册(如果您熟悉所使用的符号,请参阅 HTML scrape)以了解 jmp / call, jcc (conditionals like jge), and loop 的可用 machine-code 形式(其编码类似于jcc short).


x86 有两个主要的 jumps/calls 家族,每个家族都有一些变化:

  • Far 到新的 CS:IP(或 CS:EIP / CS:RIP,具体取决于模式)。
    几乎从未在普通的 32 位或 64 位代码中使用过(例如调用使用 syscall 指令的 64 位代码的 WOW64 32 位系统 DLL),如果您不适合,则仅在 16 位代码中使用你的程序变成 64K,或者在 MBR 引导加载程序中设置一个已知的实模式 CS,或者切换到 32 位。

    直接(64 位模式除外)或 memory-indirect,但始终是绝对的。有意思的是,x86唯一的绝对直接跳转。
    没有条件远跳,只有 jmpcall / retf
    语法细节取决于 assembler,但通常 jmp 0x10:foojmp far [eax] 在 NASM 中工作,后者从 [DS:EAX] 加载 6 个字节到 [=202] =] 如果 运行 在 32 位模式下。

  • Near是正常跳跃,不改变CS,只是设置了一个新的IP/EIP/RIP。可以使用以下表格:

    • 间接(使用绝对目标,例如函数指针或跳转表)like
      jmp axjmp qword [rsi],或与call相同。 (不是条件间接 jcc)。 short 与否没有意义,因为该指令的机器代码仅对查找新 [ER]IP 的位置进行编码,而不直接编码如何到达它。 ax vs. eax 是 operand-size 的问题,或者 [esi] vs. [rsi] 是 address-size 的问题。 [rsi+0x1230] 是用作寻址模式一部分的 disp8 / disp32 的问题。
    • Direct 使用添加到 IP/EIP/RIP 的相对位移(编码到指令的机器代码中)。所以它们是相对于指令的结尾1这是您从普通 jmp foojle .else 得到的结果,assembler 通常会为您选择一个长度。
      • short 表示使用 8 位(1 字节)相对位移,又名 rel8.
        适用于 jcc rel8jmp rel8(以及 loop),不适用于 call

      • non-short,使用rel16(16位模式2)或rel32(其他模式) .所以一个2字节或4字节的相对位移。
        您可以在 asm 源代码中使用 nearlong 强制此编码。
        适用于 jmp rel16/32call rel16/32,以及 386 和更高版本 jcc rel16/32。如果使用限制为 286 或更早的指令进行组装,如果到目标标签的距离超出了 rel8 的 [-128, +127] 范围,则 assembler 会抱怨,或者使用 [=57 之类的回退=] 在 jmp.

      • 没有近乎绝对的直接。对于那个使用 mov eax, 0x123456 / jmp eax (在 register-indirect 附近)如果你不能保证(或者很容易让工具链在 link 时间计算)这段代码和绝对目标。


脚注 1: 例如 EB 00 是一个使用短 jmp.
的慢速 NOP 或者 E8 00 00 00 00 / 5B 是一个 call next (或 call $+5)/ next: pop ebx 就像你可能用来读取 EIP 而实际上在 32 位模式下没有去任何地方一样RIP-relative LEA 不可用)

脚注 2::从技术上讲,您 可以 在 32 位模式下使用 jmp rel16,但它将 t运行将 EIP 转换为 16 位。 (并且编码使用 66h operand-size 前缀,因此它仅比 jmp rel32 短 1 个字节)

在 16 位和 32 位模式下,jmp rel16/rel32 可以达到任何其他 IP/EIP 值,但在 64 位模式下,+-2GiB 范围只是虚拟地址的一小部分 space。尽管如此,单个可执行文件的代码假设它适合 2GiB 是正常的,因此任何代码都可以到达同一库中的任何其他代码 或主可执行文件 相对接近 jump/call. “大型”代码模型需要 mov reg, imm64 / jmp reg 或类似低效的东西。或者更糟糕的是 position-independent.


LONG 是不常见的术语。在大多数 assemblers 中,编码覆盖是 short (rel8) 或 near(rel16 或 rel32,取决于模式)以强制长度(以及你可以跳多远)近jumps (cs 不变,只是在 IP/EIP/RIP 上增加一个偏移量)

根据此处的其他答案,在 assemblers 中 long 是一个东西,它与 NASM jmp near foo 相同的 rel16 或 rel32 覆盖.

NASM 列表 (nasm -felf32 foo.asm -l/dev/stdout)

     1                                  foo:
     2 00000000 E9FBFFFFFF              jmp  near foo
     3 00000005 EBF9                    jmp  foo           ; optimizes to short by default
     4 00000007 EBF7                    jmp short foo

NASM 进行 multi-pass 优化以找到可用于每个分支的最短编码。这通常是最佳的,但 seWhy is the "start small" algorithm for branch displacement not optimal? 对于手动强制一个分支的编码可能允许更小代码的极端情况。

如果分支目标在另一个文件中,所以 NASM 在 assemble 时不知道它有多远,它假设 near (non-short).如果你知道你将 link 在一起的文件很小(或者代码在一个特殊的部分),你可以强制这样做。

或者如果你想为其他东西留下完整的 rel32 来修改此机器代码并写入新的偏移量,那将为 near 的 use-case。例如,在 Linux 上动态 linking 中使用的 PLT 曾经以这种方式工作(我认为),重写 jmp rel32 中的偏移量而不是使用 GOT 条目进行间接 jmp .

从历史上看,一些 assemblers 不如 NASM 聪明,如果你想要跳转的短编码,总是需要手动提示。特别是对于向前跳转,到 assembler 还没有看到的标签。 (如果你正在使用那个时代的旧工具处理 16 位代码,你可能 运行 进入这个。)甚至 NASM 过去在旧版本中默认禁用优化,这将使它选择长编码。

此外,jcc near 仅在 386 及更高版本上受支持,因此如果您希望 assembler 实际发出该信号,则可能需要明确说明。