您的位置:首页 > 运维架构 > Linux

Linux环境下的编译,链接与库的使用

2013-08-23 21:11 253 查看
链接参数控制

链接器中提供了-dn -dy 参数来控制使用的是动态库还是静态库,-dn表示后面使用的是静态库,-dy表示使用的是动态库

例:

g++ -Lpath -Wl,-dn -lx -Wl,-dy -lpthread 这样如果在path路径下有libx.so和libx.a这个时候只会用到 libx.a.

注意在最后的地方如果没有-Wl,-dy 让后面的库都使用动态库,可能会报出 “cannot find -lgcc_s” 的错误,这是由于glibc的.a库和.so库名字不同,–static会自动处理,但是 -Wl,-dy却不会去识别这个问题.

小提示:

如果使用–static, 由于-dy的使用导致后面的库都是共享库(dy强制屏蔽了静态库),这个时候编译出来的程序和只有动态库的情况下强制使用–static编译一样都会报错

运行报错 ”undefined reference to `xxx()’ ” 对 于动态链接库,实际的符号定位是在运行期进行的.在编译.so的时候,如果没有把它需要的库和他一起进行联编,比如libx.so 需要使用uldict, 但是忘记在编译libx.so的时候加上-luldict的话,在编译libx.so的时候不会报错,因为这个时候libx.so被认为是一个库,它里面 存在一些不知道具体实现的符号是合法的,是可以在运行期指定或者编译另外的二进制程序的时候指定.

如果是采用 g++ -Lpath -lx 的方式进行编译,链接器会发现所需要的uldict的符号表找不到从而报错,但是如果是程序采用dlopen的方式载入,由于是运行期,这个程序在这个地 方就直接运行报错了. 另外还有一种情况就是一个对外的接口在动态库中已经声明定义了,但是忘记实现了,这个时候也会产生类似的错误.

如果在运行期报出这样的错误,就要注意是否是由于某些库没有链接进来或者某些接口没有实现的原因产生

日志库问题 其实不只是日志库存在这样的问题,其他需要同时被多个动态库以及主程序同时使用的函数其实都存在这样的问题.这里主要以日志库的问题为例来说明这些问题.

有 一个程序,它通过dlopen的方式调用了一个.so文件. 在这个主程序中和.so中都使用了日志库,主程序中使用ul_openlog打开了日志, 在.so中没有用ul_openlog打开日志.这个时候发现,主程序中的日志正常输出,但.so中的日志却直接输出到了标准出错.

这个问 题的原因在前面的其实已经提到了,在默认情况下主程序中使用的接口对于.so是不可见的,.so所在的代码空间与主程序的代码空间是隔离的,这个时 候.so调用的ul_writelog其实是没有经过ul_openlog的那块代码空间,由于ul_log库使用了一些static变量(如果是带 comlog的ul_openlog那还有全局符号),只有在ul_writelog, ul_openlog都是在同一块空间上的时候才会起作用.

这个问题的一个最简单的解决方案是

在主程序的链接的时候加入-rdynamic,仍然链接libullib.a库 编译动态链接库时,不加链接libullib.a库 其实动态库这里是否链了libullib.a已经不重要了,在有-rdynamic的情况下,.so中如果有与主程序同名的函数那么会优先调用主程序中的函数, 动态库不链接libullib.a倒是可以省点空间

但是这种方式在某些情况还是不能完全解决问题

假 设有A.so, B.so,主程序main, 在A.so中调用了ul_openlog, B.so中没有调用ul_openlog, 但调用了ul_writelog. 在主程序中没有调用ul_log中的任何接口和使用任何变量.这种情况下即时使用了-rdynamic还是会导致在 A.so中正常输出日志,但在B.so中却把日志输出到标准出错.

这个问题的主要原因在于,gcc在链接的时候是以.o为单位的,如果一 个.o中的符号没有被外部所使用,那么在链接的时候就不会把这个.o中的符号给链接进行.so或者二进制程序中.在上面的问题中主程序里面没有调用到日志 库中的任何符号,所以在链接的时候就不会把ullib中的ul_openglog和ul_writelog给链接进行主程序中,这个时候即使有 -rdynamic也是做不到让.so中的动态链接库都使用.

这个问题一般有下面几种方案:

载入A.so的时候使用RTLD_GLOBAL参数,把A.so中的所有的符号都变成对外可见,这样A.so和B.so的ul_writelog都在一块代码空间中了 编译主程序的时候链接ullib的地方由-lullib改为 -Wl,–whole-archive -lullib -Wl,–no-whole-archive, 同时加上-rdynamic. -Wl, –whole-archive是链接参数,它表示把ullib的中所有的符号都链接进主程序中,而不管主程序是否调用了ullib中的符 号.-Wl,–no-whole-archive表示后面的链接取消–whole-archive的行为,毕竟其他的库没有必要采用这种方式全部链接
进来. 在主程序中随便调一下ul_log中的符号,比如可以先随便ul_openlog一下,然后ul_closelog, 后面再进行动态库调用. 把libullib.a用 ar x 命令还原成多个.o文件,采用直接链接的方式使用ul_log.o. 上 面的几个问题的产生主要还在于静态链接和动态链接混用,而两种链接方式又存在不一样的地方.事实上如果我们把ullib库 采用动态链接库的方式编译成 libullib.so, 采用前面的方式在编译期链接libullib.so,并且设置LD_LIBRARY_PATH,
上面的2个问题都不会存在. 编译期使用的.so,是全局可见的,不需要-rdynamic也可以被dlopen的动态库所用到,由于是动态链接库,所以 包含了所有的符号,不会像静态库那样只包含了所用到的.o中的符号. 事实上这也是多数第三方程序的解决方案

主程序中使用-rdynamic会对后续的升级造成一些麻烦:

加上-rdynamic后,像日志库这样的基础如果需要升级, 那么就必须要升级主程序, 使用的.so无论如何升级所用到的ul_writelog都是主程序中的. 主程序中除了日志库,还会有其他库或者函数的存在, 这些函数如果不是static的就有可能与 dlopen打开的so中的函数混到一起,造成困惑 如果.so中需要打印它自己的日志, 那样需要comlog本身功能的支持才可以实现,而不能简单的使用ul_writelog来实现 但是如果主程序中没有使用-rdynamic,那么又有下面的这些麻烦 dlopen打开的动态库日志是打印自己的,
不能和主程序统一在一起. 如果.so的程序和主程序open的是同一个日志,这相当于多进程打日志, 那必须要comlog的支持,ullog本身不支持多进程打同一个日志. 如果主程序中用dlopen+RTLD_GLOBAL的打开了某个.so 日志的问题就可能影响到其他的.so中的调用. 同时对于一些老程序, 升级前后-rdynamic 可能也会产生影响,比如两次ul_openlog 这里对于类似日志库这种需要全局状态变量支持的库提出另外方案

编译一个专门的.so, 这个.so中包括了其它.so中所需要的所有和全局量相关的接口 2. 主程序不使用-rdynamic编译, 但打开上面的.so的时候,采用RTLD_GLOBAL方式,并且是第一个打开 3. 除了打开第一个 .so, 其它的.so都不使用RTLD_GLOBAL方式, 并且在编译的时候都不把和第一.so相关的库联编 4. 第一个.so的升级需要保证没有其它.so在运行才可以dlclose, 重新dlopen

这个问题首先需要明确需求, 到底是希望每个.so打自己独立的日志,还是和主线程统一

这 里要注意另外一个问题就是目前的ullib 日志库情况比老的ullog要复杂, 在comlog中引入一些extern 出来的全局变量 在采用dlopen的时候,对于一般符号,一般都是主程序和动态库在两块空间中, 但是对于使用extern出来的变量主程序和动态库都是在一块空间中(注:由于32位下不用-fPIC也可以编译so, 在没有-fPIC的情况也是分开的, 但是由于64位一定要-fPIC所以一定会出现同一块空间的问题), 对于这个问题的解决方案是在动态库链接的时候加上 -Wl,-Bsymbolic 参数将动态库的空间和主程序的空间强行分开。

上面有提到 编译动态链接库时,不加链接libullib.a库,但主程序使用-rdynamic, 这里主要是为了避免使用到了不同的ullib导致调用了一些不同的内部符号,导致出现另外的麻烦

对于动态库中的日志建议采用下面的几个方案:

动态库完全打自己的,日志,编译二进制程序不要用-rdynamic, 动态库链接的编译加上 -Wl,-Bsymbolic 参数 , 链接ullib, 在动态库中自己open,自己控制等级 1. 动态库不链接ullib, 编译二进制程序用-rdynamic , 这样可以正确的使用主程序中的日志库,也规避了版本不一致带来的问题, 但是这样失去了对于动态库日志的控制, 而且存在升级的不便, 日志的升级是由主程序控制的。

小提示:

有关动态库使用的例子还可以参考 SoTips 在运行期可以通过设置环境变量LD_DEBUG查看每个符号具体链接到了什么地方,每个符号具体的查找过程和绑定过程.可以这样使用 export LD_DEBUG=help 随便运行一个程序就可以看到对于LD_DEBUG的使用说明

export LD_DEBUG=files./main 可以看到整个装载过程
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: