weak linking in shared object not working as expected
Asked Answered
W

3

0

I'm trying to use the cmocka unit test framework which suggests to use weak linking to be able to select a user-defined implementation over the actual implementation of a function. In my environment I have a shared object which I want to unit test. I've implemented the unit tests in a separate file which I compile and link to the shared object. My problem is that calling a function bar in the shared object which in turn calls a function foo in that shared object always leads to the real implementation of foo and not the custom one. I've created a simplified implementation of the shared object and of the unit test.

The shared library, a.c:

#include <stdio.h>

void foo(void); __attribute__((weak))
void bar(void); __attribute__((weak))

void foo(void) {
    printf("called real foo\n");
}

void bar(void) {
    printf("called real bar calling\n");
    foo();
}

The unit test, b.c:

#include <stdio.h>
#include <stdbool.h>

bool orig_foo;
bool orig_bar;

void __wrap_foo(void) {
    printf("in foo wrapper\n");
    if (orig_foo)
            __real_foo();
    else
            printf("called wrapped foo\n");
}

void __wrap_bar() {
    printf("in bar wrapper\n");
    if (orig_bar)
            __real_bar();
    else
            printf("called wrapped bar\n");
}

int main(void) {

    orig_bar = true;
    orig_foo = false;

    printf("calling foo from main\n");
    foo();

    printf("\n");

    printf("calling bar from main\n");
    bar();

    return 0;
}

And finally, the Makefile:

all: a.out

a.out: b.c a.so
    gcc -Wall b.c a.so -Wl,--wrap=foo -Wl,--wrap=bar

a.so: a.c
    gcc -Wall -c a.c -shared -o a.so

clean:
    rm -f a.so a.out

Running a.out produces the following output:

# ./a.out
calling foo from main
in foo wrapper
called wrapped foo

calling bar from main
in bar wrapper
called real bar
called real foo

From main, the direct call to foo results in __wrap_foo being called, as expected.

Next, I call bar from main which correctly results in __wrap_bar being called, where I redirect the call to the real implementation of bar (__real_bar). bar then calls foo but the real implementation is used, not the wrapped one. Why isn't the wrapped implementation of foo called in this case? It looks like the issue is related to from where the function call originates.

In function bar, if I replaced the called to foo with __wrap_foo I do get the expected behaviour however I don't think that this is an elegant solution.

I've managed to bypass this problem using normal linking and dlopen(3) and friends however I'm curious as to why weak linking isn't working in my case.

Wallaroo answered 31/3, 2017 at 9:12 Comment(4)
Weak attributes in shared library code are broken.Litch
Makefile line for a.so is completely broken. You create an object file instead of a shared library.Litch
Indeed, I fixed the makefile producing a.so from a.o (compiled with -fpic) but I get the exact same behaviour.Wallaroo
My example works if I split foo and bar into separate files and compile everything together.Wallaroo
D
0

In bar() the linker does not store a reference to foo() but to an offset in the text section of the translation unit. Therefore the name is lost.

The "weak" attribute does not help here.

Also it does not help to use -ffunction_sections because the reference will be to an offset of the section of foo().

One straight forward way to get the desired result is to separate all functions in their own translation unit. You don't need separated source files for this, some conditional compilation will also help. But it makes the source ugly.

You might like to look into my answer to the question "Rename a function without changing its references", too.

Dyspeptic answered 8/11, 2019 at 21:13 Comment(0)
L
1

Ld's --wrap works by replacing calls to real functions by calls to their wrappers only in files which are linked with this flag. Your library isn't, so it just does plain foo and bar calls. So they get resolved to where they are implemented (a.so).

As for weak symbols, dynamic linker ignores their weakness and treats them as normal symbols (unless you run with LD_DYNAMIC_WEAK which isn't recommended).

In your particular case (overriding symbols in shared library) you'd probably need to apply --wrap to a.so as well. Or - you could use standard symbol interposition. Say if library-under-test is in libsut.so and you want to replace some functions with custom implementations in libstub.so, link them to driver program in a proper order:

LDFLAGS += -lstub -lsut

Then definitions in libstub.so will prevail over libsut.so ones.

Litch answered 31/3, 2017 at 10:53 Comment(1)
Adding --wrap when building a.so didn't help, nor did defining LD_DYNAMIC_WEAK=. I'm already using symbol interpolation which works fine, it's the weak linking I'm trying to make it work.Wallaroo
Z
0

One error is that attribute syntax is not correct. Correct ones:

void foo(void) __attribute__((weak));
void bar(void) __attribute__((weak));

An alternative solution would be the standard Linux function interposition:

// a.c
#include <stdio.h>

void foo(void) {
    printf("called real foo\n");
}

void bar(void) {
    printf("called real bar calling\n");
    foo();
}


// b.c

#define _GNU_SOURCE
#include <dlfcn.h>
#include <stdio.h>
#include <stdbool.h>

bool orig_foo;
bool orig_bar;

void foo(void) {
    printf("in foo wrapper\n");

    static void(*__real_foo)(void);
    if(!__real_foo)
        __real_foo = dlsym(RTLD_NEXT, "foo");

    if (orig_foo)
            __real_foo();
    else
            printf("called wrapped foo\n");
}

void bar() {
    printf("in bar wrapper\n");

    static void(*__real_bar)(void);
    if(!__real_bar)
        __real_bar = dlsym(RTLD_NEXT, "bar");

    if (orig_bar)
            __real_bar();
    else
            printf("called wrapped bar\n");
}

int main(void) {

    orig_bar = true;
    orig_foo = false;

    printf("calling foo from main\n");
    foo();

    printf("\n");

    printf("calling bar from main\n");
    bar();

    return 0;
}


$ gcc -o a.so -shared -Wall -Wextra -fPIC a.c
$ gcc -o b -Wall -Wextra b.c -L. -l:a.so -Wl,-rpath='$ORIGIN' -ldl 
$ ./b
calling foo from main
in foo wrapper
called wrapped foo

calling bar from main
in bar wrapper
called real bar calling
in foo wrapper
called wrapped foo
Zakarias answered 31/3, 2017 at 11:12 Comment(1)
Indeed the attribute syntax is incorrect however even by correcting it the problem persists. Regarding dlsym, that's what how I had solved it however I'm still trying to figure out why weak linking isn't working.Wallaroo
D
0

In bar() the linker does not store a reference to foo() but to an offset in the text section of the translation unit. Therefore the name is lost.

The "weak" attribute does not help here.

Also it does not help to use -ffunction_sections because the reference will be to an offset of the section of foo().

One straight forward way to get the desired result is to separate all functions in their own translation unit. You don't need separated source files for this, some conditional compilation will also help. But it makes the source ugly.

You might like to look into my answer to the question "Rename a function without changing its references", too.

Dyspeptic answered 8/11, 2019 at 21:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.