MSVC:直接通过按值 returns 的 VTable 调用方法时崩溃 (C++)
MSVC: Crash when invoking a method directly via the VTable that returns by value (C++)
我试图理解为什么以下代码在 Windows 下的 MSVC 中无法正常工作。它通过value.
直接调用一个VTable方法returns
#include <iostream>
#define RETURN_BY_VALUE 1
struct Value {
int Secret;
};
class IFoo {
public:
#if RETURN_BY_VALUE
virtual Value GetValue() = 0;
#else
virtual int GetValue() = 0;
#endif
};
class Foo : public IFoo {
public:
#if RETURN_BY_VALUE
virtual Value GetValue() { return Value{42}; }
#else
virtual int GetValue() { return 42; }
#endif
};
int main() {
IFoo* f = new Foo;
void* vtable = (void *)(*(std::intptr_t *)f);
#if RETURN_BY_VALUE
std::cout << f->GetValue().Secret << std::endl;
// The pointer to the GetValue function is at 0 byte offset in the Vtable
auto method = (Value(*)(IFoo*)) (*(std::intptr_t *)(vtable));
std::cout << method(f).Secret << std::endl;
#else
std::cout << f->GetValue() << std::endl;
auto method = (int(*)(IFoo*)) (*(std::intptr_t *)(vtable));
std::cout << method(f) << std::endl;
#endif
delete f;
return 0;
}
输出为
> test.exe
42
1820312
如果我们将 RETURN_BY_VALUE
设置为 0
,一切都会按预期进行。
> test.exe
42
42
运行godbolt中的示例表明gcc和clang没有这个问题https://godbolt.org/z/hn4MxK.
这在现实世界中的应用是在 d3d12 中挂钩 GetAdapterLuid。
- 别忘了
__stdcall
。没有 __stdcall
,自由函数和方法的调用约定差异更大。我猜 GetAdapterLuid
是 __stdcall
。
- 使用 Windows ABI returned 结构作为隐藏参数传递。看起来
this
和隐藏参数之间的顺序在方法和自由函数上是不同的。你可以定义你的函数,这样它就是真正的参数。
这似乎对我有用,但确实闻起来很香:
#include <iostream>
#define RETURN_BY_VALUE 1
struct Value {
int Secret;
};
class IFoo {
public:
#if RETURN_BY_VALUE
virtual Value __stdcall GetValue() = 0;
#else
virtual int __stdcall GetValue() = 0;
#endif
};
class Foo : public IFoo {
public:
#if RETURN_BY_VALUE
virtual Value __stdcall GetValue() { return Value{42}; }
#else
virtual int __stdcall GetValue() { return 42; }
#endif
};
int main() {
IFoo* f = new Foo;
void* vtable = (void *)(*(std::intptr_t *)f);
#if RETURN_BY_VALUE
std::cout << f->GetValue().Secret << std::endl;
// The pointer to the GetValue function is at 0 byte offset in the Vtable
Value v;
auto method = (void(__stdcall *)(IFoo*, Value*)) (*(std::intptr_t *)(vtable));
std::cout << (method(f, &v), v.Secret) << std::endl;
#else
std::cout << f->GetValue() << std::endl;
auto method = (int(__stdcall *)(IFoo*)) (*(std::intptr_t *)(vtable));
std::cout << method(f) << std::endl;
#endif
delete f;
return 0;
}
gcc / clang 会 return 你的结构完全是整数,类 Unix 系统有不同的 ABI。
实现挂钩的最简洁的方法是将方法实现为方法。在你的 hook vtable 中定位方法,就像在目标 vtable 中一样,那么你就不需要使用自由函数来伪造方法。
我试图理解为什么以下代码在 Windows 下的 MSVC 中无法正常工作。它通过value.
直接调用一个VTable方法returns#include <iostream>
#define RETURN_BY_VALUE 1
struct Value {
int Secret;
};
class IFoo {
public:
#if RETURN_BY_VALUE
virtual Value GetValue() = 0;
#else
virtual int GetValue() = 0;
#endif
};
class Foo : public IFoo {
public:
#if RETURN_BY_VALUE
virtual Value GetValue() { return Value{42}; }
#else
virtual int GetValue() { return 42; }
#endif
};
int main() {
IFoo* f = new Foo;
void* vtable = (void *)(*(std::intptr_t *)f);
#if RETURN_BY_VALUE
std::cout << f->GetValue().Secret << std::endl;
// The pointer to the GetValue function is at 0 byte offset in the Vtable
auto method = (Value(*)(IFoo*)) (*(std::intptr_t *)(vtable));
std::cout << method(f).Secret << std::endl;
#else
std::cout << f->GetValue() << std::endl;
auto method = (int(*)(IFoo*)) (*(std::intptr_t *)(vtable));
std::cout << method(f) << std::endl;
#endif
delete f;
return 0;
}
输出为
> test.exe
42
1820312
如果我们将 RETURN_BY_VALUE
设置为 0
,一切都会按预期进行。
> test.exe
42
42
运行godbolt中的示例表明gcc和clang没有这个问题https://godbolt.org/z/hn4MxK.
这在现实世界中的应用是在 d3d12 中挂钩 GetAdapterLuid。
- 别忘了
__stdcall
。没有__stdcall
,自由函数和方法的调用约定差异更大。我猜GetAdapterLuid
是__stdcall
。 - 使用 Windows ABI returned 结构作为隐藏参数传递。看起来
this
和隐藏参数之间的顺序在方法和自由函数上是不同的。你可以定义你的函数,这样它就是真正的参数。
这似乎对我有用,但确实闻起来很香:
#include <iostream>
#define RETURN_BY_VALUE 1
struct Value {
int Secret;
};
class IFoo {
public:
#if RETURN_BY_VALUE
virtual Value __stdcall GetValue() = 0;
#else
virtual int __stdcall GetValue() = 0;
#endif
};
class Foo : public IFoo {
public:
#if RETURN_BY_VALUE
virtual Value __stdcall GetValue() { return Value{42}; }
#else
virtual int __stdcall GetValue() { return 42; }
#endif
};
int main() {
IFoo* f = new Foo;
void* vtable = (void *)(*(std::intptr_t *)f);
#if RETURN_BY_VALUE
std::cout << f->GetValue().Secret << std::endl;
// The pointer to the GetValue function is at 0 byte offset in the Vtable
Value v;
auto method = (void(__stdcall *)(IFoo*, Value*)) (*(std::intptr_t *)(vtable));
std::cout << (method(f, &v), v.Secret) << std::endl;
#else
std::cout << f->GetValue() << std::endl;
auto method = (int(__stdcall *)(IFoo*)) (*(std::intptr_t *)(vtable));
std::cout << method(f) << std::endl;
#endif
delete f;
return 0;
}
gcc / clang 会 return 你的结构完全是整数,类 Unix 系统有不同的 ABI。
实现挂钩的最简洁的方法是将方法实现为方法。在你的 hook vtable 中定位方法,就像在目标 vtable 中一样,那么你就不需要使用自由函数来伪造方法。