简体   繁体   English

共享对象中的弱链接未按预期工作

[英]weak linking in shared object not working as expected

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.我正在尝试使用cmocka单元测试框架,该框架建议使用弱链接来选择用户定义的实现而不是函数的实际实现。 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.我的问题是,调用共享对象中的函数bar反过来调用该共享对象中的函数foo总是会导致foo的真正实现,而不是自定义实现。 I've created a simplified implementation of the shared object and of the unit test.我创建了共享对象和单元测试的简化实现。

The shared library, ac :共享库ac

#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, bc :单元测试, bc

#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 :最后, 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会产生以下输出:

# ./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.正如预期的那样,从 main 直接调用foo导致调用__wrap_foo

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 ).接下来,我从 main 调用bar ,它正确地导致__wrap_bar被调用,在那里我将调用重定向到bar ( __real_bar ) 的实际实现。 bar then calls foo but the real implementation is used, not the wrapped one. bar然后调用foo但使用了真正的实现,而不是包装的实现。 Why isn't the wrapped implementation of foo called in this case?为什么在这种情况下不调用foo的包装实现? 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.在函数bar ,如果我用__wrap_foo替换了对foo的调用,我确实得到了预期的行为,但是我认为这不是一个优雅的解决方案。

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.我已经设法使用普通链接和dlopen(3)和朋友绕过了这个问题,但是我很好奇为什么弱链接在我的情况下不起作用。

Ld's --wrap works by replacing calls to real functions by calls to their wrappers only in files which are linked with this flag. Ld 的--wrap工作原理是通过仅在与此标志链接的文件中调用包装器来替换对实际函数的调用。 Your library isn't, so it just does plain foo and bar calls.你的图书馆不是,所以它只是简单的foobar调用。 So they get resolved to where they are implemented ( a.so ).所以他们解决了他们实施的地方( 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).至于弱符号,动态链接器会忽略它们的弱点并将它们视为普通符号(除非您使用不推荐的LD_DYNAMIC_WEAK运行)。

In your particular case (overriding symbols in shared library) you'd probably need to apply --wrap to a.so as well.在您的特定情况下(覆盖共享库中的符号),您可能还需要将--wrap应用于a.so 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:假设被测库是否在libsut.so并且您想用libstub.so自定义实现替换某些函数,请按正确顺序将它们链接到驱动程序:

LDFLAGS += -lstub -lsut

Then definitions in libstub.so will prevail over libsut.so ones.那么libstub.so定义将优先于libsut.so的定义。

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:另一种解决方案是标准的 Linux 函数插入:

// 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

In bar() the linker does not store a reference to foo() but to an offset in the text section of the translation unit.bar() ,链接器不存储对foo()的引用,而是存储翻译单元text部分中的偏移量。 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() .此外,使用-ffunction_sections因为引用将指向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.您可能也想查看我对“重命名函数而不更改其引用”这个问题的回答

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM