在 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 - 第一个参数 - thisimplicit 参数,始终传递通过 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>