今天同事遇到了符号冲突的问题,比较有意思,拿出来跟大家分享一下。先简单描述一下问题:
一个程序依赖于libzookeeper_mt.a和libmemcached.a,但是有一个符号htonll在两个lib中都出现了,这样在链接的时候,就报了"multiple definition of htonll"的错误。
最简单的思路,上google搜吧,在同事的电脑上,用google搜索"linux multi definition"后,发现,前两页的结果链接都是红的,都被他点过了(昨天干到一点多,敬业啊!!!)。估计是这些文章没啥帮助了,就开始换个思路,链接的时候出问题,能不能让ld跳过这种问题呢。
allow-multiple-definition
接下来,开始man ld,查找multi,果然收获丰富。
--allow-multiple-definition -z muldefs Normally when a symbol is defined multiple times, the linker will report a fatal error. These options allow multiple definitions and the first definition will be used. |
--allow-multiple-definition 强制编译过去,使用第一个被定义的地方,也就是首先被链接的库,跟链接的顺序相关。
接下来需要做的就是把这个选项通过g++传递给ld,man g++,发现:
-Xlinker option Pass option as an option to the linker. You can use this to supply system-specific linker options which GCC does not know how to recognize. If you want to pass an option that takes an argument, you must use -Xlinker twice, once for the option and once for the argument. For example, to pass -assert definitions, you must write -Xlinker -assert -Xlinker definitions. It does not work to write -Xlinker "-assert definitions", because this passes the entire string as a single argument, which is not what the linker expects. -Wl,option Pass option as an option to the linker. If option contains commas, it is split into multiple options at the commas. |
到现在,看来通过--allow-multiple-definition让ld强制使用某个lib中的htonll就可以解决问题,但是--allow-multiple-definition使用的时候需要注意两个实现最好是差不多的,好在htonll实现类似,可以直接这么搞:-)
问题似乎是到此结束了,但是好像还有一些地方没有闹明白,之前我们也这么用过,为什么之前没有出错,现在出错了呢?
查看了一下Makefile,忽然发现一个问题。之前使用是自己手写Makefile,-Llib_path -llibname的方式使用的;新的是使用公司的自动编译工具生成的,-Xlinker -( lib_full_path -)形式的。
做下实验吧:
libx.cpp
|
|
g++ -o main main.cpp libx.o liby.o |
但是如果把libx.o和liby.o分别打包成libx.a和liby.a用下面的方式编译
g++ -o main main.cpp -L./ -lx -ly |
但是注意不是所有的情况都是这样的,由于链接是以.o为单位的,完全可以不用某个.o的时候才不会出错误,否则依然会出现multipe的错误, 这种情况下的建议是查看一下这些函数的行为是什么样子,是否是一致的,如果不一致,还是想办法规避, 如果是一致的话可以用 -Wl,--allow-multiple-definition 强制编译过去,这样会使用第一个碰到的库,但不推荐这样做。
可以通过
g++ -o main main.cpp -L./ -lx -ly -Wl,--trace-symbol=_Z3foov |
g++ -o main main.cpp -L./ -lx -ly -Wl, --cref |
symbol rename
问题就这么解决了吗?似乎感觉还不是很完美,multiple definition不就是符号冲突了吗,重命名一下符号不就好了吗,那怎么重命名符号呢?
开始各种man,什么objcopy,objdump,nm,ld,拿着rename去爆长的man中查找,无果,尝试换个关键词redefine,让我撞上了狗屎运(没找到之前我都杯具得要自己写工具了)。
--redefine-sym old=new Change the name of a symbol old, to new. This can be useful when one is trying link two things together for which you have no source, and there are name collisions. --redefine-syms=filename Apply --redefine-sym to each symbol pair "old new" listed in the file filename. filename is simply a flat file, with one symbol pair per line. Line comments may be introduced by the hash character. This option may be given more than once. |
做个简单的测试:
test.c和main.c,main.c调用test.c中的test1函数,将test.o和main.o中的test1符号改为test2。
Makefile巨简单:
test: gcc -c test.c gcc -c main.c objcopy --redefine-sym test1=test2 test.o new_test.o objcopy --redefine-sym test1=test2 main.o new_main.o gcc -o $@ new_test.o new_main.o |
运行结果:
debian-wangyao:~/Test/symbol$ ./test in test: test1 debian-wangyao:~/Test/symbol$ nm test.o 0000000d r __func__.1705 U printf 00000000 T test 0000001c T test1 debian-wangyao:~/Test/symbol$ nm new_test.o 0000000d r __func__.1705 U printf 00000000 T test 0000001c T test2 debian-wangyao:~/Test/symbol$ nm main.o 00000000 T main U test1 debian-wangyao:~/Test/symbol$ nm new_main.o 00000000 T main U test2 |
好了,接下来的事情就简单多了,解开.a库,把所有的.o文件中的需要修改的符号进行修改就可以了。
直接上脚本。
|
问题是不是就可以这么解决了呢?还有没有其他办法呢?似乎可以将冲突的符号改为弱符号来搞定,这样起到的作用就是让ld只链接强符号的库,来解决multiple definition。具体怎么搞,留给大家吧~~~
总结
1. C++中命名空间很重要啊
2. C风格的封装如果是常见的函数名,最好加些特殊前缀啊;实在不行,可以最后进行define
3. 自己的代码一定要注意符号冲突,上面这些仅限于第三方库冲突的时候;这种情况最好是联系开源社区,改代码