78

I want to override certain function calls to various APIs for the sake of logging the calls, but I also might want to manipulate data before it is sent to the actual function.

For example, say I use a function called getObjectName thousands of times in my source code. I want to temporarily override this function sometimes because I want to change the behaviour of this function to see the different result.

I create a new source file like this:

#include <apiheader.h>    

const char *getObjectName (object *anObject)
{
    if (anObject == NULL)
        return "(null)";
    else
        return "name should be here";
}

I compile all my other source as I normally would, but I link it against this function first before linking with the API's library. This works fine except I can obviously not call the real function inside my overriding function.

Is there an easier way to "override" a function without getting linking/compiling errors/warnings? Ideally I want to be able to override the function by just compiling and linking an extra file or two rather than fiddle around with linking options or altering the actual source code of my program.

Tim Post
  • 33,371
  • 15
  • 110
  • 174
dreamlax
  • 93,976
  • 29
  • 161
  • 209
  • @dreamlax, we're moving from the general (C) to specific (gcc/linux) solutions now - it would be a good idea to clarify what you're running on so as to better target the answers. – paxdiablo Mar 06 '09 at 03:36
  • 1
    Well I'm developing on Linux but the targets are Mac OS, Linux and Windows. In fact one of the reasons I want to override functions is because I suspect they behave differently on different operating systems. – dreamlax Mar 06 '09 at 03:49
  • A related thread: https://stackoverflow.com/questions/68689260/why-the-weak-symbol-defined-in-the-same-a-file-but-different-o-file-is-not-use – smwikipedia Aug 07 '21 at 12:19

9 Answers9

92

With gcc, under Linux you can use the --wrap linker flag like this:

gcc program.c -Wl,-wrap,getObjectName -o program

and define your function as:

const char *__wrap_getObjectName (object *anObject)
{
    if (anObject == NULL)
        return "(null)";
    else
        return __real_getObjectName( anObject ); // call the real function
}

This will ensure that all calls to getObjectName() are rerouted to your wrapper function (at link time). This very useful flag is however absent in gcc under Mac OS X.

Remember to declare the wrapper function with extern "C" if you're compiling with g++ though.

codelogic
  • 71,764
  • 9
  • 59
  • 54
  • 1
    I'm disappointed that ld on Mac OS X doesn't support the `--wrap` flag. – Daryl Spitzer Jan 06 '11 at 20:04
  • 3
    By the way, the compiler does not provide the declaration of the __real* symbol. You have to declare it yourself with `extern char *__real_getObjectName(object *anObject)`. – Brendan May 13 '16 at 03:13
82

If it's only for your source that you want to capture/modify the calls, the simplest solution is to put together a header file (intercept.h) with:

#ifdef INTERCEPT
    #define getObjectName(x) myGetObjectName(x)
#endif

Then you implement the function as follows (in intercept.c which doesn't include intercept.h):

const char *myGetObjectName (object *anObject) {
    if (anObject == NULL) return "(null)";
    return getObjectName(anObject);

Then make sure each source file where you want to intercept the call has the following at the top:

#include "intercept.h"

When you compile with "-DINTERCEPT", all files will call your function rather than the real one, whereas your function will still call the real one.

Compiling without the "-DINTERCEPT" will prevent interception from occurring.

It's a bit trickier if you want to intercept all calls (not just those from your source) - this can generally be done with dynamic loading and resolution of the real function (with dlload- and dlsym-type calls) but I don't think it's necessary in your case.

paxdiablo
  • 854,327
  • 234
  • 1,573
  • 1,953
  • Using a define is not a true polymorphic answer, but I'll agree for the use of ingenuity :P – Suroot Mar 06 '09 at 02:50
  • Thanks, that's a really good idea but like I said I want to try and avoid modifying the source code. If I can't find another way then I will have to do it like this I suppose. It needs to be easy to turn off the interception. – dreamlax Mar 06 '09 at 02:51
  • 1
    Use a compile-time flag for controlling interception (see updated answer). Again, this can be done at runtime as well, you just need to detect it in myGetObjectName() and always call getObjectName if the runtime flag is set (i.e., still intercept but change behavior). – paxdiablo Mar 06 '09 at 02:54
  • That does make the switching on and off easy, but it still requires modification to the source. I'm tempted to modify it anyway just because this way seems elegant enough for me, but it will be tedious to update so many files :(. – dreamlax Mar 06 '09 at 02:59
  • You don't have a tool to do search and replace across multiple files? – jmucchiello Mar 06 '09 at 03:03
  • I'd bite the bullet and do it. The alternative using dl* functions require code changes as well and they're a lot hairier to implement. – paxdiablo Mar 06 '09 at 03:04
  • @Pax, won't the getObjectName in the intercepted function be interpreted as a macro and expanded to myGetObjectName? – dreamlax Mar 06 '09 at 03:07
  • @dreamlax, you don't include intercept.h in intercept.c. That prevents the morphing. I should have made it clear that you only include intercept.h into those source files where you want the morphing to happen - intercept.c is not one of those places. – paxdiablo Mar 06 '09 at 03:12
  • What platform are you running on, @dreamlax? I could whip up a quick bash script or cmd script to update all your files in one hit. – paxdiablo Mar 06 '09 at 03:26
  • @Pax, thanks for the offer, but I was just working on a script just now. I figure worse case scenario, I can include this script with the source and say that I didn't modify the original source ;) – dreamlax Mar 06 '09 at 03:35
  • 13
    You may use the option "-include file" to make GCC include the file automatically. you won't even have to touch any file then :) – Johannes Schaub - litb Mar 06 '09 at 04:40
  • 1
    though automatically including may cause problems when including the original API file. its declarations will be replaced too. not too easy :) – Johannes Schaub - litb Mar 06 '09 at 04:43
  • 2
    This works great for unit test suites (like using CGreen) where you are want to stub some dependencies within a function. Has been a great answer for my purposes. – nrjohnstone Jul 10 '13 at 02:13
39

You can override a function using LD_PRELOAD trick - see man ld.so. You compile shared lib with your function and start the binary (you even don't need to modify the binary!) like LD_PRELOAD=mylib.so myprog.

In the body of your function (in shared lib) you write like this:

const char *getObjectName (object *anObject) {
  static char * (*func)();

  if(!func)
    func = (char *(*)()) dlsym(RTLD_NEXT, "getObjectName");
  printf("Overridden!\n");     
  return(func(anObject));    // call original function
}

You can override any function from shared library, even from stdlib, without modifying/recompiling the program, so you could do the trick on programs you don't have a source for. Isn't it nice?

BenMorel
  • 34,448
  • 50
  • 182
  • 322
qrdl
  • 34,062
  • 14
  • 56
  • 86
  • Shouldn't it be `static char * (*func)(object*);` in the function pointer declaration, and `(char *(*)(object*))` for the cast? – Yun Feb 18 '23 at 14:31
  • 1
    @Yun Not necessarily - leaving arg list empty (not `void`) means "function takes any number of arguments of any type" – qrdl Mar 03 '23 at 08:50
27

If you use GCC, you can make your function weak. Those can be overridden by non-weak functions:

test.c:

#include <stdio.h>

__attribute__((weak)) void test(void) { 
    printf("not overridden!\n"); 
}

int main() {
    test();
}

What does it do?

$ gcc test.c
$ ./a.out
not overridden!

test1.c:

#include <stdio.h>

void test(void) {
    printf("overridden!\n");
}

What does it do?

$ gcc test1.c test.c
$ ./a.out
overridden!

Sadly, that won't work for other compilers. But you can have the weak declarations that contain overridable functions in their own file, placing just an include into the API implementation files if you are compiling using GCC:

weakdecls.h:

__attribute__((weak)) void test(void);
... other weak function declarations ...

functions.c:

/* for GCC, these will become weak definitions */
#ifdef __GNUC__
#include "weakdecls.h"
#endif

void test(void) { 
    ...
}

... other functions ...

Downside of this is that it does not work entirely without doing something to the api files (needing those three lines and the weakdecls). But once you did that change, functions can be overridden easily by writing a global definition in one file and linking that in.

Johannes Schaub - litb
  • 496,577
  • 130
  • 894
  • 1,212
  • 2
    That would require modifying the API, wouldn't it? – dreamlax Mar 06 '09 at 03:20
  • your function name will be the same. also won't change the ABI or API in any way. just include the overriding file when linking, and calls will be made to the non-weak function. libc/pthread do that trick: when pthread is linked in, its threadsafe functions are used instead of libc's weak one – Johannes Schaub - litb Mar 06 '09 at 03:22
  • i've added a link. i don't know whether it suits your targets (i.e whether you can live with that GCC thingy. if msvc has something similar, you could #define WEAK it thoigh). but if on linux, i would use that one (maybe there is even a better way. i have no idea. look into versioning too). – Johannes Schaub - litb Mar 06 '09 at 03:26
9

You can define a function pointer as a global variable. The callers syntax would not change. When your program starts, it could check if some command-line flag or environment variable is set to enable logging, then save the function pointer's original value and replace it with your logging function. You would not need a special "logging enabled" build. Users could enable logging "in the field".

You will need to be able to modify the callers' source code, but not the callee (so this would work when calling third-party libraries).

foo.h:

typedef const char* (*GetObjectNameFuncPtr)(object *anObject);
extern GetObjectNameFuncPtr GetObjectName;

foo.cpp:

const char* GetObjectName_real(object *anObject)
{
    return "object name";
}

const char* GetObjectName_logging(object *anObject)
{
    if (anObject == null)
        return "(null)";
    else
        return GetObjectName_real(anObject);
}

GetObjectNameFuncPtr GetObjectName = GetObjectName_real;

void main()
{
    GetObjectName(NULL); // calls GetObjectName_real();

    if (isLoggingEnabled)
        GetObjectName = GetObjectName_logging;

    GetObjectName(NULL); // calls GetObjectName_logging();
}
Chris Peterson
  • 2,377
  • 1
  • 21
  • 24
  • 2
    I've considered this method but it does require modifying the source code, something I'm not really wanting to do unless I have to. Although this has the added benefit of switching during run-time. – dreamlax Mar 06 '09 at 03:08
5

Building on @Johannes Schaub's answer with a solution suitable for code you don't own.

Alias the function you want to override to a weakly-defined function, and then reimplement it yourself.

override.h

#define foo(x) __attribute__((weak))foo(x)

foo.c

function foo() { return 1234; }

override.c

function foo() { return 5678; }

Use pattern-specific variable values in your Makefile to add the compiler flag -include override.h.

%foo.o: ALL_CFLAGS += -include override.h

Aside: Perhaps you could also use -D 'foo(x) __attribute__((weak))foo(x)' to define your macros.

Compile and link the file with your reimplementation (override.c).

  • This allows you to override a single function from any source file, without having to modify the code.

  • The downside is that you must use a separate header file for each file you want to override.

vaughan
  • 6,982
  • 6
  • 47
  • 63
  • Got me this: A fatal error occurred: Segment loaded at 0x3f000120 lands in same 64KB flash mapping as segment loaded at 0x3f000020. Can't generate binary. Suggest changing linker script or ELF to merge sections. – xtrinch Jun 08 '21 at 13:14
  • This method cannot handle static functions. – smwikipedia Jul 29 '21 at 07:18
  • I think the important part is, first to include the `override.h` without modifying the original source. Second, the `foo.c` is compiled with `override.h`, where the original `foo()` is decorated as a `weak` symbol. While the `override.c` is compiled without `override.h` to provide a non-weak symbol of `foo()`. – smwikipedia Jul 29 '21 at 13:58
  • If the `foo()` function is also used where it is defined, the gcc gives `error: expected declaration specifiers or ‘...’ before numeric constant`. – smwikipedia Jul 29 '21 at 14:07
  • @vaughan Please see my answer. Thanks. – smwikipedia Aug 06 '21 at 15:35
3

There's also a tricky method of doing it in the linker involving two stub libraries.

Library #1 is linked against the host library and exposes the symbol being redefined under another name.

Library #2 is linked against library #1, interecepting the call and calling the redefined version in library #1.

Be very careful with link orders here or it won't work.

Joshua
  • 40,822
  • 8
  • 72
  • 132
  • Sounds tricky, but it does avoid modifying the source. Very nice suggestion. – dreamlax Mar 06 '09 at 03:01
  • I don't think you can force getObjectName to go to a specific library without dlopen/dlsym trickery. – paxdiablo Mar 06 '09 at 03:28
  • 1
    Any link-time operation that drags in the host library will result in a multiply-defined symbol. – paxdiablo Mar 06 '09 at 03:30
  • 1
    Example: you link against lib2, the linker requires l1getObj (the redefined name). But l1getObj requires getObj (which is already in l2 so the linker won't bring in the host objects) - this leads to infinite recursion. – paxdiablo Mar 06 '09 at 03:32
  • 1
    There's something particularly odd about the behavior when the libraries are dynamic rather than static that makes this work. I discovered it by accident. – Joshua Mar 06 '09 at 04:00
2

Below are my experiments. There are 4 conclusions in the body and in the end.

Short Version

Generally speaking, to successfully override a function, you have to consider:

  • weak attribute
  • translation unit arrangement

Long Version

I have these source files.

.
├── decl.h
├── func3.c
├── main.c
├── Makefile1
├── Makefile2
├── override.c
├── test_target.c
└── weak_decl.h

main.c

#include <stdio.h>

void main (void)
{
    func1();    
}

test_target.c

#include <stdio.h>

void func3(void);

void func2 (void)
{
    printf("in original func2()\n");
}

void func1 (void)
{
    printf("in original func1()\n");
    func2();
    func3();
}

func3.c

#include <stdio.h>

void func3 (void)
{
    printf("in original func3()\n");
}

decl.h

void func1 (void);
void func2 (void);
void func3 (void);

weak_decl.h

void func1 (void);

__attribute__((weak))
void func2 (void);

__attribute__((weak))
void func3 (void);

override.c

#include <stdio.h>

void func2 (void)
{
    printf("in mock func2()\n");
}

void func3 (void)
{
    printf("in mock func3()\n");
}

Makefile1:

ALL:
    rm -f *.o *.a
    gcc -c override.c -o override.o
    gcc -c func3.c -o func3.o
    gcc -c test_target.c -o test_target_weak.o -include weak_decl.h
    ar cr all_weak.a test_target_weak.o func3.o
    gcc main.c all_weak.a override.o -o main -include decl.h 

Makefile2:

ALL:
    rm -f *.o *.a
    gcc -c override.c -o override.o
    gcc -c func3.c -o func3.o
    gcc -c test_target.c -o test_target_strong.o -include decl.h # HERE -include differs!!
    ar cr all_strong.a test_target_strong.o func3.o
    gcc main.c all_strong.a override.o -o main -include decl.h 

Output for Makefile1 result:

in original func1()
in mock func2()
in mock func3()

Output for Makefile2:

rm *.o *.a
gcc -c override.c -o override.o
gcc -c func3.c -o func3.o
gcc -c test_target.c -o test_target_strong.o -include decl.h # -include differs!!
ar cr all_strong.a test_target_strong.o func3.o
gcc main.c all_strong.a override.o -o main -include decl.h 
override.o: In function `func2':
override.c:(.text+0x0): multiple definition of `func2'  <===== HERE!!!
all_strong.a(test_target_strong.o):test_target.c:(.text+0x0): first defined here
override.o: In function `func3':
override.c:(.text+0x13): multiple definition of `func3' <===== HERE!!!
all_strong.a(func3.o):func3.c:(.text+0x0): first defined here
collect2: error: ld returned 1 exit status
Makefile4:2: recipe for target 'ALL' failed
make: *** [ALL] Error 1

The symbol table:

all_weak.a:

test_target_weak.o:
0000000000000013 T func1  <=== 13 is the offset of func1 in test_target_weak.o, see below disassembly
0000000000000000 W func2  <=== func2 is [W]eak symbol with default value assigned
                 w func3  <=== func3 is [w]eak symbol without default value
                 U _GLOBAL_OFFSET_TABLE_
                 U puts

func3.o:
0000000000000000 T func3 <==== func3 is a strong symbol
                 U _GLOBAL_OFFSET_TABLE_
                 U puts

all_strong.a:

test_target_strong.o:
0000000000000013 T func1
0000000000000000 T func2 <=== func2 is strong symbol
                 U func3 <=== func3 is undefined symbol, there's no address value on the left-most column because func3 is not defined in test_target_strong.c
                 U _GLOBAL_OFFSET_TABLE_
                 U puts

func3.o:
0000000000000000 T func3  <=== func3 is strong symbol
                 U _GLOBAL_OFFSET_TABLE_
                 U puts

In both cases, the override.o symbols:

0000000000000000 T func2  <=== func2 is strong symbol
0000000000000013 T func3  <=== func3 is strong symbol
                 U _GLOBAL_OFFSET_TABLE_
                 U puts

disassembly:

test_target_weak.o:     file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <func2>: <===== HERE func2 offset is 0
   0:   55                      push   %rbp
   1:   48 89 e5                mov    %rsp,%rbp
   4:   48 8d 3d 00 00 00 00    lea    0x0(%rip),%rdi        # b <func2+0xb>
   b:   e8 00 00 00 00          callq  10 <func2+0x10>
  10:   90                      nop
  11:   5d                      pop    %rbp
  12:   c3                      retq   

0000000000000013 <func1>: <====== HERE func1 offset is 13
  13:   55                      push   %rbp
  14:   48 89 e5                mov    %rsp,%rbp
  17:   48 8d 3d 00 00 00 00    lea    0x0(%rip),%rdi        # 1e <func1+0xb>
  1e:   e8 00 00 00 00          callq  23 <func1+0x10>
  23:   e8 00 00 00 00          callq  28 <func1+0x15>
  28:   e8 00 00 00 00          callq  2d <func1+0x1a>
  2d:   90                      nop
  2e:   5d                      pop    %rbp
  2f:   c3                      retq   

So the conclusion is:

  1. A function defined in .o file can override the same function defined in .a file. In above Makefile1, the func2() and func3() in override.o overrides the counterparts in all_weak.a. I tried with both .o files but it don't work.

  2. For GCC, You don't need to split the functions into separate .o files as said in here for Visual Studio toolchain. We can see in above example, both func2() (in the same file as func1()) and func3() (in a separate file) can be overridden.

  3. To override a function, when compiling its consumer's translation unit, you need to specify that function as weak. That will record that function as weak in the consumer.o. In above example, when compiling the test_target.c, which consumes func2() and func3(), you need to add -include weak_decl.h, which declares func2() and func3() as weak. The func2() is also defined in test_target.c but it's OK.

Some further experiment

Still with the above source files. But change the override.c a bit:

override.c

#include <stdio.h>

void func2 (void)
{
    printf("in mock func2()\n");
}

// void func3 (void)
// {
//     printf("in mock func3()\n");
// }

Here I removed the override version of func3(). I did this because I want to fall back to the original func3() implementation in the func3.c.

I still use Makefile1 to build. The build is OK. But a runtime error happens as below:

xxx@xxx-host:~/source/override$ ./main
in original func1()
in mock func2()
Segmentation fault (core dumped)

So I checked the symbols of the final main:

0000000000000696 T func1
00000000000006b3 T func2
                 w func3

So we can see the func3 has no valid address. That's why segment fault happens.

So why? Didn't I add the func3.o into the all_weak.a archive file?

ar cr all_weak.a func3.o test_target_weak.o

I tried the same thing with func2, where I removed the func2 implementation from ovrride.c. But this time there's no segment fault.

override.c

#include <stdio.h>

// void func2 (void)
// {
//     printf("in mock func2()\n");
// }

void func3 (void)
{
    printf("in mock func3()\n");
}

Output:

xxx@xxx-host:~/source/override$ ./main
in original func1()
in original func2()  <====== the original func2() is invoked as a fall back
in mock func3()

My guess is, because func2 is defined in the same file/translation unit as func1. So func2 is always brought in with func1. So the linker can always resolve func2, be it from the test_target.c or override.c.

But for func3, it is defined in a separate file/translation unit (func3.c). If it is declared as weak, the consumer test_target.o will still record func3() as weak. But unfortunately the GCC linker will not check the other .o files from the same .a file to look for an implementation of func3(). Though it is indeed there.

all_weak.a:

func3.o:
0000000000000000 T func3 <========= func3 is indeed here!
                 U _GLOBAL_OFFSET_TABLE_
                 U puts

test_target_weak.o:
0000000000000013 T func1
0000000000000000 W func2
                 w func3
                 U _GLOBAL_OFFSET_TABLE_
                 U puts

So I must provide an override version in override.c otherwise the func3() cannot be resolved.

But I still don't know why GCC behaves like this. If someone can explain, please.

(Update 9:01 AM 8/8/2021: this thread may explain this behavior, hopefully.)

So further conclusion is:

  1. If you declare some symbol as weak, you'd better provide override versions of all the weak functions. Otherwise, the original version cannot be resolved unless it lives within the same file/translation unit of the caller/consumer.
smwikipedia
  • 61,609
  • 92
  • 309
  • 482
  • This does not provide an answer to the question. To critique or request clarification from an author, leave a comment below their post. - [From Review](/review/low-quality-posts/29529354) – Richard Chambers Aug 05 '21 at 03:16
  • @RichardChambers Yes, the post is not finished yet. And it is too long to fit in a comment. – smwikipedia Aug 05 '21 at 06:58
0

You could use a shared library (Unix) or a DLL (Windows) to do this as well (would be a bit of a performance penalty). You can then change the DLL/so that gets loaded (one version for debug, one version for non-debug).

I have done a similar thing in the past (not to achieve what you are trying to achieve, but the basic premise is the same) and it worked out well.

[Edit based on OP comment]

In fact one of the reasons I want to override functions is because I suspect they behave differently on different operating systems.

There are two common ways (that I know of) of dealing with that, the shared lib/dll way or writing different implementations that you link against.

For both solutions (shared libs or different linking) you would have foo_linux.c, foo_osx.c, foo_win32.c (or a better way is linux/foo.c, osx/foo.c and win32/foo.c) and then compile and link with the appropriate one.

If you are looking for both different code for different platforms AND debug -vs- release I would probably be inclined to go with the shared lib/DLL solution as it is the most flexible.

TofuBeer
  • 60,850
  • 18
  • 118
  • 163