Skip to content

Commit

Permalink
create Windows64Convention for default windows x64 cc
Browse files Browse the repository at this point in the history
  • Loading branch information
matcool committed Jun 3, 2024
1 parent c57571f commit 7e52ec4
Show file tree
Hide file tree
Showing 5 changed files with 49 additions and 25 deletions.
2 changes: 2 additions & 0 deletions include/tulip/platform/PlatformConvention.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ namespace tulip::hook {
using PlatformConvention = CdeclConvention;
#elif defined(TULIP_HOOK_MACOS) && defined(TULIP_HOOK_X64)
using PlatformConvention = SystemVConvention;
#elif defined(TULIP_HOOK_WINDOWS) && defined(TULIP_HOOK_X64)
using PlatformConvention = Windows64Convention;
#else
using PlatformConvention = DefaultConvention;
#endif
Expand Down
16 changes: 12 additions & 4 deletions include/tulip/platform/Windows64Convention.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,30 @@

#if defined(TULIP_HOOK_WINDOWS) && defined(TULIP_HOOK_X64)

#include "../CallingConvention.hpp"
#include "DefaultConvention.hpp"

#include <memory>
#include <string>

namespace tulip::hook {
class AbstractFunction;

class TULIP_HOOK_DLL ThiscallConvention : public CallingConvention {
class TULIP_HOOK_DLL Windows64Convention : public DefaultConvention {
public:
~ThiscallConvention() override;
~Windows64Convention() override;

void generateDefaultCleanup(BaseAssembler& a, AbstractFunction const& function) override;
void generateIntoDefault(BaseAssembler& a, AbstractFunction const& function) override;

static std::shared_ptr<Windows64Convention> create();
};

class TULIP_HOOK_DLL ThiscallConvention : public Windows64Convention {
public:
~ThiscallConvention() override;

void generateIntoDefault(BaseAssembler& a, AbstractFunction const& function) override;
void generateIntoOriginal(BaseAssembler& a, AbstractFunction const& function) override;
void generateOriginalCleanup(BaseAssembler& a, AbstractFunction const& function) override;
bool needsWrapper(AbstractFunction const& function) const override;

static std::shared_ptr<ThiscallConvention> create();
Expand Down
2 changes: 2 additions & 0 deletions src/Main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ std::shared_ptr<CallingConvention> tulip::hook::createConvention(TulipConvention
default:
#if defined(TULIP_HOOK_MACOS) && defined(TULIP_HOOK_X64)
return SystemVConvention::create();
#elif defined(TULIP_HOOK_WINDOWS) && defined(TULIP_HOOK_X64)
return Windows64Convention::create();
#else
return DefaultConvention::create();
#endif
Expand Down
51 changes: 32 additions & 19 deletions src/convention/Windows64Convention.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ namespace {
}
}

ThiscallConvention::~ThiscallConvention() {}
Windows64Convention::~Windows64Convention() {}

void ThiscallConvention::generateDefaultCleanup(BaseAssembler& a_, AbstractFunction const& function) {
void Windows64Convention::generateDefaultCleanup(BaseAssembler& a_, AbstractFunction const& function) {
size_t paddedSize = getPaddedStackParamSize(function);
if (paddedSize > 0) {
auto& a = static_cast<X64Assembler&>(a_);
Expand All @@ -41,25 +41,11 @@ void ThiscallConvention::generateDefaultCleanup(BaseAssembler& a_, AbstractFunct
}
}

// Member functions deal with struct return differently, since in the windows x64 convention
// a struct return is as a hidden first parameter, member functions end up considering the first parameter
// the one after the `this`, whereas static functions do not.
//
// So where a static function would behave like this:
// SomeStruct* func(SomeStruct* ret_ptr, Class* self, int a, int b);
// a member function would behave like this:
// SomeStruct* Class::method(Class* this, SomeStruct* ret_ptr, int a, int b);
// so to undo this we just swap the first two parameters (RCX and RDX).

void ThiscallConvention::generateIntoDefault(BaseAssembler& a_, AbstractFunction const& function) {
void Windows64Convention::generateIntoDefault(BaseAssembler& a_, AbstractFunction const& function) {
auto& a = static_cast<X64Assembler&>(a_);
using enum X64Register;
RegMem64 m;

if (function.m_return.m_kind == AbstractTypeKind::Other) {
a.xchg(RCX, RDX);
}

size_t stackParamSize = getStackParamSize(function);
if (stackParamSize > 0) {
auto const paddedSize = (stackParamSize % 16) ? stackParamSize + 8 : stackParamSize;
Expand All @@ -81,17 +67,44 @@ void ThiscallConvention::generateIntoDefault(BaseAssembler& a_, AbstractFunction
}
}

void ThiscallConvention::generateIntoOriginal(BaseAssembler& a_, AbstractFunction const& function) {
std::shared_ptr<Windows64Convention> Windows64Convention::create() {
return std::make_shared<Windows64Convention>();
}

// Member functions deal with struct return differently, since in the windows x64 convention
// a struct return is as a hidden first parameter, member functions end up considering the first parameter
// the one after the `this`, whereas static functions do not.
//
// So where a static function would behave like this:
// SomeStruct* func(SomeStruct* ret_ptr, Class* self, int a, int b);
// a member function would behave like this:
// SomeStruct* Class::method(Class* this, SomeStruct* ret_ptr, int a, int b);
// so to undo this we just swap the first two parameters (RCX and RDX).

ThiscallConvention::~ThiscallConvention() {}

void ThiscallConvention::generateIntoDefault(BaseAssembler& a_, AbstractFunction const& function) {
auto& a = static_cast<X64Assembler&>(a_);
using enum X64Register;
RegMem64 m;

if (function.m_return.m_kind == AbstractTypeKind::Other) {
a.xchg(RCX, RDX);
}

Windows64Convention::generateIntoDefault(a, function);
}

void ThiscallConvention::generateOriginalCleanup(BaseAssembler& a, AbstractFunction const& function) {
void ThiscallConvention::generateIntoOriginal(BaseAssembler& a_, AbstractFunction const& function) {
auto& a = static_cast<X64Assembler&>(a_);
using enum X64Register;
RegMem64 m;

if (function.m_return.m_kind == AbstractTypeKind::Other) {
a.xchg(RCX, RDX);
}

Windows64Convention::generateIntoOriginal(a, function);
}

bool ThiscallConvention::needsWrapper(AbstractFunction const& function) const {
Expand Down
3 changes: 1 addition & 2 deletions test/Hook.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -223,8 +223,7 @@ int checkParamsHook(int a, int b, int c, int d, int e, int f, int g, float h, in

TEST(HookTest, SingleHookCheckParams) {
HandlerMetadata handlerMetadata;
// cheating using thiscall because default convention is not properly implemented
handlerMetadata.m_convention = tulip::hook::createConvention(tulip::hook::TulipConvention::Thiscall);
handlerMetadata.m_convention = std::make_unique<PlatformConvention>();
handlerMetadata.m_abstract = AbstractFunction::from(&checkParams);

auto handleRes = createHandler(reinterpret_cast<void*>(&checkParams), handlerMetadata);
Expand Down

0 comments on commit 7e52ec4

Please sign in to comment.