Смешивание объектов 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, чтобы понять, хорошая ли это идея / умная, и я предполагаю, что в данном случае это не нужно, а скорее это происходит потому, что кто-то не позаботился о том, чтобы найти правильный способ сделать это при лавировании о новых вещах в сборке.

Мой вопрос:

  1. Это безопасно?
  2. Это хорошая идея
  3. Какие потенциальные проблемы могут возникнуть в результате
  4. Если я переключу все на PIC, есть ли какие-нибудь неочевидные ошибки, на которые я мог бы обратить внимание.

person Brian Vandenberg    schedule 30.11.2011    source источник


Ответы (1)


Забыл, что даже написал этот вопрос.

Сначала несколько пояснений:

  • Код, отличный от 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.
    • ... to include segfaults, bus errors, stack corruption, and probably more besides.
  • Не-PIC в общих объектах, вероятно, не вызовет серьезных проблем, хотя это может привести к использованию большего объема оперативной памяти, если библиотека используется много раз в процессах и/или пользователями.

обновление (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 заставляет их жить в одном и том же месте в памяти, но по-прежнему запускает конструкторы/деструкторы один раз для исполняемого файла и каждой библиотеки, которая включает оскорбительный заголовок - отсюда и двойное освобождение.

person Brian Vandenberg    schedule 05.04.2013