1
0
mirror of https://github.com/alliedmodders/metamod-source.git synced 2024-12-01 13:24:25 +01:00
HLMetaModOfficial/core/sourcehook/sourcehook_hookmangen.cpp

2041 lines
63 KiB
C++

/* ======== SourceHook ========
* Copyright (C) 2004-2010 Metamod:Source Development Team
* No warranties of any kind
*
* License: zlib/libpng
*
* Author(s): Pavol "PM OnoTo" Marko
* Contributor(s): Borja "faluco" Ferav (many thanks for assitance!)
* David "BAILOPAN" Anderson
* ============================
*/
// recommended literature:
// http://www.cs.umbc.edu/~chang/cs313.s02/stack.shtml
// http://www.angelcode.com/dev/callconv/callconv.html
// http://www.arl.wustl.edu/~lockwood/class/cs306/books/artofasm/Chapter_6/CH06-1.html
#include <stdio.h>
#include "sourcehook_impl.h"
#include "sourcehook_hookmangen.h"
#include "sourcehook_hookmangen_x86.h"
#include "sh_memory.h"
#include <stdarg.h> // we might need the address of vsnprintf
#if SH_COMP == SH_COMP_MSVC
# define GCC_ONLY(x)
# define MSVC_ONLY(x) x
#elif SH_COMP == SH_COMP_GCC
# define GCC_ONLY(x) x
# define MSVC_ONLY(x)
#endif
// :TODO: test BIG vtable indices
namespace SourceHook
{
namespace Impl
{
CPageAlloc GenBuffer::ms_Allocator(16);
template <class T>
jit_int32_t DownCastPtr(T ptr)
{
return reinterpret_cast<jit_int32_t>(ptr);
}
jit_uint32_t DownCastSize(size_t size)
{
return static_cast<jit_uint32_t>(size);
}
GenContext::GenContext(const ProtoInfo *proto, int vtbl_offs, int vtbl_idx, ISourceHook *pSHPtr)
: m_GeneratedPubFunc(NULL), m_OrigProto(proto), m_Proto(proto), m_VtblOffs(vtbl_offs),
m_VtblIdx(vtbl_idx), m_SHPtr(pSHPtr), m_pHI(NULL), m_HookfuncVfnptr(NULL), m_RegCounter(0)
{
m_pHI = new void*;
m_HookfuncVfnptr = new void*;
m_BuiltPI = new ProtoInfo;
m_BuiltPI_Params = NULL;
m_BuiltPI_Params2 = NULL;
}
GenContext::~GenContext()
{
Clear();
delete m_pHI;
delete m_HookfuncVfnptr;
delete m_BuiltPI;
}
void GenContext::Clear()
{
m_HookFunc.clear();
m_PubFunc.clear();
if (m_BuiltPI_Params)
{
delete [] m_BuiltPI_Params;
m_BuiltPI_Params = NULL;
}
if (m_BuiltPI_Params2)
{
delete [] m_BuiltPI_Params2;
m_BuiltPI_Params2 = NULL;
}
}
void GenContext::BuildProtoInfo()
{
m_BuiltPI->convention = m_Proto.GetConvention();
m_BuiltPI->numOfParams = m_Proto.GetNumOfParams();
m_BuiltPI->retPassInfo.size = m_Proto.GetRet().size;
m_BuiltPI->retPassInfo.type = m_Proto.GetRet().type;
m_BuiltPI->retPassInfo.flags = m_Proto.GetRet().flags;
m_BuiltPI->retPassInfo2.pNormalCtor = m_Proto.GetRet().pNormalCtor;
m_BuiltPI->retPassInfo2.pCopyCtor = m_Proto.GetRet().pCopyCtor;
m_BuiltPI->retPassInfo2.pDtor = m_Proto.GetRet().pDtor;
m_BuiltPI->retPassInfo2.pAssignOperator = m_Proto.GetRet().pAssignOperator;
if (m_BuiltPI_Params)
delete m_BuiltPI_Params;
m_BuiltPI_Params = new PassInfo[m_BuiltPI->numOfParams + 1];
if (m_BuiltPI_Params2)
delete m_BuiltPI_Params2;
m_BuiltPI_Params2 = new PassInfo::V2Info[m_BuiltPI->numOfParams + 1];
m_BuiltPI_Params[0].size = 1; // Version 1
m_BuiltPI_Params[0].type = 0;
m_BuiltPI_Params[0].flags = 0;
for (int i = 0; i < m_Proto.GetNumOfParams(); ++i)
{
m_BuiltPI_Params[i+1].size = m_Proto.GetParam(i).size;
m_BuiltPI_Params[i+1].type = m_Proto.GetParam(i).type;
m_BuiltPI_Params[i+1].flags = m_Proto.GetParam(i).flags;
m_BuiltPI_Params2[i+1].pNormalCtor = m_Proto.GetParam(i).pNormalCtor;
m_BuiltPI_Params2[i+1].pCopyCtor = m_Proto.GetParam(i).pCopyCtor;
m_BuiltPI_Params2[i+1].pDtor = m_Proto.GetParam(i).pDtor;
m_BuiltPI_Params2[i+1].pAssignOperator = m_Proto.GetParam(i).pAssignOperator;
}
m_BuiltPI->paramsPassInfo = m_BuiltPI_Params;
m_BuiltPI->paramsPassInfo2 = m_BuiltPI_Params2;
}
jit_int32_t GenContext::GetRealSize(const IntPassInfo &info)
{
if (info.flags & (PassInfo::PassFlag_ByRef | PassFlag_ForcedByRef))
{
return SIZE_PTR;
}
return static_cast<jit_int32_t>(info.size);
}
jit_int32_t GenContext::AlignSize(jit_int32_t x, jit_int32_t boundary)
{
if (x % boundary != 0)
x = (x & ~(boundary-1)) + boundary;
return x;
}
// Computes size on the stack
jit_int32_t GenContext::GetParamStackSize(const IntPassInfo &info)
{
// Align up to 4 byte boundaries
return AlignSize(GetRealSize(info), 4);
}
jit_int8_t GenContext::NextRegEBX_ECX_EDX()
{
switch ((m_RegCounter++) % 3)
{
case 0:
return REG_EBX;
case 1:
return REG_ECX;
case 2:
default:
m_RegCounter = 0;
return REG_EDX;
}
}
void GenContext::BitwiseCopy_Setup()
{
//cld
//push edi
//push esi
IA32_Cld(&m_HookFunc);
IA32_Push_Reg(&m_HookFunc, REG_EDI);
IA32_Push_Reg(&m_HookFunc, REG_ESI);
}
void GenContext::BitwiseCopy_Do(size_t size)
{
jit_uint32_t dwords = DownCastSize(size) / 4;
jit_uint32_t bytes = DownCastSize(size) % 4;
//if dwords
// mov ecx, <dwords>
// rep movsd
//if bytes
// mov ecx, <bytes>
// rep movsb
//pop esi
//pop edi
if (dwords)
{
IA32_Mov_Reg_Imm32(&m_HookFunc, REG_ECX, dwords);
IA32_Rep(&m_HookFunc);
IA32_Movsd(&m_HookFunc);
}
if (bytes)
{
IA32_Mov_Reg_Imm32(&m_HookFunc, REG_ECX, bytes);
IA32_Rep(&m_HookFunc);
IA32_Movsb(&m_HookFunc);
}
IA32_Pop_Reg(&m_HookFunc, REG_ESI);
IA32_Pop_Reg(&m_HookFunc, REG_EDI);
}
jit_int32_t GenContext::AlignStackBeforeCall(int paramsize, int flags)
{
paramsize +=
GCC_ONLY( ((flags & AlignStack_GCC_ThisOnStack)!=0 ? SIZE_PTR : 0) + )
MSVC_ONLY( ((flags & AlignStack_MSVC_ThisOnStack)!=0 ? SIZE_PTR : 0) + )
((flags & AlignStack_MemRet)!=0 ? SIZE_PTR : 0);
// At the beginning of the hookfunc, the stack is aligned to a 16 bytes boundary.
// Then, m_BytesPushedAfterInitialAlignment were pushed (can also be 0).
// After this function is called, paramsize bytes will be pushed onto the stack
// After that, the alignment has to be a 16 bytes boundary again.
// How many bytes we would subtract if the alignment was alright now:
int subtractFromEsp = 16 - (paramsize % 16);
if (subtractFromEsp == 16)
subtractFromEsp = 0;
// But: there might be bytes pushed alreay!
subtractFromEsp -= m_BytesPushedAfterInitialAlignment;
// For example: paramsize was 0 and m_BytesPushedAfterInitialAlignment was 4.
// we then have to push another 12 bytes to reach 16 bytes alignment again.
if (subtractFromEsp < 0)
subtractFromEsp = 16 - ((-subtractFromEsp) % 16);
if (subtractFromEsp != 0)
IA32_Sub_Rm_ImmAuto(&m_HookFunc, REG_ESP, subtractFromEsp, MOD_REG);
return subtractFromEsp;
}
void GenContext::AlignStackAfterCall(jit_int32_t numofbytes)
{
if (numofbytes != 0)
IA32_Add_Rm_ImmAuto(&m_HookFunc, REG_ESP, numofbytes, MOD_REG);
}
void GenContext::CheckAlignmentBeforeCall()
{
#if 0
IA32_Test_Rm_Imm32(&m_HookFunc, REG_ESP, 15, MOD_REG);
IA32_Jump_Cond_Imm8(&m_HookFunc, CC_Z, 1);
IA32_Int3(&m_HookFunc);
#endif
}
short GenContext::GetParamsTotalStackSize()
{
short acc = 0;
for (int i = 0; i < m_Proto.GetNumOfParams(); ++i)
{
acc += GetParamStackSize(m_Proto.GetParam(i));
}
return acc;
}
short GenContext::GetForcedByRefParamOffset(int p)
{
short off = 0;
for (int i = 0; i < p; ++i)
{
if (m_Proto.GetParam(i).flags & PassFlag_ForcedByRef)
off += AlignSize(static_cast<jit_int32_t>(m_Proto.GetParam(i).size), 4);
}
return off;
}
short GenContext::GetForcedByRefParamsSize()
{
return GetForcedByRefParamOffset(m_Proto.GetNumOfParams());
}
jit_int32_t GenContext::PushRef(jit_int32_t param_offset, const IntPassInfo &pi)
{
// push [ebp+<offset>]
IA32_Push_Rm_DispAuto(&m_HookFunc, REG_EBP, param_offset);
return SIZE_PTR;
}
jit_int32_t GenContext::PushBasic(jit_int32_t param_offset, const IntPassInfo &pi)
{
int reg;
int reg2;
switch (pi.size)
{
default:
SH_ASSERT(0, ("Unsupported!"));
return 0;
case 1:
reg = NextRegEBX_ECX_EDX();
//movzx reg, BYTE PTR [ebp+<offset>]
//push reg
IA32_Movzx_Reg32_Rm8_DispAuto(&m_HookFunc, reg, REG_EBP, param_offset);
IA32_Push_Reg(&m_HookFunc, reg);
return 4;
case 2:
reg = NextRegEBX_ECX_EDX();
//movzx reg, WORD PTR [ebp+<offset>]
//push reg
m_HookFunc.write_ubyte(IA32_16BIT_PREFIX);
IA32_Movzx_Reg32_Rm16_DispAuto(&m_HookFunc, reg, REG_EBP, param_offset);
IA32_Push_Reg(&m_HookFunc, reg);
return 4;
case 4:
reg = NextRegEBX_ECX_EDX();
//mov reg, DWORD PTR [ebp+<offset>]
//push reg
IA32_Mov_Reg_Rm_DispAuto(&m_HookFunc, reg, REG_EBP, param_offset);
IA32_Push_Reg(&m_HookFunc, reg);
return 4;
case 8:
reg = NextRegEBX_ECX_EDX();
reg2 = NextRegEBX_ECX_EDX();
//mov reg, DWORD PTR [ebp+<offset>+4]
//mov reg2, DWORD PTR [ebp+<offset>]
//push reg
//push reg2
IA32_Mov_Reg_Rm_DispAuto(&m_HookFunc, reg, REG_EBP, param_offset+4);
IA32_Mov_Reg_Rm_DispAuto(&m_HookFunc, reg2, REG_EBP, param_offset);
IA32_Push_Reg(&m_HookFunc, reg);
IA32_Push_Reg(&m_HookFunc, reg2);
return 8;
}
}
jit_int32_t GenContext::PushFloat(jit_int32_t param_offset, const IntPassInfo &pi)
{
switch (pi.size)
{
default:
SH_ASSERT(0, ("Unsupported!"));
return 0;
case 4:
//fld DWORD PTR [ebp+<offset>]
//push reg
//fstp DWORD PTR [esp]
IA32_Fld_Mem32_DispAuto(&m_HookFunc, REG_EBP, param_offset);
IA32_Push_Reg(&m_HookFunc, NextRegEBX_ECX_EDX());
IA32_Fstp_Mem32_ESP(&m_HookFunc);
return 4;
case 8:
//fld QWORD PTR [ebp+<offset>]
//sub esp, 8
//fstp QWORD PTR [esp]
IA32_Fld_Mem64_DispAuto(&m_HookFunc, REG_EBP, param_offset);
IA32_Sub_Rm_Imm8(&m_HookFunc, REG_ESP, 8, MOD_REG);
IA32_Fstp_Mem64_ESP(&m_HookFunc);
return 8;
}
}
jit_int32_t GenContext::PushObject(jit_int32_t param_offset, const IntPassInfo &pi, jit_int32_t place_fbrr)
{
if ((pi.flags & PassFlag_ForcedByRef) == 0)
{
// make room on the stack
// sub esp, <size>
IA32_Sub_Rm_ImmAuto(&m_HookFunc, REG_ESP, GetParamStackSize(pi), MOD_REG);
}
// if there is a copy constructor..
if (pi.pCopyCtor)
{
// :TODO: alignment here?
// can't use normal alignment methods
// because an unknown number of bytes has been pushed already (the other params)
// save eax
IA32_Push_Reg(&m_HookFunc, REG_EAX);
// compute dest addr to ECX
// = forcedbyref ? ebp+place_fbrr : esp+12
if (pi.flags & PassFlag_ForcedByRef)
IA32_Lea_DispRegImmAuto(&m_HookFunc, REG_ECX, REG_EBP, place_fbrr);
else
IA32_Lea_Reg_DispRegMultImm8(&m_HookFunc, REG_ECX, REG_NOIDX, REG_ESP, NOSCALE, 4);
// compute src addr to EAX
IA32_Lea_DispRegImmAuto(&m_HookFunc, REG_EAX, REG_EBP, param_offset);
// push params (gcc: also this)
IA32_Push_Reg(&m_HookFunc, REG_EAX);
GCC_ONLY(IA32_Push_Reg(&m_HookFunc, REG_ECX));
// call
IA32_Mov_Reg_Imm32(&m_HookFunc, REG_EDX, DownCastPtr(pi.pCopyCtor));
IA32_Call_Reg(&m_HookFunc, REG_EDX);
// gcc: clean up stack
GCC_ONLY(IA32_Add_Rm_ImmAuto(&m_HookFunc, REG_ESP, 2 * SIZE_PTR, MOD_REG));
// restore eax
IA32_Pop_Reg(&m_HookFunc, REG_EAX);
}
else
{
// bitwise copy
BitwiseCopy_Setup();
//if forcedbyref:
// lea edi, [ebp_place_fbrr]
//else
// lea edi, [esp+8] - bc_setup pushed two regs onto the stack!
//lea esi, [ebp+<offs>]
if (pi.flags & PassFlag_ForcedByRef)
IA32_Lea_DispRegImmAuto(&m_HookFunc, REG_EDI, REG_EBP, place_fbrr);
else
IA32_Lea_Reg_DispRegMultImm8(&m_HookFunc, REG_EDI, REG_NOIDX, REG_ESP, NOSCALE, 8);
IA32_Lea_DispRegImmAuto(&m_HookFunc, REG_ESI, REG_EBP, param_offset);
BitwiseCopy_Do(pi.size);
}
// forcedref: push reference to ebp+place_fbrr
if (pi.flags & PassFlag_ForcedByRef)
{
IA32_Lea_DispRegImmAuto(&m_HookFunc, REG_ECX, REG_EBP, place_fbrr);
IA32_Push_Reg(&m_HookFunc, REG_ECX);
return SIZE_PTR;
}
return GetParamStackSize(pi);
}
void GenContext::DestroyParams(jit_int32_t fbrr_base)
{
for (int i = m_Proto.GetNumOfParams() - 1; i >= 0; --i)
{
const IntPassInfo &pi = m_Proto.GetParam(i);
if (pi.type == PassInfo::PassType_Object && (pi.flags & PassInfo::PassFlag_ODtor) &&
(pi.flags & PassInfo::PassFlag_ByVal) && (pi.flags & PassFlag_ForcedByRef))
{
// Actually, this is only for GCC (see line above: ForcedByRef)
jit_int32_t tmpAlign = AlignStackBeforeCall(0, AlignStack_GCC_ThisOnStack);
IA32_Lea_DispRegImmAuto(&m_HookFunc, REG_ECX, REG_EBP, fbrr_base + GetForcedByRefParamOffset(i));
IA32_Push_Reg(&m_HookFunc, REG_ECX);
IA32_Mov_Reg_Imm32(&m_HookFunc, REG_EAX, DownCastPtr(pi.pDtor));
CheckAlignmentBeforeCall();
IA32_Call_Reg(&m_HookFunc, REG_EAX);
IA32_Pop_Reg(&m_HookFunc, REG_ECX);
AlignStackAfterCall(tmpAlign);
}
}
}
// May not touch eax!
jit_int32_t GenContext::PushParams(jit_int32_t param_base_offset, jit_int32_t save_ret_to, int v_place_for_memret,
jit_int32_t v_place_fbrr_base)
{
jit_int32_t added_to_stack = 0;
jit_int32_t ret = 0;
// compute the offset _after_ the last parameter
jit_int32_t cur_offset = param_base_offset;
for (int i = 0; i < m_Proto.GetNumOfParams(); ++i)
{
cur_offset += GetParamStackSize(m_Proto.GetParam(i));
}
// push parameters in reverse order
for (int i = m_Proto.GetNumOfParams() - 1; i >= 0; --i)
{
const IntPassInfo &pi = m_Proto.GetParam(i);
cur_offset -= GetParamStackSize(pi);
if (pi.flags & PassInfo::PassFlag_ByVal)
{
switch (pi.type)
{
case PassInfo::PassType_Basic:
ret = PushBasic(cur_offset, pi);
break;
case PassInfo::PassType_Float:
ret = PushFloat(cur_offset, pi);
break;
case PassInfo::PassType_Object:
ret = PushObject(cur_offset, pi, v_place_fbrr_base + GetForcedByRefParamOffset(i));
break;
}
}
else if (pi.flags & PassInfo::PassFlag_ByRef)
{
ret = PushRef(cur_offset, pi);
}
else
{
SH_ASSERT(0, ("Unsupported!"));
}
added_to_stack += ret;
}
return added_to_stack;
}
// It is IMPORTANT that PushMemRetPtr doesn't touch ecx and eax
jit_int32_t GenContext::PushMemRetPtr(jit_int32_t save_ret_to, jit_int32_t v_place_for_memret)
{
// Memory return support
if (m_Proto.GetRet().flags & PassInfo::PassFlag_RetMem)
{
// push address where to save it!
int reg = REG_EDX;
IA32_Lea_DispRegImmAuto(&m_HookFunc, reg, REG_EBP,
MemRetWithTempObj() ? v_place_for_memret : save_ret_to);
IA32_Push_Reg(&m_HookFunc, reg);
return (SH_COMP==SH_COMP_MSVC) ? 4 : 0; // varargs funcs on msvc might need this.
// gcc doesn't: callee cleans the memret ptr, caller the other params :s
}
return 0;
}
void GenContext::SaveRetVal(int v_where, int v_place_for_memret)
{
size_t size = GetRealSize(m_Proto.GetRet());
if (size == 0)
{
// No return value -> nothing
return;
}
if (m_Proto.GetRet().flags & PassInfo::PassFlag_ByRef)
{
// mov [ebp + v_plugin_ret], eax
IA32_Mov_Rm_Reg_DispAuto(&m_HookFunc, REG_EBP, REG_EAX, v_where);
return;
}
// else: ByVal
// Memory return:
if (m_Proto.GetRet().flags & PassInfo::PassFlag_RetMem)
{
if (MemRetWithTempObj())
{
// *v_where = *v_place_for_memret
// if we have an assign operator, use that
if (m_Proto.GetRet().pAssignOperator)
{
// lea edx, [ebp + v_place_for_memret] <-- src addr
// lea ecx, [ebp + v_where] <-- dest addr
// push edx
// gcc: push ecx
// call it
// gcc: clean up
jit_int32_t tmpAlign = AlignStackBeforeCall(SIZE_PTR, AlignStack_GCC_ThisOnStack);
IA32_Lea_DispRegImmAuto(&m_HookFunc, REG_EDX, REG_EBP, v_place_for_memret);
IA32_Lea_DispRegImmAuto(&m_HookFunc, REG_ECX, REG_EBP, v_where);
IA32_Push_Reg(&m_HookFunc, REG_EDX);
GCC_ONLY(IA32_Push_Reg(&m_HookFunc, REG_ECX));
IA32_Mov_Reg_Imm32(&m_HookFunc, REG_EAX, DownCastPtr(m_Proto.GetRet().pAssignOperator));
CheckAlignmentBeforeCall();
IA32_Call_Reg(&m_HookFunc, REG_EAX);
GCC_ONLY(IA32_Add_Rm_ImmAuto(&m_HookFunc, REG_ESP, 2 * SIZE_PTR, MOD_REG));
AlignStackAfterCall(tmpAlign);
}
else
{
// bitwise copy
BitwiseCopy_Setup();
//lea edi, [evp+v_where] <-- destination
//lea esi, [ebp+v_place_for_memret] <-- src
IA32_Lea_DispRegImmAuto(&m_HookFunc, REG_EDI, REG_EBP, v_where);
IA32_Lea_DispRegImmAuto(&m_HookFunc, REG_ESI, REG_EBP, v_place_for_memret);
BitwiseCopy_Do(m_Proto.GetRet().size);
}
// Then: destruct *v_place_for_memret if required
if (m_Proto.GetRet().pDtor)
{
//lea ecx, [ebp+v_place_for_memret]
//gcc: push ecx
//call it
//gcc: clean up
jit_int32_t tmpAlign = AlignStackBeforeCall(0, AlignStack_GCC_ThisOnStack);
IA32_Lea_DispRegImmAuto(&m_HookFunc, REG_ECX, REG_EBP, v_place_for_memret);
GCC_ONLY(IA32_Push_Reg(&m_HookFunc, REG_ECX));
IA32_Mov_Reg_Imm32(&m_HookFunc, REG_EAX, DownCastPtr(m_Proto.GetRet().pDtor));
CheckAlignmentBeforeCall();
IA32_Call_Reg(&m_HookFunc, REG_EAX);
GCC_ONLY(IA32_Pop_Reg(&m_HookFunc, REG_ECX));
AlignStackAfterCall(tmpAlign);
}
}
else
{
// Already copied to correct address -> we're done
return;
}
}
if (m_Proto.GetRet().type == PassInfo::PassType_Float)
{
if (size == 4)
IA32_Fstp_Mem32_DispAuto(&m_HookFunc, REG_EBP, v_where);
else if (size == 8)
IA32_Fstp_Mem64_DispAuto(&m_HookFunc, REG_EBP, v_where);
}
else if (m_Proto.GetRet().type == PassInfo::PassType_Basic)
{
if (size <= 4)
{
// size <= 4: return in EAX
// We align <4 sizes up to 4
// mov [ebp + v_plugin_ret], eax
IA32_Mov_Rm_Reg_DispAuto(&m_HookFunc, REG_EBP, REG_EAX, v_where);
}
else if (size <= 8)
{
// size <= 4: return in EAX:EDX
// We align 4<x<8 sizes up to 8
// mov [ebp + v_plugin_ret], eax
// mov [ebp + v_plugin_ret + 4], edx
IA32_Mov_Rm_Reg_DispAuto(&m_HookFunc, REG_EBP, REG_EAX, v_where);
IA32_Mov_Rm_Reg_DispAuto(&m_HookFunc, REG_EBP, REG_EDX, v_where + 4);
}
}
else if (m_Proto.GetRet().type == PassInfo::PassType_Object)
{
if (m_Proto.GetRet().flags & PassInfo::PassFlag_RetReg)
{
if (size <= 4)
{
// size <= 4: return in EAX
// We align <4 sizes up to 4
// mov [ebp + v_plugin_ret], eax
IA32_Mov_Rm_Reg_DispAuto(&m_HookFunc, REG_EBP, REG_EAX, v_where);
}
else if (size <= 8)
{
// size <= 4: return in EAX:EDX
// We align 4<x<8 sizes up to 8
// mov [ebp + v_plugin_ret], eax
// mov [ebp + v_plugin_ret + 4], edx
IA32_Mov_Rm_Reg_DispAuto(&m_HookFunc, REG_EBP, REG_EAX, v_where);
IA32_Mov_Rm_Reg_DispAuto(&m_HookFunc, REG_EBP, REG_EDX, v_where + 4);
}
else
{
SH_ASSERT(0, ("RetReg and size > 8 !"));
}
}
}
}
bool GenContext::MemRetWithTempObj()
{
// Memory return AND (has destructor OR has assign operator)
return ((m_Proto.GetRet().flags & PassInfo::PassFlag_RetMem)
&& (m_Proto.GetRet().flags & (PassInfo::PassFlag_ODtor | PassInfo::PassFlag_AssignOp)));
}
void GenContext::ProcessPluginRetVal(int v_cur_res, int v_pContext, int v_plugin_ret)
{
// only for non-void functions!
if (m_Proto.GetRet().size == 0)
return;
// if (cur_res >= MRES_OVERRIDE)
// *reinterpret_cast<my_rettype*>(pContext->GetOverrideRetPtr()) = plugin_ret;
// eax = cur_res
// cmp eax,MRES_OVERRIDE
// jnge thelabel
// pContext->GetOverrideRetPtr() --> overrideretptr in eax
// *eax = plugin_ret
// thelabel:
//
jitoffs_t tmppos, counter;
IA32_Mov_Reg_Rm_DispAuto(&m_HookFunc, REG_EAX, REG_EBP, v_cur_res);
IA32_Cmp_Rm_Imm32(&m_HookFunc, MOD_REG, REG_EAX, MRES_OVERRIDE);
tmppos = IA32_Jump_Cond_Imm8(&m_HookFunc, CC_NGE, 0);
m_HookFunc.start_count(counter);
// eax = pContext->GetOverrideRetPtr() no alignment needs
// ECX = pContext
// gcc: push ecx
// eax = [ecx]
// eax = [eax + 4]
// call eax
// gcc: clean up
IA32_Mov_Reg_Rm_DispAuto(&m_HookFunc, REG_ECX, REG_EBP, v_pContext);
GCC_ONLY(IA32_Push_Reg(&m_HookFunc, REG_ECX));
// vtbloffs=0, vtblidx=1
IA32_Mov_Reg_Rm(&m_HookFunc, REG_EAX, REG_ECX, MOD_MEM_REG);
IA32_Mov_Reg_Rm_DispAuto(&m_HookFunc, REG_EAX, REG_EAX, 4);
IA32_Call_Reg(&m_HookFunc, REG_EAX);
GCC_ONLY(IA32_Pop_Reg(&m_HookFunc, REG_ECX));
// *eax = plugin_ret
if (m_Proto.GetRet().flags & PassInfo::PassFlag_ByRef)
{
// mov ecx, [ebp+v_plugin_ret]
// mov [eax], ecx
IA32_Mov_Reg_Rm_DispAuto(&m_HookFunc, REG_ECX, REG_EBP, v_plugin_ret);
IA32_Mov_Rm_Reg(&m_HookFunc, REG_EAX, REG_ECX, MOD_MEM_REG);
}
else
{
if (m_Proto.GetRet().pAssignOperator)
{
// lea edx, [ebp + v_plugin_ret]
// push edx <-- src addr
// msvc: ecx = eax <-- dest addr
// gcc: push eax <-- dest addr
// call it
// gcc: clean up
jit_int32_t tmpAlign = AlignStackBeforeCall(SIZE_PTR, AlignStack_GCC_ThisOnStack);
IA32_Lea_DispRegImmAuto(&m_HookFunc, REG_EDX, REG_EBP, v_plugin_ret);
IA32_Push_Reg(&m_HookFunc, REG_EDX);
#if SH_COMP == SH_COMP_MSVC
IA32_Mov_Reg_Rm(&m_HookFunc, REG_ECX, REG_EAX, MOD_REG);
#elif SH_COMP == SH_COMP_GCC
IA32_Push_Reg(&m_HookFunc, REG_EAX);
#endif
IA32_Mov_Reg_Imm32(&m_HookFunc, REG_EAX, DownCastPtr(m_Proto.GetRet().pAssignOperator));
CheckAlignmentBeforeCall();
IA32_Call_Reg(&m_HookFunc, REG_EAX);
GCC_ONLY(IA32_Add_Rm_ImmAuto(&m_HookFunc, REG_ESP, 2 * SIZE_PTR, MOD_REG));
AlignStackAfterCall(tmpAlign);
}
else
{
// bitwise copy
BitwiseCopy_Setup();
//mov edi, eax <-- destination
//lea esi, [ebp+v_plugin_ret] <-- src
IA32_Mov_Reg_Rm(&m_HookFunc, REG_EDI, REG_EAX, MOD_REG);
IA32_Lea_DispRegImmAuto(&m_HookFunc, REG_ESI, REG_EBP, v_plugin_ret);
BitwiseCopy_Do(m_Proto.GetRet().size);
}
}
m_HookFunc.end_count(counter);
m_HookFunc.rewrite(tmppos, static_cast<jit_uint8_t>(counter));
}
void GenContext::PrepareReturn(int v_status, int v_pContext, int v_retptr)
{
// only for non-void functions!
if (m_Proto.GetRet().size == 0)
return;
// retptr = status >= MRES_OVERRIDE ? pContext->GetOverrideRetPtr() : pContext->GetOrigRetPtr()
// OverrideRetPtr: vtblidx = 1
// OrigRetPtr: vtbldix = 2
// vtblidx = (status >= MRES_OVERRIDE) ? 1 : 2
//
// eax = pContext->GetOverrideRetPtr()
// ECX = pContext
// gcc: push ecx
// eax = (status < MRES_OVERRIDE) ? 1 : 0
// xor eax, eax
// cmp [ebp + v_status], MRES_OVERRIDE
// setl al <-- setcc optimization for ternary operators,
// lea eax, [4*eax + 0x4]
// edx = [ecx]
// add edx, eax
// mov edx, [edx]
// call edx
// gcc: clean up
IA32_Mov_Reg_Rm_DispAuto(&m_HookFunc, REG_ECX, REG_EBP, v_pContext);
GCC_ONLY(IA32_Push_Reg(&m_HookFunc, REG_ECX));
IA32_Xor_Reg_Rm(&m_HookFunc, REG_EAX, REG_EAX, MOD_REG);
IA32_Cmp_Rm_Disp8_Imm8(&m_HookFunc, REG_EBP, v_status, MRES_OVERRIDE);
IA32_SetCC_Rm8(&m_HookFunc, REG_EAX, CC_L);
IA32_Lea_Reg_RegMultImm32(&m_HookFunc, REG_EAX, REG_EAX, SCALE4, 4);
IA32_Mov_Reg_Rm(&m_HookFunc, REG_EDX, REG_ECX, MOD_MEM_REG);
IA32_Add_Reg_Rm(&m_HookFunc, REG_EDX, REG_EAX, MOD_REG);
IA32_Mov_Reg_Rm(&m_HookFunc, REG_EDX, REG_EDX, MOD_MEM_REG);
IA32_Call_Reg(&m_HookFunc, REG_EDX);
GCC_ONLY(IA32_Pop_Reg(&m_HookFunc, REG_ECX));
IA32_Mov_Rm_Reg_DispAuto(&m_HookFunc, REG_EBP, REG_EAX, v_retptr);
}
void GenContext::DoReturn(int v_retptr, int v_memret_outaddr)
{
size_t size = m_Proto.GetRet().size;
if (!size)
return;
// Get real ret pointer into ecx
// mov ecx, [ebp + v_ret_ptr]
IA32_Mov_Reg_Rm_DispAuto(&m_HookFunc, REG_ECX, REG_EBP, v_retptr);
if (m_Proto.GetRet().flags & PassInfo::PassFlag_ByRef)
{
// mov eax, [ecx]
IA32_Mov_Reg_Rm(&m_HookFunc, REG_EAX, REG_ECX, MOD_MEM_REG);
return;
}
// else: byval
if (m_Proto.GetRet().type == PassInfo::PassType_Float)
{
if (size == 4)
IA32_Fld_Mem32(&m_HookFunc, REG_ECX);
else if (size == 8)
IA32_Fld_Mem64(&m_HookFunc, REG_ECX);
}
else if (m_Proto.GetRet().type == PassInfo::PassType_Basic ||
((m_Proto.GetRet().type == PassInfo::PassType_Object) && (m_Proto.GetRet().flags & PassInfo::PassFlag_RetReg)) )
{
if (size <= 4)
{
// size <= 4: return in EAX
// We align <4 sizes up to 4
// mov eax, [ecx]
IA32_Mov_Reg_Rm(&m_HookFunc, REG_EAX, REG_ECX, MOD_MEM_REG);
}
else if (size <= 8)
{
// size <= 4: return in EAX:EDX
// We align 4<x<8 sizes up to 8
// mov eax, [ecx]
// mov edx, [ecx+4]
IA32_Mov_Reg_Rm(&m_HookFunc, REG_EAX, REG_ECX, MOD_MEM_REG);
IA32_Mov_Reg_Rm_DispAuto(&m_HookFunc, REG_EDX, REG_ECX, 4);
}
else
{
// size >8: return in memory
// handled later
}
}
if (m_Proto.GetRet().flags & PassInfo::PassFlag_RetMem)
{
// *memret_outaddr = plugin_ret
if (m_Proto.GetRet().pCopyCtor)
{
// mov edx, ecx <-- src ( we set ecx to [ebp+v_retptr] before )
// push edx <-- src addr
// msvc: ecx = [ebp + v_memret_outaddr] <-- dest addr
// gcc: push [ebp + v_memret_outaddr] <-- dest addr
// call it
// gcc: clean up
jit_int32_t tmpAlign = AlignStackBeforeCall(SIZE_PTR, AlignStack_GCC_ThisOnStack);
IA32_Mov_Reg_Rm(&m_HookFunc, REG_EDX, REG_ECX, MOD_REG);
IA32_Push_Reg(&m_HookFunc, REG_EDX);
#if SH_COMP == SH_COMP_MSVC
IA32_Mov_Reg_Rm_DispAuto(&m_HookFunc, REG_ECX, REG_EBP, v_memret_outaddr);
#elif SH_COMP == SH_COMP_GCC
IA32_Push_Rm_DispAuto(&m_HookFunc, REG_EBP, v_memret_outaddr);
#endif
IA32_Mov_Reg_Imm32(&m_HookFunc, REG_EAX, DownCastPtr(m_Proto.GetRet().pCopyCtor));
CheckAlignmentBeforeCall();
IA32_Call_Reg(&m_HookFunc, REG_EAX);
GCC_ONLY(IA32_Add_Rm_ImmAuto(&m_HookFunc, REG_ESP, 2 * SIZE_PTR, MOD_REG));
AlignStackAfterCall(tmpAlign);
}
else
{
// bitwise copy
BitwiseCopy_Setup();
//mov edi, [ebp+v_memret_outaddr] <-- destination
//mov esi, ecx <-- src ( we set ecx to [ebp+v_retptr] before )
IA32_Mov_Reg_Rm_DispAuto(&m_HookFunc, REG_EDI, REG_EBP, v_memret_outaddr);
IA32_Mov_Reg_Rm(&m_HookFunc, REG_ESI, REG_ECX, MOD_REG);
BitwiseCopy_Do(m_Proto.GetRet().size);
}
// In both cases: return the pointer in EAX
// mov eax, [ebp + v_memret_outaddr]
IA32_Mov_Reg_Rm_DispAuto(&m_HookFunc, REG_EAX, REG_EBP, v_memret_outaddr);
}
}
void GenContext::GenerateCallHooks(int v_status, int v_prev_res, int v_cur_res, int v_iter,
int v_pContext, int base_param_offset, int v_plugin_ret, int v_place_for_memret, jit_int32_t v_place_fbrr_base, jit_int32_t v_va_buf)
{
jitoffs_t counter, tmppos;
jitoffs_t counter2, tmppos2;
jitoffs_t loop_begin_counter;
// prev_res = MRES_IGNORED
IA32_Mov_Rm_Imm32_Disp8(&m_HookFunc, REG_EBP, MRES_IGNORED, v_prev_res);
m_HookFunc.start_count(loop_begin_counter);
// eax = pContext->GetNext()
// ECX = pContext
// gcc: push ecx
// eax = [ecx]
// eax = [eax]
// call eax
// gcc: clean up
IA32_Mov_Reg_Rm_DispAuto(&m_HookFunc, REG_ECX, REG_EBP, v_pContext);
GCC_ONLY(IA32_Push_Reg(&m_HookFunc, REG_ECX));
// vtbloffs=0, vtblidx=0
IA32_Mov_Reg_Rm(&m_HookFunc, REG_EAX, REG_ECX, MOD_MEM_REG);
IA32_Mov_Reg_Rm(&m_HookFunc, REG_EAX, REG_EAX, MOD_MEM_REG);
IA32_Call_Reg(&m_HookFunc, REG_EAX);
GCC_ONLY(IA32_Pop_Reg(&m_HookFunc, REG_ECX));
// quit on zero
// test eax, eax
// jz exit
IA32_Test_Rm_Reg(&m_HookFunc, REG_EAX, REG_EAX, MOD_REG);
tmppos = IA32_Jump_Cond_Imm32(&m_HookFunc, CC_Z, 0);
m_HookFunc.start_count(counter);
// prev_res = MRES_IGNORED
IA32_Mov_Rm_Imm32_Disp8(&m_HookFunc, REG_EBP, MRES_IGNORED, v_cur_res);
// iter->call()
// push params
// ecx = eax
// gcc: push ecx
// eax = [ecx]
// eax = [eax+2*SIZE_PTR]
// call eax
// gcc: clean up
jit_int32_t caller_clean_bytes = 0; // gcc always, msvc never (hooks never have varargs!)
jit_int32_t alignBytes = AlignStackBeforeCall(
GetParamsTotalStackSize() + ((m_Proto.GetConvention() & ProtoInfo::CallConv_HasVafmt)!=0 ? SIZE_PTR : 0),
AlignStack_GCC_ThisOnStack | ((m_Proto.GetRet().flags & PassInfo::PassFlag_RetMem) == 0 ? 0 : AlignStack_MemRet)
);
// vafmt: push va_buf
if (m_Proto.GetConvention() & ProtoInfo::CallConv_HasVafmt)
{
IA32_Lea_DispRegImmAuto(&m_HookFunc, REG_ECX, REG_EBP, v_va_buf);
IA32_Push_Reg(&m_HookFunc, REG_ECX);
caller_clean_bytes += SIZE_PTR;
}
caller_clean_bytes += PushParams(base_param_offset, v_plugin_ret, v_place_for_memret, v_place_fbrr_base);
IA32_Mov_Reg_Rm(&m_HookFunc, REG_ECX, REG_EAX, MOD_REG);
if (SH_COMP == SH_COMP_GCC)
IA32_Push_Reg(&m_HookFunc, REG_ECX);
caller_clean_bytes += PushMemRetPtr(v_plugin_ret, v_place_for_memret);
IA32_Mov_Reg_Rm(&m_HookFunc, REG_EAX, REG_ECX, MOD_MEM_REG);
IA32_Mov_Reg_Rm_DispAuto(&m_HookFunc, REG_EAX, REG_EAX, 2*SIZE_PTR);
CheckAlignmentBeforeCall();
IA32_Call_Reg(&m_HookFunc, REG_EAX);
AlignStackAfterCall(alignBytes);
// cleanup (gcc only)
// params + thisptr
if (SH_COMP == SH_COMP_GCC)
IA32_Add_Rm_ImmAuto(&m_HookFunc, REG_ESP, caller_clean_bytes + SIZE_PTR, MOD_REG);
DestroyParams(v_place_fbrr_base);
SaveRetVal(v_plugin_ret, v_place_for_memret);
// process meta return:
// prev_res = cur_res
// if (cur_res > status) status = cur_res;
//
// eax = cur_res
// edx = status
// prev_res = eax
// cmp eax,edx
// jng thelabel
// status = eax
// thelabel:
//
IA32_Mov_Reg_Rm_DispAuto(&m_HookFunc, REG_EAX, REG_EBP, v_cur_res);
IA32_Mov_Reg_Rm_DispAuto(&m_HookFunc, REG_EDX, REG_EBP, v_status);
IA32_Mov_Rm_Reg_Disp8(&m_HookFunc, REG_EBP, REG_EAX, v_prev_res);
IA32_Cmp_Reg_Rm(&m_HookFunc, REG_EAX, REG_EDX, MOD_REG);
tmppos2 = IA32_Jump_Cond_Imm8(&m_HookFunc, CC_NG, 0);
m_HookFunc.start_count(counter2);
IA32_Mov_Rm_Reg_Disp8(&m_HookFunc, REG_EBP, REG_EAX, v_status);
m_HookFunc.end_count(counter2);
m_HookFunc.rewrite(tmppos2, static_cast<jit_uint8_t>(counter2));
// process retval for non-void functions
ProcessPluginRetVal(v_cur_res, v_pContext, v_plugin_ret);
// jump back to loop begin
tmppos2 = IA32_Jump_Imm32(&m_HookFunc, 0);
m_HookFunc.end_count(loop_begin_counter);
m_HookFunc.rewrite(tmppos2, -static_cast<jit_int32_t>(loop_begin_counter));
m_HookFunc.end_count(counter);
m_HookFunc.rewrite(tmppos, static_cast<jit_int32_t>(counter));
}
void GenContext::GenerateCallOrig(int v_status, int v_pContext, int param_base_offs, int v_this,
int v_vfnptr_origentry, int v_orig_ret, int v_override_ret, int v_place_for_memret, jit_int32_t v_place_fbrr_base, jit_int32_t v_va_buf)
{
jitoffs_t counter, tmppos;
jitoffs_t counter2, tmppos2;
jitoffs_t counter3, tmppos3;
// if (status != MRES_SUPERCEDE && pConteext->ShouldCallOrig())
// *v_orig_ret = orig_call()
// else
// *v_orig_ret = *v_override_ret
// mov eax, status
// cmp eax, MRES_SUPERCEDE
// je dont_call
// call pContext->ShouldCallOrig()
// test al, al !! important: al, not eax! bool is only stored in the LSbyte
// jz dont_call
//
// orig_call()
// SaveRet(v_orig_ret)
// jmp skip_dont_call:
//
// dont_call:
// *v_orig_ret = *v_override_ret
// skip_dont_call:
IA32_Mov_Reg_Rm_DispAuto(&m_HookFunc, REG_EAX, REG_EBP, v_status);
IA32_Cmp_Rm_Imm32(&m_HookFunc, MOD_REG, REG_EAX, MRES_SUPERCEDE);
tmppos = IA32_Jump_Cond_Imm32(&m_HookFunc, CC_E, 0);
m_HookFunc.start_count(counter);
// eax = pContext->ShouldCallOrig()
// ECX = pContext
// gcc: push ecx
// eax = [ecx]
// eax = [eax + 3*SIZE_PTR]
// call eax
// gcc: clean up
IA32_Mov_Reg_Rm_DispAuto(&m_HookFunc, REG_ECX, REG_EBP, v_pContext);
GCC_ONLY(IA32_Push_Reg(&m_HookFunc, REG_ECX));
// vtbloffs=0, vtblidx=3
IA32_Mov_Reg_Rm(&m_HookFunc, REG_EAX, REG_ECX, MOD_MEM_REG);
IA32_Mov_Reg_Rm_Disp8(&m_HookFunc, REG_EAX, REG_EAX, 3*SIZE_PTR);
IA32_Call_Reg(&m_HookFunc, REG_EAX);
GCC_ONLY(IA32_Pop_Reg(&m_HookFunc, REG_ECX));
IA32_Test_Rm_Reg8(&m_HookFunc, REG_EAX, REG_EAX, MOD_REG);
tmppos2 = IA32_Jump_Cond_Imm32(&m_HookFunc, CC_Z, 0);
m_HookFunc.start_count(counter2);
jit_int32_t caller_clean_bytes = 0; // gcc always, msvc when cdecl-like (varargs)
jit_int32_t alignBytes = AlignStackBeforeCall(
GetParamsTotalStackSize() + ((m_Proto.GetConvention() & ProtoInfo::CallConv_HasVafmt)!=0 ? 2*SIZE_PTR : 0),
AlignStack_GCC_ThisOnStack |
((m_Proto.GetRet().flags & PassInfo::PassFlag_RetMem) == 0 ? 0 : AlignStack_MemRet) |
((m_Proto.GetConvention() & ProtoInfo::CallConv_HasVarArgs) == 0 ? 0 : AlignStack_MSVC_ThisOnStack)
);
// vafmt: push va_buf, then "%s"
if (m_Proto.GetConvention() & ProtoInfo::CallConv_HasVafmt)
{
IA32_Lea_DispRegImmAuto(&m_HookFunc, REG_ECX, REG_EBP, v_va_buf);
IA32_Push_Reg(&m_HookFunc, REG_ECX);
IA32_Push_Imm32(&m_HookFunc, DownCastPtr("%s"));
caller_clean_bytes += 2*SIZE_PTR;
}
// push params
caller_clean_bytes += PushParams(param_base_offs, v_orig_ret, v_place_for_memret, v_place_fbrr_base);
// thisptr
IA32_Mov_Reg_Rm_DispAuto(&m_HookFunc, REG_ECX, REG_EBP, v_this);
if (SH_COMP == SH_COMP_GCC)
{
// on gcc/mingw, this is the first parameter
IA32_Push_Reg(&m_HookFunc, REG_ECX);
// on msvc without varargs, simply leave it in ecx
// actually, if we're returning in memory, this pointer is the second param
// and the memret pointer is the real first parameter
caller_clean_bytes += PushMemRetPtr(v_orig_ret, v_place_for_memret);
}
else
{
// On msvc, if we're returning in memory, the memret pointer is the first parameter
caller_clean_bytes += PushMemRetPtr(v_orig_ret, v_place_for_memret);
// actually, with varargs, the this pointer is the first param and the memret ptr
// is the second one
if (m_Proto.GetConvention() & ProtoInfo::CallConv_HasVarArgs)
{
IA32_Push_Reg(&m_HookFunc, REG_ECX);
}
}
// call
IA32_Mov_Reg_Rm_DispAuto(&m_HookFunc, REG_EAX, REG_EBP, v_vfnptr_origentry);
CheckAlignmentBeforeCall();
IA32_Call_Reg(&m_HookFunc, REG_EAX);
AlignStackAfterCall(alignBytes);
// cleanup
if (SH_COMP == SH_COMP_GCC || (m_Proto.GetConvention() & ProtoInfo::CallConv_HasVarArgs))
IA32_Add_Rm_ImmAuto(&m_HookFunc, REG_ESP, caller_clean_bytes + SIZE_PTR, MOD_REG);
DestroyParams(v_place_fbrr_base);
// save retval
SaveRetVal(v_orig_ret, v_place_for_memret);
// Skip don't call variant
tmppos3 = IA32_Jump_Imm32(&m_HookFunc, 0);
m_HookFunc.start_count(counter3);
// don't call:
m_HookFunc.end_count(counter);
m_HookFunc.rewrite(tmppos, static_cast<jit_uint32_t>(counter));
m_HookFunc.end_count(counter2);
m_HookFunc.rewrite(tmppos2, static_cast<jit_uint32_t>(counter2));
// *v_orig_ret = *v_override_ret
if (m_Proto.GetRet().flags & PassInfo::PassFlag_ByRef)
{
// mov ecx, [ebp + v_override_ret]
// mov [ebp + v_orig_ret], ecx
IA32_Mov_Reg_Rm_DispAuto(&m_HookFunc, REG_ECX, REG_EBP, v_override_ret);
IA32_Mov_Rm_Reg_DispAuto(&m_HookFunc, REG_EBP, REG_ECX, v_orig_ret);
}
else
{
if (m_Proto.GetRet().pAssignOperator)
{
// lea edx, [ebp + v_override_ret] <-- src addr
// lea ecx, [ebp + v_orig_ret] <-- dest addr
// push edx <-- src addr
// gcc: push ecx
// call it
// gcc: clean up
jit_int32_t tmpAlign = AlignStackBeforeCall(SIZE_PTR, AlignStack_GCC_ThisOnStack);
IA32_Lea_DispRegImmAuto(&m_HookFunc, REG_EDX, REG_EBP, v_override_ret);
IA32_Lea_DispRegImmAuto(&m_HookFunc, REG_ECX, REG_EBP, v_orig_ret);
IA32_Push_Reg(&m_HookFunc, REG_EDX);
GCC_ONLY(IA32_Push_Reg(&m_HookFunc, REG_ECX));
IA32_Mov_Reg_Imm32(&m_HookFunc, REG_EAX, DownCastPtr(m_Proto.GetRet().pAssignOperator));
CheckAlignmentBeforeCall();
IA32_Call_Reg(&m_HookFunc, REG_EAX);
GCC_ONLY(IA32_Add_Rm_ImmAuto(&m_HookFunc, REG_ESP, 2*SIZE_PTR, MOD_REG));
AlignStackAfterCall(tmpAlign);
}
else
{
// bitwise copy
BitwiseCopy_Setup();
//lea edi, [ebp+v_orig_ret] <-- destination
//lea esi, [ebp+v_override_ret] <-- src
IA32_Lea_DispRegImmAuto(&m_HookFunc, REG_EDI, REG_EBP, v_orig_ret);
IA32_Lea_DispRegImmAuto(&m_HookFunc, REG_ESI, REG_EBP, v_override_ret);
BitwiseCopy_Do(m_Proto.GetRet().size);
}
}
// skip don't call label target:
m_HookFunc.end_count(counter3);
m_HookFunc.rewrite(tmppos3, static_cast<jit_uint32_t>(counter3));
}
// Sets *v_pContext to return value
void GenContext::CallSetupHookLoop(int v_orig_ret, int v_override_ret,
int v_cur_res, int v_prev_res, int v_status, int v_vfnptr_origentry,
int v_this, int v_pContext)
{
// call shptr->SetupHookLoop(ms_HI, ourvfnptr, reinterpret_cast<void*>(this),
// &vfnptr_origentry, &status, &prev_res, &cur_res, &orig_ret, &override_ret);
// The last two params are null for void funcs.
if (m_Proto.GetRet().size == 0)
{
// void
IA32_Push_Imm8(&m_HookFunc, 0); // orig_ret
IA32_Push_Imm8(&m_HookFunc, 0); // override_ret
}
else
{
// orig_ret and override_ret
IA32_Lea_DispRegImmAuto(&m_HookFunc, REG_EAX, REG_EBP, v_override_ret);
IA32_Lea_DispRegImmAuto(&m_HookFunc, REG_EDX, REG_EBP, v_orig_ret);
IA32_Push_Reg(&m_HookFunc, REG_EAX);
IA32_Push_Reg(&m_HookFunc, REG_EDX);
}
// cur_res and prev_res
IA32_Lea_DispRegImm8(&m_HookFunc, REG_EAX, REG_EBP, v_cur_res);
IA32_Lea_DispRegImm8(&m_HookFunc, REG_EDX, REG_EBP, v_prev_res);
IA32_Push_Reg(&m_HookFunc, REG_EAX);
IA32_Push_Reg(&m_HookFunc, REG_EDX);
// status and vfnptr_origentry
IA32_Lea_DispRegImm8(&m_HookFunc, REG_EAX, REG_EBP, v_status);
IA32_Lea_DispRegImm8(&m_HookFunc, REG_EDX, REG_EBP, v_vfnptr_origentry);
IA32_Push_Reg(&m_HookFunc, REG_EAX);
IA32_Push_Reg(&m_HookFunc, REG_EDX);
// this
IA32_Mov_Reg_Rm_DispAuto(&m_HookFunc, REG_EAX, REG_EBP, v_this);
IA32_Push_Reg(&m_HookFunc, REG_EAX);
// our vfn ptr
// *(this + vtbloffs) + SIZE_PTR*vtblidx
IA32_Mov_Reg_Rm_DispAuto(&m_HookFunc, REG_ECX, REG_EBP, v_this); // get this into ecx (gcc!)
IA32_Mov_Reg_Rm_DispAuto(&m_HookFunc, REG_EAX, REG_ECX, m_VtblOffs);
IA32_Add_Rm_ImmAuto(&m_HookFunc, REG_EAX, m_VtblIdx * SIZE_PTR, MOD_REG);
IA32_Push_Reg(&m_HookFunc, REG_EAX);
// *m_pHI
IA32_Mov_Rm_Imm32(&m_HookFunc, REG_EDX, DownCastPtr(m_pHI), MOD_REG);
IA32_Mov_Reg_Rm(&m_HookFunc, REG_EAX, REG_EDX, MOD_MEM_REG);
IA32_Push_Reg(&m_HookFunc, REG_EAX);
// set up thisptr
#if SH_COMP == SH_COMP_GCC
// on gcc/mingw, this is the first parameter
GCC_ONLY(IA32_Push_Imm32(&m_HookFunc, DownCastPtr(m_SHPtr)));
#elif SH_COMP == SH_COMP_MSVC
// on msvc, it's ecx
IA32_Mov_Reg_Imm32(&m_HookFunc, REG_ECX, DownCastPtr(m_SHPtr));
#endif
// call the function. vtbloffs = 0, vtblidx = 19
// get vtptr into edx -- we know shptr on jit time -> dereference it here!
IA32_Mov_Reg_Imm32(&m_HookFunc, REG_EAX,
(*reinterpret_cast<jit_uint32_t**>(m_SHPtr))[19]);
IA32_Call_Reg(&m_HookFunc, REG_EAX);
// on gcc/mingw, we have to clean up after the call
// 9 params + hidden thisptr param
GCC_ONLY(IA32_Add_Rm_Imm8(&m_HookFunc, REG_ESP, 10*SIZE_PTR, MOD_REG));
// store return value
IA32_Mov_Rm_Reg_Disp8(&m_HookFunc, REG_EBP, REG_EAX, v_pContext);
}
void GenContext::CallEndContext(int v_pContext)
{
// call endcontext:
// shptr->EndContext(pContex)
IA32_Mov_Reg_Rm_DispAuto(&m_HookFunc, REG_EAX, REG_EBP, v_pContext);
IA32_Push_Reg(&m_HookFunc, REG_EAX);
// thisptr
#if SH_COMP == SH_COMP_GCC
// on gcc/mingw, this is the first parameter
IA32_Push_Imm32(&m_HookFunc, DownCastPtr(m_SHPtr));
#elif SH_COMP == SH_COMP_MSVC
// on msvc, it's ecx
IA32_Mov_Reg_Imm32(&m_HookFunc, REG_ECX, DownCastPtr(m_SHPtr));
#endif
// get vtptr into edx -- we know shptr on jit time -> dereference it here!
IA32_Mov_Reg_Imm32(&m_HookFunc, REG_EAX,
(*reinterpret_cast<jit_uint32_t**>(m_SHPtr))[20]);
IA32_Call_Reg(&m_HookFunc, REG_EAX);
// on gcc/mingw, we have to clean up after the call
// 1 param + hidden thisptr param
GCC_ONLY(IA32_Add_Rm_Imm8(&m_HookFunc, REG_ESP, 2*SIZE_PTR, MOD_REG));
}
void GenContext::ResetFrame(jit_int32_t startOffset)
{
m_HookFunc_FrameOffset = startOffset;
m_HookFunc_FrameVarsSize = 0;
}
jit_int32_t GenContext::AddVarToFrame(jit_int32_t size)
{
m_HookFunc_FrameOffset -= size;
m_HookFunc_FrameVarsSize += size;
return m_HookFunc_FrameOffset;
}
jit_int32_t GenContext::ComputeVarsSize()
{
return m_HookFunc_FrameVarsSize;
}
void * GenContext::GenerateHookFunc()
{
// prologue
IA32_Push_Reg(&m_HookFunc, REG_EBP);
IA32_Push_Reg(&m_HookFunc, REG_EBX);
IA32_Mov_Reg_Rm(&m_HookFunc, REG_EBP, REG_ESP, MOD_REG);
jit_int32_t v_this = 0;
jit_int32_t param_base_offs = 0;
if (SH_COMP == SH_COMP_GCC || (m_Proto.GetConvention() & ProtoInfo::CallConv_HasVarArgs))
{
// gcc or msvc with varargs:
v_this = 12; // first param
param_base_offs = 16;
ResetFrame(0);
}
else
{
// on msvc without varargs, save thisptr
v_this = -4;
param_base_offs = 12;
IA32_Push_Reg(&m_HookFunc, REG_ECX);
ResetFrame(-4); // start placing local vars on offset -4
// because there already is the thisptr variable
}
// ********************** stack frame **********************
// MSVC without varargs
// second param (gcc: first real param) ebp + 16
// first param (gcc: thisptr) ebp + 12
// ret address: ebp + 8
// caller's ebp ebp + 4
// saved ebx ebp
// MSVC ONLY: current this ebp - 4
// void *vfnptr_origentry ebp - 4 -4
// META_RES status = MRES_IGNORED ebp - 8 -4
// META_RES prev_res ebp - 12 -4
// META_RES cur_res ebp - 16 -4
// IMyDelegate *iter ebp - 20 -4
// IHookContext *pContext ebp - 24 -4
// == 3 ptrs + 3 enums = 24 bytes
//
// non-void: add:
// my_rettype *ret_ptr ebp - 28 -4
// my_rettype orig_ret ebp - 28 - sizeof(my_rettype) -4
// my_rettype override_ret ebp - 28 - sizeof(my_rettype)*2 -4
// my_rettype plugin_ret ebp - 28 - sizeof(my_rettype)*3 -4
// == + 3 * sizeof(my_rettype) bytes
// if required:
// my_rettype place_for_memret ebp - 28 - sizeof(my_rettype)*4 -4
// gcc only: if required:
// place forced byref params ebp - 28 - sizeof(my_rettype)*{4 or 5}
//
// varargs:
// va_list argptr
// char va_buf[something];
const jit_int8_t v_vfnptr_origentry = AddVarToFrame(SIZE_PTR);
const jit_int8_t v_status = AddVarToFrame(sizeof(META_RES));
const jit_int8_t v_prev_res = AddVarToFrame(sizeof(META_RES));
const jit_int8_t v_cur_res = AddVarToFrame(sizeof(META_RES));
const jit_int8_t v_iter = AddVarToFrame(SIZE_PTR);
const jit_int8_t v_pContext = AddVarToFrame(SIZE_PTR);
// Memory return: first param is the address
jit_int32_t v_memret_addr = 0;
if (m_Proto.GetRet().flags & PassInfo::PassFlag_RetMem)
{
if (SH_COMP == SH_COMP_GCC)
{
// gcc: now: first param = mem ret addr
// second param = this pointer
// third param = actual first param
v_memret_addr = 12;
v_this += 4;
param_base_offs += SIZE_PTR;
}
else // MSVC
{
if (m_Proto.GetConvention() & ProtoInfo::CallConv_HasVarArgs)
{
// varargs -> cdecl
// msvc: now:
// first param = this pointer
// second param = mem ret addr
// third param = actual first param
// params_base_offs is already updated to point to after the this pointer
v_memret_addr = param_base_offs;
param_base_offs += SIZE_PTR;
}
else
{
v_memret_addr = param_base_offs;
param_base_offs += SIZE_PTR;
}
}
}
jit_int32_t v_ret_ptr = 0;
jit_int32_t v_orig_ret = 0;
jit_int32_t v_override_ret = 0;
jit_int32_t v_plugin_ret = 0;
if (m_Proto.GetRet().size != 0)
{
v_ret_ptr = AddVarToFrame(SIZE_PTR);
v_orig_ret = AddVarToFrame(GetParamStackSize(m_Proto.GetRet()));
v_override_ret = AddVarToFrame(GetParamStackSize(m_Proto.GetRet()));
v_plugin_ret = AddVarToFrame(GetParamStackSize(m_Proto.GetRet()));
}
jit_int32_t v_place_for_memret = 0;
if (MemRetWithTempObj())
{
v_place_for_memret = AddVarToFrame(GetParamStackSize(m_Proto.GetRet()));
}
jit_int32_t v_place_fbrr_base = 0;
if (SH_COMP == SH_COMP_GCC && GetForcedByRefParamsSize())
{
v_place_fbrr_base = AddVarToFrame(GetForcedByRefParamsSize());
}
// Only exists for varargs functions
jit_int32_t v_va_argptr = 0;
if (m_Proto.GetConvention() & ProtoInfo::CallConv_HasVarArgs)
{
v_va_argptr = AddVarToFrame(SIZE_PTR);
}
jit_int32_t v_va_buf = 0;
if (m_Proto.GetConvention() & ProtoInfo::CallConv_HasVafmt)
{
v_va_buf = AddVarToFrame(SourceHook::STRBUF_LEN);
}
IA32_Sub_Rm_Imm32(&m_HookFunc, REG_ESP, ComputeVarsSize(), MOD_REG);
// Initial stack alignment
IA32_And_Rm_Imm32(&m_HookFunc, REG_ESP, MOD_REG, -16);
m_BytesPushedAfterInitialAlignment = 0;
// init status localvar
IA32_Mov_Rm_Imm32_Disp8(&m_HookFunc, REG_EBP, MRES_IGNORED, v_status);
// VarArgs: init argptr & format
if (m_Proto.GetConvention() & ProtoInfo::CallConv_HasVarArgs)
{
// argptr = first vararg param
// lea eax, [ebp + param_base_offs + paramssize]
// mov argptr, eax
IA32_Lea_DispRegImmAuto(&m_HookFunc, REG_EAX, REG_EBP, param_base_offs + GetParamsTotalStackSize() + SIZE_PTR); // +SIZE_PTR: last const char * is not in protoinfo
IA32_Mov_Rm_Reg_DispAuto(&m_HookFunc, REG_EBP, REG_EAX, v_va_argptr);
}
if (m_Proto.GetConvention() & ProtoInfo::CallConv_HasVafmt)
{
// vsnprintf
jit_int32_t tmpAlign = AlignStackBeforeCall(SIZE_PTR*3 + sizeof(size_t), 0);
// push valist, fmt param, maxsize, buffer
IA32_Push_Reg(&m_HookFunc, REG_EAX);
IA32_Push_Rm_DispAuto(&m_HookFunc, REG_EBP, param_base_offs + GetParamsTotalStackSize()); // last given param (+4-4, see above)
IA32_Push_Imm32(&m_HookFunc, SourceHook::STRBUF_LEN - 1);
IA32_Lea_DispRegImmAuto(&m_HookFunc, REG_ECX, REG_EBP, v_va_buf);
IA32_Push_Reg(&m_HookFunc, REG_ECX);
// call
IA32_Mov_Reg_Imm32(&m_HookFunc, REG_EAX, DownCastPtr(&vsnprintf));
CheckAlignmentBeforeCall();
IA32_Call_Reg(&m_HookFunc, REG_EAX);
// Clean up (cdecl)
IA32_Add_Rm_Imm32(&m_HookFunc, REG_ESP, 0x10, MOD_REG);
AlignStackAfterCall(tmpAlign);
// Set trailing zero
IA32_Xor_Reg_Rm(&m_HookFunc, REG_EDX, REG_EDX, MOD_REG);
IA32_Mov_Rm8_Reg8_DispAuto(&m_HookFunc, REG_EBP, REG_EDX, v_va_buf + SourceHook::STRBUF_LEN - 1);
}
// Call constructors for ret vars if required
if((m_Proto.GetRet().flags & PassInfo::PassFlag_ByVal) &&
m_Proto.GetRet().pNormalCtor)
{
jit_int32_t tmpAlign = AlignStackBeforeCall(0, AlignStack_GCC_ThisOnStack);
// orig_reg
IA32_Lea_DispRegImmAuto(&m_HookFunc, REG_ECX, REG_EBP, v_orig_ret);
GCC_ONLY(IA32_Push_Reg(&m_HookFunc, REG_ECX));
IA32_Mov_Reg_Imm32(&m_HookFunc, REG_EAX, DownCastPtr(m_Proto.GetRet().pNormalCtor));
CheckAlignmentBeforeCall();
IA32_Call_Reg(&m_HookFunc, REG_EAX);
GCC_ONLY(IA32_Pop_Reg(&m_HookFunc, REG_ECX));
// override_reg
IA32_Lea_DispRegImmAuto(&m_HookFunc, REG_ECX, REG_EBP, v_override_ret);
GCC_ONLY(IA32_Push_Reg(&m_HookFunc, REG_ECX));
IA32_Mov_Reg_Imm32(&m_HookFunc, REG_EAX, DownCastPtr(m_Proto.GetRet().pNormalCtor));
CheckAlignmentBeforeCall();
IA32_Call_Reg(&m_HookFunc, REG_EAX);
GCC_ONLY(IA32_Pop_Reg(&m_HookFunc, REG_ECX));
// plugin_ret
IA32_Lea_DispRegImmAuto(&m_HookFunc, REG_ECX, REG_EBP, v_plugin_ret);
GCC_ONLY(IA32_Push_Reg(&m_HookFunc, REG_ECX));
IA32_Mov_Reg_Imm32(&m_HookFunc, REG_EAX, DownCastPtr(m_Proto.GetRet().pNormalCtor));
CheckAlignmentBeforeCall();
IA32_Call_Reg(&m_HookFunc, REG_EAX);
GCC_ONLY(IA32_Pop_Reg(&m_HookFunc, REG_ECX));
AlignStackAfterCall(tmpAlign);
// _don't_ call a constructor for v_place_for_memret !
}
// ********************** SetupHookLoop **********************
CallSetupHookLoop(v_orig_ret, v_override_ret, v_cur_res, v_prev_res, v_status, v_vfnptr_origentry,
v_this, v_pContext);
// ********************** call pre hooks **********************
GenerateCallHooks(v_status, v_prev_res, v_cur_res, v_iter, v_pContext, param_base_offs,
v_plugin_ret, v_place_for_memret, v_place_fbrr_base, v_va_buf);
// ********************** call orig func **********************
GenerateCallOrig(v_status, v_pContext, param_base_offs, v_this, v_vfnptr_origentry, v_orig_ret,
v_override_ret, v_place_for_memret, v_place_fbrr_base, v_va_buf);
// ********************** call post hooks **********************
GenerateCallHooks(v_status, v_prev_res, v_cur_res, v_iter, v_pContext, param_base_offs,
v_plugin_ret, v_place_for_memret, v_place_fbrr_base, v_va_buf);
// ********************** end context and return **********************
PrepareReturn(v_status, v_pContext, v_ret_ptr);
CallEndContext(v_pContext);
// Call destructors of byval object params which have a destructor
jit_int32_t tmpAlign = AlignStackBeforeCall(0, AlignStack_GCC_ThisOnStack);
jit_int32_t cur_param_pos = param_base_offs;
for (int i = 0; i < m_Proto.GetNumOfParams(); ++i)
{
const IntPassInfo &pi = m_Proto.GetParam(i);
// GCC: NOT of forced byref params. the caller destructs those.
if (pi.type == PassInfo::PassType_Object && (pi.flags & PassInfo::PassFlag_ODtor) &&
(pi.flags & PassInfo::PassFlag_ByVal) && !(pi.flags & PassFlag_ForcedByRef))
{
IA32_Lea_DispRegImmAuto(&m_HookFunc, REG_ECX, REG_EBP, cur_param_pos);
IA32_Mov_Reg_Imm32(&m_HookFunc, REG_EAX, DownCastPtr(pi.pDtor));
GCC_ONLY(IA32_Push_Reg(&m_HookFunc, REG_ECX));
CheckAlignmentBeforeCall();
IA32_Call_Reg(&m_HookFunc, REG_EAX);
GCC_ONLY(IA32_Pop_Reg(&m_HookFunc, REG_ECX));
}
cur_param_pos += GetParamStackSize(pi);
}
AlignStackAfterCall(tmpAlign);
DoReturn(v_ret_ptr, v_memret_addr);
// Call destructors of orig_ret/ ...
if((m_Proto.GetRet().flags & PassInfo::PassFlag_ByVal) &&
m_Proto.GetRet().pDtor)
{
// Preserve return value in EAX(:EDX)
IA32_Push_Reg(&m_HookFunc, REG_EAX);
IA32_Push_Reg(&m_HookFunc, REG_EDX);
m_BytesPushedAfterInitialAlignment += 8;
jit_int32_t tmpAlign = AlignStackBeforeCall(0, AlignStack_GCC_ThisOnStack);
IA32_Lea_DispRegImmAuto(&m_HookFunc, REG_ECX, REG_EBP, v_plugin_ret);
GCC_ONLY(IA32_Push_Reg(&m_HookFunc, REG_ECX));
IA32_Mov_Reg_Imm32(&m_HookFunc, REG_EAX, DownCastPtr(m_Proto.GetRet().pDtor));
CheckAlignmentBeforeCall();
IA32_Call_Reg(&m_HookFunc, REG_EAX);
GCC_ONLY(IA32_Pop_Reg(&m_HookFunc, REG_ECX));
IA32_Lea_DispRegImmAuto(&m_HookFunc, REG_ECX, REG_EBP, v_override_ret);
GCC_ONLY(IA32_Push_Reg(&m_HookFunc, REG_ECX));
IA32_Mov_Reg_Imm32(&m_HookFunc, REG_EAX, DownCastPtr(m_Proto.GetRet().pDtor));
CheckAlignmentBeforeCall();
IA32_Call_Reg(&m_HookFunc, REG_EAX);
GCC_ONLY(IA32_Pop_Reg(&m_HookFunc, REG_ECX));
IA32_Lea_DispRegImmAuto(&m_HookFunc, REG_ECX, REG_EBP, v_orig_ret);
GCC_ONLY(IA32_Push_Reg(&m_HookFunc, REG_ECX));
IA32_Mov_Reg_Imm32(&m_HookFunc, REG_EAX, DownCastPtr(m_Proto.GetRet().pDtor));
CheckAlignmentBeforeCall();
IA32_Call_Reg(&m_HookFunc, REG_EAX);
GCC_ONLY(IA32_Pop_Reg(&m_HookFunc, REG_ECX));
AlignStackAfterCall(tmpAlign);
IA32_Pop_Reg(&m_HookFunc, REG_EDX);
IA32_Pop_Reg(&m_HookFunc, REG_EAX);
m_BytesPushedAfterInitialAlignment -= 8;
}
// epilogue
IA32_Mov_Reg_Rm(&m_HookFunc, REG_ESP, REG_EBP, MOD_REG);
IA32_Pop_Reg(&m_HookFunc, REG_EBX);
IA32_Pop_Reg(&m_HookFunc, REG_EBP);
if (SH_COMP == SH_COMP_MSVC && !(m_Proto.GetConvention() & ProtoInfo::CallConv_HasVarArgs))
{
// msvc without varargs:
// callee cleans the stack
short cleansize = GetParamsTotalStackSize();
// Memory return: address is first param
if (m_Proto.GetRet().flags & PassInfo::PassFlag_RetMem)
cleansize += SIZE_PTR;
IA32_Return_Popstack(&m_HookFunc, cleansize);
}
else
{
// gcc or msvc with varargs: caller cleans the stack
// exception: gcc removes the memret addr on memret:
if (SH_COMP == SH_COMP_GCC && (m_Proto.GetRet().flags & PassInfo::PassFlag_RetMem))
IA32_Return_Popstack(&m_HookFunc, SIZE_PTR);
else
IA32_Return(&m_HookFunc);
}
// Store pointer for later use
// m_HookfuncVfnPtr is a pointer to a void* because SH expects a pointer
// into the hookman's vtable
*m_HookfuncVfnptr = reinterpret_cast<void*>(m_HookFunc.GetData());
m_HookFunc.SetRE();
return m_HookFunc.GetData();
}
// Pre-condition: GenerateHookFunc() has been called!
void * GenContext::GeneratePubFunc()
{
jitoffs_t counter, tmppos;
// The pubfunc is a static cdecl function.
// C Code:
// int HookManPubFunc(
// bool store, ebp + 8
// IHookManagerInfo *hi ebp + 12
// )
// {
// if (store)
// *m_pHI = hi;
// if (hi)
// hi->SetInfo(HOOKMAN_VERSION, m_VtblOffs, m_VtblIdx, m_Proto.GetProto(), m_HookfuncVfnptr)
// }
// prologue
IA32_Push_Reg(&m_PubFunc, REG_EBP);
IA32_Mov_Reg_Rm(&m_PubFunc, REG_EBP, REG_ESP, MOD_REG);
// save store in eax, hi in ecx
IA32_Movzx_Reg32_Rm8_Disp8(&m_PubFunc, REG_EAX, REG_EBP, 8);
IA32_Mov_Reg_Rm_DispAuto(&m_PubFunc, REG_ECX, REG_EBP, 12);
// Check for store == 0
IA32_Test_Rm_Reg8(&m_PubFunc, REG_EAX, REG_EAX, MOD_REG);
tmppos = IA32_Jump_Cond_Imm8(&m_PubFunc, CC_Z, 0);
m_PubFunc.start_count(counter);
// nonzero -> store hi
IA32_Mov_Rm_Imm32(&m_PubFunc, REG_EDX, DownCastPtr(m_pHI), MOD_REG);
IA32_Mov_Rm_Reg(&m_PubFunc, REG_EDX, REG_ECX, MOD_MEM_REG);
// zero
m_PubFunc.end_count(counter);
m_PubFunc.rewrite(tmppos, static_cast<jit_uint8_t>(counter));
// check for hi == 0
IA32_Test_Rm_Reg(&m_PubFunc, REG_ECX, REG_ECX, MOD_REG);
tmppos = IA32_Jump_Cond_Imm8(&m_PubFunc, CC_Z, 0);
m_PubFunc.start_count(counter);
// nonzero -> call vfunc
// push params in reverse order
IA32_Push_Imm32(&m_PubFunc, DownCastPtr(m_HookfuncVfnptr));
IA32_Push_Imm32(&m_PubFunc, DownCastPtr(m_BuiltPI));
IA32_Push_Imm32(&m_PubFunc, m_VtblIdx);
IA32_Push_Imm32(&m_PubFunc, m_VtblOffs);
IA32_Push_Imm32(&m_PubFunc, SH_HOOKMAN_VERSION);
// hi == this is in ecx
// on gcc/mingw, ecx is the first parameter
#if SH_COMP == SH_COMP_GCC
IA32_Push_Reg(&m_PubFunc, REG_ECX);
#endif
// call the function. vtbloffs = 0, vtblidx = 0
// get vtptr into edx
IA32_Mov_Reg_Rm(&m_PubFunc, REG_EDX, REG_ECX, MOD_MEM_REG);
// get funcptr into eax
IA32_Mov_Reg_Rm(&m_PubFunc, REG_EAX, REG_EDX, MOD_MEM_REG);
IA32_Call_Reg(&m_PubFunc, REG_EAX);
// on gcc/mingw, we have to clean up after the call
#if SH_COMP == SH_COMP_GCC
// 5 params + hidden thisptr param
IA32_Add_Rm_Imm8(&m_PubFunc, REG_ESP, 6*SIZE_MWORD, MOD_REG);
#endif
// zero
m_PubFunc.end_count(counter);
m_PubFunc.rewrite(tmppos, static_cast<jit_uint8_t>(counter));
// return value
IA32_Xor_Reg_Rm(&m_PubFunc, REG_EAX, REG_EAX, MOD_REG);
// epilogue
IA32_Mov_Reg_Rm(&m_PubFunc, REG_ESP, REG_EBP, MOD_REG);
IA32_Pop_Reg(&m_PubFunc, REG_EBP);
IA32_Return(&m_PubFunc);
m_PubFunc.SetRE();
return m_PubFunc;
}
bool GenContext::PassInfoSupported(const IntPassInfo &pi, bool is_ret)
{
// :TODO: Error returns
if (pi.type != PassInfo::PassType_Basic &&
pi.type != PassInfo::PassType_Float &&
pi.type != PassInfo::PassType_Object)
{
return false;
}
if (pi.type == PassInfo::PassType_Object &&
(pi.flags & PassInfo::PassFlag_ByVal))
{
if ((pi.flags & PassInfo::PassFlag_CCtor) && !pi.pCopyCtor)
{
return false;
}
if ((pi.flags & PassInfo::PassFlag_ODtor) && !pi.pDtor)
{
return false;
}
if ((pi.flags & PassInfo::PassFlag_AssignOp) && !pi.pAssignOperator)
{
return false;
}
if ((pi.flags & PassInfo::PassFlag_OCtor) && !pi.pNormalCtor)
{
return false;
}
}
if ((pi.flags & (PassInfo::PassFlag_ByVal | PassInfo::PassFlag_ByRef)) == 0)
{
return false; // Neither byval nor byref!
}
return true;
}
void GenContext::AutoDetectRetType()
{
IntPassInfo &pi = m_Proto.GetRet();
// Only relevant for byval types
if (pi.flags & PassInfo::PassFlag_ByVal)
{
// Basic + float:
if (pi.type == PassInfo::PassType_Basic ||
pi.type == PassInfo::PassType_Float)
{
// <= 8 bytes:
// _always_ in registers, no matter what the user says
if (pi.size <= 8)
{
pi.flags &= ~PassInfo::PassFlag_RetMem;
pi.flags |= PassInfo::PassFlag_RetReg;
}
else
{
// Does this even exist? No idea, if it does: in memory!
pi.flags &= ~PassInfo::PassFlag_RetReg;
pi.flags |= PassInfo::PassFlag_RetMem;
}
}
// Object:
else if (pi.type == PassInfo::PassType_Object)
{
// If the user says nothing, auto-detect
if ((pi.flags & (PassInfo::PassFlag_RetMem | PassInfo::PassFlag_RetReg)) == 0)
{
#if SH_COMP == SH_COMP_MSVC
// MSVC seems to return _all_ structs, classes, unions in memory
pi.flags |= PassInfo::PassFlag_RetMem;
#elif SH_COMP == SH_COMP_GCC
#if SH_SYS == SH_SYS_APPLE
// Apple GCC returns in memory if size isn't a power of 2 or > 8
if ((pi.size & (pi.size - 1)) == 0 && pi.size <= 8)
{
pi.flags |= PassInfo::PassFlag_RetReg;
}
else
#endif
{
// GCC on Linux does same thing as MSVC
pi.flags |= PassInfo::PassFlag_RetMem;
}
#endif
}
}
}
else
{
// byref: make sure that the flag is _not_ set
pi.flags &= ~PassInfo::PassFlag_RetMem;
pi.flags |= PassInfo::PassFlag_RetReg;
}
}
void GenContext::AutoDetectParamFlags()
{
#if SH_COMP == SH_COMP_GCC
// On GCC, all objects are passed by reference if they have a destructor
for (int i = 0; i < m_Proto.GetNumOfParams(); ++i)
{
IntPassInfo &pi = m_Proto.GetParam(i);
if (pi.type == PassInfo::PassType_Object && (pi.flags & PassInfo::PassFlag_ByVal) &&
(pi.flags & PassInfo::PassFlag_ODtor))
{
pi.flags |= PassFlag_ForcedByRef;
}
}
#endif
}
HookManagerPubFunc GenContext::Generate()
{
Clear();
// Check conditions:
// -1) good proto version
// 0) we don't support unknown passtypes, convention, ...
// 1) we don't support functions which return objects by value or take parameters by value
// that have a constructor, a destructor or an overloaded assignment op
// (we wouldn't know how to call it!)
if (m_Proto.GetVersion() < 1)
{
return NULL;
}
AutoDetectRetType();
AutoDetectParamFlags();
// Basically, we only support ThisCall/thiscall with varargs
if ((m_Proto.GetConvention() & (~ProtoInfo::CallConv_HasVafmt)) != ProtoInfo::CallConv_ThisCall)
{
return NULL;
}
if (m_Proto.GetRet().size != 0 && !PassInfoSupported(m_Proto.GetRet(), true))
{
return NULL;
}
for (int i = 0; i < m_Proto.GetNumOfParams(); ++i)
{
if (!PassInfoSupported(m_Proto.GetParam(i), false))
return NULL;
}
BuildProtoInfo();
GenerateHookFunc();
return fastdelegate::detail::horrible_cast<HookManagerPubFunc>(GeneratePubFunc());
}
HookManagerPubFunc GenContext::GetPubFunc()
{
if (m_GeneratedPubFunc == 0)
m_GeneratedPubFunc = Generate();
return m_GeneratedPubFunc;
}
bool GenContext::Equal(const CProto &proto, int vtbl_offs, int vtbl_idx)
{
return (m_OrigProto.ExactlyEqual(proto) && m_VtblOffs == vtbl_offs && m_VtblIdx == vtbl_idx);
}
bool GenContext::Equal(HookManagerPubFunc other)
{
return m_GeneratedPubFunc == other;
}
// *********************************** class GenContextContainer
CHookManagerAutoGen::CHookManagerAutoGen(ISourceHook *pSHPtr) : m_pSHPtr(pSHPtr)
{
}
CHookManagerAutoGen::~CHookManagerAutoGen()
{
for (List<StoredContext>::iterator iter = m_Contexts.begin(); iter != m_Contexts.end(); ++iter)
{
delete iter->m_GenContext;
}
}
int CHookManagerAutoGen::GetIfaceVersion()
{
return SH_HOOKMANAUTOGEN_IFACE_VERSION;
}
int CHookManagerAutoGen::GetImplVersion()
{
return SH_HOOKMANAUTOGEN_IMPL_VERSION;
}
HookManagerPubFunc CHookManagerAutoGen::MakeHookMan(const ProtoInfo *proto, int vtbl_offs, int vtbl_idx)
{
CProto mproto(proto);
for (List<StoredContext>::iterator iter = m_Contexts.begin(); iter != m_Contexts.end(); ++iter)
{
if (iter->m_GenContext->Equal(mproto, vtbl_offs, vtbl_idx))
{
iter->m_RefCnt++;
return iter->m_GenContext->GetPubFunc();
}
}
// Not found yet -> new one
StoredContext sctx;
sctx.m_RefCnt = 1;
sctx.m_GenContext = new GenContext(proto, vtbl_offs, vtbl_idx, m_pSHPtr);
if (sctx.m_GenContext->GetPubFunc() == NULL)
{
return NULL;
}
else
{
m_Contexts.push_back(sctx);
return sctx.m_GenContext->GetPubFunc();
}
}
void CHookManagerAutoGen::ReleaseHookMan(HookManagerPubFunc pubFunc)
{
for (List<StoredContext>::iterator iter = m_Contexts.begin(); iter != m_Contexts.end(); ++iter)
{
if (iter->m_GenContext->Equal(pubFunc))
{
iter->m_RefCnt--;
if (iter->m_RefCnt == 0)
{
delete iter->m_GenContext;
m_Contexts.erase(iter);
}
break;
}
}
}
}
}