忘了我什至写了这个问题。
一些解释是按顺序排列的:
- 操作系统可以将非 PIC 代码加载到 [大多数?] 现代操作系统的内存中的任何位置。加载完所有内容后,它会经历一个修复文本段(可执行文件结束的地方)的阶段,以便正确处理全局变量;要实现这一点,文本段必须是可写的。
- PIC 可执行数据可以由操作系统加载一次,并在多个用户/进程之间共享。然而,对于操作系统来说,文本段必须是只读的——这意味着没有修复。该代码被编译为使用全局偏移表 (GOT),因此它可以处理相对于 GOT 的全局变量,从而减少了修复的需要。
- 如果在没有 PIC 的情况下构建共享对象,尽管强烈建议这样做,但它似乎并不是绝对必要的;如果操作系统必须修复文本段,则它被迫将其加载到标记为读写的内存中......这会阻止跨进程/用户共享。
- 如果一个可执行二进制文件是 /with/ 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)
该特定示例可能最终不会崩溃,但这基本上是该组代码中存在的情况。如果它确实崩溃了,它很可能在析构函数中,通常是双释放错误。
Many years previous they added -zmuldefs
to their build to get rid of multiply defined symbol errors. The compiler emits code for running constructors/destructors on global objects. -zmuldefs
forces them to live at the same location in memory but it still runs the constructors/destructors once for the exe and each library that included the offending header -- hence the double-free.