-
-
Save mooware/1174572 to your computer and use it in GitHub Desktop.
// this file is an example of how to intercept a C++ method by using the | |
// LD_PRELOAD environment variable of the GNU dynamic linker. | |
// | |
// it works like this: | |
// | |
// 1) define a method that will have the same symbol as the intercepted | |
// method when compiled. For example, the method Foo::getValue() | |
// defined here has the mangled symbol "_ZNK3Foo8getValueEv". | |
// tools like nm, objdump or readelf can display the symbols of | |
// binaries. note that depending on compiler and linker options, | |
// some functions might not be replaceable with LD_PRELOAD. | |
// | |
// 2) if you want to call the original method from your new method, | |
// you can get a pointer to it with dlsym() and the mangled name. | |
// | |
// 3) compile your intercept code into its own shared library, | |
// for example with "g++ -g -Wall -shared -fPIC -ldl foo.cpp -o foo.so". | |
// "-ldl" should only be necessary if you use dlsym(). | |
// | |
// 4) set LD_PRELOAD to the path of your new shared library and call the | |
// program in which you want to intercept something. in bash, you can | |
// do it like this: "LD_PRELOAD=./foo.so /usr/bin/some_program". | |
#include <string.h> | |
// necessary for RTLD_NEXT in dlfcn.h | |
#ifndef _GNU_SOURCE | |
#define _GNU_SOURCE | |
#endif | |
#include <dlfcn.h> | |
class Foo | |
{ | |
public: | |
unsigned int getValue() const; | |
}; | |
unsigned int Foo::getValue() const | |
{ | |
// define the pointer-to-member type | |
typedef unsigned int (Foo::*methodType)() const; | |
static methodType origMethod = 0; | |
if (origMethod == 0) | |
{ | |
// use the mangled method name here. RTLD_NEXT means something like | |
// "search this symbol in any libraries loaded after the current one". | |
void *tmpPtr = dlsym(RTLD_NEXT, "_ZNK3Foo8getValueEv"); | |
// not even reinterpret_cast can convert between void* and a method ptr, | |
// so i'm doing the worst hack i've ever seen. | |
memcpy(&origMethod, &tmpPtr, sizeof(&tmpPtr)); | |
} | |
// here we call the original method | |
unsigned int origValue = (this->*origMethod)(); | |
return 1000 + origValue; | |
} |
a static function is basically just a C function, possibly with a decorated symbol. so the type would just be a function pointer instead of a pointer-to-member-function, and a reinterpret_cast should probably work instead of the memcpy. it could also be called just like a regular function pointer, without needing the this-pointer.
I think origMethod = (methodType)tmpPtr
will work. It's marginally less hacky.
I think
origMethod = (methodType)tmpPtr
will work. It's marginally less hacky.
Did GCC change its opinion about that? Data pointers and function pointers are fundamentally different things according to the standard, and could have different sizes etc., so I'm not surprised if the compiler rejects it.
And trying it in godbolt with GCC 9.2, it still reports a compile error:
error: invalid cast from type 'void*' to type 'methodType' {aka 'unsigned int (Foo::*)() const'}
Mm. I was thinking of casting to methodType*
not messageType
. But I guess that would only work with virtual functions?
methodType
already is a pointer. but it's a pointer-to-member-function, not a data pointer, which makes them incompatible.
See also here:
https://isocpp.org/wiki/faq/pointers-to-members#cant-cvt-memfnptr-to-voidptr
Technical details: pointers to member functions and pointers to data are not necessarily represented in the same way. A pointer to a member function might be a data structure rather than a single pointer. Think about it: if it’s pointing at a virtual function, it might not actually be pointing at a statically resolvable pile of code, so it might not even be a normal address — it might be a different data structure of some sort.
What about static functions?