Back to home

这样的链接错误,您碰到过么?

初次发表于2015/9/9

【问题】
最近在做chromium相关的工作,牵扯到部分源码的改写和重新编译。玩过的人都知道,chromium源码工程异常庞大,为了方便编译10几个G的内容和解决依赖,其提供了一整套定制的编译工具和脚本。当我们在js v8引擎源码中添加了部分代码,声明了一个全局变量,会编译生成libv8.so。我们期望在v8引擎之外使用这个全局变量的时候,用其定制的编译工具,编译通过,链接却失败
$ ninja -C out/Debug/ chrome chrome_sandbox
ninja: Entering directory `out/Debug/'
[55/151] SOLINK lib/libcontent.so
FAILED: if [ ! -e lib/libcontent.so -o ! -e lib/libcontent.so.TOC ]; then ../../third_party/llvm-build/Release+Asserts/bin/clang++ -shared -Wl,-z,now -Wl,-z,relro -Wl,--fatal-warnings -Wl,-z,defs -pthread -Wl,-z,noexecstack -fPIC -fuse-ld=gold -B/mnt/disk2/chromium/src/third_party/binutils/Linux_x64/Release/bin -Wl,--disable-new-dtags -L. -Wl,-uIsHeapProfilerRunning,-uProfilerStart -Wl,-u_Z21InitialMallocHook_NewPKvj,-u_Z22InitialMallocHook_MMapPKvS0_jiiix,-u_Z22InitialMallocHook_SbrkPKvi -Wl,-u_Z21InitialMallocHook_NewPKvm,-u_Z22InitialMallocHook_MMapPKvS0_miiil,-u_Z22InitialMallocHook_SbrkPKvl -Wl,-u_ZN15HeapLeakChecker12IgnoreObjectEPKv,-u_ZN15HeapLeakChecker14UnIgnoreObjectEPKv -m64 -Wl,--detect-odr-violations -Wl,--icf=all -o lib/libcontent.so -Wl,-soname=libcontent.so @lib/libcontent.so.rsp && { readelf -d lib/libcontent.so | grep SONAME ; nm -gD -f p lib/libcontent.so | cut -f1-2 -d' '; } > lib/libcontent.so.TOC; else ../../third_party/llvm-build/Release+Asserts/bin/clang++ -shared -Wl,-z,now -Wl,-z,relro -Wl,--fatal-warnings -Wl,-z,defs -pthread -Wl,-z,noexecstack -fPIC -fuse-ld=gold -B/mnt/disk2/chromium/src/third_party/binutils/Linux_x64/Release/bin -Wl,--disable-new-dtags -L. -Wl,-uIsHeapProfilerRunning,-uProfilerStart -Wl,-u_Z21InitialMallocHook_NewPKvj,-u_Z22InitialMallocHook_MMapPKvS0_jiiix,-u_Z22InitialMallocHook_SbrkPKvi -Wl,-u_Z21InitialMallocHook_NewPKvm,-u_Z22InitialMallocHook_MMapPKvS0_miiil,-u_Z22InitialMallocHook_SbrkPKvl -Wl,-u_ZN15HeapLeakChecker12IgnoreObjectEPKv,-u_ZN15HeapLeakChecker14UnIgnoreObjectEPKv -m64 -Wl,--detect-odr-violations -Wl,--icf=all -o lib/libcontent.so -Wl,-soname=libcontent.so @lib/libcontent.so.rsp && { readelf -d lib/libcontent.so | grep SONAME ; nm -gD -f p lib/libcontent.so | cut -f1-2 -d' '; } > lib/libcontent.so.tmp && if ! cmp -s lib/libcontent.so.tmp lib/libcontent.so.TOC; then mv lib/libcontent.so.tmp lib/libcontent.so.TOC ; fi; fi
../../content/renderer/render_frame_impl.cc:2973: error: undefined reference to 'v8::tacLog'
../../content/renderer/render_frame_impl.cc:2975: error: undefined reference to 'v8::tacLog'
../../content/renderer/render_frame_impl.cc:2976: error: undefined reference to 'v8::tacLog'
../../content/renderer/render_frame_impl.cc:2977: error: undefined reference to 'v8::tacLog'
clang: error: linker command failed with exit code 1 (use -v to see invocation)
ninja: build stopped: subcommand failed.
【分析】
对于链接失败,具体成因有人总结了http://ticktick.blog.51cto.com/823160/431329,具体问题具体分析。
顺着编译脚本out/Debug/build.ninja 查找到so链接相关的内容
rule solink
command = if [ ! -e $lib -o ! -e $lib.TOC ]; then $ld -shared $ldflags -o $lib -Wl,-soname=$soname @$link_file_list $
&& { $readelf -d $lib | grep SONAME ; $nm -gD -f p $lib | cut -f1-2 -d' '; } > $lib.TOC; else $ld -shared $
$ldflags -o $lib -Wl,-soname=$soname @$link_file_list && { $readelf -d $lib | grep SONAME ; $nm -gD -f p $lib | $
cut -f1-2 -d' '; } > $lib.tmp && if ! cmp -s $lib.tmp $lib.TOC; then mv $lib.tmp $lib.TOC ; fi; fi
description = SOLINK $lib
pool = link_pool
restat = 1
rspfile = $link_file_list
rspfile_content = -Wl,--whole-archive $in $solibs -Wl,--no-whole-archive $libs
通过脚本与编译错误信息对比可知,$link_file_list=lib/libcontent.so.rsp
而在out/Debug/lib/libcontent.so.resp中,其实是包含了lib/libv8.so的。也就是说so文件存在,并且被正确地链接了。看来别人总结的成因没有覆盖我们的情况。
【解决】
nm out/Debug/lib/libv8.soreadelf -s out/Debug/lib/libv8.so查看,发现我们要使用的全局对象tacLog是有的,然而使用后者的命令能给我们更多信息:
Num: Value Size Type Bind Vis Ndx Name
45870: 0000000000972318 8 OBJECT LOCAL HIDDEN 27 _ZN2v86tacLogE
也就是说chromium源码在编译的时候,符号的可见性被隐藏了。再深究下,发现是chromium源码编译时,默认加了-fvisibility=hidden选项,这就导致在v8中定义的全局变量在外部不能访问了。
知道了原因,解决也就简单了,给全局变量和外部需要访问的接口加上__attribute__((visibility("default"))),至此编译链接通过!
【总结】
Chromium编译工具可能使用了gcc很多我们不常用的编译和优化选项,如
-fvisibility=default|internal|hidden|protected
一方面可以保证只暴露真正对外开放的接口,另一方面也可以小小提升下性能。
详见https://www.technovelty.org/code/why-symbol-visibility-is-good.html。当需要在对chromium源码进行修改扩展时,特殊的编译选项是一个排查编译问题的不容错过的点。