x64: 如何做一个相对的 jmp *%rax?

Posted

技术标签:

【中文标题】x64: 如何做一个相对的 jmp *%rax?【英文标题】:x64: How to do a relative jmp *%rax? 【发布时间】:2015-11-02 01:49:19 【问题描述】:

我想编码一个 64 位相对跳转到 x64 汇编中存储在 %rax 中的地址。 AFAIK,没有操作码,所以我手动计算相对地址对应的绝对地址,然后绝对跳转到绝对地址:

# destination address, relative to end of jmp instruction, is stored in %rax
00007ffff7ff6020: 0x0000488d1505000000   lea 0x5(%rip),%rdx # load %rip+5 (rip + size of add and jmpq) into %rdx
00007ffff7ff6027: 0x0000000000004801d0   add %rdx,%rax # calculate absolute address based on %rdx (behind jmpq) and %rax (the relative address)
00007ffff7ff602a: 0x00000000000000ffe0   jmpq *%rax # do an absolute jump to absolute address

但这在我看来是不必要的复杂。有没有更少指令的更好方法?还是应该避免 64 位相对跳转的其他原因?

【问题讨论】:

【参考方案1】:

所有相对跳转指令都将指令中的跳转距离编码为立即数(=整数常量)操作数。存在相对跳转(和调用)以在正常执行过程中将执行从代码中的一个已知位置转移到另一个位置,例如执行条件跳转(想想许多高级编程语言中的 if 运算符)、无条件跳转(想想 BASIC 或 C/C++ 中的 goto 或无条件跳转隐藏iffor 语句内)或调用子例程。出于这个目的(这是一个非常重要的目的),这就足够了。

如果您需要访问与 RIP 相关的数据,您可以这样做,因为在 64 位模式下,有一种方法可以将内存操作数编码为 RIP 相关(在 32 位模式下没有这种编码)。这有助于生成与位置无关的代码,并有助于减少将所有代码和静态数据装入 2GB 内存的程序中完整 64 位地址的开销。

其他与 xIP 相关的用途不太常见。 计算跳转也是如此(从子程序返回除外)。计算跳转是实现诸如 switch 语句之类的常用方法,例如在C/C++、Java。有几种方法可以实现切换,但最明显的一种是有一个地址数组/表来跳转到哪里,然后只需使用一个整数索引来检索与其对应的地址。然后你可以使用间接跳转来跳转,例如jmp rax(或 AT&T 语法中的任何内容)。您可以从数组/表中获取地址并一次性跳转:jmp [table + rax*8](根据您的 AT&T 语法调整)。

【讨论】:

【参考方案2】:

应避免 64 位相对跳转,因为 jmp rel32 直接跳转的 +-2GiB 代码大小范围通常足够大。

如果你的目标更远,你通常只知道绝对地址,一开始就不应该计算64位的相对位移。

只有当位移是链接时间常数时,相对位移才有意义,在这种情况下,您(链接器)通常将源和目标放置在彼此 2GiB 以内,因此您可以使用 @ 987654322@.

如果你不能这样做,那么通常你会使用(对于假设其他地址 > 32 位的“巨大”代码模型)movabs $imm64, %rax / jmp *%rax。不过,这仍然很糟糕。 (而且它不是与位置无关的,所以如果你想随机化它的加载地址,你需要对 64 位绝对目标地址进行加载时修复。不过,像 Linux 上的 ELF 这样的可执行文件格式确实支持这一点,所以即使在 PIE 可执行文件或 PIC 共享库代码中,您也可以在跳转表中使用 64 位绝对地址。)

【讨论】:

以上是关于x64: 如何做一个相对的 jmp *%rax?的主要内容,如果未能解决你的问题,请参考以下文章

x64汇编第一课

查看汇编指令的二进制代码

如何对 AT&T 语法中的标签进行相对 jmp?

movl on x64 with GCC inline

如何在 GCC (x86_64) 中使用内联汇编进行相对跳转/调用

汇编 JMP 详解