我尝试使用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
从主要方面,直接调用foo
会导致__wrap_foo
被调用,正如所料。
接下来,我从main调用bar
,这正确导致调用__wrap_bar
,我将调用重定向到bar
(__real_bar
)的实际实现。 bar
然后调用foo
但是使用了真正的实现,而不是被包装的实现。为什么在这种情况下调用foo
的包装实现?看起来这个问题与函数调用的起源有关。
在功能bar
中,如果我将被叫foo
替换为__wrap_foo
我确实得到了预期的行为,但我不认为这是一个优雅的解决方案。< / p>
我设法使用常规链接和dlopen(3)
和朋友设法绕过了这个问题,但我很好奇为什么弱连接在我的情况下不起作用。
答案 0 :(得分:0)
Ld的--wrap
通过仅在与此标志链接的文件中调用其包装器来替换对实际函数的调用。您的图书馆不是,因此它只会进行简单的foo
和bar
来电。因此,他们会被解决到实施的位置(a.so
)。
对于弱符号,动态链接器会忽略它们的弱点并将它们视为普通符号(除非您使用不推荐的LD_DYNAMIC_WEAK
运行)。
在您的特定情况下(覆盖共享库中的符号),您可能还需要将--wrap
应用于a.so
。或者 - 您可以使用标准符号插入。假设库中的库是否在libsut.so
中,并且您希望用libstub.so
中的自定义实现替换某些函数,请按顺序将它们链接到驱动程序:
LDFLAGS += -lstub -lsut
然后libstub.so
中的定义优先于libsut.so
个。
答案 1 :(得分:0)
一个错误是属性语法不正确。正确的:
~
另一种解决方案是标准的Linux功能插入:
void foo(void) __attribute__((weak));
void bar(void) __attribute__((weak));
// 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;
}
答案 2 :(得分:0)
在bar()
中,链接器不存储对foo()
的引用,但不存储对翻译单元的text
部分中的偏移量的引用。因此,该名称会丢失。
“弱”属性在这里没有帮助。
此外,使用-ffunction_sections
也无济于事,因为引用将指向foo()
部分的偏移量。
获得预期结果的一种直接方法是将所有功能分离在各自的翻译单元中。您不需要为此使用单独的源文件,某些条件编译也将有所帮助。但这使来源很难看。
您可能也想研究my answer to the question "Rename a function without changing its references"。