在 Win32 上挂钩虚拟方法(return 对象大于 ptr 崩溃)?
Hooking virtual method on Win32 (return object larger than ptr crashes)?
因此尝试挂钩 OpenVR 方法“ITrackedDeviceServerDriver::GetPose”,但是当我的自定义挂钩方法 returns 退出时我遇到了访问冲突(我认为调用约定有问题但我知道什么x64 asm 期望)。
我在“ITrackedDeviceServerDriver”中挂接的所有其他方法都可以正常工作。只有“GetPose”失败。
Class 被钩住的看起来像:
class ITrackedDeviceServerDriver
{
public:
virtual EVRInitError Activate( uint32_t unObjectId ) = 0;
virtual void Deactivate() = 0;
virtual void EnterStandby() = 0;
virtual void *GetComponent( const char *pchComponentNameAndVersion ) = 0;
virtual void DebugRequest( const char *pchRequest, char *pchResponseBuffer, uint32_t unResponseBufferSize ) = 0;
virtual DriverPose_t GetPose() = 0;
};
大于 ptr 的对象并从崩溃的方法中返回
struct DriverPose_t
{
double poseTimeOffset;
vr::HmdQuaternion_t qWorldFromDriverRotation;
double vecWorldFromDriverTranslation[ 3 ];
vr::HmdQuaternion_t qDriverFromHeadRotation;
double vecDriverFromHeadTranslation[ 3 ];
double vecPosition[ 3 ];
double vecVelocity[ 3 ];
double vecAcceleration[ 3 ];
vr::HmdQuaternion_t qRotation;
double vecAngularVelocity[ 3 ];
double vecAngularAcceleration[ 3 ];
ETrackingResult result;
bool poseIsValid;
bool willDriftInYaw;
bool shouldApplyHeadModel;
bool deviceIsConnected;
};
我如何连接虚拟方法的示例:
typedef DriverPose_t(__thiscall* GetPose_Org)(ITrackedDeviceServerDriver* thisptr);
GetPose_Org GetPose_Ptr = nullptr;
DriverPose_t __fastcall GetPose_Hook(ITrackedDeviceServerDriver* thisptr)
{
DriverPose_t result = GetPose_Ptr(thisptr);// works
//result.deviceIsConnected = true;
return result;// after return I get access violation crash
}
void TestHook(ITrackedDeviceServerDriver *pDriver)
{
MEMORY_BASIC_INFORMATION mbi;
ZeroMemory(&mbi, sizeof(MEMORY_BASIC_INFORMATION));
void** vTable = *(void***)(pDriver);
VirtualQuery((LPCVOID)vTable, &mbi, sizeof(MEMORY_BASIC_INFORMATION));
VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_EXECUTE_READWRITE, &mbi.Protect);// unlock
Activate_Ptr = (Activate_Org)vTable[0];
vTable[0] = &Activate_Hook;// Hook!
Deactivate_Ptr = (Deactivate_Org)vTable[1];
vTable[1] = &Deactivate_Hook;// Hook!
EnterStandby_Ptr = (EnterStandby_Org)vTable[2];
vTable[2] = &EnterStandby_Hook;// Hook!
GetPose_Ptr = (GetPose_Org)vTable[5];
vTable[5] = &GetPose_Hook;// Hook!
VirtualProtect(mbi.BaseAddress, mbi.RegionSize, mbi.Protect, &mbi.Protect);// lock
}
那么,为什么“GetPose”是 returns 之后唯一失败的方法?
如何修复“GetPose”的调用约定以正确使用寄存器?
如评论中所述
__thiscall
and __fastcall
are not the same calling convention
甚至 x64。对于 x86 这很明显 - __thiscall
- 通过 Ecx 传递第一个参数 (this)通过堆栈注册和下一个参数(我现在跳过 float/double 参数案例)。 __fastcall
- 首先通过 Ecx,2 秒通过 Edx,然后通过堆栈
如果 x64(再次假设没有 float/double 参数)- 前 4 个参数通过 Rcx、Rdx、R8、R9 和堆栈中的下一个。所以看起来 x64 有单一和通用的调用约定(__stdcall、__fastcall、__cdecl、[=146 之间没有区别=])
但存在关于隐式参数的问题。假设 __thiscall - 第一个参数 - this 是 implicit 参数,始终传递通过 Ecx/Rcx
作为第一个参数
现在函数尝试“return”对象时的情况。实际上在任何平台上函数只能通过 cpu 寄存器 return 某些东西。它的数量有限,并且只有其中几个可以用于 return 值存储。
关于 x86(x64) - 对于 return 值可以使用:AL, AX, EAX, RAX, DAX:EAX, RDX:RAX
所以函数只能 return 大小为 - 1、2、4、8(x64 为 16)
if function try "return" object with another size - 这是不可能直接做的,编译器需要转换你的代码。例如,如果您编写函数:
DriverPose_t GetPose(ITrackedDeviceServerDriver* thisptr)
{
DriverPose_t pos = ...;
return pos;
}
并致电
DriverPose_t pos = GetPose(thisptr);
编译器(当然这已经实现了细节,不同的编译器可以用不同的方式做到这一点!)可以将其转换为
void GetPose(DriverPose_t *ppos, ITrackedDeviceServerDriver* thisptr)
{
DriverPose_t pos = ...;
*ppos = pos;
}
并致电
DriverPose_t pos;
GetPose(&pos, thisptr);
所以这里指向对象的指针作为 first ,hidden/implicit 参数传递给函数。但是在成员函数的情况下,仍然存在另一个 hidden/implicit,参数 - this,它始终作为第一个传递,结果 hidden/implicit,return 的参数值移动到 second 位置 !
struct ITrackedDeviceServerDriver
{
DriverPose_t GetPose()
{
DriverPose_t pos = ...;
return pos;
}
};
并致电
ITrackedDeviceServerDriver obj;
DriverPose_t pos = obj.GetPose();
可以转化为
struct ITrackedDeviceServerDriver
{
void GetPose(DriverPose_t *ppos)
{
DriverPose_t pos = ...;
*ppos = pos;
}
};
并致电
ITrackedDeviceServerDriver obj;
DriverPose_t pos;
obj.GetPose(&pos);
函数的真正签名,如果“发现”这个参数是
void ITrackedDeviceServerDriver::GetPose(ITrackedDeviceServerDriver* this, DriverPose_t *ppos)
{
DriverPose_t pos = ...;
*ppos = pos;
}
并致电
ITrackedDeviceServerDriver obj;
DriverPose_t pos;
GetPose(&obj, &pos);
所以比较
void ITrackedDeviceServerDriver::GetPose(ITrackedDeviceServerDriver* this, DriverPose_t *ppos);
和
void GetPose(DriverPose_t *ppos, ITrackedDeviceServerDriver* thisptr);
你实施
DriverPose_t GetPose_Hook(ITrackedDeviceServerDriver* thisptr);
将由编译器转换为
void GetPose_Hook(DriverPose_t* ,ITrackedDeviceServerDriver* thisptr);
但你真的需要实现下一个钩子:
void GetPose_Hook(ITrackedDeviceServerDriver* thisptr, DriverPose_t* );
你混淆了第一个和第二个参数。但是我认为根本不需要更改对象 vtable,而是需要 return 客户端代理对象。 COM 接口挂钩的可能且非常通用的实现,但这里的 post 太长了。对于您的情况,可以完成下一个钩子示例:
struct DriverPose_t
{
int x, y, z;
};
struct __declspec(novtable) IDemoInterface
{
virtual DriverPose_t GetPose() = 0;
virtual ULONG GetComponent( const char * pchComponentNameAndVersion ) = 0;
virtual void Delete() = 0;
};
class DemoObject : public IDemoInterface
{
ULONG v;
DriverPose_t pos;
virtual DriverPose_t GetPose()
{
DbgPrint("%s<%p>\n", __FUNCTION__, this);
return pos;
}
virtual ULONG GetComponent( const char * pchComponentNameAndVersion )
{
DbgPrint("%s<%p>(%s)\n", __FUNCTION__, this, pchComponentNameAndVersion);
return v;
}
virtual void Delete()
{
delete this;
}
public:
DemoObject(ULONG v, int x, int y, int z) : v(v)
{
pos.x = x, pos.y = y, pos.z = z;
DbgPrint("%s<%p>\n", __FUNCTION__, this);
}
virtual ~DemoObject()
{
DbgPrint("%s<%p>\n", __FUNCTION__, this);
}
};
class Hook_DemoInterface : public IDemoInterface
{
IDemoInterface* pItf;
virtual DriverPose_t GetPose()
{
DriverPose_t pos = pItf->GetPose();
DbgPrint("%s<%p>=<%d, %d, %d>\n", __FUNCTION__, this, pos.x, pos.y, pos.z);
return pos;
}
virtual ULONG GetComponent( const char * pchComponentNameAndVersion )
{
ULONG v = pItf->GetComponent(pchComponentNameAndVersion);
DbgPrint("%s<%p>(%s)=%u\n", __FUNCTION__, this, pchComponentNameAndVersion);
return v;
}
virtual void Delete()
{
delete this;
}
public:
Hook_DemoInterface(IDemoInterface* pItf) : pItf(pItf)
{
DbgPrint("%s<%p>\n", __FUNCTION__, this);
}
~Hook_DemoInterface()
{
pItf->Delete();
DbgPrint("%s<%p>\n", __FUNCTION__, this);
}
};
BOOL CreateDemoIface(IDemoInterface** ppItf)
{
if (DemoObject* pObj = new DemoObject (1, -1, 2, 3))
{
*ppItf = pObj;
return TRUE;
}
return FALSE;
}
BOOL Hook_CreateDemoIface(IDemoInterface** ppItf)
{
IDemoInterface* pItf;
if (CreateDemoIface(&pItf))
{
if (Hook_DemoInterface* pObj = new Hook_DemoInterface(pItf))
{
*ppItf = pObj;
return TRUE;
}
*ppItf = pItf;
return TRUE;
}
return FALSE;
}
void UseIface(IDemoInterface* pItf)
{
ULONG v = pItf->GetComponent("some string");
DriverPose_t pos = pItf->GetPose();
DbgPrint("v=%u, pos<%d, %d, %d>\n", v, pos.x, pos.y, pos.z);
}
void test()
{
IDemoInterface* pItf;
if (CreateDemoIface(&pItf))
{
UseIface(pItf);
pItf->Delete();
}
if (Hook_CreateDemoIface(&pItf))
{
UseIface(pItf);
pItf->Delete();
}
}
代替挂钩对象 vtable - 挂钩(如果可能)对象创建 CreateDemoIface
- 将其替换为自身 Hook_CreateDemoIface
哪个 return 代理对象.
调试输出
DemoObject::DemoObject<0000012C31E08F70>
DemoObject::GetComponent<0000012C31E08F70>(some string)
DemoObject::GetPose<0000012C31E08F70>
v=1, pos<-1, 2, 3>
DemoObject::~DemoObject<0000012C31E08F70>
DemoObject::DemoObject<0000012C31E08F70>
Hook_DemoInterface::Hook_DemoInterface<0000012C31DFAA10>
DemoObject::GetComponent<0000012C31E08F70>(some string)
Hook_DemoInterface::GetComponent<0000012C31DFAA10>(some string)=1
DemoObject::GetPose<0000012C31E08F70>
Hook_DemoInterface::GetPose<0000012C31DFAA10>=<-1, 2, 3>
v=1, pos<-1, 2, 3>
DemoObject::~DemoObject<0000012C31E08F70>
Hook_DemoInterface::~Hook_DemoInterface<0000012C31DFAA10>
因此尝试挂钩 OpenVR 方法“ITrackedDeviceServerDriver::GetPose”,但是当我的自定义挂钩方法 returns 退出时我遇到了访问冲突(我认为调用约定有问题但我知道什么x64 asm 期望)。
我在“ITrackedDeviceServerDriver”中挂接的所有其他方法都可以正常工作。只有“GetPose”失败。
Class 被钩住的看起来像:
class ITrackedDeviceServerDriver
{
public:
virtual EVRInitError Activate( uint32_t unObjectId ) = 0;
virtual void Deactivate() = 0;
virtual void EnterStandby() = 0;
virtual void *GetComponent( const char *pchComponentNameAndVersion ) = 0;
virtual void DebugRequest( const char *pchRequest, char *pchResponseBuffer, uint32_t unResponseBufferSize ) = 0;
virtual DriverPose_t GetPose() = 0;
};
大于 ptr 的对象并从崩溃的方法中返回
struct DriverPose_t
{
double poseTimeOffset;
vr::HmdQuaternion_t qWorldFromDriverRotation;
double vecWorldFromDriverTranslation[ 3 ];
vr::HmdQuaternion_t qDriverFromHeadRotation;
double vecDriverFromHeadTranslation[ 3 ];
double vecPosition[ 3 ];
double vecVelocity[ 3 ];
double vecAcceleration[ 3 ];
vr::HmdQuaternion_t qRotation;
double vecAngularVelocity[ 3 ];
double vecAngularAcceleration[ 3 ];
ETrackingResult result;
bool poseIsValid;
bool willDriftInYaw;
bool shouldApplyHeadModel;
bool deviceIsConnected;
};
我如何连接虚拟方法的示例:
typedef DriverPose_t(__thiscall* GetPose_Org)(ITrackedDeviceServerDriver* thisptr);
GetPose_Org GetPose_Ptr = nullptr;
DriverPose_t __fastcall GetPose_Hook(ITrackedDeviceServerDriver* thisptr)
{
DriverPose_t result = GetPose_Ptr(thisptr);// works
//result.deviceIsConnected = true;
return result;// after return I get access violation crash
}
void TestHook(ITrackedDeviceServerDriver *pDriver)
{
MEMORY_BASIC_INFORMATION mbi;
ZeroMemory(&mbi, sizeof(MEMORY_BASIC_INFORMATION));
void** vTable = *(void***)(pDriver);
VirtualQuery((LPCVOID)vTable, &mbi, sizeof(MEMORY_BASIC_INFORMATION));
VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_EXECUTE_READWRITE, &mbi.Protect);// unlock
Activate_Ptr = (Activate_Org)vTable[0];
vTable[0] = &Activate_Hook;// Hook!
Deactivate_Ptr = (Deactivate_Org)vTable[1];
vTable[1] = &Deactivate_Hook;// Hook!
EnterStandby_Ptr = (EnterStandby_Org)vTable[2];
vTable[2] = &EnterStandby_Hook;// Hook!
GetPose_Ptr = (GetPose_Org)vTable[5];
vTable[5] = &GetPose_Hook;// Hook!
VirtualProtect(mbi.BaseAddress, mbi.RegionSize, mbi.Protect, &mbi.Protect);// lock
}
那么,为什么“GetPose”是 returns 之后唯一失败的方法? 如何修复“GetPose”的调用约定以正确使用寄存器?
如评论中所述
__thiscall
and__fastcall
are not the same calling convention
甚至 x64。对于 x86 这很明显 - __thiscall
- 通过 Ecx 传递第一个参数 (this)通过堆栈注册和下一个参数(我现在跳过 float/double 参数案例)。 __fastcall
- 首先通过 Ecx,2 秒通过 Edx,然后通过堆栈
如果 x64(再次假设没有 float/double 参数)- 前 4 个参数通过 Rcx、Rdx、R8、R9 和堆栈中的下一个。所以看起来 x64 有单一和通用的调用约定(__stdcall、__fastcall、__cdecl、[=146 之间没有区别=])
但存在关于隐式参数的问题。假设 __thiscall - 第一个参数 - this 是 implicit 参数,始终传递通过 Ecx/Rcx
作为第一个参数现在函数尝试“return”对象时的情况。实际上在任何平台上函数只能通过 cpu 寄存器 return 某些东西。它的数量有限,并且只有其中几个可以用于 return 值存储。
关于 x86(x64) - 对于 return 值可以使用:AL, AX, EAX, RAX, DAX:EAX, RDX:RAX 所以函数只能 return 大小为 - 1、2、4、8(x64 为 16)
if function try "return" object with another size - 这是不可能直接做的,编译器需要转换你的代码。例如,如果您编写函数:
DriverPose_t GetPose(ITrackedDeviceServerDriver* thisptr)
{
DriverPose_t pos = ...;
return pos;
}
并致电
DriverPose_t pos = GetPose(thisptr);
编译器(当然这已经实现了细节,不同的编译器可以用不同的方式做到这一点!)可以将其转换为
void GetPose(DriverPose_t *ppos, ITrackedDeviceServerDriver* thisptr)
{
DriverPose_t pos = ...;
*ppos = pos;
}
并致电
DriverPose_t pos;
GetPose(&pos, thisptr);
所以这里指向对象的指针作为 first ,hidden/implicit 参数传递给函数。但是在成员函数的情况下,仍然存在另一个 hidden/implicit,参数 - this,它始终作为第一个传递,结果 hidden/implicit,return 的参数值移动到 second 位置 !
struct ITrackedDeviceServerDriver
{
DriverPose_t GetPose()
{
DriverPose_t pos = ...;
return pos;
}
};
并致电
ITrackedDeviceServerDriver obj;
DriverPose_t pos = obj.GetPose();
可以转化为
struct ITrackedDeviceServerDriver
{
void GetPose(DriverPose_t *ppos)
{
DriverPose_t pos = ...;
*ppos = pos;
}
};
并致电
ITrackedDeviceServerDriver obj;
DriverPose_t pos;
obj.GetPose(&pos);
函数的真正签名,如果“发现”这个参数是
void ITrackedDeviceServerDriver::GetPose(ITrackedDeviceServerDriver* this, DriverPose_t *ppos)
{
DriverPose_t pos = ...;
*ppos = pos;
}
并致电
ITrackedDeviceServerDriver obj;
DriverPose_t pos;
GetPose(&obj, &pos);
所以比较
void ITrackedDeviceServerDriver::GetPose(ITrackedDeviceServerDriver* this, DriverPose_t *ppos);
和
void GetPose(DriverPose_t *ppos, ITrackedDeviceServerDriver* thisptr);
你实施
DriverPose_t GetPose_Hook(ITrackedDeviceServerDriver* thisptr);
将由编译器转换为
void GetPose_Hook(DriverPose_t* ,ITrackedDeviceServerDriver* thisptr);
但你真的需要实现下一个钩子:
void GetPose_Hook(ITrackedDeviceServerDriver* thisptr, DriverPose_t* );
你混淆了第一个和第二个参数。但是我认为根本不需要更改对象 vtable,而是需要 return 客户端代理对象。 COM 接口挂钩的可能且非常通用的实现,但这里的 post 太长了。对于您的情况,可以完成下一个钩子示例:
struct DriverPose_t
{
int x, y, z;
};
struct __declspec(novtable) IDemoInterface
{
virtual DriverPose_t GetPose() = 0;
virtual ULONG GetComponent( const char * pchComponentNameAndVersion ) = 0;
virtual void Delete() = 0;
};
class DemoObject : public IDemoInterface
{
ULONG v;
DriverPose_t pos;
virtual DriverPose_t GetPose()
{
DbgPrint("%s<%p>\n", __FUNCTION__, this);
return pos;
}
virtual ULONG GetComponent( const char * pchComponentNameAndVersion )
{
DbgPrint("%s<%p>(%s)\n", __FUNCTION__, this, pchComponentNameAndVersion);
return v;
}
virtual void Delete()
{
delete this;
}
public:
DemoObject(ULONG v, int x, int y, int z) : v(v)
{
pos.x = x, pos.y = y, pos.z = z;
DbgPrint("%s<%p>\n", __FUNCTION__, this);
}
virtual ~DemoObject()
{
DbgPrint("%s<%p>\n", __FUNCTION__, this);
}
};
class Hook_DemoInterface : public IDemoInterface
{
IDemoInterface* pItf;
virtual DriverPose_t GetPose()
{
DriverPose_t pos = pItf->GetPose();
DbgPrint("%s<%p>=<%d, %d, %d>\n", __FUNCTION__, this, pos.x, pos.y, pos.z);
return pos;
}
virtual ULONG GetComponent( const char * pchComponentNameAndVersion )
{
ULONG v = pItf->GetComponent(pchComponentNameAndVersion);
DbgPrint("%s<%p>(%s)=%u\n", __FUNCTION__, this, pchComponentNameAndVersion);
return v;
}
virtual void Delete()
{
delete this;
}
public:
Hook_DemoInterface(IDemoInterface* pItf) : pItf(pItf)
{
DbgPrint("%s<%p>\n", __FUNCTION__, this);
}
~Hook_DemoInterface()
{
pItf->Delete();
DbgPrint("%s<%p>\n", __FUNCTION__, this);
}
};
BOOL CreateDemoIface(IDemoInterface** ppItf)
{
if (DemoObject* pObj = new DemoObject (1, -1, 2, 3))
{
*ppItf = pObj;
return TRUE;
}
return FALSE;
}
BOOL Hook_CreateDemoIface(IDemoInterface** ppItf)
{
IDemoInterface* pItf;
if (CreateDemoIface(&pItf))
{
if (Hook_DemoInterface* pObj = new Hook_DemoInterface(pItf))
{
*ppItf = pObj;
return TRUE;
}
*ppItf = pItf;
return TRUE;
}
return FALSE;
}
void UseIface(IDemoInterface* pItf)
{
ULONG v = pItf->GetComponent("some string");
DriverPose_t pos = pItf->GetPose();
DbgPrint("v=%u, pos<%d, %d, %d>\n", v, pos.x, pos.y, pos.z);
}
void test()
{
IDemoInterface* pItf;
if (CreateDemoIface(&pItf))
{
UseIface(pItf);
pItf->Delete();
}
if (Hook_CreateDemoIface(&pItf))
{
UseIface(pItf);
pItf->Delete();
}
}
代替挂钩对象 vtable - 挂钩(如果可能)对象创建 CreateDemoIface
- 将其替换为自身 Hook_CreateDemoIface
哪个 return 代理对象.
调试输出
DemoObject::DemoObject<0000012C31E08F70>
DemoObject::GetComponent<0000012C31E08F70>(some string)
DemoObject::GetPose<0000012C31E08F70>
v=1, pos<-1, 2, 3>
DemoObject::~DemoObject<0000012C31E08F70>
DemoObject::DemoObject<0000012C31E08F70>
Hook_DemoInterface::Hook_DemoInterface<0000012C31DFAA10>
DemoObject::GetComponent<0000012C31E08F70>(some string)
Hook_DemoInterface::GetComponent<0000012C31DFAA10>(some string)=1
DemoObject::GetPose<0000012C31E08F70>
Hook_DemoInterface::GetPose<0000012C31DFAA10>=<-1, 2, 3>
v=1, pos<-1, 2, 3>
DemoObject::~DemoObject<0000012C31E08F70>
Hook_DemoInterface::~Hook_DemoInterface<0000012C31DFAA10>