一、背景介绍
需求描述:业务提供一个鉴权的静态库,我只需要从url 和 参数中提取相应的参数传给业务的静态库,将静态库的处理结果直接返回给业务(return < 0, 直接返回403)。
但是最近业务反馈,请求部分url直接返回Error:-1015错误,不符合预期。
二、分析过程
业务刚开始反馈问题,我的第一反应:怎么可能是我的问题,我就仅仅写了一个so(暂时命名1.so),直接调用了你们静态库,这个-1015不就是你们库返回的结果。 有问题也是业务提供的库有问题。 或者就是业务配置不正确。 我临时解决方案:
- 让业务检查配置 ,是否和校验级别有关系?
- 双方md5的静态库,本地调试结果是否正确?
- 上述方案不行: 采用最挫的方案, 既然静态库吐出来的结果,疯狂加printf,我就不信这个邪了
最后还是走到方案3,业务说printf的结果不符合预期,经业务提醒,突然意识到是不是调用链接到其他库的xx_func?
因为项目比较大, 涉及的库比较多,我用nm认真检查了编译时加载的库,竟然还真找到一模一样的接口xx_func
nm ./lib/liba.a | grep xx_func
编译代码的时候,赶紧去掉了同名函数库(因为在makefile文件编译的时候某些公共库默认都会编译进去)。重新编译版本。
没有想到,去掉同名函数库,还是存在问题,为什么呢?到底是哪里的问题?
随手直接 nm *.so | grep xx_func
,结果在另外一个so(暂时命名2.so)中,发现了同名函数xx_func。 看了一下配置,程序启动的时候首先加载了2.so动态库, 1.so是在2.so后面加载的. 因为优先加载哪个库,就使用哪个库的同名函数,并不会覆盖的情况(自己现网测试 + 测试代码)
到这里问题的原因也算是找到了,下面接着就是如何解决问题?
三、解决方案
1. 最简单的方案 -- 修改函数名称
因为业务本身提供的静态库,用nm
查看函数也不多,为了安全起见,修改每个函数的名称(增加一些业务私有的东西)。 批量修改函数名可直接使用sed
2. -fvisibility=hidden -- 最安全最推荐的做法
现在C/C++这一块有几十年的历史,该踩的坑大家都应该踩过去了。gcc编译器就直接提供-fvisibility=hidden
编译选项,直接让库函数global可见变成local可见, 使用__attribute__((visibility("default")))
直接将需要的函数暴漏在外面就行
-fvisibility=[default|internal|hidden|protected] Set the default ELF image symbol visibility to the specified option---all symbols are marked with this unless overridden within the code. Using this feature can very substantially improve linking and load times of shared object libraries, produce more optimized code, provide near-perfect API export and prevent symbol clashes. It is strongly recommended that you use this in any shared objects you distribute. A good explanation of the benefits offered by ensuring ELF symbols have the correct visibility is given by "How To Write Shared Libraries" by Ulrich Drepper (which can be found at http://www.akkadia.org/drepper/)---however a superior solution made possible by this option to marking things hidden when the default is public is to make the default hidden and mark things public.
将默认ELF符号可见性设置为指定选项---除非在代码中覆盖,否则所有符号都标记为此。 使用此功能可以极大地改善共享对象库的链接和加载时间,生成更优化的代码,提供近乎完美的API导出并防止符号冲突。 强烈建议您在分发的任何共享对象中使用它 Ulrich Drepper的“如何编写共享库”(可在http://www.akkadia.org/drepper/上找到)中给出了确保ELF符号具有正确可见性所带来的好处的一个很好的解释 - - 当默认为公共时,通过此选项可以标记隐藏的内容的优秀解决方案是隐藏默认值并将事物标记为公共
四、练习实践---验证动态库的同名函数调串
1.代码结构很简单
.
├── libqqmusic.c
├── libtmemusic.c
├── main
├── main.c
├── makefile
└── README.md
2. libqqmusic.c
//libqqmusic.c
#include<stdio.h>
int get_music_id()
{
return 1111;
}
void qqmusic_print()
{
printf("qqmusic id: %d\n", get_music_id());
return;
}
3. libtmemusic.c
//libtmemusic.c
#include<stdio.h>
int get_music_id()
{
return 2222;
}
void tmemusic_print()
{
printf("tmemusic id: %d\n", get_music_id());
return;
}
4. main.c
//main.c
#include<stdio.h>
int main(int argc, char *argv[])
{
printf("start\n");
tmemusic_print();
qqmusic_print();
tmemusic_print();
printf("end\n");
return 0;
}
5.makefile
# 1. create /etc/ld.so.conf.d/vaynedu-test.conf
# /usr/lib64/vaynedu
# 2. mkdir /usr/lib64/vaynedu
#
# 3. \cp *.so /usr/lib64/vaynedu
all:
gcc -g -fpic -c -o libqqmusic.o libqqmusic.c
gcc -g -shared -o libqqmusic.so libqqmusic.o
gcc -g -fpic -c -o libtmemusic.o libtmemusic.c
gcc -g -shared -o libtmemusic.so libtmemusic.o
\cp *.so /usr/lib64/vaynedu
@ldconfig
#gcc -g -o main main.c -L. -lqqmusic -ltmemusic
gcc -g -o main main.c -L. -ltmemusic -lqqmusic
@echo -e "\n\ncompile complete\n\n"
clean:
rm -fr *.so *.o
6.测试结果--证明同名函数库受编译时加载顺序的影响
将tmemusic放在前面gcc -g -o main main.c -L. -ltmemusic -lqqmusic
将qqmusic放在前面
gcc -g -o main main.c -L. -lqqmusic -ltmemusic
五、总结
《程序的自我修养-链接、装载与库》应该好好的啃一遍, 这个一个后台开发必备的知识技能
-fvisibility=hidden的用法 https://blog.csdn.net/caspiansea/article/details/77113066 多共享动态库中同名对象重复析构问题的解决方法 https://www.ibm.com/developerworks/cn/linux/l-cn-sdlstatic/ GCC的符号可见性——解决多个库同名符号冲突问题 http://blog.chinaunix.net/uid-27875-id-5759441.html