分类: LINUX
2010-03-26 11:24:13
/usr/bin/ld: /tmp/ccQ1dkqh.o: relocation R_X86_64_32 against `a local symbol' can not be used when making a shared object; recompile with -fPIC
提示说需要-fPIC编译,然后在链接动态库的地方加上-fPIC的参数编译结果还是报错,需要把共享库所用到的所有静态库都采用-fPIC编译一边才可以成功的在64位环境下编译出动态库。
这里的-fPIC指的是地址无关代码
这里首先先说明一下装载时重定位的问题,一个程序如果没有用到任何动态库,那么由于已经知道了所有的代码,那么装载器在把程序载入内存的过程中就可 以直接安装静态库在链接的时候定好的代码段位置直接加载进内存中的对应位置就可以了。但是在面对动态的库的时候 ,这种方式就不行了。假设需要载入共享库A,但是在编译链接的时候使用的共享库和最后运行的不一定是同一个库,在编译期就没办法知道具体的库长度,在链接 的时候就没办法确定它或者其他动态库的具体位置。另一个方面动态库中也会用到一些全局的符号,这些符号可能是来自其他的动态库,这在编译器是没办法假设的 (如果可以假设那就全是静态库了)
基于上面的原因,就要求在载入动态库的时候对于使用到的符号地址实现重定位。在实现上在编译链接的时候不做重定位操作,地址都采用相对地址,一但到了需要载入的时候,根据相对地址的偏移计算出最后的绝对地址载入内存中。
但是这种采用装载时重定位的方式存在一个问题就是相同的库代码(不包括数据部分)不能在多个进程间共享(每个代码都放到了它自己的进程空间中),这个失去了动态库节省内存的优势。
为了解决这个问题,ELF中的做法是在数据段中建立一个指向那些需要被使用(内部的位置无关简单采用相对地址访问就可以实现)的地址列表(也被称为全局偏移表,Global offset table, GOT). 可以通过GOT相对应的位置进行间接引用.
对于我们的32位环境来说, 编译时是否加上-fPIC, 都不会对链接产生影响, 只是一份代码的在内存中有几个副本的问题(而且对于静态库而言结果都是一样的).但在64位的环境下装载时重定位的方式存在一个问题就是在我们的64位环 境下用来进行位置偏移定位的cpu指令只支持32位的偏移, 但实际中位置的偏移是完全可能超过64位的,所以在这种情况下编译器要求用户必须采用fPIC的方式进行编译的程序才可以在共享库中使用
从理论上来说-fPIC由于多一次内存取址的调用,在性能上会有所损失.不过从目前的一些测试中还无法明显的看出加上-fPIC后对库的性能有多大的损失,这个可能和我们现在使用的机器缓存以及大量寄存器的存在相关.
小提示:
共享内存在只读的情况下性能和读普通内存是一样的(如果不算第一载入的消耗),而且由于是多个进程共享对cpu cache还显的相对友好。
前面提到编译动态库的时候有提到编译动态库可以像编译静态库那样采用-Lpath -lxx的方式进行, 但这里存在一个问题,如果在path目录下既有动态库又有静态库的时候的行为又是什么样地? 事实上在这种情下, 链接器优先选择采用动态库的方式进行编译.比如在同一目录下存在 libx.a 和 libx.so, 那么在链接的时候会优先选择libx.so进行链接. 这也是为什么在com组维护的第三方库(third, third-64)中绝大多数库的产出物中只有.a的存在, 主要就是为了避免在默认情况下使用到.so的库, 导致在上线的时候出现麻烦(特别是一些系统中存在,但又与我们需要使用的版本有出入的库).
为了能够控制动态库和静态库的编译, 有下面的几种方式
编译的时候如果不采用-Lpath -lxx的方式进行编译, 而且直接写上 path/libx.a 或者 path/libx.so 进行编译,那么在链接的时候就是使用我们指定的 .a 或者 .so进行编译不会出现 所谓的动态库优先还是静态库优先的问题. 但这个方案需要知道编译库的路径,一些情况下并不适合使用。
在gcc的编译的时候加上--static参数, 这样在编译的时候就会优先选择静态库进行编译,而不是按照默认的情况选择动态库进行编译.
不过使用--static参数会带来另外的问题,不推荐使用,主要会带来下面的问题
warning: Using 'getservbyport_r' in statically linked applications requires at runtime the shared libraries from the glibc version used for linking这个主要原因是由于getservbyport_r 这样的接口还是需要动态库的支持才可以运行,许多glibc的函数都存在这样的问题, 特别是网络编程的接口中是很常见的.
链接参数控制
链接器中提供了-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编译一样都会报错
对于动态链接库,实际的符号定位是在运行期进行的.在编译.so的时候,如果没有把它需要的库和他一起进行联编,比如libx.so 需要使用uldict, 但是忘记在编译libx.so的时候加上-luldict的话,在编译libx.so的时候不会报错,因为这个时候libx.so被认为是一个库,它里面 存在一些不知道具体实现的符号是合法的,是可以在运行期指定或者编译另外的二进制程序的时候指定.
如果是采用 g++ -Lpath -lx 的方式进行编译,链接器会发现所需要的uldict的符号表找不到从而报错,但是如果是程序采用dlopen的方式载入,由于是运行期,这个程序在这个地 方就直接运行报错了.另外还有一种情况就是一个对外的接口在动态库中已经声明定义了,但是忘记实现了,这个时候也会产生类似的错误.
如果在运行期报出这样的错误,就要注意是否是由于某些库没有链接进来或者某些接口没有实现的原因产生
=================================================
收集了一些与编译,链接相关的问题, 有问题随时欢迎提问.
上文已经介绍过了, 在g++的环境中直接编译的结果会导致符合表与gcc编译的结果不同导致不能混合编译.
gcc使用g++编译的库原则:
1. g++编译库的时候需要把被外界使用的接口按照纯C++可以接受的方式用extern "C" 包起来,并且加上__cplusplus宏的判断,可以参考public/mcpack, public/nshead中的写法. 对于一些特殊情况,比如已经是g++编译出来的库又不适合修改,比如ullib, 分词库等,可以自己写一个 xxx.cpp的程序,在xxx.cpp对需要使用的接口再做一次纯C接口的封装,同时用extern "C"把纯C接口导出使用.使用g++编译,并且在链接的时候加上ullib等库即可. 2. gcc编译g++库在我们的64位环境中需要在最后加上-lstdc++
gcc使用g++编译的库多见于需要将基础库与php扩展,apache mod进行联编;
g++使用gcc编译出来的库: 这个比较简单,只需要gcc编译的提供的头文件采用了extern "C"封装即可.
如果环境完全一样包括编译路径,环境变量等都是一样的,一般情况下确实是一样的,但是许多环境的情况我们很难做到一样,比如程序使用一些DATA这样与时间相关宏就会导致每次编译的结果都是不一样的,有时候甚至内存的多少也会影响编译的结果
链接的时候查找顺序:
动态库调用的查找顺序:
一般情况链接的时候我们采用-L的方式指定查找路径, 调用动态链接库的时候采用LD_LIBRARY_PATH的方式指定链接路径.
另外注意一个问题,就是只要查找到第一个就会返回,后面的不会再查找. 比如-L./A -L./B -lx 在A中有libx.a B中有libx.a和libx.so, 这个时候会使用在./A的libx.a 而不会遵循动态库优先的原则,因为./A是先找到的,并且没有同名动态库存在.
这里再总结一下这个问题可能出现的场景:
可以,命令是 ld -r a.o b.o -o x.o, 不过不推荐这样做,这样做唯一的好处是静态库在链接的时候如果使用到了a.o中的符号也可以同时把b.o中的符号链接进来,可以避免--whole-archive的应用.
但是不推荐这样做,无形中增加了对源文件维护的麻烦
首先加了inline的函数是否可以被inline这个是由编译器决定,很多时候即时是指定了inline但还是无法被inline
另外注意到gcc中,只有在使用-O以上的优化后inline才会起作用,没有-O, -O2, -O3这些优化手段,无论是否加上了-finline-functions gcc都是不会进行inline优化的,这个时候的inline相当于一个普通函数(其实还是有一点区别,在符号表中表示是不一样的).程序在编译的时候 加上了-finline-functions 但如果没有-OX(X>=1)的配合, -finline-functions其实是无效的,不会起作用也不会报错
gcc里面为了能够支持在不加-OX(X>=1)的情况下能够将函数inline, 提供了一个扩展always_inline, 将函数写成下面这样
__attribute__((always_inline)) int foo()
{
...
}
就可以在不加-OX(X>=1)的情况下把foo inline进程序,不过always_inline 这个扩展只在gcc3以后支持,32位环境中使用的2.96 gcc是不支持的.
理论上是可以的, 在64位机器上的64位gcc中提供了-m32的参数,可以指定进行32位的编译, 但是编译问题虽然解决链接问题却还是存在,在64位的机器上可以用进行链接的库主要有2个一个是供64位程序使用的,另外一个供gcc2.96编译程序在 64位机器上运行的,这两个库都不能给gcc -m32出来的结果提供链接环境(32位库不能连接64位库,给gcc2.96的库太老的不兼容), 所以在编译机器环境上是不能直接编译出可执行的32位程序(编译成.o文件还是可以的)
在共享库的总结中介绍了如何实现共享库可以自己运行,但是有些时候会出现undefined reference error的错误导致共享库不能被运行。
这种情况产生的原因是:动态库中采用了类似 static int val = func(xxx);的写法, 其中val 是一个全局变量(或者静态全局变量)。 动态库被载入内存中使用的时候会直接先运行func这个函数,如果func是来自其他的库(比如一些情况下主程序使用-rdynamic编译,动态库使用 主程序的空间), 在编译动态链接的库的时候又没有被链接上, 这个时候就会出现这样的问题。
对于这样的问题主要考虑下面的解决方案:
1. 不要采用static int val = func(xxx);这种写法
如果在main函数开始前执行代码,一般有下面的两种方法
在实现上,编译器把它们放到一个特殊的符号 _init 中,在程序被载入内存的时候被执行
但是这种方式我们不推荐使用,特别是在这些执行代码中存在库与库之间的依赖关系的时候, 比如下面的场景:
libA.cpp
class Aclass
{
public:
Aclass()
{
int * u = Bfunc(); //这是另外一个库libB中的函数
int c = u[0];
}
}
static Aclass s_test;
libB.cpp
static int *s_test = test_init(); //初始化s_test
int *Bfunc()
{
return s_test;
}
上面的程序中有2个库,A库有一个static变量的构造函数依赖了 B库中的一个函数, B库中的这个函数又操作了一个由函数test_init初始化的static变量.
按照程序的要求我们必须要让test_init()这个函数在Aclass这个函数之前运行, 但是可惜的在某些情况我们很难做到这点, 这里涉及到链接器对库链接和初始化顺序的问题.
在默认情况下, test_init()和s_test的构造函数的执行顺序是按照链接的时候-l的顺序从右到左, 比如-lB -lA 那么Aclass的构造函数会在test_init()前执行,这个时候就会出现问题,需要保证-lA -lB的顺序才可以正常.
这里又涉及到另外一个问题, 就是 正常情况既然A依赖B, 那么在链接的时候肯定需要 保证 -lA在-lB. 但是这里我们只能说需要把越基础的库放在越后面,而不是必需放在最后面.还是上面的例子. 如果这个时候有一个test.cpp 使用了 A库, 并且在test中没有直接使用到B库中的东西, 这个时候如果-lB放在-lA前面,链接器会报错, 因为符号在从左往右展开的时候, 由于test没有使用到B的东西,所以没有做任何展开, 从这个角度而言在链接A的时候就找不到符号. 但是如果在test中有使用到B中和test_init相关联的函数,那么这个时候如果把-lB放在-lA的前面展开B函数的时候会把test_init 导出, 这样导致A会认为已经存在了test_init, 从而不报编译错误. 但是这样的结果就是test_init的初始化顺序被放到Aclass之后, 那么在程序运行的时候就可能导致错误.
对这种问题解决,主要有几种考虑
在使用全局变量的时候 需要特别注意这种初始化的顺序问题.小提示:构造初始化等,是在_init中处理, 另一个方面_fini是存在在程序退出前的执行析构等操作