【问题标题】:How to execute a call instruction with a 64-bit absolute address?如何使用 64 位绝对地址执行调用指令?
【发布时间】:2016-12-22 00:28:07
【问题描述】:

我正在尝试从机器代码调用一个函数——在编译和链接时它应该有一个绝对地址。我正在创建一个指向所需函数的函数指针并尝试将其传递给调用指令,但我注意到调用指令最多需要一个 16 位或 32 位地址。有没有办法调用绝对 64 位地址?

我正在为 x86-64 架构进行部署,并使用 NASM 生成机器代码。

如果我可以保证可执行文件肯定会映射到底部的 4GB 内存,我可以使用 32 位地址,但我不确定在哪里可以找到该信息。

编辑:我不能使用 callf 指令,因为这需要我禁用 64 位模式。

第二次编辑:我也不想将地址存储在寄存器中并调用寄存器,因为这对性能至关重要,而且我不能承受间接函数调用的开销和性能损失.

最终编辑:通过确保我的机器代码映射到前 2GB 内存,我能够使用 rel32 调用指令。这是通过带有 MAP_32BIT 标志的 mmap 实现的(我使用的是 linux):

MAP_32BIT(自 Linux 2.4.20、2.6 起) 将映射放入前 2 GB 进程地址空间。仅支持此标志 在 x86-64 上,用于 64 位程序。它被添加到 允许在某处分配线程堆栈 前 2GB 内存,以提高上下文- 在一些早期的 64 位处理器上切换性能。 现代 x86-64 处理器不再具有此特性 形成问题,所以使用这个标志不是 在这些系统上需要。 MAP_32BIT 标志是 设置 MAP_FIXED 时忽略。

【问题讨论】:

  • 把它放在一个 64 位的寄存器中然后调用它?应该很容易验证。
  • 为什么这个标签是 C++?
  • 他可能使用 C++ 函数指针从 C++ 调用他的汇编程序,但这确实与关于 x86-64 汇编程序的问题无关。
  • @Brian 我取出了 C++ 标签
  • @RudyVelthuis 查看我对问题的第二次编辑

标签: assembly x86-64 nasm jit function-call


【解决方案1】:

相关:Handling calls to (potentially) far away ahead-of-time compiled functions from JITed code 有更多关于 JIT 的内容,尤其是在它要调用的代码附近分配 JIT 缓冲区,因此您可以使用高效的call rel32。不然怎么办。

另外,Call an absolute pointer in x86 machine code 是关于calljmp 到绝对地址的一个很好的规范问答。


TL:DR:要通过名称调用函数,只需像普通人一样使用call func,然后让汇编器+链接器来处理它。既然您说您使用的是 NASM,我猜您实际上是在使用汇编程序生成机器代码。这听起来像是一个更复杂的问题,但我认为您只是想问正常方式是否安全。


Indirect call r/m64 (FF /2) 在 64 位模式下采用 64 位寄存器或内存操作数。

所以你可以这样做

func equ  0x123456789ab
; or if func is a regular label

mov   rax, func          ; mov r64, imm64,  or mov r32, imm32 if it fits
call  rax

通常您会将标签地址放入带有lea rax, [rel func] 的寄存器中,但如果这是可编码的,那么您只需使用call rel32


或者,如果你知道你的机器码将存储在哪个地址,你可以使用正常的直接call rel32编码,在你计算出从目标到结尾的地址差之后call 指令。

如果您不想使用间接调用,那么rel32 编码是您唯一的选择。确保您的机器代码进入低 2GiB,以便它可以到达低 4GiB 中的任何地址。


如果我可以保证可执行文件肯定会映射到底部的 4GB 内存

是的,这是 Linux、Windows 和 OS X 的默认代码模型。AMD64 调用/跳转指令和 RIP 相对寻址,仅使用rel32 编码,因此所有系统默认为“小”代码模型其中代码和静态数据在低 2GiB 中,因此可以保证链接器只需填写 rel32 即可达到最高 2G 前向或 2G 后向。

x86-64 System V ABI 确实讨论了大型/巨大的代码模型,但如果有人使用过它,IDK,因为寻址数据和进行调用的效率低下。


re: 效率:是的,mov / call rax 效率较低。我认为如果分支预测未命中并且无法从 BTB 提供目标预测,它会明显变慢。然而,即使call rel32jmp rel32 仍然需要 BTB 才能获得全部性能。请参阅 Slow jmp-instruction 以获取来自相对 jmp next_insn 的实验结果,当巨型循环中有太多时会减慢速度。

使用热分支预测器,间接版本只是额外的代码大小和额外的微指令(mov)。它可能会消耗更多的预测资源,但也可能不会。

另见What branch misprediction does the Branch Target Buffer detect?

【讨论】:

  • 感谢您提供有关默认代码模型的有用信息。你碰巧有我可以查看的来源吗?
  • @AlexanderBolinsky:是的,我回答的最后一段中的那个 URL 是官方 ABI 标准的链接。另请参阅 x86 tag wiki 以获取其他 ABI 的链接。
  • @PeterCordes:call rel32call r64 的性能有什么区别?
  • 我明白他们的所作所为。这两者之间的性能有何不同?
  • @RudyVelthuis:添加了一些关于直接跳转性能的内容,这些内容仍然需要预测资源。糟糕,我看到我在阅读您之前的评论时漏掉了一个词。在这个和另一个答案线程之间切换了太多的心理上下文。
猜你喜欢
  • 2011-04-04
  • 1970-01-01
  • 2012-10-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-01-29
  • 2018-02-21
  • 1970-01-01
相关资源
最近更新 更多