【问题标题】:weak linking in shared object not working as expected共享对象中的弱链接未按预期工作
【发布时间】:2017-08-25 11:58:37
【问题描述】:

我正在尝试使用cmocka 单元测试框架,该框架建议使用弱链接来选择用户定义的实现而不是函数的实际实现。在我的环境中,我有一个共享对象,我想对其进行单元测试。我在一个单独的文件中实现了单元测试,我编译并链接到共享对象。我的问题是,在共享对象中调用函数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 的调用,我确实得到了预期的行为,但我不认为这是一个优雅的解决方案。

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

【问题讨论】:

  • 共享库代码中的弱属性被破坏。
  • a.so 的 Makefile 行已完全损坏。您创建一个对象文件而不是共享库。
  • 确实,我修复了从a.o 生成a.so 的makefile(使用-fpic 编译),但我得到了完全相同的行为。
  • 如果我将 foobar 拆分为单独的文件并将所有内容编译在一起,我的示例将有效。

标签: c unit-testing shared-libraries dynamic-linking weak-linking


【解决方案1】:

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

“弱”属性在此处没有有帮助。

此外,使用-ffunction_sections 也无济于事,因为引用将指向foo() 部分的偏移量。

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

你也可以关注my answer to the question "Rename a function without changing its references"

【讨论】:

    【解决方案2】:

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

    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
    

    【讨论】:

    • 确实属性语法不正确,但是即使通过更正它,问题仍然存在。关于 dlsym,这就是我解决它的方法,但是我仍在试图找出为什么弱链接不起作用。
    【解决方案3】:

    Ld 的--wrap 的工作原理是通过仅在与此标志链接的文件中调用其包装器来替换对真实函数的调用。你的图书馆不是,所以它只是简单的foobar 调用。所以他们得到解决他们实施的地方(a.so)。

    对于弱符号,动态链接器会忽略它们的弱点并将它们视为普通符号(除非您使用LD_DYNAMIC_WEAK 运行,否则不推荐)。

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

    LDFLAGS += -lstub -lsut
    

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

    【讨论】:

    • 在构建a.so 时添加--wrap 没有帮助,定义LD_DYNAMIC_WEAK= 也没有帮助。我已经在使用符号插值,它工作正常,这是我试图让它工作的弱链接。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-07-12
    • 1970-01-01
    • 1970-01-01
    • 2012-11-02
    • 2013-05-11
    • 2020-04-13
    • 2017-08-14
    相关资源
    最近更新 更多