在共享库中混合PIC和非PIC对象

时间:2011-11-30 19:06:20

标签: compiler-flags sunstudio

此问题与this one及其答案有关。

我刚刚在我正在制作的构建中发现了一些丑陋。情况看起来有点像以下(用gmake格式编写);请注意,这特别适用于sparc和x86硬件上的32位内存模型:

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,是否有任何我可能需要注意的非显而易见的问题。

1 个答案:

答案 0 :(得分:4)

忘了我甚至写了这个问题。

首先要说明一些解释:

  • 操作系统可以将非PIC代码加载到[大多数]现代操作系统的内存中的任何位置。加载完所有内容后,它会经历一个修复文本段(可执行文件最终结束)的阶段,以便正确地处理全局变量;要将其关闭,文本段必须是可写的。
  • PIC可执行数据可以由操作系统加载一次,并在多个用户/进程之间共享。但是,要使操作系统执行此操作,文本段必须是只读的 - 这意味着无需修复。代码被编译为使用全局偏移表(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和每个包含违规标题的库运行构造函数/析构函数 - 因此是双重释放。