sylveon / member_thunk

Dynamically creates executable code to allow C callbacks to call C++ member functions without the need for a user data parameter
MIT License
13 stars 2 forks source link

Support batching creation #27

Closed sylveon closed 4 years ago

sylveon commented 4 years ago

It might be undesirable to create a thunk and leave it world-writable. We should allow a way to directly allocate a page, create thunks in it, and the set that page as execute-only once done.

The api would look something like this, not unlike a stack allocator

member_thunk::thunk_page page; // allocates a page in read-write

const auto thunk1 = page.create_thunk(this, &Foo::bar);
const auto thunk2 = page.create_thunk(this, &Foo::buz);

page.finalize(); // page set to read-execute

Once thunk_page::finalize is called, trying to create any more thunks will throw an exception.

thunk_page would have ownership of the page therefore being a move-only type, and frees the page once out of scope.

It should automatically grow if required. This can be done by either using a "linked-list" form, where a thunk_page owns the next thunk_page and forwards all calls to that once it's full, or by using VirtualAlloc to reserve an entire 64k of memory, and then committing pages as required, but would also introduce a theorical cap to the number of thunks (but does not throw away virtual memory, as the allocation granularity is 64k anyways).

This would also allow to optimize calls to FlushInstructionCache and SetProcessValidTargets to only run within finalize rather than for each new thunk, as well as skipping the alignment machinery (page is already 4k aligned, and everything is already 16 or 32 bytes, so consecutive stack allocations are already aligned)

This can introduce potential use-after-free (or use-before-executable) issues, so should be used with care.

sylveon commented 4 years ago

This will require work to move the initialization and destruction of the thunk outside of the constructors and destructors. Thankfully, our use of factory functions will allow the former, while C++20 destroying delete will allow the later.