这个问题与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 构建可执行二进制文件,我不知道幕后出了什么问题,但我亲眼目睹了一些工具变得不稳定(神秘的崩溃等)。
答案:
- Mixing PIC/non-PIC, or using PIC in executables can cause hard to predict and track down instabilities. I don't have a technical explanation for why.
- ...包括段错误、总线错误、堆栈损坏,可能还有更多。
- 共享对象中的非 PIC 可能不会导致任何严重问题,但如果跨进程和/或用户多次使用库,可能会导致使用更多 RAM。
更新(4/17)
我从此发现了原因some我以前见过的车祸。为了显示:
/*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)
该特定示例可能不会最终崩溃,但这基本上是该组代码中存在的情况。如果它does崩溃很可能发生在析构函数中,通常是双重释放错误。
许多年前他们补充道-zmuldefs
到他们的构建中以消除多重定义的符号错误。编译器发出用于在全局对象上运行构造函数/析构函数的代码。-zmuldefs
强制它们位于内存中的同一位置,但它仍然为 exe 和包含有问题标头的每个库运行一次构造函数/析构函数 - 因此是双重释放。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)