在Win32上挂钩虚拟方法返回的对象大于ptr崩溃?

问题描述

因此,尝试挂接OpenVR方法“ ITrackedDeviceServerDriver :: GetPose”,但是自定义的挂接方法返回时,我遇到访问冲突(我认为调用约定有问题,但对x64 asm的期望却不高。) / p>

我挂在“ ITrackedDeviceServerDriver”中的所有其他方法都可以正常工作。只有“ GetPose”失败。

被钩住的类如下:

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.Protect,&mbi.Protect);// lock
}

那么,为什么“ GetPose”是返回后唯一失败的方法? 如何固定“ GetPose”上的调用约定以正确使用寄存器?

解决方法

已在评论中指出

__thiscall__fastcall是不同的调用约定

即使是 x64 。对于 x86 ,这很明显-__thiscall-通过 Ecx 寄存器传递第一个参数( this ),并通过堆栈传递下一个参数(i立即跳过float / double参数的情况)。 __fastcall-首先通过 Ecx ,第二次通过 Edx ,然后通过堆栈

x64 情况下(再次假设没有浮点/双精度参数)-通过 Rcx,Rdx,R8,R9 的前4个参数,然后是堆栈中的下一个。所以看起来 x64 具有单一和通用的调用约定( __ stdcall,__fastcall,__cdecl和__thiscall 之间没有区别)

但存在关于隐式参数的问题。例如说 __ thiscall -第一个参数- this 隐式参数,始终作为第一个参数通过 Ecx / Rcx 传递>

和现在的情况下,当函数尝试“返回”对象时。实际上,任何平台上的函数只能通过cpu寄存器返回某些内容。它是有限计数,只有其中几个可以用于返回值存储。

x86(x64)上的

say-可以使用返回值: AL,AX,EAX,RAX,DAX:EAX,RDX:RAX 因此函数只能返回大小为1、2、4、8(对于 x64 为16)的东西

如果函数尝试“返回”另一个大小的对象-这是不可能直接执行的,则编译器需要转换您的代码。例如,如果您编写函数:

DriverPose_t GetPose(ITrackedDeviceServerDriver* thisptr)
{
    DriverPose_t pos = ...;
    return pos;
}

并致电

DriverPose_t pos = GetPose(thisptr);

compiler(当然,这已经是实现的细节了,不同的编译器可以用不同的方式来实现!! )可以将其转换为

void GetPose(DriverPose_t *ppos,ITrackedDeviceServerDriver* thisptr)
{
    DriverPose_t pos = ...;
    *ppos = pos;
}

并致电

DriverPose_t pos;
GetPose(&pos,thisptr);

因此这里指向对象的指针传递给功能,该功能作为 first (隐藏/隐式)参数。但是在成员函数的情况下,仍然存在另一个隐式/隐式参数- this ,该参数始终作为第一个传递,结果隐式/隐式地将返回值的参数移至 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);

是函数的真实签名,如果“ uncover” 这个参数是

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,而是需要将代理对象返回给客户端。 COM 接口挂钩的可能且非常通用的实现,但在此处过长。您的情况可以在下一个钩子示例中完成:

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",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",this);
    }

    virtual ~DemoObject()
    {
        DbgPrint("%s<%p>\n",this);
    }
};

class Hook_DemoInterface : public IDemoInterface
{
    IDemoInterface* pItf;

    virtual DriverPose_t GetPose()
    {
        DriverPose_t pos = pItf->GetPose();
        DbgPrint("%s<%p>=<%d,%d,%d>\n",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",pchComponentNameAndVersion);
        return v;
    }

    virtual void Delete()
    {
        delete this;
    }
public:
    
    Hook_DemoInterface(IDemoInterface* pItf) : pItf(pItf) 
    {
        DbgPrint("%s<%p>\n",this);
    }

    ~Hook_DemoInterface() 
    { 
        pItf->Delete(); 
        DbgPrint("%s<%p>\n",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,v,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

调试输出

DemoObject::DemoObject<0000012C31E08F70>
DemoObject::GetComponent<0000012C31E08F70>(some string)
DemoObject::GetPose<0000012C31E08F70>
v=1,pos<-1,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,3>
v=1,3>
DemoObject::~DemoObject<0000012C31E08F70>
Hook_DemoInterface::~Hook_DemoInterface<0000012C31DFAA10>

相关问答

错误1:Request method ‘DELETE‘ not supported 错误还原:...
错误1:启动docker镜像时报错:Error response from daemon:...
错误1:private field ‘xxx‘ is never assigned 按Alt...
报错如下,通过源不能下载,最后警告pip需升级版本 Requirem...