You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何解析远跳转FF汇编指令?虚函数调用跳转地址预测方法

C++虚函数调用底层实现与FF 25指令解析

问题场景

先定义三个C++类:

class Base { virtual void foo() {...} };
class A : Base { virtual void foo() override {...} };
class B : A { virtual void bar() {...} /* 新增bar,未重写foo */ };

在分析虚函数调用的底层实现时,发现两种不同的汇编指令行为:

  • 调用A a; a.foo()时,编译器生成Call指令FF 10,指向近跳转指令E9 01 02 03 04,通过虚表记录的相对偏移找到实际函数,逻辑清晰。
  • 调用B b; b.foo()时,生成的Call指令指向FF 25跳转指令,Visual Studio反汇编如下:
00007FFA2CAB1C85 FF 25 AD 76 21 01    jmp         qword ptr [A::foo (07FFA2DCC9338h)]

原本预期直接跳转到07FFA2DCC9338h,但实际跳转到了07FFA4AD05885h,该地址处是另一条跳转指令:

00007FFA4AD05885 E9 96 5D 01 00       jmp         A::foo (07FFA4AD1B620h)

最终通过这条E9指令指向实际函数。

核心疑问:

  1. FF 25指令的工作原理是什么?
  2. 已知当前指令地址(RIP)和跳转参数,能否预测最终跳转的目标位置?

指令解析与原理

FF 25指令本质:RIP相对寻址的间接跳转

FF 25并非远跳转(x86-64的远跳转是FF 2D配合前缀或JMP FAR),而是基于RIP的间接跳转,执行逻辑分为两步:

  1. 计算内存地址:
    • 当前RIP的值 = 当前指令地址 + 指令长度(FF 25是6字节指令,所以RIP = 00007FFA2CAB1C85 + 6 = 00007FFA2CAB1C8B)
    • 加上指令后4字节的偏移量(注意小端序,指令中的AD 76 21 01实际是0x012176AD)
    • 得到目标内存地址:00007FFA2CAB1C8B + 0x012176AD = 00007FFA2DCC9338,也就是VS反汇编标注的[A::foo (07FFA2DCC9338h)]
  2. 读取跳转目标:
    从上述内存地址中读取一个8字节的QWORD值(x86-64下是64位指针),这个值就是FF 25指令实际跳转到的地址——也就是你看到的07FFA4AD05885h。

为什么B的foo调用会走这个流程?

因为B没有重写foo,它的虚表中foo的条目指向的是编译器生成的跳转桩(thunk),而非直接指向A::foo的实际代码。跳转桩的作用通常包括:

  • 处理COMDAT代码折叠(多个模块中相同的函数实现合并)
  • 支持增量编译/链接时的地址重定向
  • 统一函数的导出地址,方便动态链接

这个跳转桩就是07FFA4AD05885h处的E9指令,最终会跳转到A::foo的实际实现地址。

能否用RIP和参数预测跳转目标?

可以,步骤如下:

  1. 确定当前指令地址(即FF 25指令的起始地址),计算RIP:RIP = 当前指令地址 + 6(因为FF 25是6字节指令)
  2. 提取指令后4字节的偏移量,转换为32位有符号整数(注意小端序)
  3. 计算内存地址:内存地址 = RIP + 偏移量
  4. 读取该内存地址处的8字节QWORD值,就是FF 25指令直接跳转的目标地址
  5. 如果该目标地址是跳转桩(如E9指令),则需要进一步解析该跳转指令的相对偏移,才能得到最终的函数地址

内容的提问来源于stack exchange,提问作者Vladislav

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.22 02:48:24