Смешивание объектов PIC и не-PIC в общей библиотеке
Этот вопрос связан как с этим, так и с его ответом.
Я только что обнаружил уродство в сборке, над которой я работаю. Ситуация выглядит примерно так (написано в формате gmake); обратите внимание, это особенно относится к 32-битной модели памяти на оборудовании sparc и x86:
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, есть ли какие-то неочевидные ошибки, за которыми я бы хотел следить.
1 ответ
Забыл, я даже написал этот вопрос.
Некоторые объяснения в порядке:
- Код не PIC может быть загружен ОС в любую позицию в памяти в [большинстве?] Современных ОС. После того, как все загружено, оно проходит фазу, которая фиксирует текстовый сегмент (где заканчивается исполняемый материал), чтобы он правильно обращался к глобальным переменным; чтобы осуществить это, текстовый сегмент должен быть доступен для записи.
- Исполняемые данные PIC могут загружаться ОС один раз и совместно использоваться несколькими пользователями / процессами. Однако для того, чтобы ОС делала это, текстовый сегмент должен быть доступен только для чтения, что означает отсутствие исправлений. Код скомпилирован для использования Глобальной таблицы смещений (GOT), чтобы он мог адресовать глобальные переменные относительно GOT, устраняя необходимость исправлений.
- Если общий объект создается без PIC, хотя это настоятельно рекомендуется, то, по-видимому, это не является строго необходимым; если операционная система должна исправить текстовый сегмент, то она вынуждена загрузить его в память, помеченную как чтение-запись..., что предотвращает совместное использование между процессами / пользователями.
- Если исполняемый бинарный файл создается / с / PIC, я не знаю, что происходит под капотом, но я был свидетелем того, как несколько инструментов стали нестабильными (таинственные сбои и тому подобное).
Ответы:
- Смешивание PIC/non-PIC или использование PIC в исполняемых файлах может затруднить прогнозирование и отслеживание нестабильности. У меня нет технического объяснения почему.
- ... чтобы включить ошибки сегмента, ошибки шины, повреждение стека и, возможно, многое другое.
- Non-PIC в общих объектах, вероятно, не вызовет каких-либо серьезных проблем, хотя это может привести к увеличению объема используемой оперативной памяти, если библиотека многократно используется в процессах и / или пользователях.
обновление (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 и каждой библиотеки, которая содержала ошибочный заголовок - следовательно, double-free.