0

我正在尝试使用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

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

在 functionbar中,如果我将被调用替换为foo__wrap_foo确实会得到预期的行为,但我认为这不是一个优雅的解决方案。

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

4

3 回答 3

1

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

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

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

LDFLAGS += -lstub -lsut

然后定义libstub.so将优先于libsut.so那些。

于 2017-03-31T10:53:13.247 回答
0

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

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
于 2017-03-31T11:12:28.627 回答
0

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

“弱”属性在这里没有帮助。

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

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

您可能还想查看我对“重命名函数而不更改其引用”问题的回答

于 2019-11-08T21:13:44.953 回答