Skip to content

Add default convention to CallingConvention. #344

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
3d0d875
Added default convention to ICallingConvention.
CookStar Aug 5, 2020
fb87d77
Changed constructor to create the default convention from MakeDynamic…
CookStar Aug 14, 2020
f2c403c
Removed "ICallingConventionWrapper *" from the held type.
CookStar Aug 15, 2020
025a421
Fixed removal of setting null.
CookStar Aug 15, 2020
dd295d3
Changed m_pCallingConvention to m_pDefaultCallingConvention.
CookStar Aug 15, 2020
f39a6c4
Simplified CFunction destructor.
CookStar Aug 16, 2020
adbc65a
Fixed a crash introduced by removing "ICallingConventionWrapper *".
CookStar Aug 16, 2020
f3aa765
Removed the convention headers.
CookStar Aug 17, 2020
854d96f
Fixed a crash on unloading Source.Python.
CookStar Aug 24, 2020
f275acc
Fix for VC++ 2010.
CookStar Aug 25, 2020
c47ca12
Fix2 for VC++ 2010.
CookStar Aug 25, 2020
1ae3f1d
Merge branch 'master' into add_default_callingconvention
CookStar Aug 29, 2020
0062175
Added an overload for C-type functions to CFunction::AddHook.
CookStar Sep 1, 2020
3c4935b
Resolved conflicts.
CookStar Sep 29, 2020
8aab63b
Fixed CallingConvention's leaks/issues.
jordanbriere Oct 2, 2020
6b68369
Minor fixes.
jordanbriere Oct 2, 2020
118070b
Merge pull request #2 from jordanbriere/add_default_callingconvention
CookStar Oct 3, 2020
b4cb2c0
Modified CFunction to initialize CallingConvention with default_conve…
CookStar Oct 5, 2020
121a496
Added custom_convention(m_oCallingConvention) attribute to Function.
CookStar Oct 5, 2020
282bf03
Added post-construction initialization support.
jordanbriere Oct 5, 2020
6d26bca
Fixed built-in conventions leaking when they are being unhooked while…
jordanbriere Oct 5, 2020
98d9853
Replaced the post-construction wrapper with stackable policies.
jordanbriere Oct 6, 2020
958cb03
Merge pull request #3 from jordanbriere/add_default_callingconvention
CookStar Oct 6, 2020
f651f57
Fixed namespace issue.
CookStar Oct 6, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 23 additions & 4 deletions src/core/modules/memory/memory_function.cpp
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -149,17 +150,17 @@ CFunction::CFunction(unsigned long ulAddr, object oCallingConvention, object oAr
catch( ... )
{
PyErr_Clear();

// A custom calling convention will be used...
m_eCallingConvention = CONV_CUSTOM;
object _oCallingConvention = oCallingConvention(m_tArgs, m_eReturnType);
m_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<ICallingConvention*>(_oCallingConvention);
Py_INCREF(m_oCallingConvention.ptr());
m_pCallingConvention = extract<ICallingConvention*>(m_oCallingConvention);

// We didn't allocate the calling convention, someone else is responsible for it.
m_bAllocatedCallingConvention = false;
Expand Down Expand Up @@ -188,6 +189,24 @@ CFunction::CFunction(unsigned long ulAddr, Convention_t eCallingConvention,

CFunction::~CFunction()
{
// If we created custom calling convention, clean it up.
// This does not apply to hooked calling convention.
if (!m_oCallingConvention.is_none())
{
CHook* pHook = GetHookManager()->FindHook((void *) m_ulAddr);
if (!pHook || pHook->m_pCallingConvention != m_pCallingConvention)
{
ICallingConventionWrapper* _pCallingConventionWrapper = extract<ICallingConventionWrapper*>(m_oCallingConvention);

Py_DECREF(m_oCallingConvention.ptr());

delete _pCallingConventionWrapper;
m_pCallingConvention = NULL;
}

return;
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this part is required. Boost should take care of deleting it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, m_oCallingConvention will never be released, so it needs to Py_DECREF to release it when function is unhooked.

// 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(m_oCallingConvention.ptr());
m_pCallingConvention = extract<ICallingConvention*>(m_oCallingConvention);
// We didn't allocate the calling convention, someone else is responsible for it.
m_bAllocatedCallingConvention = false;

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Py_DECREF call is valid, but I actually meant the rest. m_oCallingConvention is created by Boost and when the reference counter reaches 0, Boost will take care of deleting its C++ instance.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In my initial tests, Boost.Python did not call the destructor.

Also, in the test to verify the behavior, the destructor was not called if a pointer was passed to class_.

C++:

#include <string>
#include "export_main.h"

class Test
{
public:
	Test(){
		printf("Test\n");
	}

	~Test(){
		printf("~Test\n");
	}
};

void export_test(scope);

DECLARE_SP_MODULE(_test)
{
	export_test(_test);
}

void export_test(scope _test)
{
	class_<Test, Test *, boost::noncopyable>("Test", init<>())
	;
}

Python:

from _test import Test

def test():
    print("make test")
    test_object = Test()
    del test_object
    print("deleted test")

test()

Output:

make test
Test
deleted test

I looked at the behavior of passing a pointer to class_ in the Boost.Python documentation and code, but I couldn't find a definitive answer.

I assumed that object's call policy would act like reference_existing_object when passing a pointer to class_, is this not the case?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not fully sure why we added ICallingConventionWrapper * as the held type. I guess this was done to have full control over the deletion of the C++ instance.

I would remove the held type and make sure we decref only the instances that are not used by DynamicHooks.

Copy link
Contributor Author

@CookStar CookStar Oct 6, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EDIT: I was thinking about that while doing something else and we can actually fix it with a bit of ingenuity.

I have a little question, why are you checking ICallingConventionWrapper here?
https://github.com/jordanbriere/Source.Python/blob/6d26bca14429b498e5862ccbbfbae57fd0bb7c8a/src/core/modules/memory/memory_function.cpp#L199-L200

I couldn't understand the problem. Is this about general documentation? Or is it about the docstring?

The problem isn't about documentation, but that CallingConvention should be self-initializing and shouldn't rely on CFunction to be fully functional. The following addresses that: 282bf03

It also change the retrieval priority of default_convention which should be a fallback if no default convention was passed on construction of the instance itself. Basically, this:

class Conv(CallingConvention):
    default_convention = Convention.THISCALL

conv = Conv(..., default_convention=Convention.CDECL)

Should prioritize CDECL because it was explicitly given on instantiation of the instance, as opposed to be set to the class itself so it follows Python resolution order policies.

I understand completely now, I think this is the proper implementation.

For the record, in the first implementation, instead of using the existing ICallingConventionWrapper, I made a separate class template for ICallingConvention and created a class that inherits the default Calling Convention(x86MsCdecl, etc.). However, I thought that the binary would become too big for the functionality, so I implemented it as it is now, but I think this post-construction initialization is a necessary feature in any case.

Copy link
Contributor

@jordanbriere jordanbriere Oct 6, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a little question, why are you checking ICallingConventionWrapper here?

Checking m_eCallingConvention should be enough, but since it is declared as public, it could end up being externally mutated at some point which would potentially introduce hard to diagnose issues if the convention is being managed by both; DeleteHook and ICallingConventionWrapper. The dynamic_cast really just add extra security to ensure we are indeed working with a built-in convention.

I understand completely now, I think this is the proper implementation.

For the record, in the first implementation, instead of using the existing ICallingConventionWrapper, I made a separate class template for ICallingConvention and created a class that inherits the default Calling Convention(x86MsCdecl, etc.). However, I thought that the binary would become too big for the functionality, so I implemented it as it is now, but I think this post-construction initialization is a necessary feature in any case.

Yes, I think this is a great addition in general. Though I don't really like the current implementation as it adds an extra layer and force us to re-pack the arguments twice. I think stackable policies could probably be better. 🤔

EDIT: This sure sounds like a better approach: 98d9853

Copy link
Contributor Author

@CookStar CookStar Oct 6, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checking m_eCallingConvention should be enough, but since it is declared as public, it could end up being externally mutated at some point which would potentially introduce hard to diagnose issues if the convention is being managed by both; DeleteHook and ICallingConventionWrapper. The dynamic_cast really just add extra security to ensure we are indeed working with a built-in convention.

Okay.

Yes, I think this is a great addition in general. Though I don't really like the current implementation as it adds an extra layer and force us to re-pack the arguments twice. I think stackable policies could probably be better.

EDIT: This sure sounds like a better approach: 98d9853

Nice! One small error is that in my environment, I get an ambiguous error if I don't have boost::python with detail and make_tuple.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice! One small error is that in my environment, I get an ambiguous error if I don't have boost::python with detail and make_tuple.

Feel free to add them, it is likely an header that includes wrap_macros.h while also including [in]directly from a boost/python/detail/*'s file that defines into the detail scope consequently clashing with our using nam....

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Feel free to add them, it is likely an header that includes wrap_macros.h while also including [in]directly from a boost/python/detail/*'s file that defines into the detail scope consequently clashing with our using nam....

Okey. f651f57

I think the default convention now works perfectly! Thank you @jordanbriere!

// If we didn't allocate the calling convention, then it is not our responsibility.
if (!m_bAllocatedCallingConvention)
return;
Expand Down
3 changes: 3 additions & 0 deletions src/core/modules/memory/memory_function.h
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,9 @@ 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 = object();
};


Expand Down
19 changes: 11 additions & 8 deletions src/core/modules/memory/memory_wrap.cpp
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -825,13 +825,14 @@ void export_calling_convention(scope _memory)
"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<int> >(
(arg("arg_types"), arg("return_type"), arg("alignment")),
init< object, DataType_t, optional<int, Convention_t> >(
(arg("arg_types"), arg("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."
)
)

Expand All @@ -844,14 +845,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",
Expand All @@ -865,11 +867,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",
Expand Down
99 changes: 82 additions & 17 deletions src/core/modules/memory/memory_wrap.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,14 @@
// ============================================================================
// DynamicHooks
#include "convention.h"
#include "conventions/x86MsCdecl.h"
#include "conventions/x86MsThiscall.h"
#include "conventions/x86MsStdcall.h"
#include "conventions/x86GccCdecl.h"
#include "conventions/x86GccThiscall.h"

// Memory
#include "memory_function.h"

// Utilities
#include "memory_utilities.h"
Expand All @@ -48,14 +56,55 @@ using namespace boost::python;
class ICallingConventionWrapper: public ICallingConvention, public wrapper<ICallingConvention>
{
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)
{
#ifdef _WIN32
switch (eDefaultConv)
{
case CONV_CUSTOM:
break;
case CONV_CDECL:
m_pCallingConvention = new x86MsCdecl(m_vecArgTypes, m_returnType, m_iAlignment);
break;
case CONV_THISCALL:
m_pCallingConvention = new x86MsThiscall(m_vecArgTypes, m_returnType, m_iAlignment);
break;
case CONV_STDCALL:
m_pCallingConvention = new x86MsStdcall(m_vecArgTypes, m_returnType, m_iAlignment);
break;
default:
BOOST_RAISE_EXCEPTION(PyExc_ValueError, "Unsupported calling convention.")
}
#else
switch (eDefaultConv)
{
case CONV_CUSTOM:
break;
case CONV_CDECL:
m_pCallingConvention = new x86GccCdecl(m_vecArgTypes, m_returnType, m_iAlignment);
break;
case CONV_THISCALL:
m_pCallingConvention = new x86GccThiscall(m_vecArgTypes, m_returnType, m_iAlignment);
break;
default:
BOOST_RAISE_EXCEPTION(PyExc_ValueError, "Unsupported calling convention.")
}
#endif
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could use MakeDynamicHooksConvention for this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed! fb87d77


~ICallingConventionWrapper()
{
delete m_pCallingConvention;
m_pCallingConvention = nullptr;
}

virtual std::list<Register_t> GetRegisters()
{
override get_registers = get_override("get_registers");
if (!get_registers && m_pCallingConvention) {
return m_pCallingConvention->GetRegisters();
}
CHECK_OVERRIDE(get_registers);

object registers = get_registers();
Expand All @@ -71,47 +120,60 @@ class ICallingConventionWrapper: public ICallingConvention, public wrapper<ICall
virtual int GetPopSize()
{
override get_pop_size = get_override("get_pop_size");
if (!get_pop_size && m_pCallingConvention) {
return m_pCallingConvention->GetPopSize();
}
CHECK_OVERRIDE(get_pop_size);

return get_pop_size();
}

virtual void* GetArgumentPtr(int iIndex, CRegisters* pRegisters)
{
CPointer* ptr = extract<CPointer*>(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_pCallingConvention) {
return m_pCallingConvention->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<CPointer*>(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_pCallingConvention) {
m_pCallingConvention->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<CPointer*>(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_pCallingConvention) {
return m_pCallingConvention->GetReturnPtr(pRegisters);
}
CHECK_OVERRIDE(get_return_ptr)

object return_ptr = get_return_ptr(ptr(pRegisters));
CPointer* _ptr = extract<CPointer*>(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_pCallingConvention) {
m_pCallingConvention->ReturnPtrChanged(pRegisters, pReturnPtr);
return;
}
CHECK_OVERRIDE(return_ptr_changed);

return_ptr_changed(ptr(pRegisters), CPointer((unsigned long) pReturnPtr));
}

Expand All @@ -125,6 +187,9 @@ class ICallingConventionWrapper: public ICallingConvention, public wrapper<ICall

return tuple(argumentTypes);
}

public:
ICallingConvention* m_pCallingConvention = nullptr;
};


Expand Down