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

Mac M(AArch64)平台下获取UE5继承虚方法RHICopyTexture的地址

UE5中获取继承虚方法地址的问题解决

环境信息

  • CPU:Mac M(AArch64)
  • 编译器:XCode(Clang)

UE5类定义与继承关系

class IRHIComputeContext {}

class IRHICommandContext : public IRHIComputeContext
{
    // 纯虚方法
    virtual void RHICopyTexture(FRHITexture* SourceTexture, FRHITexture* DestTexture, const FRHICopyTextureInfo& CopyInfo) = 0;
}

class FMetalRHICommandContext : public IRHICommandContext
{
    // 重写的最终虚方法
    virtual void RHICopyTexture(FRHITexture* SourceTextureRHI, FRHITexture* DestTextureRHI, const FRHICopyTextureInfo& CopyInfo) final override;
}

class FMetalRHIImmediateCommandContext : public FMetalRHICommandContext
{
}

继承关系:FMetalRHIImmediateCommandContext <-- FMetalRHICommandContext <-- IRHICommandContext <-- IRHIComputeContext
其中RHICopyTexture在IRHICommandContext中定义,在FMetalRHICommandContext中重写。

问题场景

持有FMetalRHIImmediateCommandContext*指针,想要获取RHICopyTexture的地址用于Hook,尝试了以下代码:

FMetalRHIImmediateCommandContext* Context = StaticCast<FMetalRHIImmediateCommandContext*>(GDynamicRHI->RHIGetDefaultContext());
check(Context);
FMetalDeviceContext& MetalDeviceContext = Context->GetInternalContext();

void** const pCastedToHeader = reinterpret_cast<void**>(Context);
for (size_t i = 0; i < sizeof(*Context) / sizeof(void*); i++)
{
    UE_LOG(LogTemp, Warning, TEXT("Pointer[%04zu] = 0x%016X"), i, pCastedToHeader[i]);
}

UE_LOG(LogTemp, Warning, TEXT("&IRHICommandContext::RHICopyTexture = %d"), &IRHICommandContext::RHICopyTexture);
UE_LOG(LogTemp, Warning, TEXT("&FMetalRHICommandContext::RHICopyTexture = %d"), &FMetalRHICommandContext::RHICopyTexture);
UE_LOG(LogTemp, Warning, TEXT("&FMetalRHIImmediateCommandContext::RHICopyTexture = %d"), &FMetalRHIImmediateCommandContext::RHICopyTexture);

输出结果:

&IRHICommandContext::RHICopyTexture = 720
&FMetalRHICommandContext::RHICopyTexture = 720
&FMetalRHIImmediateCommandContext::RHICopyTexture = 720

得到RHICopyTexture的偏移为720(0x2D0),与get_cxx_member_func_ptr方法获取的结果一致,但Pointer[720]为0,且超出了sizeof(*Context)(仅512)的有效内存范围。

补充信息

存在两个虚表:

  • _ZTV23FMetalRHICommandContext:FMetalRHICommandContext类的虚表,包含RHICopyTexture的定义
  • _ZTV32FMetalRHIImmediateCommandContext:派生类FMetalRHIImmediateCommandContext的虚表

FMetalRHIImmediateCommandContext*指针指向vptr地址,vptr地址比其虚表地址_ZTV32FMetalRHIImmediateCommandContext大16字节(0x10)。问题在于,派生类虚表中RHICopyTexture对应的槽位为0,但该方法存在于基类的虚表中。

疑问

现有代码对当前类定义的其他虚方法有效,但对该继承的虚方法无效,如何正确获取RHICopyTexture的真实地址?

附get_cxx_member_func_ptr实现(已翻译注释):

// AArch64架构下C++成员函数指针的表示规范
template<typename Func>
inline void* get_cxx_member_func_ptr(void* BaseAddress, Func f) {
    // 成员函数指针是由两个字组成的对 <指针, 调整值>
    verify(sizeof(f) == 16);
    union {
        Func Fcn;
        struct {
          // 通用C++ ABI规定:指针的最低位用于区分
          // (0) 非虚成员函数的地址
          // (1) 虚函数在类虚表中的偏移
          // 但这种编码在AArch64架构下无法工作,因为该架构要求代码地址的所有位都有效。
          // 本ABI规定:调整值包含两倍的this指针调整量,若为虚函数则加1。
          // 调整值的最低位可以实现与Itanium架构中指针最低位相同的区分作用。
          // 指向要调用的成员函数的指针。如果是虚函数,这将是一个转发到对应虚表槽位的转接函数。
          void *FunctionPointerOrVirtualThunk;
          int IsVirtual;
        };
    };
    Fcn = f;
    if (!IsVirtual){
        return FunctionPointerOrVirtualThunk;
    }
    else {
        uint64_t ThunkIndex = reinterpret_cast<uint64_t>(FunctionPointerOrVirtualThunk);
        void*** VTable = reinterpret_cast<void***>(BaseAddress);
        return *(VTable[ThunkIndex]);
    }
}

解决方案

问题核心在于多重继承下虚表的层级布局以及AArch64架构对成员函数指针的特殊编码,以下是正确解决步骤:

  1. 定位基类虚表指针
    多重继承类的对象内存中,每个基类对应独立的虚表指针。需要先将派生类指针转换为目标基类指针,再获取其虚表指针:

    IRHICommandContext* BaseContext = static_cast<IRHICommandContext*>(Context);
    void** VTablePtr = *reinterpret_cast<void***>(BaseContext);
    
  2. 修正虚表起始地址
    AArch64下,对象的虚表指针指向的地址比实际虚表起始地址大16字节(0x10),需要减去该偏移得到真实虚表:

    uintptr_t RealVTableAddr = reinterpret_cast<uintptr_t>(VTablePtr) - 0x10;
    void** RealVTable = reinterpret_cast<void**>(RealVTableAddr);
    
  3. 计算虚表槽位索引
    得到的720是成员函数指针编码中的偏移值,除以指针大小(8字节)得到虚表中的槽位索引:

    size_t SlotIndex = 720 / sizeof(void*);
    void* RHICopyTextureAddr = RealVTable[SlotIndex];
    
  4. 修正工具函数逻辑
    修改get_cxx_member_func_ptr,增加基类类型参数,确保从对应基类的虚表中查找:

    template<typename Func, typename BaseClass>
    inline void* get_cxx_member_func_ptr(void* DerivedAddress, Func f) {
        verify(sizeof(f) == 16);
        union {
            Func Fcn;
            struct {
                void *FunctionPointerOrVirtualThunk;
                int IsVirtual;
            };
        };
        Fcn = f;
        if (!IsVirtual){
            return FunctionPointerOrVirtualThunk;
        }
        else {
            // 转换为目标基类指针
            BaseClass* BasePtr = static_cast<BaseClass*>(static_cast<void*>(DerivedAddress));
            void** VTablePtr = *reinterpret_cast<void***>(BasePtr);
            // 修正AArch64虚表偏移
            uintptr_t RealVTableAddr = reinterpret_cast<uintptr_t>(VTablePtr) - 0x10;
            void** RealVTable = reinterpret_cast<void**>(RealVTableAddr);
            
            uint64_t ThunkIndex = reinterpret_cast<uint64_t>(FunctionPointerOrVirtualThunk);
            size_t SlotIndex = ThunkIndex / sizeof(void*);
            return RealVTable[SlotIndex];
        }
    }
    

    使用方式:

    void* FuncAddr = get_cxx_member_func_ptr<decltype(&IRHICommandContext::RHICopyTexture), IRHICommandContext>(Context, &IRHICommandContext::RHICopyTexture);
    
  5. 验证地址有效性
    通过UE_LOG打印或调试器确认获取的地址指向有效代码段,确保Hook逻辑可正常执行。


内容的提问来源于stack exchange,提问作者Mr.Wang from Next Door

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 22:32:02