当前位置: 首页 > 科技观察

原来编译链接的套路还真多

时间:2023-03-15 09:39:06 科技观察

本文转载自微信公众号《程序喵大师》,作者程序喵大师。转载本文请联系程序大师喵公众号。大家好,我是程序喵。不知道大家在编程的过程中,使用动态链接库的频率有多少。如果一个程序引用了无数的动态链接库,就可能会引入符号冲突。问题如下:在我们的想象中,我们将在下面尝试解决它。:开头介绍g++的基本命令参数:g++-c编译源文件,但不链接-o指定输出文件名-sstrip,去除符号信息-L

命令搜索链接库-l的路径指定要链接的链接库-shared生成动态目标??文件先看一段代码:#includevoidDoThing(){printf("work\n");}定义一个简单的main.cc程序:#includevoidDoThing();intmain(){printf("start\n");DoThing();printf("finished\n");return0;}编译这两个文件,并将它们打包成静态库:g++-cwork.cc-owork.oarrclibwork.awork.og++-cmain.cc-omain.oarrclibmain.amain.o现在链接这两个静态库进入可执行文件,注意链接如果链接器发现当前库中使用了未定义的符号,它只会向后查找。因此,最底层没有其他依赖的库应该放在最右边。如果存在符号冲突,链接器将使用最左边的符号。如果你这样链接:$g++-s-L.-omain.exe-lwork-lmain./libmain.a(main.o):Infunction`main':main.cc:(.text+0x11):undefinedreferenceto`DoThing()'collect2:error:ldreturned1exitstatus链接失败,因为主库中的DoThing符号没有定义,链接器向后查找,没有找到对应的符号定义,这里改变链接库的顺序:g++-s-L.-omain.exe-lmain-lwork$./main.exestartworkfinished链接成功。现在编写一个简单的容易出现符号冲突的文件conflict.cc:#includevoidDoThing(){printf("conflict\n");}编译打包成静态库:g++-cconflict.cc-oconflict如果.oarrclibconflict.aconflict.o按以下顺序链接到可执行程序:$g++-s-L.-omain.exe-lmain-lwork-lconflict$./main.exestartworkfinished如果链接顺序稍作更改:$g++-s-L.-omain.exe-lmain-lconflict-lwork$./main.exestartconflictfinished这里发现是顺序的不同导致程序输出的内容不同,原因是潜在的符号冲突。现在再试试动态库,先介绍一下动态库的使用方法:$rmlibconflict.a$g++-sharedconflict.o-olibconflict.so$g++-s-L.-omain.exe-lmain-lconflict$LD_LIBRARY_PATH=../main.exestartconflictfinished现在引用一个中间层调用动态链接库中的冲突文件layer.cc#includevoidDoThing();voidDoLayer(){printf("layer\n");DoThing();}并将有冲突的layer打包成动态链接库:$g++-clayer.cc-olayer.o$g++-sharedlayer.oconflict.o-olibconflict.so然后更新main.c程序,在main中调用layer,调用层冲突:#includevoidDoLayer();intmain(){printf("start\n");DoLayer();printf("finished\n");return0;}编译链接执行:$g++-cmain.cc-omain.o$arrclibmain.amain.o$g++-s-L.-omain.exe-lmain-lconflict$LD_LIBRARY_PATH=../main.exestartlayerconflictfinished输出正常,没问题,现在把之前的work.cc放进去主要的。在cc中,观察冲突:#includevoidDoThing();voidDoLayer();intmain(){printf("start\n");DoThing();DoLayer();printf("finished\n");return0;}将work.o和main.o打包成一个库,然后用冲突链接成可执行程序,运行:$g++-cmain.cc-omain.o$arrclibmain.amain.owork.o$g++-s-L.-omain.exe-lmain-lconflict$LD_LIBRARY_PATH=../main.exestartworklayerworkfinished这里输出了两个作品。一般情况下,第二个工作应该输出冲突。如何解决?是考虑使用-fvisibility=hidden隐藏内部符号,链接库内部使用的符号隐藏它,不让它导出,外部不会改变它的调用路径先使用nm看一下libconflict.so里面的符号:$nm-CDlibconflict.sow_ITM_deregisterTMCloneTablew_ITM_registerTMCloneTable000000000000065aTDoLayer()0000000000000672TDoThing()0000000000201030B__bss_startw__cxa_finalizew__gmon_start__0000000000201030D_edata0000000000201038B_end0000000000000688T_fini0000000000000528T_initUputs如果把符号隐藏掉,$g++-fvisibility=hidden-clayer.cc-olayer.o$g++-fvisibility=hidden-cconflict.cc-oconflict.o$g++-sharedlayer.oconflict.o-olibconflict.so再使用nm看一下libconflict.so里面的符号:$nm-CDlibconflict.sow_ITM_deregisterTMCloneTablew_ITM_registerTMCloneTable0000000000201028B__bss_startw__cxa_finalizew__gmon_start__0000000000201028D_edata0000000000201030B_end0000000000000618T_fini00000000000004c0T_initUputs这样的话main函数肯定不能调用DoLayer啦,因为DoLayerSymbolnotexposed:$g++-s-L.-omain.exe-lmain-lconflict./libmain.a(main.o):Infunction`main':main.cc:(.text+0x16):undefinedreferenceto`DoLayer()'collect2:error:ldreturned1exitstatuthenhowtoexposespecificsymbols,lookatthecodedirectly,changelayer.cc:#includevoidDoThing();__attribute__((visibility("default")))voidDoLayer(){printf("layer\n");DoThing();}再次编译链接看结果:$g++-fvisibility=hidden-clayer.cxx-olayer.o$g++-sharedlayer.oconflict.o-olibconflict.so$g++-s-L.-omain.exe-lmain-lconflict$LD_LIBRARY_PATH=../main.exestartworklayerconflictfinished发现是我们预期的结果,符号冲突是不是觉得解决问题很麻烦?是否每个要暴露的符号都必须用__attribute__修改?这里实际上可以写一个导出文件,告诉编译器要导出的所有符号是什么。export.txt{global:*DoLayer*;local:*;};g++-Wl,--version-script=export.txt-s-sharedlayer.oconflict.o-olibconflict.so不过这个方法只有gcc才有可以使用。我尝试在clang中使用但是没有成功,所以为了兼容不建议使用这种方式。最好停止使用__attribute__来解决符号冲突问题。