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

weak linking in shared object not working as expected

我正在尝试使用 cmocka 单元测试框架,它建议使用弱 linking 能够 select 用户定义的实现而不是实际实现一个功能。在我的环境中,我有一个共享对象,我想对其进行单元测试。我在一个单独的文件中实现了单元测试,我编译了这个文件并将 link 到共享对象。我的问题是,在共享对象中调用一个函数 bar,而该函数又在该共享对象中调用一个函数 foo 总是导致 foo 的真正实现,而不是自定义实现。我创建了共享对象和单元测试的简化实现。

共享库,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();
}

单元测试,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;
}

最后,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

运行 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

在 main 中,直接调用 foo 导致调用 __wrap_foo,正如预期的那样。

接下来,我从 main 中调用 bar,这会正确地导致 __wrap_bar 被调用,我将调用重定向到 bar (__real_bar) 的实际实现. bar 然后调用 foo 但使用的是真正的实现,而不是包装的。为什么在这种情况下不调用 foo 的包装实现?看起来问题与函数调用的来源有关。

在函数 bar 中,如果我用 __wrap_foo 替换对 foo 的调用,我确实得到了预期的行为,但我认为这不是一个优雅的解决方案。

我已经设法使用普通 linking 和 dlopen(3) 和朋友绕过了这个问题,但是我很好奇为什么弱 linking 在我的系统中不起作用案例.

Ld 的 --wrap 的工作方式是仅在使用此标志 linked 的文件中通过调用包装器来替换对实际函数的调用。您的图书馆不是,所以它只是进行普通的 foobar 调用。所以他们解决了他们实施的地方(a.so)。

至于弱符号,动态 linker 会忽略它们的弱点并将它们视为普通符号(除非您 运行 和 LD_DYNAMIC_WEAK 不推荐)。

在您的特定情况下(覆盖共享库中的符号),您可能还需要将 --wrap 应用于 a.so。或者 - 您可以使用标准符号插入。假设被测库在 libsut.so 中并且您想用 libstub.so 中的自定义实现替换一些函数,link 它们以正确的顺序驱动程序:

LDFLAGS += -lstub -lsut

那么 libstub.so 中的定义将优先于 libsut.so 中的定义。

一个错误是属性语法不正确。正确的:

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

另一种解决方案是标准 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

bar() 中,链接器 不是 存储对 foo() 的引用,而是存储对翻译单元 text 部分中的偏移量的引用.因此名字丢失了。

"weak" 属性没有帮助。

此外,使用 -ffunction_sections 也无济于事,因为引用将指向 foo().

部分的偏移量

获得所需结果的一种直接方法是将所有函数分离到它们自己的翻译单元中。您不需要单独的源文件,一些条件编译也会有所帮助。但它使来源丑陋。

您可能也想研究 my answer to the question "Rename a function without changing its references"