我们有来自 UE5 的以下类
class IRHIComputeContext {}
class IRHICommandContext : public IRHIComputeContext
{
// pure virtual method
virtual void RHICopyTexture(FRHITexture* SourceTexture, FRHITexture* DestTexture, const FRHICopyTextureInfo& CopyInfo) = 0;
}
class FMetalRHICommandContext : public IRHICommandContext
{
// overridden final virtual method
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
的地址进行挂钩。
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]
为零,超出了 sizeof(*Context)
的有效内存范围,即 512。
据我了解,每个类类型都有自己的独立 vtable,其中包含指向基表中方法的槽。我的代码适用于当前类中定义的其他虚拟方法,但不适用于此处继承的方法。如何找到
RHICopyTexture
的真实地址?
// https://github.com/ARM-software/abi-aa/blob/main/cppabi64/cppabi64.rst#representation-of-pointer-to-member-function
template<typename Func>
inline void* get_cxx_member_func_ptr(void* BaseAddress, Func f) {
// a pointer to member function is a pair of words <ptr, adj>
verify(sizeof(f) == 16);
union {
Func Fcn;
struct {
// The generic C++ ABI [GCPPABI] specifies that the least significant bit of ptr discriminates between
// (0) the address of a non-virtual member function and
// (1) the offset in the class’s virtual table of the address of a virtual function.
// However, this encoding cannot work for the AArch64 instruction set where the architecture reserves all bits of code addresses.
// This ABI specifies that adj contains twice the this adjustment, plus 1 if the member function is virtual.
// The least significant bit of adj then makes exactly the same discrimination as the least significant bit of ptr does for Itanium.
// A pointer to the member function to call. If the member function is
// virtual, this will be a thunk that forwards to the appropriate vftable
// slot.
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]);
}
}
更新:
有两个虚拟桌子。
_ZTV23FMetalRHICommandContext
类 FMetalRHICommandContext
包含 RHICopyTexture
_ZTV32FMetalRHIImmediateCommandContext
对于派生类 FMetalRHIImmediateCommandContext
FMetalRHIImmediateCommandContext*
的指针指的是vptr
地址。 vptr
地址比其 vtable
地址 _ZTV32FMetalRHIImmediateCommandContext
大 16 个字节 (0x10)。
问题是,
_ZTV32FMetalRHIImmediateCommandContext
中的槽对于继承的虚拟方法RHICopyTexture
来说是零。
但它确实存在于基类的 vtable 中
_ZTV23FMetalRHICommandContext
。
这里是在 ARM64(Clang) 中检索成员函数指针的代码
template<typename Func>
inline void* get_cxx_member_func_ptr(const gpointer Instance, Func f) {
// a pointer to member function is a pair of words <ptr, adj>
verify(sizeof(f) == 16);
union {
Func Fcn;
struct {
// A pointer to the member function to call. If the member function is
// virtual, this will be a thunk that forwards to the appropriate vftable
// slot.
void *FunctionPointerOrVirtualThunk;
int IsVirtual;
};
};
Fcn = f;
if (!IsVirtual){
return FunctionPointerOrVirtualThunk;
}
else {
void** VPtr = *(reinterpret_cast<void***>(Instance));
uint64_t ThunkIndex = reinterpret_cast<uint64_t>(FunctionPointerOrVirtualThunk);
// vtable starts with offset_to_top and RTTI
// hence vptr pointer is 16bytes behind vtable. e.g.
// VTable = 0x0000000324E02708
// VPtr = 0x0000000324E02718
// More details: https://taylorbowland.com/posts/virtual-tables/
return VPtr[ThunkIndex/sizeof(void*) - 2];
}
}
如果你不喜欢上面回复编译器实现的肮脏方法,也许你可以通过导出符号找到
vtable
地址
extern "C" {
#include "frida-gum.h"
#include <fcntl.h>
#include <unistd.h>
}
static gboolean find_export_in_module(const GumModuleDetails * details, gpointer user_data)
{
GumExportDetails* target = (GumExportDetails*)user_data;
if (target) {
target->address = gum_module_find_export_by_name( details->name, target->name);
}
return target->address == 0;
}
static gpointer find_vtable_address(const char* export_name) {
gum_init_embedded();
GumExportDetails target;
target.name = export_name;
target.address = GPOINTER_TO_SIZE(nullptr);
target.type = GUM_EXPORT_VARIABLE;
gum_process_enumerate_modules(find_export_in_module, &target);
return GSIZE_TO_POINTER(target.address);
}
void** VTable = reinterpret_cast<void**>(find_vtable_address("_ZTV32FMetalRHIImmediateCommandContext"));