共享对象中的弱链接未按预期工作
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 的文件中通过调用包装器来替换对实际函数的调用。您的图书馆不是,所以它只是进行普通的 foo
和 bar
调用。所以他们解决了他们实施的地方(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"。
我正在尝试使用 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 的文件中通过调用包装器来替换对实际函数的调用。您的图书馆不是,所以它只是进行普通的 foo
和 bar
调用。所以他们解决了他们实施的地方(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"。