Skip to content

Commit 6b6dbc6

Browse files
authored
Dhooks safetyhook (#2205)
* remove libudis86 * switch to safetyhook * Don't leak memory * fix issue with the trampoline * Add x86_64 detour support (windows) * Allow to differentiate platforms --------- Co-authored-by: Kenzzer <kenzzer@users.noreply.github.com>
1 parent 0a6fa10 commit 6b6dbc6

27 files changed

+1034
-12632
lines changed

extensions/dhooks/AMBuilder

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -42,27 +42,22 @@ for cxx in builder.targets:
4242
'util.cpp',
4343
'dynhooks_sourcepawn.cpp',
4444
'../../public/smsdk_ext.cpp',
45-
'asm/asm.c',
46-
'libudis86/decode.c',
47-
'libudis86/itab.c',
48-
'libudis86/syn-att.c',
49-
'libudis86/syn-intel.c',
50-
'libudis86/syn.c',
51-
'libudis86/udis86.c',
5245
# Dynamic Hooks
53-
os.path.join('DynamicHooks', 'registers.cpp')
46+
os.path.join('DynamicHooks', 'registers.cpp'),
47+
os.path.join('DynamicHooks', 'hook.cpp'),
48+
os.path.join('DynamicHooks', 'manager.cpp')
5449
]
50+
SM.AddCDetour(binary)
5551

5652
if binary.compiler.target.arch == 'x86':
5753
binary.sources += ['../../sourcepawn/vm/x86/assembler-x86.cpp']
54+
5855
binary.compiler.cxxincludes += [
5956
os.path.join(builder.sourcePath, 'public', 'jit', 'x86'),
6057
os.path.join(builder.sourcePath, 'sourcepawn', 'vm', 'x86')
6158
]
6259
# DynamicHooks
6360
binary.sources += [
64-
os.path.join('DynamicHooks', 'hook.cpp'),
65-
os.path.join('DynamicHooks', 'manager.cpp'),
6661
os.path.join('DynamicHooks', 'conventions', 'x86MsCdecl.cpp'),
6762
os.path.join('DynamicHooks', 'conventions', 'x86MsStdcall.cpp'),
6863
os.path.join('DynamicHooks', 'conventions', 'x86MsFastcall.cpp'),
@@ -77,6 +72,16 @@ for cxx in builder.targets:
7772
binary.compiler.defines += ['DHOOKS_DYNAMIC_DETOUR']
7873

7974
elif binary.compiler.target.arch == 'x86_64':
80-
binary.compiler.defines += ['PLATFORM_X64']
75+
binary.compiler.defines += ['DYNAMICHOOKS_x86_64', 'DHOOKS_DYNAMIC_DETOUR']
76+
77+
if binary.compiler.target.platform == 'windows':
78+
binary.sources += [
79+
os.path.join('DynamicHooks', 'conventions', 'x86_64MicrosoftDefault.cpp')
80+
]
81+
# Linux follows System V definition
82+
elif binary.compiler.target.platform == 'linux':
83+
binary.sources += [
84+
os.path.join('DynamicHooks', 'conventions', 'x86_64SystemVDefault.cpp')
85+
]
8186

8287
SM.extensions += [builder.Add(binary)]
Lines changed: 281 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,281 @@
1+
/**
2+
* =============================================================================
3+
* DynamicHooks-x86_64
4+
* Copyright (C) 2024 Benoist "Kenzzer" André. All rights reserved.
5+
* Copyright (C) 2024 AlliedModders LLC. All rights reserved.
6+
* =============================================================================
7+
*
8+
* This software is provided 'as-is', without any express or implied warranty.
9+
* In no event will the authors be held liable for any damages arising from
10+
* the use of this software.
11+
*
12+
* Permission is granted to anyone to use this software for any purpose,
13+
* including commercial applications, and to alter it and redistribute it
14+
* freely, subject to the following restrictions:
15+
*
16+
* 1. The origin of this software must not be misrepresented; you must not
17+
* claim that you wrote the original software. If you use this software in a
18+
* product, an acknowledgment in the product documentation would be
19+
* appreciated but is not required.
20+
*
21+
* 2. Altered source versions must be plainly marked as such, and must not be
22+
* misrepresented as being the original software.
23+
*
24+
* 3. This notice may not be removed or altered from any source distribution.
25+
*/
26+
27+
// ============================================================================
28+
// >> INCLUDES
29+
// ============================================================================
30+
#include "x86_64MicrosoftDefault.h"
31+
#include <smsdk_ext.h>
32+
33+
// ============================================================================
34+
// >> CLASSES
35+
// ============================================================================
36+
x86_64MicrosoftDefault::x86_64MicrosoftDefault(std::vector<DataTypeSized_t> &vecArgTypes, DataTypeSized_t returnType, int iAlignment) :
37+
ICallingConvention(vecArgTypes, returnType, iAlignment),
38+
m_stackArgs(0)
39+
{
40+
const Register_t params_reg[] = { RCX, RDX, R8, R9 };
41+
const Register_t params_floatreg[] = { XMM0, XMM1, XMM2, XMM3 };
42+
//const char* regNames[] = { "RCX or XMM0", "RDX or XMM1", "R8 or XMM2", "R9 or XMM3"};
43+
const std::uint8_t num_reg = sizeof(params_reg) / sizeof(Register_t);
44+
45+
bool used_reg[] = { false, false, false, false };
46+
47+
// Figure out if any register has been used
48+
auto retreg = m_returnType.custom_register;
49+
used_reg[0] = (retreg == RCX || retreg == XMM0);
50+
used_reg[1] = (retreg == RDX || retreg == XMM1);
51+
used_reg[2] = (retreg == R8 || retreg == XMM2);
52+
used_reg[3] = (retreg == R9 || retreg == XMM3);
53+
54+
for (const auto& arg : m_vecArgTypes) {
55+
int reg_index = -1;
56+
if (arg.custom_register == RCX || arg.custom_register == XMM0) {
57+
reg_index = 0;
58+
} else if (arg.custom_register == RDX || arg.custom_register == XMM1) {
59+
reg_index = 1;
60+
} else if (arg.custom_register == R8 || arg.custom_register == XMM2) {
61+
reg_index = 2;
62+
} else if (arg.custom_register == R9 || arg.custom_register == XMM3) {
63+
reg_index = 3;
64+
}
65+
66+
if (reg_index != -1) {
67+
if (used_reg[reg_index]) {
68+
puts("Argument register is used twice, or shared with return");
69+
return;
70+
}
71+
used_reg[reg_index] = true;
72+
}
73+
}
74+
75+
// Special return type
76+
if (m_returnType.custom_register == None && m_returnType.type == DATA_TYPE_OBJECT &&
77+
// If size unknown, or doesn't fit on 1, 2, 4 or 8 bytes
78+
// special place must have been allocated for it
79+
(m_returnType.size == 0
80+
|| m_returnType.size == 3
81+
|| m_returnType.size == 5
82+
|| m_returnType.size == 6
83+
|| m_returnType.size == 7
84+
|| m_returnType.size > 8)) {
85+
for (std::uint8_t i = 0; i < num_reg && m_returnType.custom_register == None; i++) {
86+
if (!used_reg[i]) {
87+
m_returnType.custom_register = params_reg[i];
88+
used_reg[i] = true;
89+
}
90+
// Couldn't find a free register, this is a big problem
91+
if (m_returnType.custom_register == None) {
92+
puts("Missing free register for return pointer");
93+
return;
94+
}
95+
}
96+
}
97+
98+
for (auto& arg : m_vecArgTypes) {
99+
if (arg.custom_register == None) {
100+
for (std::uint8_t i = 0; i < num_reg && arg.custom_register == None; i++) {
101+
// Register is unused assign it
102+
if (!used_reg[i]) {
103+
arg.custom_register = (arg.type == DATA_TYPE_FLOAT || arg.type == DATA_TYPE_DOUBLE) ? params_floatreg[i] : params_reg[i];
104+
used_reg[i] = true;
105+
}
106+
}
107+
// Couldn't find a free register, it's therefore a stack parameter
108+
if (arg.custom_register == None) {
109+
m_stackArgs++;
110+
}
111+
}
112+
}
113+
}
114+
115+
std::vector<Register_t> x86_64MicrosoftDefault::GetRegisters()
116+
{
117+
std::vector<Register_t> registers;
118+
119+
registers.push_back(RSP);
120+
121+
if (m_returnType.custom_register != None)
122+
{
123+
registers.push_back(m_returnType.custom_register);
124+
}
125+
else if (m_returnType.type == DATA_TYPE_FLOAT || m_returnType.type == DATA_TYPE_DOUBLE)
126+
{
127+
registers.push_back(XMM0);
128+
}
129+
else
130+
{
131+
registers.push_back(RAX);
132+
}
133+
134+
for (size_t i = 0; i < m_vecArgTypes.size(); i++)
135+
{
136+
auto reg = m_vecArgTypes[i].custom_register;
137+
if (reg == None)
138+
{
139+
continue;
140+
}
141+
registers.push_back(m_vecArgTypes[i].custom_register);
142+
}
143+
144+
return registers;
145+
}
146+
147+
int x86_64MicrosoftDefault::GetPopSize()
148+
{
149+
// Clean-up is caller handled
150+
return 0;
151+
}
152+
153+
int x86_64MicrosoftDefault::GetArgStackSize()
154+
{
155+
// Shadow space (32 bytes) + 8 bytes * amount of stack arguments
156+
return 32 + 8 * m_stackArgs;
157+
}
158+
159+
void** x86_64MicrosoftDefault::GetStackArgumentPtr(CRegisters* registers)
160+
{
161+
// Skip shadow space + return address
162+
return (void **)(registers->m_rsp->GetValue<uintptr_t>() + 8 + 32);
163+
}
164+
165+
int x86_64MicrosoftDefault::GetArgRegisterSize()
166+
{
167+
int argRegisterSize = 0;
168+
169+
for (size_t i = 0; i < m_vecArgTypes.size(); i++)
170+
{
171+
if (m_vecArgTypes[i].custom_register != None)
172+
{
173+
// It doesn't matter, it's always 8 bytes or less
174+
argRegisterSize += 8;
175+
}
176+
}
177+
178+
return argRegisterSize;
179+
}
180+
181+
void* x86_64MicrosoftDefault::GetArgumentPtr(unsigned int index, CRegisters* registers)
182+
{
183+
//g_pSM->LogMessage(myself, "Retrieving argument %d (max args %d) registers %p", index, m_vecArgTypes.size(), registers);
184+
if (index >= m_vecArgTypes.size())
185+
{
186+
//g_pSM->LogMessage(myself, "Not enough arguments");
187+
return nullptr;
188+
}
189+
190+
// Check if this argument was passed in a register.
191+
if (m_vecArgTypes[index].custom_register != None)
192+
{
193+
CRegister* reg = registers->GetRegister(m_vecArgTypes[index].custom_register);
194+
if (!reg)
195+
{
196+
//g_pSM->LogMessage(myself, "Register does not exit");
197+
return nullptr;
198+
}
199+
//g_pSM->LogMessage(myself, "Register arg %d", m_vecArgTypes[index].custom_register);
200+
return reg->m_pAddress;
201+
}
202+
203+
// Return address + shadow space
204+
size_t offset = 8 + 32;
205+
for (unsigned int i = 0; i < index; i++)
206+
{
207+
if (m_vecArgTypes[i].custom_register == None)
208+
{
209+
// No matter what, the stack is allocated in slices of 8 bytes
210+
offset += 8;
211+
}
212+
}
213+
return (void *) (registers->m_rsp->GetValue<uintptr_t>() + offset);
214+
}
215+
216+
void x86_64MicrosoftDefault::ArgumentPtrChanged(unsigned int index, CRegisters* registers, void* argumentPtr)
217+
{
218+
}
219+
220+
void* x86_64MicrosoftDefault::GetReturnPtr(CRegisters* registers)
221+
{
222+
// Custom return value register
223+
if (m_returnType.custom_register != None)
224+
{
225+
return registers->GetRegister(m_returnType.custom_register)->m_pAddress;
226+
}
227+
228+
if (m_returnType.type == DATA_TYPE_FLOAT || m_returnType.type == DATA_TYPE_DOUBLE)
229+
{
230+
// Floating point register
231+
return registers->m_xmm0->m_pAddress;
232+
}
233+
return registers->m_rax->m_pAddress;
234+
}
235+
236+
void x86_64MicrosoftDefault::ReturnPtrChanged(CRegisters* pRegisters, void* pReturnPtr)
237+
{
238+
}
239+
240+
void x86_64MicrosoftDefault::SaveReturnValue(CRegisters* registers)
241+
{
242+
// It doesn't matter what the return value is, it will always be fitting on 8 bytes (or less)
243+
std::unique_ptr<uint8_t[]> savedReturn = std::make_unique<uint8_t[]>(8);
244+
memcpy(savedReturn.get(), GetReturnPtr(registers), 8);
245+
m_pSavedReturnBuffers.push_back(std::move(savedReturn));
246+
}
247+
248+
void x86_64MicrosoftDefault::RestoreReturnValue(CRegisters* registers)
249+
{
250+
// Like stated in SaveReturnValue, it will always fit within 8 bytes
251+
// the actual underlining type does not matter
252+
uint8_t* savedReturn = m_pSavedReturnBuffers.back().get();
253+
memcpy(GetReturnPtr(registers), savedReturn, 8);
254+
ReturnPtrChanged(registers, savedReturn);
255+
m_pSavedReturnBuffers.pop_back();
256+
}
257+
258+
void x86_64MicrosoftDefault::SaveCallArguments(CRegisters* registers)
259+
{
260+
int size = GetArgStackSize() + GetArgRegisterSize();
261+
std::unique_ptr<uint8_t[]> savedCallArguments = std::make_unique<uint8_t[]>(size);
262+
size_t offset = 0;
263+
for (unsigned int i = 0; i < m_vecArgTypes.size(); i++) {
264+
// Doesn't matter the type, it will always be within 8 bytes
265+
memcpy((void *)((uintptr_t)savedCallArguments.get() + offset), GetArgumentPtr(i, registers), 8);
266+
offset += 8;
267+
}
268+
m_pSavedCallArguments.push_back(std::move(savedCallArguments));
269+
}
270+
271+
void x86_64MicrosoftDefault::RestoreCallArguments(CRegisters* registers)
272+
{
273+
uint8_t *savedCallArguments = m_pSavedCallArguments.back().get();
274+
size_t offset = 0;
275+
for (size_t i = 0; i < m_vecArgTypes.size(); i++) {
276+
// Doesn't matter the type, it will always be within 8 bytes
277+
memcpy(GetArgumentPtr((unsigned int)i, registers), (void *)((uintptr_t)savedCallArguments + offset), 8);
278+
offset += 8;
279+
}
280+
m_pSavedCallArguments.pop_back();
281+
}

0 commit comments

Comments
 (0)