Interceptor functions can now intercept on the way in, and also on the way out. I have it working for x86_64. More architectures to come later.
This means that you can lock a mutex before jumping to the target function, and then you can unlock the mutex before returning control back to the caller. If you only want to intercept on the way in, then things like Controlflow Enforcement Technology (CET) won't kill the process because no return addresses are edited on the stack. If you want to intercept on the way out though, you'll need to disable the likes of CET, which is easy to do on a development PC running MS-Windows, macOS and Linux.
I think I pretty much have this at a level now where I can torture test it on x86_64, perhaps by taking a massive program (such as a 3D graphics design program), and creating ‘Man In The Middle’ DLL files to stand between the executable and all of its DLL dependencies.
Here’s a GodBolt to demonstrate it working:
I’m currently fantasizing about figuring out a way to catch an exception thrown from the target function, and to make sure the ‘outward’ interceptor gets executed before re-throwing the exception.
Here's how the compiler patch is looking for the time being:
And here’s the GodBolt copy-pasted:
#include <cstdio>
#include <iostream>
#include <interceptor>
#include <mutex>
using std::cout, std::endl;
struct Mutex {
std::mutex m;
void lock() { cout << " --- locking --- \n"; m. lock(); }
void unlock() { cout << " --- unlocking --- \n"; m.unlock();}
};
/* The following interceptor function has two purposes:
* (1) Toggle the output between stdout to stderr
* (2) Lock and unlock a mutex before invoking std::fprintf */
[[interceptor]] void MyInterceptor(void) noexcept
{
static Mutex m;
/**/ if ( __inter.arg0p() == stdout ) __inter.arg0p() = stderr;
else if ( __inter.arg0p() == stderr ) __inter.arg0p() = stdout;
m.lock();
__inter.outward() = [](){ m.unlock(); };
goto -> std::fprintf;
}
int main(void)
{
cout << "First line in main\n";
// We make a function pointer to point to the interceptor
auto const fp = (decltype(std::fprintf)*)&MyInterceptor;
// We invoke the interceptor exactly as though we were invoking 'fprintf'
fp(stderr,"Hello %s.\n", "world");
cout << "Last line in main\n";
}