Mac M(AArch64)平台下获取UE5继承虚方法RHICopyTexture的地址
环境信息
- 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架构对成员函数指针的特殊编码,以下是正确解决步骤:
定位基类虚表指针
多重继承类的对象内存中,每个基类对应独立的虚表指针。需要先将派生类指针转换为目标基类指针,再获取其虚表指针:IRHICommandContext* BaseContext = static_cast<IRHICommandContext*>(Context); void** VTablePtr = *reinterpret_cast<void***>(BaseContext);修正虚表起始地址
AArch64下,对象的虚表指针指向的地址比实际虚表起始地址大16字节(0x10),需要减去该偏移得到真实虚表:uintptr_t RealVTableAddr = reinterpret_cast<uintptr_t>(VTablePtr) - 0x10; void** RealVTable = reinterpret_cast<void**>(RealVTableAddr);计算虚表槽位索引
得到的720是成员函数指针编码中的偏移值,除以指针大小(8字节)得到虚表中的槽位索引:size_t SlotIndex = 720 / sizeof(void*); void* RHICopyTextureAddr = RealVTable[SlotIndex];修正工具函数逻辑
修改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);验证地址有效性
通过UE_LOG打印或调试器确认获取的地址指向有效代码段,确保Hook逻辑可正常执行。
内容的提问来源于stack exchange,提问作者Mr.Wang from Next Door

