在汇编语言中,LONG跳转和FAR跳转有何不同(如果有的话)?

15

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

原始 jmp 是 SHORT jmp,而我需要到达的终点无法使用此指令。

现在我有三个选项,要么删除“SHORT”,要么插入“LONG”,要么插入“FAR”。

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


3
FYI,“long” 是不寻常的术语。在大多数汇编程序中,编码覆盖是“short”(rel8)或“near”(rel16或rel32,取决于模式)。根据这里的答案,“long” 是与 NASM jmp near foo 相同的 rel16 / rel32 覆盖。请参见 https://www.felixcloutier.com/x86/jmp 以获取机器代码中可用的形式。 - Peter Cordes
3个回答

29
我假设你的问题涉及x86架构;你在问题中没有具体说明。
一个"SHORT"跳转是从当前指令指针地址跳转到特定偏移量的跳转。一个"NEAR"跳转可以使用更大的偏移值,因此可以跳跃到离当前指令指针地址更远的位置。这两种跳转类型通常是"相对的" - 也就是说,操作数是相对于当前指令指针的偏移量(尽管在汇编源代码中,你通常提供目标标签 - 汇编器或链接器然后计算偏移量)。它们都不会跳转到不同的代码段。
一个"FAR"跳转同时指定了段和偏移量,它们都是"绝对的",因为它们指定了所需的代码段和指令指针,而不是相对于当前代码段/指令指针的偏移量。
一个"long"跳转通常是"FAR"跳转的另一个名称(例如,AT&T语法使用"ljmp"作为"jmp far"的等效形式),但你的问题暗示了使用一个将其等同于"NEAR"跳转的汇编器。
总结一下,有三种直接跳转的类型:短跳近跳,它们都能够在同一代码段中跳转不同的相对距离,还有远跳(或长跳),可以跳转到任意绝对地址(段和偏移)。
(注意,还可以执行间接绝对跳转,其中您指定一个操作数,该操作数保存了您希望跳转到的绝对地址。在这种情况下,跳转可以是近跳或远跳 - 即可以包含或不包含所需的代码段)。
如果您不指定跳转的“距离”,汇编器将决定是使用短跳、近跳还是远跳。大多数现代汇编器都是“两遍扫描”的,如果可能的话会使用短跳,否则会使用近跳或远跳 - 只有在必要时才会使用后者。
如果您需要帮助理解我所说的“段”的含义,请参阅wikipedia's entry on x86 memory segmentation
请参阅这个x86 JMP指令的描述,了解可能的JMP指令寻址模式的详细信息。

完全忘记在这个问题上选择答案了。真是糊涂。谢谢你的解释,非常有帮助! - spoorlezer

8

一个SHORT跳转:

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

LONG跳转可以使用更大的偏移量。

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


2
@JimMischel那不是真的,请看我在上面回答中给出的JMP指令细节链接。LONG跳转可以是相对或absolute indirect,而FAR跳转总是指定一个绝对地址,但LONG跳转不是这样的。 - davmac
简而言之,短跳转是一个相对于EIP/RIP的有符号8位位移。 - Peter Cordes

2
TL:DR: shortlong/near只是强制指令长度的选择。far则完全不同。
  • jmp short foojmp rel8
  • jmp long foojmp near foojmp rel16/rel32(取决于模式)
  • jmp far [rdi] 是跳转/调用到一个新的CS:[ER]IP。你很少需要这样做。
请参考英特尔的手册(或者如果您熟悉所使用的符号,则可以进行HTML抓取),了解jmp/calljcc(如jge等条件语句)和loop(其编码方式类似于jcc short)。
x86有两个主要的跳转/调用家族,在每个家族内部还有一些变化:
远跳转(Far)到一个新的CS:IP(或者CS:EIP/CS:RIP,取决于模式)。在正常的32位或64位代码中几乎不使用(例如,WOW64 32位系统DLL调用进入使用syscall指令的64位代码),只有在16位代码中,如果无法将程序放入64K中,或在MBR引导加载程序中设置已知的实模式下的CS,或者切换到32位时才使用。
直接跳转(除了在64位模式下)或内存间接跳转,但始终是绝对跳转。有趣的是,x86唯一的绝对直接跳转。没有条件远跳转,只有jmp或call/retf。语法细节取决于汇编器,但通常像jmp 0x10:foo或jmp far [eax]这样的东西适用于NASM,后者在32位模式下运行时从[DS:EAX]加载6个字节到CS:EIP中。
近跳转(Near)是一个普通的跳转,不改变CS,只设置新的IP/EIP/RIP。有以下形式:
- 内存间接(使用绝对目标,例如函数指针或跳转表)如jmp ax或jmp qword [rsi],或者与call相同。没有条件间接jcc。短的或不重要的,因为指令的机器码只编码找到新的[ER]IP的位置,而不是直接如何到达它。ax vs eax是操作数大小的问题,[esi] vs [rsi]是地址大小的问题。[rsi+0x1230]是作为寻址模式的一部分使用的disp8/disp32的问题。 - 直接使用相对位移(编码到指令的机器码中),将添加到IP/EIP/RIP中。所以它们相对于指令的末尾1。这是从正常的jmp foo或jle.else得到的,汇编器通常会为您选择一个长度。 - 短表示使用8位(1字节)相对位移,也称为rel8。可用于jcc rel8和jmp rel8(以及loop),而不是call。 - 非短,使用rel16(16位模式2)或rel32(其他模式)。因此,是一个2字节或4字节的相对位移。您可以在asm源代码中使用near或long强制使用此编码。可用于jmp rel16/32和call rel16/32,并且在386及更高版本上可用jcc rel16/32。如果使用的指令被限制为286或更早版本,则汇编器将抱怨目标标签的距离超出了rel8的[-128,+127]范围,或者使用回退,例如jnl覆盖jmp。 - 没有近距离的绝对直接跳转。如果无法保证(或轻松地让工具链在链接时计算)此代码与绝对目标之间的距离,请使用mov eax,0x123456 / jmp eax(近寄存器间接)。
脚注1:例如EB 00是使用短的jmp进行缓慢的NOP。或者E8 00 00 00 00 / 5B是一个call next(或call $+5),next: pop ebx,就像您在32位模式下读取EIP但实际上不去任何地方一样,其中RIP相对LEA不可用。
脚注2:从技术上讲,您可以在32位模式下使用jmp rel16,但它会将EIP截断为16位。(并且编码使用一个66h操作数大小前缀,因此它只比jmp rel32少1个字节)
在16位和32位模式下,jmp rel16/rel32 可以到达任何其他IP/EIP值,但是在64位模式下,+-2GiB范围仅占虚拟地址空间的一小部分。尽管如此,对于单个可执行文件的代码来说,假设它适合于2GiB是很正常的,因此任何代码都可以通过相对近跳转/调用到达同一库或主可执行文件中的任何其他代码。一个“大型”代码模型需要 mov reg, imm64 / jmp reg 或类似低效的东西。甚至更糟糕的是让它成为位置无关的。
“LONG” 是不寻常的术语。在大多数汇编程序中,编码覆盖是“short”(rel8)或“near”(rel16或rel32,取决于模式),以强制近跳转的长度(因此可以跳转多远)(“cs”不变,只是向IP/EIP/RIP添加偏移量)。
根据这里的其他答案,在支持“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进行多遍优化以找到每个分支可使用的最短编码。这通常是最优的,但在一些边角情况下,手动强制一个分支的编码可能会允许更小的代码,请参见为什么“从小开始”的分支位移算法不是最优的?
如果分支目标在另一个文件中,因此NASM在汇编时不知道它距离有多远,它会假定为near(非短)。如果您知道要链接的文件很小(或代码在特殊部分中),则可以强制执行该操作。
或者,如果您想留下一个完整的rel32供其他内容修改这段机器码并编写新的偏移量,则near是一种用例。例如,在Linux上用于动态链接的PLT曾经就是这样工作的(我认为),重写jmp rel32中的偏移量而不是使用GOT条目进行间接jmp。
历史上,一些汇编器不如NASM智能,并且如果您想要短跳转编码,它们总是需要手动提示。特别是对于前向跳转,到一个汇编器尚未看到的标签。(如果您使用那个时代的旧工具处理16位代码,则可能会遇到此问题。)即使是NASM旧版本也默认禁用优化,这将使其选择长编码。
此外,jcc near仅支持386及更高版本,因此如果您希望汇编器实际发出它,您可能需要明确指出。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接