这个问题与 这个 以及它的答案。

我刚刚在正在努力的构建中发现了一些丑陋。情况看起来有些类似于以下(以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 ${@} ${^}

显然,它可以在共享对象中混合和不带有图片的对象混合(已经使用了多年)。我对图片的了解不足以知道这是一个好主意/聪明,我的猜测是在这种情况下是不需要的,而是在发生,因为有人在固定时不太在意找到正确的方法在构建的新东西上。

我的问题是:

  1. 这是安全的吗?
  2. 这是不是一个好主意
  3. 结果可能会出现什么潜在问题
  4. 如果我将所有内容都切换到PIC,是否有任何我可能想提防的不可思议的陷阱。
有帮助吗?

解决方案

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

首先是一些解释:

  • 非PIC代码可以由OS加载到[大多数?]现代OSS中的内存中的任何位置。加载了所有内容后,它会经过一个阶段,该阶段可以修复文本段(可执行文件最终的位置),因此它正确地解决了全局变量。要实现这一目标,文本段必须是可写的。
  • PIC可执行数据可以由OS加载一次,并通过多个用户/进程共享。但是,要执行操作系统,文本段必须仅读取 - 这意味着没有修复。该代码被编译以使用全局偏置表(GOT),因此可以相对于GOT解决全球范围,从而减轻了对修复的需求。
  • 如果强烈鼓励没有图片建造共享对象,但似乎并不是严格必要的;如果操作系统必须修复文本段,则被迫将其加载到标记为读写的内存中...,这阻止了跨流程/用户共享。
  • 如果构建 /使用 /图片构建了可执行的二进制文件,我不知道引擎盖下出了什么问题,但是我目睹了一些工具变得不稳定(神秘的崩溃等)。

答案:

  • 混合PIC/非PIC或在可执行文件中使用PIC可能会导致难以预测和跟踪不稳定性。我没有关于原因的技术解释。
    • ...包括Segfaults,公共汽车错误,堆栈腐败等等。
  • 共享对象中的非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和每个库,其中包括有问题的标题,因此是双重的。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top