一文搞懂系列——你真的了解如何生成动态库了吗?

引言

动态库的编译,这有什么难度,这不是手到擒来的事情吗?无非不就是:

gcc -FPIC -shared -o libxxx.so  *.o  *.c  

我若是提出这些需求场景,阁下又如何应对呢?

  • 动态库A依赖其他部分提供的能力。但是却不想将内部的能力暴露出去
  • 动态库A依赖外部函数func_xxx,但是该符号即可能存在我们自己的库B中,也可能存在客户动态库C中。如何保证调用指定接口
  • 对外提供的库,如何让客户只能访问指定接口,实现其它接口的隐藏等。

至此你还能面不改色,自信的说:动态库编译简单吗?嘴角颤抖,不屈的低语:就是简单

也有朋友可能就会说:"现实工作中怎么会有这么奇葩的要求?就是你难为人,没事找事"。但是我想说的是,这些场景真的很常见,我在工作中就遇到过,不妨听我细说。

-Wl,--exclude-libs,ALL

工作场景:当今IT行业,一个产品的输出,基本都是有多个部门相互合作,紧密配合才能实现的。而各个部门之间的常见的配合方式就是提供SDK。比如:我之前在海康是做门禁产品的。其中有一个重要功能就是人脸识别。该功能流程可以分解

视频流获取 --> 提取图片帧 --> 人脸识别算法获取唯一ID --> 比对数据库中的ID --> 放行

其中视频流获取 --> 提取图片帧是bsp团队开发,他们提供动态库libB.a,我们调用其中对应接口。人脸识别算法计算唯一ID则是研究院团队提供的算法库libC.a。比对数据库中的ID --> 放行则是我们团队的开发内容。我们对外提供libA.so

很明显我们,我们仅仅是做门禁业务开发,总不能把BSP团队或研究院的核心能力也提供给甲方吧?否则一定要加钱的。

但是我们该怎么做呢?因为我们知道,正常编译静态库libC.so的方式,肯定会将bsp和研究院提供的能力对外开放。客户集成时,是可以直接引用到libA.a和libB.a的对外接口。代码示例如下:

//a.c
extern int printf(char* ftm,...);
int a()
{
    printf("i'am liba.so");
    return 0;
}

//b.c
extern int printf(char* ftm,...);
int b()
{
    printf("i'am libb.so");
}

//c.c
extern int a(void);
extern int b(void);

int c()
{
    a();
    b();
    return 0;
}

编译:

yihua@ubuntu:~/test/1207$ gcc -c a.c
yihua@ubuntu:~/test/1207$ ar rcs -o libA.a a.o
yihua@ubuntu:~/test/1207$ gcc -c b.c
yihua@ubuntu:~/test/1207$ ar rcs -o libB.a b.o
yihua@ubuntu:~/test/1207$ gcc -FPIC -shared -o libC.so c.c -lA -lB -L.

符号关系:

如图所示,外部是可以通过libC.so去直接调用libA.a和libB.a的接口

如何解决这个问题呢?

链接器为我们提供了 -Wl,--exclude-libs 参数选项:隐藏静态库文件的符号。

我们加上编译选项再试试。

yihua@ubuntu:~/test/1207$
yihua@ubuntu:~/test/1207$ gcc -FPIC -shared -o libC.so c.c -L.  -Wl,--exclude-libs,ALL -lA -lB
yihua@ubuntu:~/test/1207$

符号关系:

由图可知,libA.a 和 libB.a的内部符号已经被隐藏了。完结,撒花~~~

-Wl,-Bsymbolic

工作场景:在工作中,我们无法避免的会依赖其它动态库。比如:我司开发的SDK libA.so底层用到了mqtt通信,因此会依赖开源库libpaho-mqtt3c.so,但是我司对内部源码做了一些定制化修改;同时,我们也依赖第三方供应商的SDK libB.so,并且他们内部也采用了mqtt协议通信,同样集成了mqtt开源库,也许他们也在内部做了定制化处理。

关系如下:

分析:

情况一:当admfotaApp运行时,链接器会根据它的动态库依赖关系,加载相应的动态库。而libpaho-mqtt3c.so仅会加载一次。之后再进行符号链接时,就会出现异常。------ 加载库了非预期的库

情况二:会导致mqtt开源库代码段被加载两次,也就是说进程中会有两套相同的符号和对应的代码段。libB.so和libA.so在进行符号链接时,可能就会出现异常。 ------ 符号链接时出现问题

示例代码如下:

//a.c
extern int printf(const char* ftm,...);
int a()
{
    printf("i'am OEM a\n");
}

int c()
{
    printf("i'am OEM c\n");
}

//b.c
extern int printf(const char* ftm,...);
extern int c(void);
extern int d(void);
int b()
{
    printf("i'am abup a\n");
    c();
    d();
    return 0;
}

//c.c
extern int printf(const char* ftm,...);
int c()
{
    printf("i'am abup c\n");
}

//d.c
extern int printf(const char* ftm,...);
int d()
{
    printf("i'am abup d\n");
}

//main.c
extern int printf(const char* ftm,...);
extern int a(void);
extern int b(void);
int main()
{
        a();
        b();
        return 0;
}

编译如下:

gcc -c c.c
gcc -c d.c
ar -crs -o libC.a c.o d.o   // 生成静态库
gcc -FPIC -shared -o libB.so b.c -lC -L.    //从这可以看出,b.c期望时引用c.c中的c()
gcc -FPIC -shared -o libA.so a.c
gcc main.c -o main -lA -lB -lC -L.

运行:

由上可知:输出内容是非预期的。我们更新一下编译命令gcc main.c -o main -lB -lA -L.,运行结果如下:

链接库的顺序不一样,居然会有不一样的结果?这是为什么呢?有兴趣的朋友可以搜索:全局符号介入 相关知识点。或参考我的一篇博客:全局符号介入引起的问题

针对该场景如何处理呢?

链接器中的-Wl,-Bsymbolic参数:告诉链接器强制使用本地的符号,也就是说,编译libB.so时,就确定符号地址。不需要等待运行时再链接。

比如我们在编译main.c时,增加该参数:

gcc -FPIC -shared -o libB.so b.c -lC -L. -Wl,-Bsymbolic
gcc main.c -o main -lA -lB -L.

输出:

完结撒花~~~。

有兴趣的同学可以通过反汇编查看增加-Wl,-Bsymbolic参数前后,libB.so的汇编内容。

总结

本文从两个实际存在的场景,向大家介绍了动态库生成过程中的一些特定需求。简单介绍了 -Wl,-Bsymbolic-Wl,--exclude-libs,ALL两个链接属性及使用方式。

当然很多其它常用的参数比如:-fvisibility=hidden-Wl,--whole-archive。有兴趣的朋友可以了解一下。

若我的内容对您有所帮助,还请关注我的公众号。不定期分享干活,剖析案例,也可以一起讨论分享。

我的宗旨:

踩完您工作中的所有坑并分享给您,让你的工作无bug,人生尽是坦途

相关推荐
远歌已逝21 分钟前
维护在线重做日志(二)
数据库·oracle
qq_433099401 小时前
Ubuntu20.04从零安装IsaacSim/IsaacLab
数据库
Dlwyz1 小时前
redis-击穿、穿透、雪崩
数据库·redis·缓存
工业甲酰苯胺3 小时前
Redis性能优化的18招
数据库·redis·性能优化
没书读了4 小时前
ssm框架-spring-spring声明式事务
java·数据库·spring
i道i5 小时前
MySQL win安装 和 pymysql使用示例
数据库·mysql
小怪兽ysl5 小时前
【PostgreSQL使用pg_filedump工具解析数据文件以恢复数据】
数据库·postgresql
wqq_9922502775 小时前
springboot基于微信小程序的食堂预约点餐系统
数据库·微信小程序·小程序
爱上口袋的天空5 小时前
09 - Clickhouse的SQL操作
数据库·sql·clickhouse
聂 可 以7 小时前
Windows环境安装MongoDB
数据库·mongodb