引言
动态库的编译,这有什么难度,这不是手到擒来的事情吗?无非不就是:
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,人生尽是坦途