diff --git a/src/core/modules/memory/memory_function.cpp b/src/core/modules/memory/memory_function.cpp index 1eb5c6d5c..cc2e5571f 100755 --- a/src/core/modules/memory/memory_function.cpp +++ b/src/core/modules/memory/memory_function.cpp @@ -35,6 +35,7 @@ #include "memory_function.h" #include "memory_utilities.h" #include "memory_hooks.h" +#include "memory_wrap.h" // DynamicHooks #include "conventions/x86MsCdecl.h" @@ -80,7 +81,7 @@ int GetDynCallConvention(Convention_t eConv) case CONV_FASTCALL: return DC_CALL_C_X86_WIN32_FAST_MS; #endif } - + BOOST_RAISE_EXCEPTION(PyExc_ValueError, "Unsupported calling convention.") return -1; } @@ -89,7 +90,7 @@ int GetDynCallConvention(Convention_t eConv) // ============================================================================ // >> MakeDynamicHooksConvention // ============================================================================ -ICallingConvention* MakeDynamicHooksConvention(Convention_t eConv, std::vector vecArgTypes, DataType_t returnType, int iAlignment=4) +ICallingConvention* MakeDynamicHooksConvention(Convention_t eConv, std::vector vecArgTypes, DataType_t returnType, int iAlignment) { #ifdef _WIN32 switch (eConv) @@ -145,27 +146,19 @@ CFunction::CFunction(unsigned long ulAddr, object oCallingConvention, object oAr // If this line succeeds the user wants to create a function with the built-in calling conventions m_eCallingConvention = extract(oCallingConvention); m_pCallingConvention = MakeDynamicHooksConvention(m_eCallingConvention, ObjectToDataTypeVector(m_tArgs), m_eReturnType); - - // We allocated the calling convention, we are responsible to cleanup. - m_bAllocatedCallingConvention = true; + m_oCallingConvention = object(); } catch( ... ) { PyErr_Clear(); - + // A custom calling convention will be used... m_eCallingConvention = CONV_CUSTOM; - object _oCallingConvention = oCallingConvention(m_tArgs, m_eReturnType); - - // FIXME: - // This is required to fix a crash, but it will also cause a memory leak, - // because no calling convention object that is created via this method will ever be deleted. - // TODO: Pretty sure this was required due to the missing held type definition. It was added, but wasn't tested yet. - Py_INCREF(_oCallingConvention.ptr()); - m_pCallingConvention = extract(_oCallingConvention); + m_oCallingConvention = oCallingConvention(m_tArgs, m_eReturnType); + m_pCallingConvention = extract(m_oCallingConvention); - // We didn't allocate the calling convention, someone else is responsible for it. - m_bAllocatedCallingConvention = false; + // Reserve a Python reference for DynamicHooks. + Py_INCREF(m_oCallingConvention.ptr()); } // Step 4: Get the DynCall calling convention @@ -179,9 +172,7 @@ CFunction::CFunction(unsigned long ulAddr, Convention_t eCallingConvention, m_eCallingConvention = eCallingConvention; m_iCallingConvention = iCallingConvention; m_pCallingConvention = NULL; - - // We didn't allocate the calling convention, someone else is responsible for it. - m_bAllocatedCallingConvention = false; + m_oCallingConvention = object(); m_tArgs = tArgs; m_eReturnType = eReturnType; @@ -190,18 +181,24 @@ CFunction::CFunction(unsigned long ulAddr, Convention_t eCallingConvention, CFunction::~CFunction() { - // If we didn't allocate the calling convention, then it is not our responsibility. - if (!m_bAllocatedCallingConvention) + if (!m_pCallingConvention) return; - CHook* pHook = GetHookManager()->FindHook((void *) m_ulAddr); - - // DynamicHooks will take care of it for us from there. - if (pHook && pHook->m_pCallingConvention == m_pCallingConvention) - return; + // If the convention isn't flagged as hooked, then we need to take care of it. + if (!m_pCallingConvention->m_bHooked) + { + // If we don't have a Python instance, then we can safely delete it. + if (m_oCallingConvention.is_none()) + delete m_pCallingConvention; + // Otherwise, just release our reference and let Python take care of it. + else if (Py_REFCNT(m_oCallingConvention.ptr()) > 1) + Py_DECREF(m_oCallingConvention.ptr()); + } + // If we are using a built-in convention that is currently hooked, let's flag it as no longer hooked + // so that we know we are not bound to a CFunction anymore and can be deleted. + else if (m_eCallingConvention != CONV_CUSTOM && !dynamic_cast(m_pCallingConvention)) + m_pCallingConvention->m_bHooked = false; - // Cleanup. - delete m_pCallingConvention; m_pCallingConvention = NULL; } @@ -287,7 +284,7 @@ object CFunction::Call(tuple args, dict kw) dcArgPointer(g_pCallVM, ulAddr); break; - } + } case DATA_TYPE_STRING: dcArgPointer(g_pCallVM, (unsigned long) (void *) extract(arg)); break; default: BOOST_RAISE_EXCEPTION(PyExc_ValueError, "Unknown argument type.") } @@ -357,10 +354,10 @@ void CFunction::AddHook(HookType_t eType, PyObject* pCallable) { if (!IsHookable()) BOOST_RAISE_EXCEPTION(PyExc_ValueError, "Function is not hookable.") - + Validate(); CHook* pHook = GetHookManager()->FindHook((void *) m_ulAddr); - + // Prepare arguments for log message str type = str(eType); const char* szType = extract(type); @@ -391,16 +388,31 @@ void CFunction::AddHook(HookType_t eType, PyObject* pCallable) if (!pHook) { pHook = HookFunctionHelper((void *) m_ulAddr, m_pCallingConvention); - - // DynamicHooks will handle our convention from there, regardless if we allocated it or not. - m_bAllocatedCallingConvention = false; } - + // Add the hook handler. If it's already added, it won't be added twice pHook->AddCallback(eType, (HookHandlerFn *) (void *) &SP_HookHandler); g_mapCallbacks[pHook][eType].push_back(object(handle<>(borrowed(pCallable)))); } +bool CFunction::AddHook(HookType_t eType, HookHandlerFn* pFunc) +{ + if (!IsHookable()) + return false; + + CHook* pHook = GetHookManager()->FindHook((void*) m_ulAddr); + + if (!pHook) { + pHook = GetHookManager()->HookFunction((void*) m_ulAddr, m_pCallingConvention); + + if (!pHook) + return false; + } + + pHook->AddCallback(eType, pFunc); + return true; +} + void CFunction::RemoveHook(HookType_t eType, PyObject* pCallable) { Validate(); @@ -418,6 +430,26 @@ void CFunction::DeleteHook() return; g_mapCallbacks.erase(pHook); + + ICallingConventionWrapper *pConv = dynamic_cast(pHook->m_pCallingConvention); + if (pConv) + { + if (pConv->m_bHooked) + { + // Flag the convention as no longer hooked and being taken care of by DynamicHooks. + pHook->m_pCallingConvention->m_bHooked = false; + + // Release the Python reference we reserved for DynamicHooks. + PyObject *pOwner = detail::wrapper_base_::owner(pConv); + if (pOwner && Py_REFCNT(pOwner)) + Py_DECREF(pOwner); + } + } + // If we are a built-in convention bound to a CHook instance but not flagged as hooked anymore, then that + // means we are no longer bound to a CFunction instance and can be safely deleted. + else if (!pHook->m_pCallingConvention->m_bHooked) + delete pHook->m_pCallingConvention; + // Set the calling convention to NULL, because DynamicHooks will delete it otherwise. pHook->m_pCallingConvention = NULL; GetHookManager()->UnhookFunction((void *) m_ulAddr); diff --git a/src/core/modules/memory/memory_function.h b/src/core/modules/memory/memory_function.h old mode 100644 new mode 100755 index 011d27c7a..b680dca26 --- a/src/core/modules/memory/memory_function.h +++ b/src/core/modules/memory/memory_function.h @@ -90,6 +90,8 @@ class CFunction: public CPointer, private boost::noncopyable void DeleteHook(); + bool AddHook(HookType_t eType, HookHandlerFn* pFunc); + public: boost::python::tuple m_tArgs; object m_oConverter; @@ -104,7 +106,15 @@ class CFunction: public CPointer, private boost::noncopyable // DynamicHooks calling convention (built-in and custom) ICallingConvention* m_pCallingConvention; bool m_bAllocatedCallingConvention; + + // Custom calling convention + object m_oCallingConvention; }; +//--------------------------------------------------------------------------------- +// Functions +//--------------------------------------------------------------------------------- +ICallingConvention* MakeDynamicHooksConvention(Convention_t eConv, std::vector vecArgTypes, DataType_t returnType, int iAlignment=4); + #endif // _MEMORY_FUNCTION_H \ No newline at end of file diff --git a/src/core/modules/memory/memory_wrap.cpp b/src/core/modules/memory/memory_wrap.cpp old mode 100644 new mode 100755 index 1dcfee462..593e27f94 --- a/src/core/modules/memory/memory_wrap.cpp +++ b/src/core/modules/memory/memory_wrap.cpp @@ -487,7 +487,7 @@ void export_function(scope _memory) ) .def("add_hook", - &CFunction::AddHook, + GET_METHOD(void, CFunction, AddHook, HookType_t eType, PyObject*), "Adds a hook callback.", args("hook_type", "callback") ) @@ -540,6 +540,10 @@ void export_function(scope _memory) &CFunction::m_eCallingConvention ) + .def_readonly("custom_convention", + &CFunction::m_oCallingConvention + ) + // Properties .add_property("trampoline", make_function(&CFunction::GetTrampoline, manage_new_object_policy()), @@ -827,19 +831,33 @@ void export_registers(scope _memory) // ============================================================================ void export_calling_convention(scope _memory) { - class_( + class_, boost::noncopyable>( "CallingConvention", "An an abstract class that is used to create custom calling " "conventions (only available for hooking function and not for" " calling functions).\n", - init< object, DataType_t, optional >( - (arg("arg_types"), arg("return_type"), arg("alignment")), + no_init) + + .def("__init__", + make_constructor( + &ICallingConventionWrapper::__init__, + post_constructor_policies< + initialize_wrapper_policies > + >( + make_function( + &ICallingConventionWrapper::Initialize, + default_call_policies(), + args("self", "arg_types", "return_type", "alignment", "default_convention") + ) + ), + ("arg_types", "return_type", arg("alignment")=4, arg("default_convention")=CONV_CUSTOM) + ), "Initialize the calling convention.\n" "\n" ":param iterable arg_types: A list of :class:`DataType` values that define the argument types of a function.\n" ":param DataType return_type: The return type of a function.\n" - ":param int alignment: The stack alignment." - ) + ":param int alignment: The stack alignment.\n" + ":param Convention_t default_convention: The default convention for un override function." ) .def("get_registers", @@ -851,14 +869,15 @@ void export_calling_convention(scope _memory) &ICallingConventionWrapper::GetPopSize, "Return the number of bytes that should be added to the stack to clean up." ) - + .def("get_argument_ptr", - &ICallingConventionWrapper::GetArgumentPtrWrapper, + &ICallingConventionWrapper::GetArgumentPtr, (arg("index"), arg("registers")), "Return a pointer to the argument at the given index.\n" "\n" ":param int index: The index of the argument.\n" - ":param Registers registers: A snapshot of all saved registers." + ":param Registers registers: A snapshot of all saved registers.", + return_by_value_policy() ) .def("argument_ptr_changed", @@ -872,11 +891,12 @@ void export_calling_convention(scope _memory) ) .def("get_return_ptr", - &ICallingConventionWrapper::GetReturnPtrWrapper, + &ICallingConventionWrapper::GetReturnPtr, (arg("registers")), "Return a pointer to the return value.\n" "\n" - ":param Registers registers: A snapshot of all saved registers." + ":param Registers registers: A snapshot of all saved registers.", + return_by_value_policy() ) .def("return_ptr_changed", diff --git a/src/core/modules/memory/memory_wrap.h b/src/core/modules/memory/memory_wrap.h index 0ce6e9bc4..ee1ac713a 100755 --- a/src/core/modules/memory/memory_wrap.h +++ b/src/core/modules/memory/memory_wrap.h @@ -33,6 +33,9 @@ // DynamicHooks #include "convention.h" +// Memory +#include "memory_function.h" + // Utilities #include "memory_utilities.h" #include "memory_rtti.h" @@ -48,14 +51,77 @@ using namespace boost::python; class ICallingConventionWrapper: public ICallingConvention, public wrapper { public: - ICallingConventionWrapper(object oArgTypes, DataType_t returnType, int iAlignment=4) + ICallingConventionWrapper(object oArgTypes, DataType_t returnType, int iAlignment=4, Convention_t eDefaultConv=CONV_CUSTOM) :ICallingConvention(ObjectToDataTypeVector(oArgTypes), returnType, iAlignment) { + if (eDefaultConv != CONV_CUSTOM) { + m_pDefaultCallingConvention = MakeDynamicHooksConvention(eDefaultConv, m_vecArgTypes, m_returnType, m_iAlignment); + } + else { + m_pDefaultCallingConvention = nullptr; + } + } + + virtual ~ICallingConventionWrapper() + { + // If we are still flagged as hooked, then that means DynamicHooks is done with us. + if (m_bHooked) + { + // Release the Python reference we reserved for DynamicHooks. + PyObject *pOwner = detail::wrapper_base_::owner(this); + if (pOwner && Py_REFCNT(pOwner)) + Py_DECREF(pOwner); + } + + delete m_pDefaultCallingConvention; + m_pDefaultCallingConvention = nullptr; + } + + static void Deleter(ICallingConventionWrapper *pThis) + { + // If we are still hooked, DynamicHooks will take care of us. + if (pThis->m_bHooked) + return; + + // We are not hooked, nor referenced anymore so we can be deleted. + delete pThis; + } + + static boost::shared_ptr __init__( + object oArgTypes, DataType_t returnType, int iAlignment=4, Convention_t eDefaultConv=CONV_CUSTOM) + { + return boost::shared_ptr( + new ICallingConventionWrapper(oArgTypes, returnType, iAlignment, eDefaultConv), &Deleter + ); + } + + void Initialize(object self, object oArgTypes, DataType_t returnType, int iAlignment, Convention_t eDefaultConv) + { + // If we didn't receive a default convention on construction, try to resolve one from the Python instance. + if (!m_pDefaultCallingConvention) + { + try + { + m_pDefaultCallingConvention = MakeDynamicHooksConvention( + extract(self.attr("default_convention")), m_vecArgTypes, m_returnType, m_iAlignment + ); + } + catch (error_already_set &) + { + if (!PyErr_ExceptionMatches(PyExc_AttributeError)) + throw_error_already_set(); + + PyErr_Clear(); + } + } } virtual std::list GetRegisters() { override get_registers = get_override("get_registers"); + if (!get_registers && m_pDefaultCallingConvention) { + return m_pDefaultCallingConvention->GetRegisters(); + } CHECK_OVERRIDE(get_registers); object registers = get_registers(); @@ -71,47 +137,60 @@ class ICallingConventionWrapper: public ICallingConvention, public wrapperGetPopSize(); + } CHECK_OVERRIDE(get_pop_size); + return get_pop_size(); } - - virtual void* GetArgumentPtr(int iIndex, CRegisters* pRegisters) - { - CPointer* ptr = extract(GetArgumentPtrWrapper(iIndex, pRegisters)); - return (void *) ptr->m_ulAddr; - } - object GetArgumentPtrWrapper(int iIndex, CRegisters* pRegisters) + virtual void* GetArgumentPtr(int iIndex, CRegisters* pRegisters) { override get_argument_ptr = get_override("get_argument_ptr"); + if (!get_argument_ptr && m_pDefaultCallingConvention) { + return m_pDefaultCallingConvention->GetArgumentPtr(iIndex, pRegisters); + } CHECK_OVERRIDE(get_argument_ptr); - return get_argument_ptr(iIndex, ptr(pRegisters)); + + object argument_ptr = get_argument_ptr(iIndex, ptr(pRegisters)); + CPointer* _ptr = extract(argument_ptr); + return (void *) _ptr->m_ulAddr; } virtual void ArgumentPtrChanged(int iIndex, CRegisters* pRegisters, void* pArgumentPtr) { override argument_ptr_changed = get_override("argument_ptr_changed"); + if (!argument_ptr_changed && m_pDefaultCallingConvention) { + m_pDefaultCallingConvention->ArgumentPtrChanged(iIndex, pRegisters, pArgumentPtr); + return; + } CHECK_OVERRIDE(argument_ptr_changed); argument_ptr_changed(iIndex, ptr(pRegisters), CPointer((unsigned long) pArgumentPtr)); } virtual void* GetReturnPtr(CRegisters* pRegisters) - { - CPointer* ptr = extract(GetReturnPtrWrapper(pRegisters)); - return (void *) ptr->m_ulAddr; - } - - object GetReturnPtrWrapper(CRegisters* pRegisters) { override get_return_ptr = get_override("get_return_ptr"); - CHECK_OVERRIDE(get_return_ptr); - return get_return_ptr(ptr(pRegisters)); + if (!get_return_ptr && m_pDefaultCallingConvention) { + return m_pDefaultCallingConvention->GetReturnPtr(pRegisters); + } + CHECK_OVERRIDE(get_return_ptr) + + object return_ptr = get_return_ptr(ptr(pRegisters)); + CPointer* _ptr = extract(return_ptr); + return (void *) _ptr->m_ulAddr; } virtual void ReturnPtrChanged(CRegisters* pRegisters, void* pReturnPtr) { override return_ptr_changed = get_override("return_ptr_changed"); + if (!return_ptr_changed && m_pDefaultCallingConvention) { + m_pDefaultCallingConvention->ReturnPtrChanged(pRegisters, pReturnPtr); + return; + } CHECK_OVERRIDE(return_ptr_changed); + return_ptr_changed(ptr(pRegisters), CPointer((unsigned long) pReturnPtr)); } @@ -125,6 +204,9 @@ class ICallingConventionWrapper: public ICallingConvention, public wrapperFindHook((void*) func->m_ulAddr); - if (!pHook) + if (!func->AddHook(this->hook_type, this->hook_handler)) { - pHook = GetHookManager()->HookFunction( - (void*) func->m_ulAddr, - func->m_pCallingConvention); - - if (!pHook) - { - PythonLog(0, "Could not create a hook for %s.", this->func_name); - return true; - } + PythonLog(0, "Could not create a hook for %s.", this->func_name); + return true; } - pHook->AddCallback(this->hook_type, this->hook_handler); - PythonLog(3, "Core hook (%s) has been initialized.", this->func_name); return true; } diff --git a/src/core/utilities/wrap_macros.h b/src/core/utilities/wrap_macros.h old mode 100644 new mode 100755 index d70c420b3..621f955e5 --- a/src/core/utilities/wrap_macros.h +++ b/src/core/utilities/wrap_macros.h @@ -248,4 +248,54 @@ typedef return_value_policy copy_const_reference_policy; typedef return_value_policy return_by_value_policy; +//--------------------------------------------------------------------------------- +// Call policies that initializes the wrapper hierarchy. +//--------------------------------------------------------------------------------- +template +struct initialize_wrapper_policies : BasePolicies +{ + template + static PyObject *postcall(const ArgumentPackage &args, PyObject *pResult) + { + PyObject *pSelf = boost::python::detail::get(boost::mpl::int_(), args); + boost::python::detail::initialize_wrapper( + pSelf, + get_pointer((HeldType)extract(pSelf)) + ); + + return BasePolicies::postcall(args, pResult); + } +}; + +//--------------------------------------------------------------------------------- +// Provides post-construction initialization support of the Python instances. +//--------------------------------------------------------------------------------- +template +struct post_constructor_policies : BasePolicies +{ +public: + post_constructor_policies(object initializer): + m_initializer(initializer) + { + } + + template + PyObject *postcall(const ArgumentPackage &args, PyObject *pResult) + { + BasePolicies::postcall(args, pResult); + m_initializer( + *(boost::python::make_tuple( + object(handle<>(incref(boost::python::detail::get(boost::mpl::int_(), args))))) + + boost::python::tuple(handle<>(args.base)) + ) + ); + + decref(pResult); + return incref(Py_None); // __init__ should always return None + } + +private: + object m_initializer; +}; + #endif // _WRAP_MACROS_H diff --git a/src/thirdparty/DynamicHooks/include/convention.h b/src/thirdparty/DynamicHooks/include/convention.h index cb737b250..2a42afcaf 100644 --- a/src/thirdparty/DynamicHooks/include/convention.h +++ b/src/thirdparty/DynamicHooks/include/convention.h @@ -143,8 +143,14 @@ class ICallingConvention m_vecArgTypes = vecArgTypes; m_returnType = returnType; m_iAlignment = iAlignment; + m_bHooked = false; } + /* + Destructs the calling convention. + */ + virtual ~ICallingConvention() {}; + /* This should return a list of Register_t values. These registers will be saved for later access. @@ -187,6 +193,7 @@ class ICallingConvention std::vector m_vecArgTypes; DataType_t m_returnType; int m_iAlignment; + bool m_bHooked; }; #endif // _CONVENTION_H \ No newline at end of file