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

标签 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 的了解不够,不知道它是否是一个好主意/聪明,我的猜测是在这种情况下不需要它,而是它正在发生,因为有人不太在乎找到正确的方法来做这件事关于构建的新内容。

我的问题是:
  • 这安全吗
  • 这是个好主意
  • 结果会出现哪些潜在问题
  • 如果我将所有内容都切换到 PIC,是否有任何我可能需要注意的非明显问题。
  • 最佳答案

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

    先按顺序做一些解释:

  • 操作系统可以将非 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 和包含有问题的头文件的每个库运行一次构造函数/析构函数——因此是双重释放。

    关于compiler-flags - 在共享库中混合 PIC 和非 PIC 对象,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8331456/

    相关文章:

    macros - 检测预处理器中的 -xarch 选项?

    ios - iOS编译器定义使用什么

    c++ - 使用 Solaris CC 的 std::BinaryPredicate 问题

    c++ - 警告(不合时宜): Assigning void(*)(int) to extern "C" void(*)(int)

    c - 我如何知道哪些标志可用于 GCC 以更轻松地链接库?

    c++ - 为什么 cout 不打印 extern "C"变量?

    linux - 在 solaris 和 linux 中构建 ace 时出错

    c - 在 C 中使用 makefile 标志进行调试

    c - 对 C 有用的 GCcflags是什么?

    unicode - 如何在 Cmake 生成的 ALL_BUILD 和 ZERO_CHECK Visual Studio 2013 项目中将 unicode 设置为字符集?