From 151cbf18f4f6360cf008e1e32805f968d28a688d Mon Sep 17 00:00:00 2001 From: rtldg Date: Sat, 1 Mar 2025 13:37:34 +0000 Subject: [PATCH 01/20] import DynamicHooks x86_64SystemV convention from the x86_64Microsoft convention (cherry picked from commit a333fc967c3d4ec7595c903618ea53eb651a2d2e) --- .../conventions/x86_64SystemVDefault.cpp | 258 +++++++++++++++++- .../conventions/x86_64SystemVDefault.h | 45 ++- 2 files changed, 301 insertions(+), 2 deletions(-) diff --git a/extensions/dhooks/DynamicHooks/conventions/x86_64SystemVDefault.cpp b/extensions/dhooks/DynamicHooks/conventions/x86_64SystemVDefault.cpp index 10b909f814..24367bb381 100644 --- a/extensions/dhooks/DynamicHooks/conventions/x86_64SystemVDefault.cpp +++ b/extensions/dhooks/DynamicHooks/conventions/x86_64SystemVDefault.cpp @@ -22,4 +22,260 @@ * misrepresented as being the original software. * * 3. This notice may not be removed or altered from any source distribution. -*/ \ No newline at end of file +*/ + +// ============================================================================ +// >> INCLUDES +// ============================================================================ +#include "x86_64MicrosoftDefault.h" +#include + +// ============================================================================ +// >> CLASSES +// ============================================================================ +x86_64MicrosoftDefault::x86_64MicrosoftDefault(std::vector &vecArgTypes, DataTypeSized_t returnType, int iAlignment) : + ICallingConvention(vecArgTypes, returnType, iAlignment), + m_stackArgs(0) +{ + const Register_t params_reg[] = { RCX, RDX, R8, R9 }; + const Register_t params_floatreg[] = { XMM0, XMM1, XMM2, XMM3 }; + //const char* regNames[] = { "RCX or XMM0", "RDX or XMM1", "R8 or XMM2", "R9 or XMM3"}; + const std::uint8_t num_reg = sizeof(params_reg) / sizeof(Register_t); + + bool used_reg[] = { false, false, false, false }; + + // Figure out if any register has been used + auto retreg = m_returnType.custom_register; + used_reg[0] = (retreg == RCX || retreg == XMM0); + used_reg[1] = (retreg == RDX || retreg == XMM1); + used_reg[2] = (retreg == R8 || retreg == XMM2); + used_reg[3] = (retreg == R9 || retreg == XMM3); + + for (const auto& arg : m_vecArgTypes) { + int reg_index = -1; + if (arg.custom_register == RCX || arg.custom_register == XMM0) { + reg_index = 0; + } else if (arg.custom_register == RDX || arg.custom_register == XMM1) { + reg_index = 1; + } else if (arg.custom_register == R8 || arg.custom_register == XMM2) { + reg_index = 2; + } else if (arg.custom_register == R9 || arg.custom_register == XMM3) { + reg_index = 3; + } + + if (reg_index != -1) { + if (used_reg[reg_index]) { + puts("Argument register is used twice, or shared with return"); + return; + } + used_reg[reg_index] = true; + } + } + + // Special return type + if (m_returnType.custom_register == None && m_returnType.type == DATA_TYPE_OBJECT && + // If size unknown, or doesn't fit on 1, 2, 4 or 8 bytes + // special place must have been allocated for it + (m_returnType.size == 0 + || m_returnType.size == 3 + || m_returnType.size == 5 + || m_returnType.size == 6 + || m_returnType.size == 7 + || m_returnType.size > 8)) { + for (std::uint8_t i = 0; i < num_reg && m_returnType.custom_register == None; i++) { + if (!used_reg[i]) { + m_returnType.custom_register = params_reg[i]; + used_reg[i] = true; + } + // Couldn't find a free register, this is a big problem + if (m_returnType.custom_register == None) { + puts("Missing free register for return pointer"); + return; + } + } + } + + for (auto& arg : m_vecArgTypes) { + if (arg.custom_register == None) { + for (std::uint8_t i = 0; i < num_reg && arg.custom_register == None; i++) { + // Register is unused assign it + if (!used_reg[i]) { + arg.custom_register = (arg.type == DATA_TYPE_FLOAT || arg.type == DATA_TYPE_DOUBLE) ? params_floatreg[i] : params_reg[i]; + used_reg[i] = true; + } + } + // Couldn't find a free register, it's therefore a stack parameter + if (arg.custom_register == None) { + m_stackArgs++; + } + } + } +} + +std::vector x86_64MicrosoftDefault::GetRegisters() +{ + std::vector registers; + + registers.push_back(RSP); + + if (m_returnType.custom_register != None) + { + registers.push_back(m_returnType.custom_register); + } + else if (m_returnType.type == DATA_TYPE_FLOAT || m_returnType.type == DATA_TYPE_DOUBLE) + { + registers.push_back(XMM0); + } + else + { + registers.push_back(RAX); + } + + for (size_t i = 0; i < m_vecArgTypes.size(); i++) + { + auto reg = m_vecArgTypes[i].custom_register; + if (reg == None) + { + continue; + } + registers.push_back(m_vecArgTypes[i].custom_register); + } + + return registers; +} + +int x86_64MicrosoftDefault::GetPopSize() +{ + // Clean-up is caller handled + return 0; +} + +int x86_64MicrosoftDefault::GetArgStackSize() +{ + // Shadow space (32 bytes) + 8 bytes * amount of stack arguments + return 32 + 8 * m_stackArgs; +} + +void** x86_64MicrosoftDefault::GetStackArgumentPtr(CRegisters* registers) +{ + // Skip shadow space + return address + return (void **)(registers->m_rsp->GetValue() + 8 + 32); +} + +int x86_64MicrosoftDefault::GetArgRegisterSize() +{ + int argRegisterSize = 0; + + for (size_t i = 0; i < m_vecArgTypes.size(); i++) + { + if (m_vecArgTypes[i].custom_register != None) + { + // It doesn't matter, it's always 8 bytes or less + argRegisterSize += 8; + } + } + + return argRegisterSize; +} + +void* x86_64MicrosoftDefault::GetArgumentPtr(unsigned int index, CRegisters* registers) +{ + //g_pSM->LogMessage(myself, "Retrieving argument %d (max args %d) registers %p", index, m_vecArgTypes.size(), registers); + if (index >= m_vecArgTypes.size()) + { + //g_pSM->LogMessage(myself, "Not enough arguments"); + return nullptr; + } + + // Check if this argument was passed in a register. + if (m_vecArgTypes[index].custom_register != None) + { + CRegister* reg = registers->GetRegister(m_vecArgTypes[index].custom_register); + if (!reg) + { + //g_pSM->LogMessage(myself, "Register does not exit"); + return nullptr; + } + //g_pSM->LogMessage(myself, "Register arg %d", m_vecArgTypes[index].custom_register); + return reg->m_pAddress; + } + + // Return address + shadow space + size_t offset = 8 + 32; + for (unsigned int i = 0; i < index; i++) + { + if (m_vecArgTypes[i].custom_register == None) + { + // No matter what, the stack is allocated in slices of 8 bytes + offset += 8; + } + } + return (void *) (registers->m_rsp->GetValue() + offset); +} + +void x86_64MicrosoftDefault::ArgumentPtrChanged(unsigned int index, CRegisters* registers, void* argumentPtr) +{ +} + +void* x86_64MicrosoftDefault::GetReturnPtr(CRegisters* registers) +{ + // Custom return value register + if (m_returnType.custom_register != None) + { + return registers->GetRegister(m_returnType.custom_register)->m_pAddress; + } + + if (m_returnType.type == DATA_TYPE_FLOAT || m_returnType.type == DATA_TYPE_DOUBLE) + { + // Floating point register + return registers->m_xmm0->m_pAddress; + } + return registers->m_rax->m_pAddress; +} + +void x86_64MicrosoftDefault::ReturnPtrChanged(CRegisters* pRegisters, void* pReturnPtr) +{ +} + +void x86_64MicrosoftDefault::SaveReturnValue(CRegisters* registers) +{ + // It doesn't matter what the return value is, it will always be fitting on 8 bytes (or less) + std::unique_ptr savedReturn = std::make_unique(8); + memcpy(savedReturn.get(), GetReturnPtr(registers), 8); + m_pSavedReturnBuffers.push_back(std::move(savedReturn)); +} + +void x86_64MicrosoftDefault::RestoreReturnValue(CRegisters* registers) +{ + // Like stated in SaveReturnValue, it will always fit within 8 bytes + // the actual underlining type does not matter + uint8_t* savedReturn = m_pSavedReturnBuffers.back().get(); + memcpy(GetReturnPtr(registers), savedReturn, 8); + ReturnPtrChanged(registers, savedReturn); + m_pSavedReturnBuffers.pop_back(); +} + +void x86_64MicrosoftDefault::SaveCallArguments(CRegisters* registers) +{ + int size = GetArgStackSize() + GetArgRegisterSize(); + std::unique_ptr savedCallArguments = std::make_unique(size); + size_t offset = 0; + for (unsigned int i = 0; i < m_vecArgTypes.size(); i++) { + // Doesn't matter the type, it will always be within 8 bytes + memcpy((void *)((uintptr_t)savedCallArguments.get() + offset), GetArgumentPtr(i, registers), 8); + offset += 8; + } + m_pSavedCallArguments.push_back(std::move(savedCallArguments)); +} + +void x86_64MicrosoftDefault::RestoreCallArguments(CRegisters* registers) +{ + uint8_t *savedCallArguments = m_pSavedCallArguments.back().get(); + size_t offset = 0; + for (size_t i = 0; i < m_vecArgTypes.size(); i++) { + // Doesn't matter the type, it will always be within 8 bytes + memcpy(GetArgumentPtr((unsigned int)i, registers), (void *)((uintptr_t)savedCallArguments + offset), 8); + offset += 8; + } + m_pSavedCallArguments.pop_back(); +} diff --git a/extensions/dhooks/DynamicHooks/conventions/x86_64SystemVDefault.h b/extensions/dhooks/DynamicHooks/conventions/x86_64SystemVDefault.h index 10b909f814..fb6b8e1081 100644 --- a/extensions/dhooks/DynamicHooks/conventions/x86_64SystemVDefault.h +++ b/extensions/dhooks/DynamicHooks/conventions/x86_64SystemVDefault.h @@ -22,4 +22,47 @@ * misrepresented as being the original software. * * 3. This notice may not be removed or altered from any source distribution. -*/ \ No newline at end of file +*/ + +#ifndef _X86_64_MICROSOFT_DEFAULT_H +#define _X86_64_MICROSOFT_DEFAULT_H + +// ============================================================================ +// >> INCLUDES +// ============================================================================ +#include "../convention.h" + +// ============================================================================ +// >> CLASSES +// ============================================================================ +class x86_64MicrosoftDefault : public ICallingConvention +{ +public: + x86_64MicrosoftDefault(std::vector& vecArgTypes, DataTypeSized_t returnType, int iAlignment = 8); + virtual ~x86_64MicrosoftDefault() = default; + + virtual std::vector GetRegisters() override; + virtual int GetPopSize() override; + + virtual int GetArgStackSize() override; + virtual void** GetStackArgumentPtr(CRegisters* registers) override; + + virtual int GetArgRegisterSize() override; + + virtual void* GetArgumentPtr(unsigned int index, CRegisters* registers) override; + virtual void ArgumentPtrChanged(unsigned int index, CRegisters* registers, void* argumentPtr) override; + + virtual void* GetReturnPtr(CRegisters* registers) override; + virtual void ReturnPtrChanged(CRegisters* registers, void* returnPtr) override; + + virtual void SaveReturnValue(CRegisters* registers) override; + virtual void RestoreReturnValue(CRegisters* registers) override; + + virtual void SaveCallArguments(CRegisters* pRegisters) override; + virtual void RestoreCallArguments(CRegisters* pRegisters) override; + +protected: + std::uint32_t m_stackArgs; +}; + +#endif // _X86_64_MICROSOFT_DEFAULT_H From 6cd860f328bc6ec85c280bd66a524e2e9512d6b7 Mon Sep 17 00:00:00 2001 From: rtldg Date: Sat, 1 Mar 2025 13:47:10 +0000 Subject: [PATCH 02/20] Do some x86_64SystemVDefault convention stuff (cherry picked from commit dfa865906248d0912174e30173c04bd2788e0177) --- .../conventions/x86_64SystemVDefault.cpp | 144 +++++++++++------- .../conventions/x86_64SystemVDefault.h | 12 +- 2 files changed, 92 insertions(+), 64 deletions(-) diff --git a/extensions/dhooks/DynamicHooks/conventions/x86_64SystemVDefault.cpp b/extensions/dhooks/DynamicHooks/conventions/x86_64SystemVDefault.cpp index 24367bb381..da2213e4e3 100644 --- a/extensions/dhooks/DynamicHooks/conventions/x86_64SystemVDefault.cpp +++ b/extensions/dhooks/DynamicHooks/conventions/x86_64SystemVDefault.cpp @@ -27,61 +27,78 @@ // ============================================================================ // >> INCLUDES // ============================================================================ -#include "x86_64MicrosoftDefault.h" +#include "x86_64SystemVDefault.h" #include // ============================================================================ // >> CLASSES // ============================================================================ -x86_64MicrosoftDefault::x86_64MicrosoftDefault(std::vector &vecArgTypes, DataTypeSized_t returnType, int iAlignment) : +x86_64SystemVDefault::x86_64SystemVDefault(std::vector &vecArgTypes, DataTypeSized_t returnType, int iAlignment) : ICallingConvention(vecArgTypes, returnType, iAlignment), m_stackArgs(0) { - const Register_t params_reg[] = { RCX, RDX, R8, R9 }; - const Register_t params_floatreg[] = { XMM0, XMM1, XMM2, XMM3 }; + const Register_t params_reg[] = { RDI, RSI, RDX, RCX, R8, R9 }; + const Register_t params_floatreg[] = { XMM0, XMM1, XMM2, XMM3, XMM4, XMM5, XMM6, XMM7 }; //const char* regNames[] = { "RCX or XMM0", "RDX or XMM1", "R8 or XMM2", "R9 or XMM3"}; const std::uint8_t num_reg = sizeof(params_reg) / sizeof(Register_t); + const std::uint8_t num_floatreg = sizeof(params_floatreg) / sizeof(Register_t); - bool used_reg[] = { false, false, false, false }; + bool used_reg[] = { false, false, false, false, false, false }; + bool used_floatreg[] = { false, false, false, false, false, false, false, false }; // Figure out if any register has been used auto retreg = m_returnType.custom_register; - used_reg[0] = (retreg == RCX || retreg == XMM0); - used_reg[1] = (retreg == RDX || retreg == XMM1); - used_reg[2] = (retreg == R8 || retreg == XMM2); - used_reg[3] = (retreg == R9 || retreg == XMM3); + for (int i = 0; i < num_reg; ++i) { + if (retreg == params_reg[i]) { + used_reg[i] = true; + break; + } + } + for (int i = 0; i < num_floatreg; ++i) { + if (retreg == params_floatreg[i]) { + used_floatreg[i] = true; + break; + } + } for (const auto& arg : m_vecArgTypes) { - int reg_index = -1; - if (arg.custom_register == RCX || arg.custom_register == XMM0) { - reg_index = 0; - } else if (arg.custom_register == RDX || arg.custom_register == XMM1) { - reg_index = 1; - } else if (arg.custom_register == R8 || arg.custom_register == XMM2) { - reg_index = 2; - } else if (arg.custom_register == R9 || arg.custom_register == XMM3) { - reg_index = 3; + bool found_as_int_or_ptr = false; + + for (int i = 0; i < num_reg; ++i) { + if (arg.custom_register == params_reg[i]) { + if (used_reg[i]) { + puts("Argument (int/ptr) register is used twice, or shared with return"); + return; + } + used_reg[i] = true; + found_as_int_or_ptr = true; + break; + } } - if (reg_index != -1) { - if (used_reg[reg_index]) { - puts("Argument register is used twice, or shared with return"); - return; + if (found_as_int_or_ptr) continue; + + for (int i = 0; i < num_floatreg; ++i) { + if (arg.custom_register == params_floatreg[i]) { + if (used_floatreg[i]) { + puts("Argument (float/double) register is used twice, or shared with return"); + return; + } + used_floatreg[i] = true; + break; } - used_reg[reg_index] = true; } } + // TODO: Figure out if we need to do something different for Linux... // Special return type if (m_returnType.custom_register == None && m_returnType.type == DATA_TYPE_OBJECT && // If size unknown, or doesn't fit on 1, 2, 4 or 8 bytes // special place must have been allocated for it - (m_returnType.size == 0 - || m_returnType.size == 3 - || m_returnType.size == 5 - || m_returnType.size == 6 - || m_returnType.size == 7 - || m_returnType.size > 8)) { + (m_returnType.size != 1 + && m_returnType.size != 2 + && m_returnType.size != 4 + && m_returnType.size != 8)) { for (std::uint8_t i = 0; i < num_reg && m_returnType.custom_register == None; i++) { if (!used_reg[i]) { m_returnType.custom_register = params_reg[i]; @@ -96,26 +113,37 @@ x86_64MicrosoftDefault::x86_64MicrosoftDefault(std::vector &vec } for (auto& arg : m_vecArgTypes) { - if (arg.custom_register == None) { - for (std::uint8_t i = 0; i < num_reg && arg.custom_register == None; i++) { - // Register is unused assign it + if (arg.custom_register != None) continue; + + if (arg.type == DATA_TYPE_FLOAT || arg.type == DATA_TYPE_DOUBLE) { + for (int i = 0; i < num_reg && arg.custom_register == None; ++i) { + // Register is unused. Assign it. + if (!used_floatreg[i]) { + arg.custom_register = params_floatreg[i]; + used_floatreg[i] = true; + } + } + } else { + for (int i = 0; i < num_reg && arg.custom_register == None; ++i) { + // Register is unused. Assign it. if (!used_reg[i]) { - arg.custom_register = (arg.type == DATA_TYPE_FLOAT || arg.type == DATA_TYPE_DOUBLE) ? params_floatreg[i] : params_reg[i]; + arg.custom_register = params_reg[i]; used_reg[i] = true; } } - // Couldn't find a free register, it's therefore a stack parameter - if (arg.custom_register == None) { - m_stackArgs++; - } + } + + // Couldn't find a free register, it's therefore a stack parameter + if (arg.custom_register == None) { + m_stackArgs++; } } } -std::vector x86_64MicrosoftDefault::GetRegisters() +std::vector x86_64SystemVDefault::GetRegisters() { std::vector registers; - + registers.push_back(RSP); if (m_returnType.custom_register != None) @@ -144,25 +172,25 @@ std::vector x86_64MicrosoftDefault::GetRegisters() return registers; } -int x86_64MicrosoftDefault::GetPopSize() +int x86_64SystemVDefault::GetPopSize() { // Clean-up is caller handled return 0; } -int x86_64MicrosoftDefault::GetArgStackSize() +int x86_64SystemVDefault::GetArgStackSize() { - // Shadow space (32 bytes) + 8 bytes * amount of stack arguments - return 32 + 8 * m_stackArgs; + // 8 bytes * amount of stack arguments + return 8 * m_stackArgs; } -void** x86_64MicrosoftDefault::GetStackArgumentPtr(CRegisters* registers) +void** x86_64SystemVDefault::GetStackArgumentPtr(CRegisters* registers) { - // Skip shadow space + return address - return (void **)(registers->m_rsp->GetValue() + 8 + 32); + // Skip return address + return (void **)(registers->m_rsp->GetValue() + 8); } -int x86_64MicrosoftDefault::GetArgRegisterSize() +int x86_64SystemVDefault::GetArgRegisterSize() { int argRegisterSize = 0; @@ -178,7 +206,7 @@ int x86_64MicrosoftDefault::GetArgRegisterSize() return argRegisterSize; } -void* x86_64MicrosoftDefault::GetArgumentPtr(unsigned int index, CRegisters* registers) +void* x86_64SystemVDefault::GetArgumentPtr(unsigned int index, CRegisters* registers) { //g_pSM->LogMessage(myself, "Retrieving argument %d (max args %d) registers %p", index, m_vecArgTypes.size(), registers); if (index >= m_vecArgTypes.size()) @@ -186,7 +214,7 @@ void* x86_64MicrosoftDefault::GetArgumentPtr(unsigned int index, CRegisters* reg //g_pSM->LogMessage(myself, "Not enough arguments"); return nullptr; } - + // Check if this argument was passed in a register. if (m_vecArgTypes[index].custom_register != None) { @@ -200,8 +228,8 @@ void* x86_64MicrosoftDefault::GetArgumentPtr(unsigned int index, CRegisters* reg return reg->m_pAddress; } - // Return address + shadow space - size_t offset = 8 + 32; + // Return address + size_t offset = 8; for (unsigned int i = 0; i < index; i++) { if (m_vecArgTypes[i].custom_register == None) @@ -213,11 +241,11 @@ void* x86_64MicrosoftDefault::GetArgumentPtr(unsigned int index, CRegisters* reg return (void *) (registers->m_rsp->GetValue() + offset); } -void x86_64MicrosoftDefault::ArgumentPtrChanged(unsigned int index, CRegisters* registers, void* argumentPtr) +void x86_64SystemVDefault::ArgumentPtrChanged(unsigned int index, CRegisters* registers, void* argumentPtr) { } -void* x86_64MicrosoftDefault::GetReturnPtr(CRegisters* registers) +void* x86_64SystemVDefault::GetReturnPtr(CRegisters* registers) { // Custom return value register if (m_returnType.custom_register != None) @@ -233,11 +261,11 @@ void* x86_64MicrosoftDefault::GetReturnPtr(CRegisters* registers) return registers->m_rax->m_pAddress; } -void x86_64MicrosoftDefault::ReturnPtrChanged(CRegisters* pRegisters, void* pReturnPtr) +void x86_64SystemVDefault::ReturnPtrChanged(CRegisters* pRegisters, void* pReturnPtr) { } -void x86_64MicrosoftDefault::SaveReturnValue(CRegisters* registers) +void x86_64SystemVDefault::SaveReturnValue(CRegisters* registers) { // It doesn't matter what the return value is, it will always be fitting on 8 bytes (or less) std::unique_ptr savedReturn = std::make_unique(8); @@ -245,7 +273,7 @@ void x86_64MicrosoftDefault::SaveReturnValue(CRegisters* registers) m_pSavedReturnBuffers.push_back(std::move(savedReturn)); } -void x86_64MicrosoftDefault::RestoreReturnValue(CRegisters* registers) +void x86_64SystemVDefault::RestoreReturnValue(CRegisters* registers) { // Like stated in SaveReturnValue, it will always fit within 8 bytes // the actual underlining type does not matter @@ -255,7 +283,7 @@ void x86_64MicrosoftDefault::RestoreReturnValue(CRegisters* registers) m_pSavedReturnBuffers.pop_back(); } -void x86_64MicrosoftDefault::SaveCallArguments(CRegisters* registers) +void x86_64SystemVDefault::SaveCallArguments(CRegisters* registers) { int size = GetArgStackSize() + GetArgRegisterSize(); std::unique_ptr savedCallArguments = std::make_unique(size); @@ -268,7 +296,7 @@ void x86_64MicrosoftDefault::SaveCallArguments(CRegisters* registers) m_pSavedCallArguments.push_back(std::move(savedCallArguments)); } -void x86_64MicrosoftDefault::RestoreCallArguments(CRegisters* registers) +void x86_64SystemVDefault::RestoreCallArguments(CRegisters* registers) { uint8_t *savedCallArguments = m_pSavedCallArguments.back().get(); size_t offset = 0; diff --git a/extensions/dhooks/DynamicHooks/conventions/x86_64SystemVDefault.h b/extensions/dhooks/DynamicHooks/conventions/x86_64SystemVDefault.h index fb6b8e1081..802300a1cc 100644 --- a/extensions/dhooks/DynamicHooks/conventions/x86_64SystemVDefault.h +++ b/extensions/dhooks/DynamicHooks/conventions/x86_64SystemVDefault.h @@ -24,8 +24,8 @@ * 3. This notice may not be removed or altered from any source distribution. */ -#ifndef _X86_64_MICROSOFT_DEFAULT_H -#define _X86_64_MICROSOFT_DEFAULT_H +#ifndef _X86_64_SYSTEMV_DEFAULT_H +#define _X86_64_SYSTEMV_DEFAULT_H // ============================================================================ // >> INCLUDES @@ -35,11 +35,11 @@ // ============================================================================ // >> CLASSES // ============================================================================ -class x86_64MicrosoftDefault : public ICallingConvention +class x86_64SystemVDefault : public ICallingConvention { public: - x86_64MicrosoftDefault(std::vector& vecArgTypes, DataTypeSized_t returnType, int iAlignment = 8); - virtual ~x86_64MicrosoftDefault() = default; + x86_64SystemVDefault(std::vector& vecArgTypes, DataTypeSized_t returnType, int iAlignment = 8); + virtual ~x86_64SystemVDefault() = default; virtual std::vector GetRegisters() override; virtual int GetPopSize() override; @@ -65,4 +65,4 @@ class x86_64MicrosoftDefault : public ICallingConvention std::uint32_t m_stackArgs; }; -#endif // _X86_64_MICROSOFT_DEFAULT_H +#endif // _X86_64_SYSTEMV_DEFAULT_H From c6847e6127d92590b7cddfbcf6af0001a77c66fd Mon Sep 17 00:00:00 2001 From: rtldg Date: Sat, 1 Mar 2025 13:47:34 +0000 Subject: [PATCH 03/20] Enable dhooks linux64 builds (cherry picked from commit ecbf53c26b2b2e47b9d22bca64986cd44c301592) --- extensions/dhooks/AMBuilder | 3 --- 1 file changed, 3 deletions(-) diff --git a/extensions/dhooks/AMBuilder b/extensions/dhooks/AMBuilder index 3ea7987166..5dc23a9f6b 100644 --- a/extensions/dhooks/AMBuilder +++ b/extensions/dhooks/AMBuilder @@ -7,9 +7,6 @@ for cxx in builder.targets: # mac isn't supported if binary.compiler.target.platform == 'mac': continue - # Presently only x86_64 on Windows is supported - if binary.compiler.target.arch == 'x86_64' and binary.compiler.target.platform != 'windows': - continue binary.compiler.defines += [ 'META_NO_HL2SDK', From 2aac2ae267981a15119e89f2188538efe1bb20d0 Mon Sep 17 00:00:00 2001 From: rtldg Date: Sat, 1 Mar 2025 14:10:24 +0000 Subject: [PATCH 04/20] Small linux64 changes to get things started in dhooks (cherry picked from commit 0fca4f98d57f70d82d8f1449cbb284741033b3a5) --- extensions/dhooks/dynhooks_sourcepawn.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/extensions/dhooks/dynhooks_sourcepawn.cpp b/extensions/dhooks/dynhooks_sourcepawn.cpp index 9f7057c3e5..e71a78eae9 100644 --- a/extensions/dhooks/dynhooks_sourcepawn.cpp +++ b/extensions/dhooks/dynhooks_sourcepawn.cpp @@ -49,6 +49,8 @@ typedef x86MsFastcall x86DetourFastCall; #endif #elif defined KE_LINUX #ifdef DYNAMICHOOKS_x86_64 +#include "conventions/x86_64SystemVDefault.h" +typedef x86_64SystemVDefault x86_64DetourCall; #else #include "conventions/x86GccCdecl.h" #include "conventions/x86GccThiscall.h" From 4aaeb7673a92d962705997f8cf9b30ba8c101eb7 Mon Sep 17 00:00:00 2001 From: rtldg Date: Sat, 1 Mar 2025 14:12:30 +0000 Subject: [PATCH 05/20] Maybe correctly fix the crash in UpdateRegisterArgumentSizes() (cherry picked from commit ed7d51f5c21adffcc4f4e4bd3e2888d3e30679f6) --- extensions/dhooks/dynhooks_sourcepawn.cpp | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/extensions/dhooks/dynhooks_sourcepawn.cpp b/extensions/dhooks/dynhooks_sourcepawn.cpp index e71a78eae9..22fb73ca52 100644 --- a/extensions/dhooks/dynhooks_sourcepawn.cpp +++ b/extensions/dhooks/dynhooks_sourcepawn.cpp @@ -257,15 +257,17 @@ ICallingConvention *ConstructCallingConvention(HookSetup *setup) returnType.custom_register = None; #ifdef DYNAMICHOOKS_x86_64 -#ifdef WIN32 if (setup->callConv == CallConv_THISCALL) { DataTypeSized_t type; type.type = DATA_TYPE_POINTER; type.size = GetDataTypeSize(type, sizeof(void*)); +#ifdef PLATFORM_WINDOWS type.custom_register = RCX; +#else + type.custom_register = RDI; +#endif vecArgTypes.insert(vecArgTypes.begin(), type); } -#endif #endif ICallingConvention *pCallConv = nullptr; @@ -310,19 +312,27 @@ bool UpdateRegisterArgumentSizes(CHook* pDetour, HookSetup *setup) ICallingConvention* callingConvention = pDetour->m_pCallingConvention; std::vector &argTypes = callingConvention->m_vecArgTypes; int numArgs = argTypes.size(); + int argTypesOffset = 0; + +#ifdef DYNAMICHOOKS_x86_64 + if (setup->callConv == CallConv_THISCALL) { + argTypesOffset = 1; + --numArgs; + } +#endif for (int i = 0; i < numArgs; i++) { // Ignore regular arguments on the stack. - if (argTypes[i].custom_register == None) + if (argTypes[argTypesOffset+i].custom_register == None) continue; - CRegister *reg = pDetour->m_pRegisters->GetRegister(argTypes[i].custom_register); + CRegister *reg = pDetour->m_pRegisters->GetRegister(argTypes[argTypesOffset+i].custom_register); // That register can't be handled yet. if (!reg) return false; - argTypes[i].size = reg->m_iSize; + argTypes[argTypesOffset+i].size = reg->m_iSize; setup->params[i].size = reg->m_iSize; } From a7d7e204bc96be690ce87954478989a1dabb4793 Mon Sep 17 00:00:00 2001 From: rtldg Date: Sun, 2 Mar 2025 09:21:59 +0000 Subject: [PATCH 06/20] Add some Linux64 stack alignment too (cherry picked from commit 1bde66492806d7ddc954123583aac1c518aea06d) --- extensions/dhooks/DynamicHooks/hook.cpp | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/extensions/dhooks/DynamicHooks/hook.cpp b/extensions/dhooks/DynamicHooks/hook.cpp index 55e4f8aa50..6945c15b11 100644 --- a/extensions/dhooks/DynamicHooks/hook.cpp +++ b/extensions/dhooks/DynamicHooks/hook.cpp @@ -364,6 +364,8 @@ void CHook::Write_ModifyReturnAddress(x64JitWriter& jit) // Shadow space 32 bytes + 8 bytes to keep it aligned on 16 bytes MSVC_ONLY(jit.sub(rsp, 40)); + // We need to keep it aligned to 16 bytes on Linux too... + GCC_ONLY(jit.sub(rsp, 8)); // 1st param (this) GCC_ONLY(jit.mov(rdi, reinterpret_cast(this))); @@ -381,6 +383,8 @@ void CHook::Write_ModifyReturnAddress(x64JitWriter& jit) jit.mov(rax, func.address); jit.call(rax); + // Free Linux stack alignemnt + GCC_ONLY(jit.add(rsp, 8)); // Free shadow space MSVC_ONLY(jit.add(rsp, 40)); @@ -420,12 +424,14 @@ void CHook::CreatePostCallback() // Shadow space 32 bytes + 8 bytes to keep it aligned on 16 bytes MSVC_ONLY(jit.sub(rsp, 40)); + // We need to keep it aligned to 16 bytes on Linux too... + GCC_ONLY(jit.sub(rsp, 8)); // 1st param (this) GCC_ONLY(jit.mov(rdi, reinterpret_cast(this))); MSVC_ONLY(jit.mov(rcx, reinterpret_cast(this))); - // 2n parameter (rsp) + // 2nd parameter (rsp) GCC_ONLY(jit.lea(rsi, rsp())); MSVC_ONLY(jit.lea(rdx, rsp(40))); @@ -433,6 +439,8 @@ void CHook::CreatePostCallback() jit.mov(rax, func.address); jit.call(rax); + // Free Linux stack alignemnt + GCC_ONLY(jit.add(rsp, 8)); // Free shadow space MSVC_ONLY(jit.add(rsp, 40)); @@ -453,6 +461,8 @@ void CHook::Write_CallHandler(x64JitWriter& jit, HookType_t type) // Shadow space 32 bytes + 8 bytes to keep it aligned on 16 bytes MSVC_ONLY(jit.sub(rsp, 40)); + // We need to keep it aligned to 16 bytes on Linux too... + GCC_ONLY(jit.sub(rsp, 8)); // Call the global hook handler @@ -467,6 +477,8 @@ void CHook::Write_CallHandler(x64JitWriter& jit, HookType_t type) jit.mov(rax, func.address); jit.call(rax); + // Free Linux stack alignemnt + GCC_ONLY(jit.add(rsp, 8)); // Free shadow space MSVC_ONLY(jit.add(rsp, 40)); } From 855b14045cb5f4900c6d2259837715837d681722 Mon Sep 17 00:00:00 2001 From: rtldg Date: Mon, 3 Mar 2025 09:26:52 +0000 Subject: [PATCH 07/20] Handle Object sizes in GetArgumentPtr (cherry picked from commit d84bb8842c2d287017fcaec107041cbc046f3608) --- .../DynamicHooks/conventions/x86_64SystemVDefault.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/extensions/dhooks/DynamicHooks/conventions/x86_64SystemVDefault.cpp b/extensions/dhooks/DynamicHooks/conventions/x86_64SystemVDefault.cpp index da2213e4e3..e2d51aae5b 100644 --- a/extensions/dhooks/DynamicHooks/conventions/x86_64SystemVDefault.cpp +++ b/extensions/dhooks/DynamicHooks/conventions/x86_64SystemVDefault.cpp @@ -234,8 +234,10 @@ void* x86_64SystemVDefault::GetArgumentPtr(unsigned int index, CRegisters* regis { if (m_vecArgTypes[i].custom_register == None) { - // No matter what, the stack is allocated in slices of 8 bytes - offset += 8; + // "Regular" types will have a size of 8. + // An object's alignment depends on its contents, which we don't know, so we have to assume the user passed in a size that includes the alignment. + // We at least align object sizes to 8 here though. + offset += Align(m_vecArgTypes[i].size, m_iAlignment); } } return (void *) (registers->m_rsp->GetValue() + offset); From ab46a736d7c269e1941319f82680ef07e8bdb1bd Mon Sep 17 00:00:00 2001 From: rtldg Date: Mon, 3 Mar 2025 09:50:06 +0000 Subject: [PATCH 08/20] Fix indentation (cherry picked from commit daccb883407136e8c0103906688ff6a83bfc641f) --- extensions/dhooks/dynhooks_sourcepawn.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/extensions/dhooks/dynhooks_sourcepawn.cpp b/extensions/dhooks/dynhooks_sourcepawn.cpp index 22fb73ca52..ccb45b4eec 100644 --- a/extensions/dhooks/dynhooks_sourcepawn.cpp +++ b/extensions/dhooks/dynhooks_sourcepawn.cpp @@ -315,10 +315,10 @@ bool UpdateRegisterArgumentSizes(CHook* pDetour, HookSetup *setup) int argTypesOffset = 0; #ifdef DYNAMICHOOKS_x86_64 - if (setup->callConv == CallConv_THISCALL) { - argTypesOffset = 1; - --numArgs; - } + if (setup->callConv == CallConv_THISCALL) { + argTypesOffset = 1; + --numArgs; + } #endif for (int i = 0; i < numArgs; i++) From e9661286a771438158fac91d13c1664e4810c8ce Mon Sep 17 00:00:00 2001 From: rtldg Date: Mon, 3 Mar 2025 09:59:08 +0000 Subject: [PATCH 09/20] Pass around some more info so we can nicely return Vectors (cherry picked from commit a998e5e087a73ba17c93fda9bb68d00c153cb553) --- extensions/dhooks/DynamicHooks/convention.h | 2 ++ .../conventions/x86_64SystemVDefault.cpp | 30 +++++++------------ extensions/dhooks/dynhooks_sourcepawn.cpp | 10 +++++++ 3 files changed, 22 insertions(+), 20 deletions(-) diff --git a/extensions/dhooks/DynamicHooks/convention.h b/extensions/dhooks/DynamicHooks/convention.h index e9a63ed479..8616439bad 100644 --- a/extensions/dhooks/DynamicHooks/convention.h +++ b/extensions/dhooks/DynamicHooks/convention.h @@ -74,10 +74,12 @@ typedef struct DataTypeSized_s { type = DATA_TYPE_POINTER; size = 0; custom_register = None; + custom_register2 = None; } DataType_t type; size_t size; Register_t custom_register; + Register_t custom_register2; // Currently only used for Vector returns on Linux64 } DataTypeSized_t; diff --git a/extensions/dhooks/DynamicHooks/conventions/x86_64SystemVDefault.cpp b/extensions/dhooks/DynamicHooks/conventions/x86_64SystemVDefault.cpp index e2d51aae5b..01394f42fb 100644 --- a/extensions/dhooks/DynamicHooks/conventions/x86_64SystemVDefault.cpp +++ b/extensions/dhooks/DynamicHooks/conventions/x86_64SystemVDefault.cpp @@ -90,26 +90,16 @@ x86_64SystemVDefault::x86_64SystemVDefault(std::vector &vecArgT } } - // TODO: Figure out if we need to do something different for Linux... - // Special return type - if (m_returnType.custom_register == None && m_returnType.type == DATA_TYPE_OBJECT && - // If size unknown, or doesn't fit on 1, 2, 4 or 8 bytes - // special place must have been allocated for it - (m_returnType.size != 1 - && m_returnType.size != 2 - && m_returnType.size != 4 - && m_returnType.size != 8)) { - for (std::uint8_t i = 0; i < num_reg && m_returnType.custom_register == None; i++) { - if (!used_reg[i]) { - m_returnType.custom_register = params_reg[i]; - used_reg[i] = true; - } - // Couldn't find a free register, this is a big problem - if (m_returnType.custom_register == None) { - puts("Missing free register for return pointer"); - return; - } - } + // TODO: Object return-type register is RDI, even when thiscall. + // inRDI = out-object-address & outRAX = inRDI + // thiscall: inRDI = out-object-address & inRSI = this + // + // Currently not handling object return types other than Vector, which would fit into XMM0 & XMM1. + // + // It'd be great if we had PassInfo here... + if (m_returnType.custom_register != XMM0 && m_returnType.custom_register2 != XMM1 && m_returnType.type == DATA_TYPE_OBJECT) { + puts("Return type is an OBJECT but not a Vector. We don't support this right now."); + return; } for (auto& arg : m_vecArgTypes) { diff --git a/extensions/dhooks/dynhooks_sourcepawn.cpp b/extensions/dhooks/dynhooks_sourcepawn.cpp index ccb45b4eec..9df9b05a86 100644 --- a/extensions/dhooks/dynhooks_sourcepawn.cpp +++ b/extensions/dhooks/dynhooks_sourcepawn.cpp @@ -255,6 +255,15 @@ ICallingConvention *ConstructCallingConvention(HookSetup *setup) returnType.size = 0; // TODO: Add support for a custom return register. returnType.custom_register = None; + returnType.custom_register2 = None; + +#if defined(DYNAMICHOOKS_x86_64) && defined(PLATFORM_LINUX) + if (setup->returnType == ReturnType_Vector) { + returnType.size = 16; + returnType.custom_register = XMM0; + returnType.custom_register2 = XMM1; + } +#endif #ifdef DYNAMICHOOKS_x86_64 if (setup->callConv == CallConv_THISCALL) { @@ -387,6 +396,7 @@ ReturnAction_t HandleDetour(HookType_t hookType, CHook* pDetour) if (pWrapper->callConv == CallConv_THISCALL && pWrapper->thisType != ThisPointer_Ignore) { // The this pointer is implicitly always the first argument. + // TODO: Linux64 with an object return value can mean `this` is the second argument, but not relevant for now. auto thisAddr = pDetour->GetArgument(0); if (pWrapper->thisType == ThisPointer_CBaseEntity) { if (thisAddr == nullptr) { From 16c0d4bb8121d195517b57c580a29851b13f8539 Mon Sep 17 00:00:00 2001 From: rtldg Date: Mon, 3 Mar 2025 10:18:19 +0000 Subject: [PATCH 10/20] Some OBJECT alignment things again (cherry picked from commit f3c4c0773c7cde9ffb919050fd3dec412408c60e) --- .../DynamicHooks/conventions/x86_64SystemVDefault.cpp | 7 +++++++ extensions/dhooks/dynhooks_sourcepawn.cpp | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/extensions/dhooks/DynamicHooks/conventions/x86_64SystemVDefault.cpp b/extensions/dhooks/DynamicHooks/conventions/x86_64SystemVDefault.cpp index 01394f42fb..94288e7384 100644 --- a/extensions/dhooks/DynamicHooks/conventions/x86_64SystemVDefault.cpp +++ b/extensions/dhooks/DynamicHooks/conventions/x86_64SystemVDefault.cpp @@ -61,6 +61,13 @@ x86_64SystemVDefault::x86_64SystemVDefault(std::vector &vecArgT } } + // Let's pad our objects up since the stack must be aligned by 8 (and potentially 16 or 32). + for (auto& arg : m_vecArgTypes) { + if (arg.type == DATA_TYPE_OBJECT) { + arg.size = Align(arg.size, 8); + } + } + for (const auto& arg : m_vecArgTypes) { bool found_as_int_or_ptr = false; diff --git a/extensions/dhooks/dynhooks_sourcepawn.cpp b/extensions/dhooks/dynhooks_sourcepawn.cpp index 9df9b05a86..c1a98e26e2 100644 --- a/extensions/dhooks/dynhooks_sourcepawn.cpp +++ b/extensions/dhooks/dynhooks_sourcepawn.cpp @@ -741,7 +741,7 @@ void CDynamicHooksSourcePawn::UpdateParamsFromStruct(HookParamsStruct *params) // Keep track of the seperate stack and register arguments. if (argTypes[i].custom_register == None) { -#ifdef DYNAMICHOOKS_x86_64 +#if defined(DYNAMICHOOKS_x86_64) && defined(PLATFORM_WINDOWS) stackOffset += 8; #else stackOffset += size; From b5a6067cf2d9435107bde4d8ce6f08c77f39b8ca Mon Sep 17 00:00:00 2001 From: rtldg Date: Mon, 3 Mar 2025 10:55:32 +0000 Subject: [PATCH 11/20] A couple of arg size and return size things (cherry picked from commit 5ece3fa663a09bcf3a697a4d49d23f7049e54175) --- .../conventions/x86_64SystemVDefault.cpp | 36 +++++++++++-------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/extensions/dhooks/DynamicHooks/conventions/x86_64SystemVDefault.cpp b/extensions/dhooks/DynamicHooks/conventions/x86_64SystemVDefault.cpp index 94288e7384..2d3eb43bf9 100644 --- a/extensions/dhooks/DynamicHooks/conventions/x86_64SystemVDefault.cpp +++ b/extensions/dhooks/DynamicHooks/conventions/x86_64SystemVDefault.cpp @@ -34,8 +34,7 @@ // >> CLASSES // ============================================================================ x86_64SystemVDefault::x86_64SystemVDefault(std::vector &vecArgTypes, DataTypeSized_t returnType, int iAlignment) : - ICallingConvention(vecArgTypes, returnType, iAlignment), - m_stackArgs(0) + ICallingConvention(vecArgTypes, returnType, iAlignment) { const Register_t params_reg[] = { RDI, RSI, RDX, RCX, R8, R9 }; const Register_t params_floatreg[] = { XMM0, XMM1, XMM2, XMM3, XMM4, XMM5, XMM6, XMM7 }; @@ -129,11 +128,6 @@ x86_64SystemVDefault::x86_64SystemVDefault(std::vector &vecArgT } } } - - // Couldn't find a free register, it's therefore a stack parameter - if (arg.custom_register == None) { - m_stackArgs++; - } } } @@ -146,6 +140,11 @@ std::vector x86_64SystemVDefault::GetRegisters() if (m_returnType.custom_register != None) { registers.push_back(m_returnType.custom_register); + + if (m_returnType.custom_register2 != None) + { + registers.push_back(m_returnType.custom_register2); + } } else if (m_returnType.type == DATA_TYPE_FLOAT || m_returnType.type == DATA_TYPE_DOUBLE) { @@ -177,8 +176,15 @@ int x86_64SystemVDefault::GetPopSize() int x86_64SystemVDefault::GetArgStackSize() { - // 8 bytes * amount of stack arguments - return 8 * m_stackArgs; + size_t stackSize = 0; + for (const auto& arg : m_vecArgTypes) + { + if (arg.custom_register == None) + { + stackSize += arg.size; + } + } + return stackSize; } void** x86_64SystemVDefault::GetStackArgumentPtr(CRegisters* registers) @@ -234,6 +240,7 @@ void* x86_64SystemVDefault::GetArgumentPtr(unsigned int index, CRegisters* regis // "Regular" types will have a size of 8. // An object's alignment depends on its contents, which we don't know, so we have to assume the user passed in a size that includes the alignment. // We at least align object sizes to 8 here though. + // (And dhooks will have already aligned it but maybe this isn't coming from DHooks?) offset += Align(m_vecArgTypes[i].size, m_iAlignment); } } @@ -266,18 +273,17 @@ void x86_64SystemVDefault::ReturnPtrChanged(CRegisters* pRegisters, void* pRetur void x86_64SystemVDefault::SaveReturnValue(CRegisters* registers) { - // It doesn't matter what the return value is, it will always be fitting on 8 bytes (or less) - std::unique_ptr savedReturn = std::make_unique(8); - memcpy(savedReturn.get(), GetReturnPtr(registers), 8); + // Return value for Objects can be a bit complicated but we just have to handle it for Vector right now. + // This means it's either RAX or XMM0&XMM1. + std::unique_ptr savedReturn = std::make_unique(m_returnType.size); + memcpy(savedReturn.get(), GetReturnPtr(registers), m_returnType.size); m_pSavedReturnBuffers.push_back(std::move(savedReturn)); } void x86_64SystemVDefault::RestoreReturnValue(CRegisters* registers) { - // Like stated in SaveReturnValue, it will always fit within 8 bytes - // the actual underlining type does not matter uint8_t* savedReturn = m_pSavedReturnBuffers.back().get(); - memcpy(GetReturnPtr(registers), savedReturn, 8); + memcpy(GetReturnPtr(registers), savedReturn, m_returnType.size); ReturnPtrChanged(registers, savedReturn); m_pSavedReturnBuffers.pop_back(); } From 3a6ed0d8c4f7df59bfabefdf38c27bd177e02119 Mon Sep 17 00:00:00 2001 From: rtldg Date: Fri, 7 Mar 2025 14:47:30 +0000 Subject: [PATCH 12/20] AGHHH (cherry picked from commit 78129d665a5e677a6f8c5468d9bc01dc7ff04139) --- extensions/dhooks/DynamicHooks/convention.h | 2 +- .../conventions/x86_64SystemVDefault.h | 1 - extensions/dhooks/dynhooks_sourcepawn.cpp | 3 ++- extensions/dhooks/natives.cpp | 1 + extensions/dhooks/util.cpp | 4 +-- extensions/dhooks/vhook.cpp | 25 +++++++++++++++++++ extensions/dhooks/vhook.h | 6 ----- 7 files changed, 31 insertions(+), 11 deletions(-) diff --git a/extensions/dhooks/DynamicHooks/convention.h b/extensions/dhooks/DynamicHooks/convention.h index 8616439bad..1253ca89a6 100644 --- a/extensions/dhooks/DynamicHooks/convention.h +++ b/extensions/dhooks/DynamicHooks/convention.h @@ -193,7 +193,7 @@ class ICallingConvention virtual void** GetStackArgumentPtr(CRegisters* pRegisters) = 0; /* - Returns the number of bytes for the buffer to store all the arguments that are passed in a register in. + Returns the number of bytes for the buffer to store all the arguments that are passed in a register. */ virtual int GetArgRegisterSize() = 0; diff --git a/extensions/dhooks/DynamicHooks/conventions/x86_64SystemVDefault.h b/extensions/dhooks/DynamicHooks/conventions/x86_64SystemVDefault.h index 802300a1cc..c84b4fb611 100644 --- a/extensions/dhooks/DynamicHooks/conventions/x86_64SystemVDefault.h +++ b/extensions/dhooks/DynamicHooks/conventions/x86_64SystemVDefault.h @@ -62,7 +62,6 @@ class x86_64SystemVDefault : public ICallingConvention virtual void RestoreCallArguments(CRegisters* pRegisters) override; protected: - std::uint32_t m_stackArgs; }; #endif // _X86_64_SYSTEMV_DEFAULT_H diff --git a/extensions/dhooks/dynhooks_sourcepawn.cpp b/extensions/dhooks/dynhooks_sourcepawn.cpp index c1a98e26e2..1f91d54f7b 100644 --- a/extensions/dhooks/dynhooks_sourcepawn.cpp +++ b/extensions/dhooks/dynhooks_sourcepawn.cpp @@ -259,7 +259,7 @@ ICallingConvention *ConstructCallingConvention(HookSetup *setup) #if defined(DYNAMICHOOKS_x86_64) && defined(PLATFORM_LINUX) if (setup->returnType == ReturnType_Vector) { - returnType.size = 16; + returnType.size = 12; returnType.custom_register = XMM0; returnType.custom_register2 = XMM1; } @@ -718,6 +718,7 @@ void CDynamicHooksSourcePawn::UpdateParamsFromStruct(HookParamsStruct *params) size_t numArgs = argTypes.size(); size_t firstArg = 0; + // TODO: Linux64 will use RDI for retmem even if thiscall // TODO: Support custom register for this ptr. if (callConv == CallConv_THISCALL) firstArg = 1; diff --git a/extensions/dhooks/natives.cpp b/extensions/dhooks/natives.cpp index 92254ba618..a51305c9bf 100644 --- a/extensions/dhooks/natives.cpp +++ b/extensions/dhooks/natives.cpp @@ -353,6 +353,7 @@ cell_t Native_AddParam(IPluginContext *pContext, const cell_t *params) info.flags = PASSFLAG_BYVAL; } + // TODO: why? // DynamicDetours doesn't expose the passflags concept like SourceHook. // See if we're trying to set some invalid flags on detour arguments. if(setup->hookMethod == Detour && (info.flags & ~PASSFLAG_BYVAL) > 0) diff --git a/extensions/dhooks/util.cpp b/extensions/dhooks/util.cpp index ac9b678a10..02f48c089e 100644 --- a/extensions/dhooks/util.cpp +++ b/extensions/dhooks/util.cpp @@ -37,7 +37,7 @@ void * GetObjectAddr(HookParamType type, unsigned int flags, void **params, size if (type == HookParamType_Object) return (void *)((intptr_t)params + offset); #elif POSIX - if (type == HookParamType_Object && !(flags & PASSFLAG_ODTOR)) //Objects are passed by rrefrence if they contain destructors. + if (type == HookParamType_Object && !(flags & PASSFLAG_ODTOR)) //Objects are passed by reference if they contain destructors. return (void *)((intptr_t)params + offset); #endif return *(void **)((intptr_t)params + offset); @@ -62,7 +62,7 @@ size_t GetStackParamOffset(HookParamsStruct *paramStruct, unsigned int index) continue; } #endif -#ifdef KE_ARCH_X64 +#if defined(KE_ARCH_X64) && defined(WIN64) offset += 8; #else offset += paramStruct->dg->params[i].size; diff --git a/extensions/dhooks/vhook.cpp b/extensions/dhooks/vhook.cpp index 21b99e5093..1eca606ca2 100644 --- a/extensions/dhooks/vhook.cpp +++ b/extensions/dhooks/vhook.cpp @@ -56,6 +56,7 @@ using namespace sp; #ifdef KE_ARCH_X64 using namespace SourceHook::Asm; +#ifdef WIN32 SourceHook::Asm::x64JitWriter* GenerateThunk(HookSetup* hook) { auto masm = new x64JitWriter(); @@ -132,6 +133,28 @@ SourceHook::Asm::x64JitWriter* GenerateThunk(HookSetup* hook) masm->SetRE(); return masm; } +#else +// linux64 thunker +SourceHook::Asm::x64JitWriter* GenerateThunk(HookSetup* hook) +{ + auto masm = new x64JitWriter(); + auto type = hook->returnType; + + // Save our frame pointer. + // This also realigns the stack to 16 bytes. + masm->push(rbp); + masm->mov(rbp, rsp); + + // Restore RSP and RBP + // (same as `mov rsp, rbp` + `pop rbp`) + masm->leave(); + + masm->retn(); + + masm->SetRE(); + return masm; +} +#endif #elif !defined( WIN32 ) void *GenerateThunk(HookSetup* hook) { @@ -334,6 +357,7 @@ SourceHook::PassInfo::PassType GetParamTypePassType(HookParamType type) size_t GetStackArgsSize(DHooksCallback *dg) { size_t res = GetParamsSize(dg); +#if defined(WIN32) || !defined(KE_ARCH_X64) // linux64 shall not pass #ifdef WIN32 if(dg->returnType == ReturnType_Vector)//Account for result vector ptr. #else @@ -342,6 +366,7 @@ size_t GetStackArgsSize(DHooksCallback *dg) { res += OBJECT_OFFSET; } +#endif return res; } diff --git a/extensions/dhooks/vhook.h b/extensions/dhooks/vhook.h index fc095047ad..9ccd1010f7 100644 --- a/extensions/dhooks/vhook.h +++ b/extensions/dhooks/vhook.h @@ -133,12 +133,6 @@ struct ParamInfo Register_t custom_register; }; -#ifdef WIN32 -#define OBJECT_OFFSET sizeof(void *) -#else -#define OBJECT_OFFSET (sizeof(void *)*2) -#endif - class HookReturnStruct { public: From 616e341cb069a6a653f3e42f7ed2d97a7af98fe6 Mon Sep 17 00:00:00 2001 From: Cedric Mercier Date: Mon, 19 Oct 2026 13:55:32 +0200 Subject: [PATCH 13/20] dhooks: use pseudo-addresses for legacy 32-bit Address on x86_64 --- extensions/dhooks/dynhooks_sourcepawn.cpp | 2 +- extensions/dhooks/natives.cpp | 4 ++-- extensions/dhooks/util.h | 20 ++++++++++++++++++++ extensions/dhooks/vhook.cpp | 8 ++++---- 4 files changed, 27 insertions(+), 7 deletions(-) diff --git a/extensions/dhooks/dynhooks_sourcepawn.cpp b/extensions/dhooks/dynhooks_sourcepawn.cpp index 1f91d54f7b..73184325eb 100644 --- a/extensions/dhooks/dynhooks_sourcepawn.cpp +++ b/extensions/dhooks/dynhooks_sourcepawn.cpp @@ -409,7 +409,7 @@ ReturnAction_t HandleDetour(HookType_t hookType, CHook* pDetour) std::int64_t addr = reinterpret_cast(thisAddr); pWrapper->plugin_callback->PushArray(reinterpret_cast(&addr), 2); } else { - pWrapper->plugin_callback->PushCell((cell_t)thisAddr); + pWrapper->plugin_callback->PushCell(PtrToLegacyAddress(thisAddr)); } } } diff --git a/extensions/dhooks/natives.cpp b/extensions/dhooks/natives.cpp index a51305c9bf..e7e7d32063 100644 --- a/extensions/dhooks/natives.cpp +++ b/extensions/dhooks/natives.cpp @@ -174,7 +174,7 @@ cell_t Native_CreateHook(IPluginContext *pContext, const cell_t *params) //native Handle:DHookCreateDetour(Address:funcaddr, CallingConvention:callConv, ReturnType:returntype, ThisPointerType:thistype); cell_t Native_CreateDetour(IPluginContext *pContext, const cell_t *params) { - void *addr = reinterpret_cast(params[1]); + void *addr = LegacyAddressToPtr(params[1]); if (pContext->GetRuntime()->FindPubvarByName("__Int64_Address__", nullptr) == SP_ERROR_NONE) { cell_t* sp_addr; if (int err = pContext->LocalToPhysAddr(params[1], &sp_addr); err != SP_ERROR_NONE) { @@ -652,7 +652,7 @@ cell_t HookRawImpl(IPluginContext *pContext, const cell_t *params, int callbackI if (removalcbIndex > 0) removalcb = pContext->GetFunctionById(params[removalcbIndex]); - void *iface = reinterpret_cast(params[3]); + void *iface = LegacyAddressToPtr(params[3]); if (pContext->GetRuntime()->FindPubvarByName("__Int64_Address__", nullptr) == SP_ERROR_NONE) { cell_t* sp_addr; if (int err = pContext->LocalToPhysAddr(params[3], &sp_addr); err != SP_ERROR_NONE) { diff --git a/extensions/dhooks/util.h b/extensions/dhooks/util.h index a168c6babe..640a1c1d0b 100644 --- a/extensions/dhooks/util.h +++ b/extensions/dhooks/util.h @@ -74,6 +74,26 @@ enum PluginRegister DHookRegister_ST0 }; +// Plugins built before `Address` became int64 pass 32-bit addresses, which on +// x86_64 are SourceMod pseudo-addresses rather than raw pointers. +inline cell_t PtrToLegacyAddress(void *ptr) +{ +#ifdef KE_ARCH_X64 + return smutils->ToPseudoAddress(ptr); +#else + return reinterpret_cast(ptr); +#endif +} + +inline void *LegacyAddressToPtr(cell_t addr) +{ +#ifdef KE_ARCH_X64 + return smutils->FromPseudoAddress(addr); +#else + return reinterpret_cast(addr); +#endif +} + size_t GetParamOffset(HookParamsStruct *params, unsigned int index); void * GetObjectAddr(HookParamType type, unsigned int flags, void **params, size_t offset); size_t GetParamTypeSize(HookParamType type); diff --git a/extensions/dhooks/vhook.cpp b/extensions/dhooks/vhook.cpp index 1eca606ca2..b3b074be9d 100644 --- a/extensions/dhooks/vhook.cpp +++ b/extensions/dhooks/vhook.cpp @@ -539,7 +539,7 @@ void *Callback(DHooksCallback *dg, void **argStack) std::int64_t addr = reinterpret_cast(thisAddr); dg->plugin_callback->PushArray(reinterpret_cast(&addr), 2); } else { - dg->plugin_callback->PushCell((cell_t)thisAddr); + dg->plugin_callback->PushCell(PtrToLegacyAddress(thisAddr)); } } } @@ -737,7 +737,7 @@ float Callback_float(DHooksCallback *dg, void **argStack) std::int64_t addr = reinterpret_cast(thisAddr); dg->plugin_callback->PushArray(reinterpret_cast(&addr), 2); } else { - dg->plugin_callback->PushCell((cell_t)thisAddr); + dg->plugin_callback->PushCell(PtrToLegacyAddress(thisAddr)); } } } @@ -908,7 +908,7 @@ SDKVector *Callback_vector(DHooksCallback *dg, void **argStack) std::int64_t addr = reinterpret_cast(thisAddr); dg->plugin_callback->PushArray(reinterpret_cast(&addr), 2); } else { - dg->plugin_callback->PushCell((cell_t)thisAddr); + dg->plugin_callback->PushCell(PtrToLegacyAddress(thisAddr)); } } } @@ -1076,7 +1076,7 @@ string_t *Callback_stringt(DHooksCallback *dg, void **argStack) std::int64_t addr = reinterpret_cast(thisAddr); dg->plugin_callback->PushArray(reinterpret_cast(&addr), 2); } else { - dg->plugin_callback->PushCell((cell_t)thisAddr); + dg->plugin_callback->PushCell(PtrToLegacyAddress(thisAddr)); } } } From 57f2e9166d962258126bbaf2c2342f8d5b91cc89 Mon Sep 17 00:00:00 2001 From: Cedric Mercier Date: Mon, 19 Oct 2026 13:55:32 +0200 Subject: [PATCH 14/20] dhooks: implement the SysV x86_64 vhook thunk --- extensions/dhooks/vhook.cpp | 45 +++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/extensions/dhooks/vhook.cpp b/extensions/dhooks/vhook.cpp index b3b074be9d..874243276f 100644 --- a/extensions/dhooks/vhook.cpp +++ b/extensions/dhooks/vhook.cpp @@ -145,6 +145,51 @@ SourceHook::Asm::x64JitWriter* GenerateThunk(HookSetup* hook) masm->push(rbp); masm->mov(rbp, rsp); + // SourceHook calls us as ISHDelegate::Call(this, params...): rdi is the delegate, + // integer params follow in rsi, rdx, rcx, r8, r9, floats in xmm0-xmm7, the rest on the stack. + // Spill everything into 8-byte slots, which is the layout Callback() expects for its arg stack. + static x86_64_Reg arg_reg[] = { rsi, rdx, rcx, r8, r9 }; + static x86_64_FloatReg arg_reg_float[] = { xmm0, xmm1, xmm2, xmm3, xmm4, xmm5, xmm6, xmm7 }; + + int32_t fakeStackSize = ke::Align((int32_t)hook->params.size() * 8, 16); + if (fakeStackSize > 0) + masm->sub(rsp, fakeStackSize); + + int reg_index = 0; + int float_index = 0; + int stack_index = 0; + for (size_t i = 0; i < hook->params.size(); i++) + { + auto slot = rsp((int32_t)i * 8); + bool isFloat = hook->params[i].type == HookParamType_Float && hook->params[i].flags == PASSFLAG_BYVAL; + if (isFloat && float_index < 8) + { + masm->movsd(slot, arg_reg_float[float_index++]); + } + else if (!isFloat && reg_index < 5) + { + masm->mov(slot, arg_reg[reg_index++]); + } + else + { + // return address + saved rbp sit between rbp and the caller's stack arguments + masm->mov(rax, rbp(16 + 8 * stack_index++)); + masm->mov(slot, rax); + } + } + + // Callback(dg, argStack): rdi still holds the delegate. + masm->mov(rsi, rsp); + if (type == ReturnType_Float) + { + masm->mov(rax, (uintptr_t)Callback_float); + } + else + { + masm->mov(rax, (uintptr_t)Callback); + } + masm->call(rax); + // Restore RSP and RBP // (same as `mov rsp, rbp` + `pop rbp`) masm->leave(); From e0e45840c0287b2e89cc38a345d6f58e1878c336 Mon Sep 17 00:00:00 2001 From: Cedric Mercier Date: Mon, 19 Oct 2026 13:55:42 +0200 Subject: [PATCH 15/20] testsuite: add DHooks runtime test for CS:S (virtual hooks + detours) --- .../dhooks/dhooks-css-test.games.txt | 45 ++++ plugins/testsuite/dhooks/dhooks-css-test.sp | 203 ++++++++++++++++++ 2 files changed, 248 insertions(+) create mode 100644 plugins/testsuite/dhooks/dhooks-css-test.games.txt create mode 100644 plugins/testsuite/dhooks/dhooks-css-test.sp diff --git a/plugins/testsuite/dhooks/dhooks-css-test.games.txt b/plugins/testsuite/dhooks/dhooks-css-test.games.txt new file mode 100644 index 0000000000..9a79d45a42 --- /dev/null +++ b/plugins/testsuite/dhooks/dhooks-css-test.games.txt @@ -0,0 +1,45 @@ +"Games" +{ + "cstrike" + { + "Offsets" + { + "Teleport" + { + "linux" "112" + "linux64" "112" + } + "OnTakeDamage" + { + "linux" "65" + "linux64" "65" + } + } + "Signatures" + { + "RoundRespawn" + { + "library" "server" + "linux" "@_ZN9CCSPlayer12RoundRespawnEv" + "linux64" "@_ZN9CCSPlayer12RoundRespawnEv" + } + "SwitchTeam" + { + "library" "server" + "linux" "@_ZN9CCSPlayer10SwitchTeamEi" + "linux64" "@_ZN9CCSPlayer10SwitchTeamEi" + } + "SetHibernating" + { + "library" "engine" + "linux64" "@_ZN11CGameServer14SetHibernatingEb" + } + "TerminateRound" + { + "library" "server" + "linux" "@_ZN12CCSGameRules14TerminateRoundEfi" + "linux64" "@_ZN12CCSGameRules14TerminateRoundEfi" + } + } + } +} diff --git a/plugins/testsuite/dhooks/dhooks-css-test.sp b/plugins/testsuite/dhooks/dhooks-css-test.sp new file mode 100644 index 0000000000..ac717e78a5 --- /dev/null +++ b/plugins/testsuite/dhooks/dhooks-css-test.sp @@ -0,0 +1,203 @@ +#pragma semicolon 1 +#pragma newdecls required + +#include +#include +#include +#include +#include + +// Runtime check of DHooks on CS:S (x86 and x86_64): virtual hooks + detours, +// int/float/bool/vector/object params, param changes and return values. +// +// Install dhooks-css-test.games.txt into gamedata/. Run `dhx_test` with at least one +// alive bot, or start the server with -dhx_auto to run it unattended and quit. +// Every check logs a "DHX:" line; compare the values with the ones the test passes. +DynamicHook g_hTeleport; +DynamicHook g_hOnTakeDamage; +DynamicDetour g_dRoundRespawn; +DynamicDetour g_dSwitchTeam; +DynamicDetour g_dTerminateRound; +DynamicDetour g_dSetHibernating; + +public void OnPluginStart() +{ + GameData gd = new GameData("dhooks-css-test.games"); + if (gd == null) + SetFailState("gamedata missing"); + + g_hTeleport = new DynamicHook(gd.GetOffset("Teleport"), HookType_Entity, ReturnType_Void, ThisPointer_CBaseEntity); + g_hTeleport.AddParam(HookParamType_VectorPtr); + g_hTeleport.AddParam(HookParamType_ObjectPtr); + g_hTeleport.AddParam(HookParamType_VectorPtr); + + g_hOnTakeDamage = new DynamicHook(gd.GetOffset("OnTakeDamage"), HookType_Entity, ReturnType_Int, ThisPointer_CBaseEntity); + g_hOnTakeDamage.AddParam(HookParamType_ObjectPtr); + + g_dRoundRespawn = new DynamicDetour(Address_Null, CallConv_THISCALL, ReturnType_Void, ThisPointer_CBaseEntity); + CheckDetour(g_dRoundRespawn.SetFromConf(gd, SDKConf_Signature, "RoundRespawn"), "RoundRespawn"); + CheckDetour(g_dRoundRespawn.Enable(Hook_Pre, Detour_RoundRespawn), "RoundRespawn enable"); + + g_dSwitchTeam = new DynamicDetour(Address_Null, CallConv_THISCALL, ReturnType_Void, ThisPointer_CBaseEntity); + g_dSwitchTeam.AddParam(HookParamType_Int); + CheckDetour(g_dSwitchTeam.SetFromConf(gd, SDKConf_Signature, "SwitchTeam"), "SwitchTeam"); + CheckDetour(g_dSwitchTeam.Enable(Hook_Pre, Detour_SwitchTeam), "SwitchTeam enable"); + + g_dTerminateRound = new DynamicDetour(Address_Null, CallConv_THISCALL, ReturnType_Void, ThisPointer_Address); + g_dTerminateRound.AddParam(HookParamType_Float); + g_dTerminateRound.AddParam(HookParamType_Int); + CheckDetour(g_dTerminateRound.SetFromConf(gd, SDKConf_Signature, "TerminateRound"), "TerminateRound"); + CheckDetour(g_dTerminateRound.Enable(Hook_Pre, Detour_TerminateRoundPre), "TerminateRound enable"); + CheckDetour(g_dTerminateRound.Enable(Hook_Post, Detour_TerminateRoundPost), "TerminateRound enable post"); + + // Keep the server awake so bots can join (no cvar for it on CS:S). + g_dSetHibernating = new DynamicDetour(Address_Null, CallConv_THISCALL, ReturnType_Void, ThisPointer_Address); + g_dSetHibernating.AddParam(HookParamType_Bool); + CheckDetour(g_dSetHibernating.SetFromConf(gd, SDKConf_Signature, "SetHibernating"), "SetHibernating"); + CheckDetour(g_dSetHibernating.Enable(Hook_Pre, Detour_SetHibernating), "SetHibernating enable"); + + delete gd; + if (FindCommandLineParam("-dhx_auto")) + CreateConVar("dhx_auto", "1"); + RegServerCmd("dhx_test", Command_Test); + HookEvent("round_freeze_end", Event_RoundFreezeEnd, EventHookMode_PostNoCopy); + LogMessage("DHX: loaded, hooks set up"); +} + +// Unattended mode (no console input under gdb): run once a round is live, then quit. +public void Event_RoundFreezeEnd(Event hEvent, const char[] sName, bool bDontBroadcast) +{ + static bool bDone; + if (bDone || FindConVar("dhx_auto") == null) + return; + bDone = true; + CreateTimer(2.0, Timer_AutoTest); +} + +public Action Timer_AutoTest(Handle hTimer) +{ + Command_Test(0); + CreateTimer(3.0, Timer_Quit); + return Plugin_Stop; +} + +public Action Timer_Quit(Handle hTimer) +{ + LogMessage("DHX: auto quit"); + ServerCommand("quit"); + return Plugin_Stop; +} + +void CheckDetour(bool bOk, const char[] sWhat) +{ + if (!bOk) + SetFailState("DHX: %s failed", sWhat); +} + +public Action Command_Test(int iArgs) +{ + int iBot = -1; + for (int i = 1; i <= MaxClients; i++) + { + if (IsClientInGame(i) && IsFakeClient(i) && IsPlayerAlive(i) && !IsClientSourceTV(i)) + { + iBot = i; + break; + } + } + if (iBot == -1) + { + LogMessage("DHX: no alive bot"); + return Plugin_Handled; + } + + LogMessage("DHX: testing on bot %d (%N)", iBot, iBot); + g_hTeleport.HookEntity(Hook_Pre, iBot, Hook_Teleport); + g_hOnTakeDamage.HookEntity(Hook_Pre, iBot, Hook_OnTakeDamagePre); + g_hOnTakeDamage.HookEntity(Hook_Post, iBot, Hook_OnTakeDamagePost); + + float fPos[3] = {123.5, -456.25, 64.0}; + TeleportEntity(iBot, fPos, NULL_VECTOR, NULL_VECTOR); + float fNow[3]; + GetClientAbsOrigin(iBot, fNow); + LogMessage("DHX: bot origin after TeleportEntity: %.2f %.2f %.2f", fNow[0], fNow[1], fNow[2]); + // SDKHooks_TakeDamage bypasses hooks (SH_MCALL), so hurt through a real point_hurt. + int iHealth = GetClientHealth(iBot); + DispatchKeyValue(iBot, "targetname", "dhx_victim"); + int iHurt = CreateEntityByName("point_hurt"); + DispatchKeyValue(iHurt, "DamageTarget", "dhx_victim"); + DispatchKeyValue(iHurt, "Damage", "20"); + DispatchSpawn(iHurt); + AcceptEntityInput(iHurt, "Hurt"); + RemoveEntity(iHurt); + LogMessage("DHX: health %d -> %d (pre-hook halves 20 damage: expect -10)", iHealth, GetClientHealth(iBot)); + + int iTeam = GetClientTeam(iBot); + CS_SwitchTeam(iBot, iTeam == CS_TEAM_T ? CS_TEAM_CT : CS_TEAM_T); + CS_RespawnPlayer(iBot); + CS_TerminateRound(3.0, CSRoundEnd_Draw); + LogMessage("DHX: test done"); + return Plugin_Handled; +} + +MRESReturn Hook_Teleport(int iEntity, DHookParam hParams) +{ + float fPos[3]; + hParams.GetVector(1, fPos); + LogMessage("DHX: [vhook] Teleport ent=%d origin=%.2f %.2f %.2f angNull=%d", iEntity, fPos[0], fPos[1], fPos[2], hParams.IsNull(2)); + return MRES_Ignored; +} + +MRESReturn Hook_OnTakeDamagePre(int iEntity, DHookReturn hReturn, DHookParam hParams) +{ + // CTakeDamageInfo: Vector m_vecDamageForce, m_vecDamagePosition, m_vecReportedPosition, EHANDLE x3, float m_flDamage + float fDamage = hParams.GetObjectVar(1, 48, ObjectValueType_Float); + hParams.SetObjectVar(1, 48, ObjectValueType_Float, fDamage / 2.0); + LogMessage("DHX: [vhook] OnTakeDamage pre ent=%d damage=%.2f -> %.2f", iEntity, fDamage, fDamage / 2.0); + return MRES_ChangedHandled; +} + +MRESReturn Hook_OnTakeDamagePost(int iEntity, DHookReturn hReturn, DHookParam hParams) +{ + LogMessage("DHX: [vhook] OnTakeDamage post ent=%d return=%d", iEntity, hReturn.Value); + return MRES_Ignored; +} + +MRESReturn Detour_RoundRespawn(int iClient) +{ + LogMessage("DHX: [detour] RoundRespawn client=%d", iClient); + return MRES_Ignored; +} + +MRESReturn Detour_SwitchTeam(int iClient, DHookParam hParams) +{ + LogMessage("DHX: [detour] SwitchTeam client=%d team=%d", iClient, hParams.Get(1)); + return MRES_Ignored; +} + +MRESReturn Detour_TerminateRoundPre(Address pGameRules, DHookParam hParams) +{ + float fDelay = hParams.Get(1); + int iReason = hParams.Get(2); + LogMessage("DHX: [detour] TerminateRound pre this=%s delay=%.2f reason=%d -> delay 1.5", pGameRules == Address_Null ? "null" : "ok", fDelay, iReason); + hParams.Set(1, 1.5); + return MRES_ChangedHandled; +} + +MRESReturn Detour_TerminateRoundPost(Address pGameRules, DHookParam hParams) +{ + LogMessage("DHX: [detour] TerminateRound post delay=%.2f reason=%d", view_as(hParams.Get(1)), hParams.Get(2)); + return MRES_Ignored; +} + +MRESReturn Detour_SetHibernating(Address pServer, DHookParam hParams) +{ + static bool bLogged; + if (!bLogged) + { + bLogged = true; + LogMessage("DHX: [detour engine] SetHibernating(%d) -> 0", hParams.Get(1)); + } + hParams.Set(1, false); + return MRES_ChangedHandled; +} From 914644b34eb36cf28859777095775121ae69352b Mon Sep 17 00:00:00 2001 From: Cedric Mercier Date: Mon, 19 Oct 2026 14:07:36 +0200 Subject: [PATCH 16/20] dhooks: linux64 thunk handles by-value objects and Vector/string_t returns Lay the arg stack out like GetStackParamOffset() (by-value objects take their own size, System V passes those over 16 bytes inline on the stack), keep the result pointer slot for Vector/string_t callbacks, return Vector in xmm0/xmm1, and make GetStackArgsSize() account for that slot on linux64 too. testsuite: cover AcceptInput (variant_t by hidden reference, fieldType offset from gamedata), Vector and float returns. --- extensions/dhooks/vhook.cpp | 104 +++++++++++++++--- .../dhooks/dhooks-css-test.games.txt | 22 ++++ plugins/testsuite/dhooks/dhooks-css-test.sp | 80 ++++++++++++++ 3 files changed, 190 insertions(+), 16 deletions(-) diff --git a/extensions/dhooks/vhook.cpp b/extensions/dhooks/vhook.cpp index 874243276f..d9da5c99be 100644 --- a/extensions/dhooks/vhook.cpp +++ b/extensions/dhooks/vhook.cpp @@ -147,34 +147,89 @@ SourceHook::Asm::x64JitWriter* GenerateThunk(HookSetup* hook) // SourceHook calls us as ISHDelegate::Call(this, params...): rdi is the delegate, // integer params follow in rsi, rdx, rcx, r8, r9, floats in xmm0-xmm7, the rest on the stack. - // Spill everything into 8-byte slots, which is the layout Callback() expects for its arg stack. + // Rebuild the arg stack Callback() expects, at the offsets GetStackParamOffset() computes: + // 8 bytes per param, objects passed by value take their own size. static x86_64_Reg arg_reg[] = { rsi, rdx, rcx, r8, r9 }; static x86_64_FloatReg arg_reg_float[] = { xmm0, xmm1, xmm2, xmm3, xmm4, xmm5, xmm6, xmm7 }; - int32_t fakeStackSize = ke::Align((int32_t)hook->params.size() * 8, 16); - if (fakeStackSize > 0) - masm->sub(rsp, fakeStackSize); + // Vector/string_t callbacks take a result pointer in argStack[0] and read params after OBJECT_OFFSET. + bool resultInStack = (type == ReturnType_Vector || type == ReturnType_String); + int32_t argsOffset = resultInStack ? OBJECT_OFFSET : 0; + int32_t argsSize = 0; + for (size_t i = 0; i < hook->params.size(); i++) + { + bool byRefObject = hook->params[i].type == HookParamType_Object && (hook->params[i].flags & PASSFLAG_ODTOR); + argsSize += byRefObject ? sizeof(void *) : hook->params[i].size; + } + // + 8 bytes of slack: by-value objects are copied 8 bytes at a time + int32_t resultOffset = argsOffset + argsSize + 8; + int32_t fakeStackSize = ke::Align(resultOffset + (resultInStack ? (int32_t)sizeof(SDKVector) + 4 : 0), 16); + masm->sub(rsp, fakeStackSize); + + if (resultInStack) + { + masm->lea(rax, rsp(resultOffset)); + masm->mov(rsp(0), rax); + } int reg_index = 0; int float_index = 0; - int stack_index = 0; + int32_t callerStack = 16; // return address + saved rbp + int32_t offset = argsOffset; for (size_t i = 0; i < hook->params.size(); i++) { - auto slot = rsp((int32_t)i * 8); - bool isFloat = hook->params[i].type == HookParamType_Float && hook->params[i].flags == PASSFLAG_BYVAL; - if (isFloat && float_index < 8) + const ParamInfo ¶m = hook->params[i]; + bool byRefObject = param.type == HookParamType_Object && (param.flags & PASSFLAG_ODTOR); + if (param.type == HookParamType_Float && param.flags == PASSFLAG_BYVAL) { - masm->movsd(slot, arg_reg_float[float_index++]); + if (float_index < 8) + { + masm->movsd(rsp(offset), arg_reg_float[float_index++]); + } + else + { + masm->mov(rax, rbp(callerStack)); + masm->mov(rsp(offset), rax); + callerStack += 8; + } + offset += param.size; } - else if (!isFloat && reg_index < 5) + else if (param.type == HookParamType_Object && !byRefObject) { - masm->mov(slot, arg_reg[reg_index++]); + // Trivially copyable objects: System V passes those over 16 bytes inline on the stack. + // Smaller ones are split over registers by field class, which we can't know: assume integer fields. + int32_t qwords = ke::Align((int32_t)param.size, 8) / 8; + for (int32_t q = 0; q < qwords; q++) + { + if (param.size > 16 || reg_index + qwords > 5) + { + masm->mov(rax, rbp(callerStack + q * 8)); + masm->mov(rsp(offset + q * 8), rax); + } + else + { + masm->mov(rsp(offset + q * 8), arg_reg[reg_index + q]); + } + } + if (param.size > 16 || reg_index + qwords > 5) + callerStack += qwords * 8; + else + reg_index += qwords; + offset += param.size; } else { - // return address + saved rbp sit between rbp and the caller's stack arguments - masm->mov(rax, rbp(16 + 8 * stack_index++)); - masm->mov(slot, rax); + if (reg_index < 5) + { + masm->mov(rsp(offset), arg_reg[reg_index++]); + } + else + { + masm->mov(rax, rbp(callerStack)); + masm->mov(rsp(offset), rax); + callerStack += 8; + } + offset += byRefObject ? sizeof(void *) : param.size; } } @@ -184,12 +239,31 @@ SourceHook::Asm::x64JitWriter* GenerateThunk(HookSetup* hook) { masm->mov(rax, (uintptr_t)Callback_float); } + else if (type == ReturnType_Vector) + { + masm->mov(rax, (uintptr_t)Callback_vector); + } + else if (type == ReturnType_String) + { + masm->mov(rax, (uintptr_t)Callback_stringt); + } else { masm->mov(rax, (uintptr_t)Callback); } masm->call(rax); + if (type == ReturnType_Vector) + { + // Vector is returned by value in xmm0 (x, y) and xmm1 (z). + masm->movsd(xmm0, rax()); + masm->movsd(xmm1, rax(8)); + } + else if (type == ReturnType_String) + { + masm->mov(rax, rax()); + } + // Restore RSP and RBP // (same as `mov rsp, rbp` + `pop rbp`) masm->leave(); @@ -402,7 +476,6 @@ SourceHook::PassInfo::PassType GetParamTypePassType(HookParamType type) size_t GetStackArgsSize(DHooksCallback *dg) { size_t res = GetParamsSize(dg); -#if defined(WIN32) || !defined(KE_ARCH_X64) // linux64 shall not pass #ifdef WIN32 if(dg->returnType == ReturnType_Vector)//Account for result vector ptr. #else @@ -411,7 +484,6 @@ size_t GetStackArgsSize(DHooksCallback *dg) { res += OBJECT_OFFSET; } -#endif return res; } diff --git a/plugins/testsuite/dhooks/dhooks-css-test.games.txt b/plugins/testsuite/dhooks/dhooks-css-test.games.txt index 9a79d45a42..88b4a4290c 100644 --- a/plugins/testsuite/dhooks/dhooks-css-test.games.txt +++ b/plugins/testsuite/dhooks/dhooks-css-test.games.txt @@ -14,6 +14,27 @@ "linux" "65" "linux64" "65" } + "AcceptInput" + { + "linux" "39" + "linux64" "39" + } + // After the value union (12 bytes on x86, 16 on x86_64 where string_t is a pointer) and the EHANDLE. + "variant_t::fieldType" + { + "linux" "16" + "linux64" "20" + } + "Weapon_ShootPosition" + { + "linux" "273" + "linux64" "273" + } + "GetPlayerMaxSpeed" + { + "linux" "447" + "linux64" "447" + } } "Signatures" { @@ -32,6 +53,7 @@ "SetHibernating" { "library" "engine" + "linux" "@_ZN11CGameServer14SetHibernatingEb" "linux64" "@_ZN11CGameServer14SetHibernatingEb" } "TerminateRound" diff --git a/plugins/testsuite/dhooks/dhooks-css-test.sp b/plugins/testsuite/dhooks/dhooks-css-test.sp index ac717e78a5..b99bcb5a2f 100644 --- a/plugins/testsuite/dhooks/dhooks-css-test.sp +++ b/plugins/testsuite/dhooks/dhooks-css-test.sp @@ -15,6 +15,12 @@ // Every check logs a "DHX:" line; compare the values with the ones the test passes. DynamicHook g_hTeleport; DynamicHook g_hOnTakeDamage; +DynamicHook g_hAcceptInput; +DynamicHook g_hShootPosition; +DynamicHook g_hMaxSpeed; +Handle g_hCallShootPosition; +Handle g_hCallMaxSpeed; +int g_iVariantFieldType; DynamicDetour g_dRoundRespawn; DynamicDetour g_dSwitchTeam; DynamicDetour g_dTerminateRound; @@ -34,6 +40,29 @@ public void OnPluginStart() g_hOnTakeDamage = new DynamicHook(gd.GetOffset("OnTakeDamage"), HookType_Entity, ReturnType_Int, ThisPointer_CBaseEntity); g_hOnTakeDamage.AddParam(HookParamType_ObjectPtr); + // variant_t is passed by hidden reference (CHandle has a copy constructor), hence ODTOR. + g_hAcceptInput = new DynamicHook(gd.GetOffset("AcceptInput"), HookType_Entity, ReturnType_Bool, ThisPointer_CBaseEntity); + g_hAcceptInput.AddParam(HookParamType_CharPtr); + g_hAcceptInput.AddParam(HookParamType_CBaseEntity); + g_hAcceptInput.AddParam(HookParamType_CBaseEntity); + g_hAcceptInput.AddParam(HookParamType_Object, 20, DHookPass_ByVal|DHookPass_ODTOR|DHookPass_OCTOR|DHookPass_OASSIGNOP); + g_hAcceptInput.AddParam(HookParamType_Int); + + g_iVariantFieldType = gd.GetOffset("variant_t::fieldType"); + + g_hShootPosition = new DynamicHook(gd.GetOffset("Weapon_ShootPosition"), HookType_Entity, ReturnType_Vector, ThisPointer_CBaseEntity); + g_hMaxSpeed = new DynamicHook(gd.GetOffset("GetPlayerMaxSpeed"), HookType_Entity, ReturnType_Float, ThisPointer_CBaseEntity); + + StartPrepSDKCall(SDKCall_Player); + PrepSDKCall_SetFromConf(gd, SDKConf_Virtual, "Weapon_ShootPosition"); + PrepSDKCall_SetReturnInfo(SDKType_Vector, SDKPass_ByValue); + g_hCallShootPosition = EndPrepSDKCall(); + + StartPrepSDKCall(SDKCall_Player); + PrepSDKCall_SetFromConf(gd, SDKConf_Virtual, "GetPlayerMaxSpeed"); + PrepSDKCall_SetReturnInfo(SDKType_Float, SDKPass_Plain); + g_hCallMaxSpeed = EndPrepSDKCall(); + g_dRoundRespawn = new DynamicDetour(Address_Null, CallConv_THISCALL, ReturnType_Void, ThisPointer_CBaseEntity); CheckDetour(g_dRoundRespawn.SetFromConf(gd, SDKConf_Signature, "RoundRespawn"), "RoundRespawn"); CheckDetour(g_dRoundRespawn.Enable(Hook_Pre, Detour_RoundRespawn), "RoundRespawn enable"); @@ -132,6 +161,25 @@ public Action Command_Test(int iArgs) RemoveEntity(iHurt); LogMessage("DHX: health %d -> %d (pre-hook halves 20 damage: expect -10)", iHealth, GetClientHealth(iBot)); + float fEye[3], fShoot[3]; + GetClientEyePosition(iBot, fEye); + SDKCall(g_hCallShootPosition, iBot, fShoot); + LogMessage("DHX: Weapon_ShootPosition (no hook) = %.2f %.2f %.2f", fShoot[0], fShoot[1], fShoot[2]); + g_hShootPosition.HookEntity(Hook_Post, iBot, Hook_ShootPositionPost); + SDKCall(g_hCallShootPosition, iBot, fShoot); + LogMessage("DHX: Weapon_ShootPosition (post hook, unchanged) = %.2f %.2f %.2f, eye = %.2f %.2f %.2f", fShoot[0], fShoot[1], fShoot[2], fEye[0], fEye[1], fEye[2]); + g_hShootPosition.HookEntity(Hook_Pre, iBot, Hook_ShootPositionPre); + SDKCall(g_hCallShootPosition, iBot, fShoot); + LogMessage("DHX: Weapon_ShootPosition (pre hook supercede) = %.2f %.2f %.2f (expect 1.50 -2.25 3.00)", fShoot[0], fShoot[1], fShoot[2]); + + float fSpeed = SDKCall(g_hCallMaxSpeed, iBot); + g_hMaxSpeed.HookEntity(Hook_Pre, iBot, Hook_MaxSpeedPre); + LogMessage("DHX: GetPlayerMaxSpeed %.2f -> %.2f (expect 123.50)", fSpeed, view_as(SDKCall(g_hCallMaxSpeed, iBot))); + + g_hAcceptInput.HookEntity(Hook_Pre, iBot, Hook_AcceptInput); + SetVariantString("255 0 0"); + AcceptEntityInput(iBot, "Color", iBot, iBot); + int iTeam = GetClientTeam(iBot); CS_SwitchTeam(iBot, iTeam == CS_TEAM_T ? CS_TEAM_CT : CS_TEAM_T); CS_RespawnPlayer(iBot); @@ -201,3 +249,35 @@ MRESReturn Detour_SetHibernating(Address pServer, DHookParam hParams) hParams.Set(1, false); return MRES_ChangedHandled; } + +MRESReturn Hook_ShootPositionPost(int iEntity, DHookReturn hReturn) +{ + float fVec[3]; + hReturn.GetVector(fVec); + LogMessage("DHX: [vhook] Weapon_ShootPosition post ent=%d return=%.2f %.2f %.2f", iEntity, fVec[0], fVec[1], fVec[2]); + return MRES_Ignored; +} + +MRESReturn Hook_ShootPositionPre(int iEntity, DHookReturn hReturn) +{ + float fVec[3] = {1.5, -2.25, 3.0}; + hReturn.SetVector(fVec); + return MRES_Supercede; +} + +MRESReturn Hook_MaxSpeedPre(int iEntity, DHookReturn hReturn) +{ + hReturn.Value = 123.5; + return MRES_Supercede; +} + +MRESReturn Hook_AcceptInput(int iEntity, DHookReturn hReturn, DHookParam hParams) +{ + char sInput[32], sValue[32]; + hParams.GetString(1, sInput, sizeof(sInput)); + int iActivator = hParams.IsNull(2) ? -1 : hParams.Get(2); + int iType = hParams.GetObjectVar(4, g_iVariantFieldType, ObjectValueType_Int); + hParams.GetObjectVarString(4, 0, ObjectValueType_String, sValue, sizeof(sValue)); + LogMessage("DHX: [vhook] AcceptInput ent=%d input=%s activator=%d variant type=%d value=\"%s\" outputid=%d", iEntity, sInput, iActivator, iType, sValue, hParams.Get(5)); + return MRES_Ignored; +} From 86aa08d01c2232551321e220cd8188487b678686 Mon Sep 17 00:00:00 2001 From: Cedric Mercier Date: Mon, 19 Oct 2026 14:38:20 +0200 Subject: [PATCH 17/20] DynamicHooks: carry every System V argument register through detours Only declared arguments got a register saved and restored, so anything else (e.g. a member function declared cdecl without its this, which works on x86 where it stays on the stack) reached the original function clobbered by the callbacks. Seen with LagCompensation's CEntityTouchManager::FrameUpdatePostEntityThink detour: segfault in the original on x86_64. --- .../conventions/x86_64SystemVDefault.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/extensions/dhooks/DynamicHooks/conventions/x86_64SystemVDefault.cpp b/extensions/dhooks/DynamicHooks/conventions/x86_64SystemVDefault.cpp index 2d3eb43bf9..917f284b96 100644 --- a/extensions/dhooks/DynamicHooks/conventions/x86_64SystemVDefault.cpp +++ b/extensions/dhooks/DynamicHooks/conventions/x86_64SystemVDefault.cpp @@ -28,6 +28,7 @@ // >> INCLUDES // ============================================================================ #include "x86_64SystemVDefault.h" +#include #include // ============================================================================ @@ -165,6 +166,19 @@ std::vector x86_64SystemVDefault::GetRegisters() registers.push_back(m_vecArgTypes[i].custom_register); } + // Always carry every argument register through the hook, declared or not. A member + // function declared without its `this` (fine on x86, where it stays on the stack) or + // any undeclared argument would otherwise reach the original function clobbered by + // the callbacks. + const Register_t argRegisters[] = { RDI, RSI, RDX, RCX, R8, R9, XMM0, XMM1, XMM2, XMM3, XMM4, XMM5, XMM6, XMM7 }; + for (Register_t reg : argRegisters) + { + if (std::find(registers.begin(), registers.end(), reg) == registers.end()) + { + registers.push_back(reg); + } + } + return registers; } From 5f4ef6024fd50ba0cc9cbe5524a4b17d99d642f6 Mon Sep 17 00:00:00 2001 From: Cedric Mercier Date: Mon, 19 Oct 2026 14:38:20 +0200 Subject: [PATCH 18/20] DynamicHooks: generate the post-callback before the bridge Both GenBuffers come from the same page allocator and a GenBuffer that grows marks its old block, hence the whole page, read/execute. Growing the post-callback buffer while the bridge was half written (CreatePostCallback was called from Write_ModifyReturnAddress) made the next bridge write fault once bridges got bigger. --- extensions/dhooks/DynamicHooks/hook.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/extensions/dhooks/DynamicHooks/hook.cpp b/extensions/dhooks/DynamicHooks/hook.cpp index 6945c15b11..08d1c11827 100644 --- a/extensions/dhooks/DynamicHooks/hook.cpp +++ b/extensions/dhooks/DynamicHooks/hook.cpp @@ -296,6 +296,11 @@ void CHook::CreateBridge() { auto& jit = m_bridge; + // Generate the post-hook code first. Both buffers come from the same page allocator, + // and a GenBuffer that grows marks its old block (so the whole page) read/execute: + // growing one while the other is half written makes the next write fault. + CreatePostCallback(); + //jit.breakpoint(); PrintRegisters(jit); @@ -389,7 +394,7 @@ void CHook::Write_ModifyReturnAddress(x64JitWriter& jit) MSVC_ONLY(jit.add(rsp, 40)); // Override the return address. This is a redirect to our post-hook code - CreatePostCallback(); + // (generated by CreateBridge beforehand). jit.mov(rax, reinterpret_cast(&m_pNewRetAddr)); jit.mov(rax, rax()); jit.mov(rsp(), rax); From a3343035baeebe4c0532b872a2f438e0ec3b2457 Mon Sep 17 00:00:00 2001 From: Cedric Mercier Date: Mon, 19 Oct 2026 14:50:43 +0200 Subject: [PATCH 19/20] dhooks: DHookGetParamAddress returns a pseudo-address to legacy plugins on x86_64 The raw pointer was cast to cell_t and truncated for plugins built before Address became int64. --- extensions/dhooks/natives.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/extensions/dhooks/natives.cpp b/extensions/dhooks/natives.cpp index e7e7d32063..65f101834e 100644 --- a/extensions/dhooks/natives.cpp +++ b/extensions/dhooks/natives.cpp @@ -1515,8 +1515,9 @@ cell_t Native_GetParamAddress(IPluginContext *pContext, const cell_t *params) return pContext->ThrowNativeErrorEx(err, "Could not read argument"); } *reinterpret_cast(sp_addr) = reinterpret_cast(return_ptr); + return 0; } - return reinterpret_cast(return_ptr); + return PtrToLegacyAddress(return_ptr); } sp_nativeinfo_t g_Natives[] = From 1ea979baf93c867aa57125647c7f668a78a01524 Mon Sep 17 00:00:00 2001 From: Cedric Mercier Date: Mon, 19 Oct 2026 17:35:57 +0200 Subject: [PATCH 20/20] dhooks: convert legacy 32-bit Address like the core natives SourceMod 1.13 has no pseudo-addresses (ISourceMod::To/FromPseudoAddress are stubs returning 0): legacy plugins got a null this/address on x86_64. Convert like LoadFromAddress & co. instead; such plugins have to be rebuilt with 1.13 to use addresses on x86_64. --- extensions/dhooks/util.h | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/extensions/dhooks/util.h b/extensions/dhooks/util.h index 640a1c1d0b..9b1bbcda7a 100644 --- a/extensions/dhooks/util.h +++ b/extensions/dhooks/util.h @@ -74,24 +74,17 @@ enum PluginRegister DHookRegister_ST0 }; -// Plugins built before `Address` became int64 pass 32-bit addresses, which on -// x86_64 are SourceMod pseudo-addresses rather than raw pointers. +// Plugins built before `Address` became int64 pass 32-bit addresses. Like the core +// natives (LoadFromAddress, ...), convert them as-is: lossless on x86, but on x86_64 +// such plugins can't hold a pointer and must be rebuilt with SourceMod 1.13+. inline cell_t PtrToLegacyAddress(void *ptr) { -#ifdef KE_ARCH_X64 - return smutils->ToPseudoAddress(ptr); -#else - return reinterpret_cast(ptr); -#endif + return static_cast(reinterpret_cast(ptr)); } inline void *LegacyAddressToPtr(cell_t addr) { -#ifdef KE_ARCH_X64 - return smutils->FromPseudoAddress(addr); -#else - return reinterpret_cast(addr); -#endif + return reinterpret_cast(static_cast(addr)); } size_t GetParamOffset(HookParamsStruct *params, unsigned int index);