Skip to content

Instantly share code, notes, and snippets.

@mooware
Last active November 29, 2023 16:59
Show Gist options
  • Star 25 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save mooware/1174572 to your computer and use it in GitHub Desktop.
Save mooware/1174572 to your computer and use it in GitHub Desktop.
Intercept C++ methods with LD_PRELOAD
// 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;
}
@manvir-singh
Copy link

What about static functions?

@mooware
Copy link
Author

mooware commented Sep 28, 2019

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.

@charmoniumQ
Copy link

I think origMethod = (methodType)tmpPtr will work. It's marginally less hacky.

@mooware
Copy link
Author

mooware commented Feb 10, 2020

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'}

@charmoniumQ
Copy link

Mm. I was thinking of casting to methodType* not messageType. But I guess that would only work with virtual functions?

@mooware
Copy link
Author

mooware commented Feb 11, 2020

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment