2011-11-30 68 views
6

此问题与this one以及其答案有关。在共享库中混合PIC和非PIC对象

我刚刚发现我正在构建的构建中存在一些丑陋现象。这种情况看起来有些像下面这样(用gmake格式写成);注意,这特别适用于32位的内存模型SPARC和x86硬件:

OBJ_SET1 := some objects 
OBJ_SET2 := some objects 

# note: OBJ_SET2 doesn't get this flag 
${OBJ_SET1} : CCFLAGS += -PIC 

${OBJ_SET1} ${OBJ_SET2} : %.o : %.cc 
    ${CCC} ${CCFLAGS} -m32 -o ${@} -c ${<} 

obj1.o  : ${OBJ_SET1} 
obj2.o  : ${OBJ_SET2} 
sharedlib.so : obj1.o obj2.o 
obj1.o obj2.o sharedlib.so : 
    ${LINK} ${LDFLAGS} -m32 -PIC -o ${@} ${^} 

显然它可以工作在共享对象中混合使用和不使用PIC编译对象(这已经使用多年) 。我不太了解PIC知道这是否是一个好主意/聪明,我的猜测是在这种情况下不需要,而是发生这种情况,因为有人没有足够的关注找到正确的方法来做到这一点在新的东西建设。

我的问题是:

  1. 这是安全
  2. 这是个好主意
  3. 可能会出现的结果
  4. 什么潜在的问题,如果我切换一切PIC,是否有任何非我可能想要留意的明显陷阱。

回答

4

忘记我甚至写过这个问题。

几点说明是为了第一:

  • 非PIC码可以由OS在[?大多数]现代操作系统加载到存储器中的任何位置。在加载完所有内容之后,它会经历一个修复文本段(可执行文件最终结束)的阶段,以便正确解决全局变量;为了解决这个问题,文本段必须是可写的。
  • PIC可执行数据可由OS加载一次,并在多个用户/进程间共享。但是,对于操作系统来说,文本段必须是只读的,这意味着不需要修改。该代码被编译为使用全局偏移表(GOT),因此它可以解决相对于GOT的全局问题,从而减少对修复的需求。
  • 如果一个共享对象是在没有PIC的情况下构建的,尽管强烈建议它不会出现它是绝对必要的;如果操作系统必须修复文本段,那么它将被迫将其加载到标记为可读写的内存中,这会阻止跨进程/用户共享。
  • 如果一个可执行的二进制文件是用/ PIC编译的,我不知道引擎盖下出了什么问题,但我目睹了一些工具变得不稳定(神秘的崩溃&之类)。

答案:

  • 混合PIC /非PIC,或使用PIC的可执行文件会导致很难预测和跟踪不稳定。我没有技术解释为什么。
    • ...包括段错误,总线错误,堆栈损坏,可能还有更多。
  • 共享对象中的非PIC可能不会导致任何严重问题,但如果库在进程和/或用户中多次使用,则会导致使用更多的RAM。

更新(4/17)

因为我已经发现了一些以前看到崩溃的原因。为了说明:

/*header.h*/ 
#include <map> 
typedef std::map<std::string,std::string> StringMap; 
StringMap asdf; 

/*file1.cc*/ 
#include "header.h" 

/*file2.cc*/ 
#include "header.h" 

int main(int argc, char** argv) { 
    for(int ii = 0; ii < argc; ++ii) { 
    asdf[argv[ii]] = argv[ii]; 
    } 

    return 0; 
} 

...然后:

$ g++ file1.cc -shared -PIC -o libblah1.so 
$ g++ file1.cc -shared -PIC -o libblah2.so 
$ g++ file1.cc -shared -PIC -o libblah3.so 
$ g++ file1.cc -shared -PIC -o libblah4.so 
$ g++ file1.cc -shared -PIC -o libblah5.so 

$ g++ -zmuldefs file2.cc -Wl,-{L,R}$(pwd) -lblah{1..5} -o fdsa 
#  ^^^^^^^^^ 
#  This is the evil that made it possible 
$ args=(this is the song that never ends); 
$ eval ./fdsa $(for i in {1..100}; do echo -n ${args[*]}; done) 

这个特别的例子可能最终不会崩溃,但它基本上是曾在该组的代码存在的状况。如果它确实崩溃它可能会在析构函数中,通常是一个双免费的错误。

很多年前,他们增加了-zmuldefs到他们的构建以摆脱多重定义的符号错误。编译器发出用于在全局对象上运行构造函数/析构函数的代码。 -zmuldefs强制他们住在内存中的相同位置,但它仍然运行构造函数/析构函数一次为exe文件和每个库包括有问题的标题 - 因此是双免费的。