Chinaunix首页 | 论坛 | 博客
  • 博客访问: 5760571
  • 博文数量: 675
  • 博客积分: 20301
  • 博客等级: 上将
  • 技术积分: 7671
  • 用 户 组: 普通用户
  • 注册时间: 2005-12-31 16:15
文章分类

全部博文(675)

文章存档

2012年(1)

2011年(20)

2010年(14)

2009年(63)

2008年(118)

2007年(141)

2006年(318)

分类: C/C++

2011-05-17 19:07:12

今天同事遇到了符号冲突的问题,比较有意思,拿出来跟大家分享一下。先简单描述一下问题:
一个程序依赖于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

int foo()
{
return 20;
}

liby.cpp

int foo()
{
return 20;
}

将libx.cpp, liby.cpp编译成 libx.o和liby.o两个文件
g++ -o main main.cpp libx.o liby.o
这个时候就会报出 multiple definition of `foo()' 的错误
但是如果把libx.o和liby.o分别打包成libx.a和liby.a用下面的方式编译
g++ -o main main.cpp -L./ -lx -ly
这个时候编译不会报错,它会选择第一个出现的库,上面的例子中会选择libx中的foo。

但是注意不是所有的情况都是这样的,由于链接是以.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文件中的需要修改的符号进行修改就可以了。

直接上脚本。

oldsym="htonll"
newsym="myhtonll"
targetlib="libmemcached.a"

function renamesym
{
    objcopy --redefine-sym $2=$3 $1 ${1}.new
    mv ${1}.new ${1}
}

ar x ${targetlib}

for file in `ls| grep \.o$`
do
    renamesym $file $oldsym $newsym
done

rm -rf ${targetlib}
ar rv ${targetlib} *.o
rm -rf *.o



问题是不是就可以这么解决了呢?还有没有其他办法呢?似乎可以将冲突的符号改为弱符号来搞定,这样起到的作用就是让ld只链接强符号的库,来解决multiple definition。具体怎么搞,留给大家吧~~~

总结
1. C++中命名空间很重要啊
2. C风格的封装如果是常见的函数名,最好加些特殊前缀啊;实在不行,可以最后进行define
3. 自己的代码一定要注意符号冲突,上面这些仅限于第三方库冲突的时候;这种情况最好是联系开源社区,改代码

参考:


阅读(6601) | 评论(1) | 转发(4) |
给主人留下些什么吧!~~

aanchun19892016-10-26 16:09:45

厉害,厉害,下午看了阁下的blog,真是受益匪浅,