Search code examples
c++linkeroverridinghookpointer-aliasing

Wrap a function without changing the call sites?


Suppose a function int foo(int x) is made available in library libfoo, which I don't control. I do control a codebase which calls foo() many times, in many files.

Now, I would like to insert some logic before actually making a call to foo(). Let's say its "if condition holds call foo, otherwise do something else" (and never mind the details). So far - not a problem. I write a int foo_wrapper(int x) put my logic in there, and call that one instead.

Now for the hard part: I want to not change the code at the call sites. i.e. I still want there to be foo() calls. So:

  • No foo_ almost-same-name functions
  • No macro trickery to replace the call
  • No pre-compilation source code generation

Essentially I want most of my compilation units to "not look" at libfoo, but at some translation unit I provide with a wrapper int foo(); and have the implementation in that translation unit actually call libfoo's foo() function. How achievable is this (without twisting myself into knots too hard)?

Additional information about libfoo:

  • It is a static library; in the case of a dynamic library, one could play with the library search path and have the wrapper be located instead of the original; and then the wrapper might be able to dynamically locate libfoo itself, load it, and have the original foo symbol available for use when necessary.
  • I'm working on Linux, and being cross-platform is not a requirement; of course it's always nice to maintain that.

Note: A solution which also works for C code is preferable but not required.


Solution

  • How achievable is this (without twisting myself into knots too hard)?

    Trivially achievable given the Linux-only constraint.

    If libfoo was a shared library, this is exactly what library interposers do using LD_PRELOAD and similar tricks.

    But since libfoo is an archive library, all you need to do is rename all your unresolved references from foo to foo_wrapper. You can do that by running objcopy --redefine-sym foo=foo_wrapper caller.o new_caller.o, where caller.o is your object file that was originally calling foo, and linking your binary using new_caller.o.

    (Obviously your can do "update in place" with something like objcopy ... caller.o /tmp/$$.o && mv /tmp/$$.o caller.o, so you don't have to actually modify your link line.)

    You will have to repeat this for every object in your codebase.

    P.S. Using a macro to achieve the exact same result is probably much simpler -- all you have to do is use -Dfoo=foo_wrapper on compilation command line. Not sure whether that counts as "macro trickery" in your book.