linux系统gcc编译动态库so中的attribute((weak))弱符号使用

首先说明下代码结构:一共三个源码,两个动态库so的源码:lib1.c和lib2.c,各导出一个库函数(gcc编译时默认导出,不像windows的msvc要明确指定导出dllexport),一个主执行程序源码:main.c,调用两个动态库的导出函数,如下:

lib1.c

#include <stdio.h>

void lib1_func(const char* from)
{
        printf("lib1 func from %s\n", from);
}

lib2.c

#include <stdio.h>

void lib2_func(const char* from)
{
        printf("lib2 func from %s\n", from);
}

main.c

#include <stdio.h>

void lib1_func(const char* from);
void lib2_func(const char* from);

int main()
{
        printf("hello from main\n");
        lib1_func("main");
        lib2_func("main");
        return 0;
}

接下来编译链接上述三个代码,我用的是gcc版本:gcc version 8.4.0 (Ubuntu 8.4.0-1ubuntu1~18.04),命令如下:

gcc -c -fpic lib1.c
gcc -shared -o lib1.so lib1.o
gcc -c -fpic lib2.c
gcc -shared -o lib2.so lib2.o
gcc -L./ main.c -l1 -l2
LD_LIBRARY_PATH=./ ./a.out

此时,能得到最终执行程序输出如下:

hello from main
lib1 func from main
lib2 func from main

一个很普通的执行程序链接两个动态库,并调用其中的导出函数,那么,接下来,想实现的效果是lib2调用lib1的函数,修改lib2.c的代码为如下:

#include <stdio.h>

void lib1_func(const char* from);

void lib2_func(const char* from)
{
        printf("lib2 func from %s\n", from);
        lib1_func("lib2");
}

然后再重新执行一遍上面的编译链接、执行命令,可以得到如下输出:

hello from main
lib1 func from main
lib2 func from main
lib1 func from lib2

叮!目标达成,完结撒花?。。。并没有,标题中的内容还没出现呢。那么现在的情况是怎么回事呢?这是因为默认链接命令并不会检查代码中未定义的符号,因此,增加了lib1_func调用的lib2.c直接编译链接成功产出了so,而最终链接执行程序的时候,lib2中引用的lib1符号便可以找到了,因此结果上看和预期效果是一致的。然而,事情并不是这么简单,在实际开发过程中,我们一般是需要编译工具帮忙检查错误的,比如编译阶段的语法错误,以及会影响刚才结果的链接阶段的未定义符号错误,因此,实际上在链接上述两个so加一个执行程序时,我们需要执行的是这个命令:“gcc -shared -Wl,–no-undefined -o lib2.so lib2.o”,这时就会看到链接lib2时出现了如下的错误:

lib2.o: In function `lib2_func':
lib2.c:(.text+0x2c): undefined reference to `lib1_func'
collect2: error: ld returned 1 exit status

lib1_func在链接lib2时未定义,ld命令执行失败,没有得到so!这样我们写的代码就更安全了,但是,想要做到的事情却做不到了。。。😂解决这个问题其实也有很多办法,比如在lib2.c中暴露一个设置lib1_func函数地址的接口,然后main.c在触发lib2函数调用前先把lib1_func地址通过这个接口传递进去,又或者在lib2.so加载时(attribute((constructor)))通过dlsym动态获取lib1_func函数符号地址等等。。。这些方法都需要对现有的代码以及调用方式做一定修改,比较麻烦,下面要介绍的是一种最小修改,并且又不损失ld检测未定义符号功能的做法:attribute((weak)),修改lib2.c为:

#include <stdio.h>

void lib1_func(const char* from) __attribute__((weak));

void lib2_func(const char* from)
{
        printf("lib2 func from %s\n", from);
        if (NULL != &lib1_func)
                lib1_func("lib2");
        else
                printf("lib2 invalid lib1_func\n");
}

重新编译,然后带no-undefined方式链接lib2.so,这次就不会报错了,并且重新生成主执行程序,可以看到和之前一样的成功调用的输出了!接下来再做几个实验,首先是去掉lib1.so的使用,修改main.c代码如下:

#include <stdio.h>

//void lib1_func(const char* from);
void lib2_func(const char* from);

int main()
{
        printf("hello from main\n");
        //lib1_func("main");
        lib2_func("main");
        return 0;
}

重新编译生成主执行程序,得到输出结果如下:

hello from main
lib2 func from main
lib2 invalid lib1_func

可以看到,由于最终执行程序中没有lib1_func这个符号,lib2.so中的弱符号空指针保护起了作用,打印出了lib1_func不可用的信息,如果不加这个保护,那程序执行时就会出现崩溃了。

接下来,修改main.c如下:

#include <stdio.h>

//void lib1_func(const char* from);
void lib2_func(const char* from);

void lib1_func(const char* from)
{
        printf("lib1_func in main from %s\n", from);
}

int main()
{
        printf("hello from main\n");
        //lib1_func("main");
        lib2_func("main");
        return 0;
}

重新编译、执行,得到如下输出:

hello from main
lib2 func from main
lib1_func in main from lib2

可以看到,在main中定义了一个lib1.so中同名的函数,可以看到lib2.so成功找到并调用了这个主程序中的实现!(这种用法某些情况下编译主程序时可能需要增加-rdynamic参数)

总结下,通过使用弱符号的方式,可以很方便的实现动态库调用其他动态库,或主程序中的函数,而不需要使用复杂的动态符号地址查找处理,非常适合实现日志回调函数自动绑定,插件动态库共享主执行程序公共代码等等场景。

博主友情提示:

如您在评论中需要提及如QQ号、电子邮件地址或其他隐私敏感信息,欢迎使用>>博主专用加密工具v3<<处理后发布,原文只有博主可以看到。