Защита от случайного несовместимости объектов?
TL;DR
Защита от двоичной несовместимости, возникающей из-за опечаток аргументов компилятора в директивах препроцессора общих, возможно, шаблонных заголовков, которые управляют условной компиляцией, в разных единицах компиляции?
Ex.
g++ ... -DYOUR_NORMAl_FLAG ... -o libA.so
/**Another compilation unit, or even project. **/
g++ ... -DYOUR_NORMA1_FLAG ... -o libB.so
/**Another compilation unit, or even project. **/
g++ ... -DYOUR_NORMAI_FLAG ... main.cpp libA.so //The possibilities!
Основная история
Недавно я столкнулся со странной ошибкой: симптомом был одиночный SIGSEGV, который после перекомпиляции всегда появлялся в одном и том же месте. Это привело меня к мысли, что происходит какое-то повреждение памяти, и фактический базовый указатель вообще не указатель, а какой-то раздел данных.
Я спасаю вас от долгого и напряженного путешествия, на которое уйдет почти два, в остальном, отличных рабочих дня, чтобы отследить проблему. Достаточно сказать, что Valgrind, GDB, nm, readelf, электрический забор, защита GCC от разрушения стека, а затем еще несколько мер / методов / подходов потерпели неудачу.
В полном опустошении мое внимание обратилось на мельчайшие детали в процессе сборки, который был аналогичен:
- Постройте одну маленькую библиотеку.
- Постройте одну большую библиотеку, которая использует маленькую.
- Создайте набор тестов из большой библиотеки.
Только в том случае, когда большая библиотека использовалась в качестве статической или динамической библиотечной зависимости (т.е. динамический компоновщик загружал ее автоматически, без дублирования), возникла проблема. В тестовом случае, когда весь код библиотеки был просто включен в тесты, все работало: это был самый важный ключ.
Решение"
В итоге получилась самая простая вещь: одиночная (!) Опечатка.
Оказывается, флаги компиляции отличались одним символом в наборе тестов, и большая библиотека: определение, которое управляло поведением маленькой библиотеки, было написано с ошибкой. Критический информационный кусочек: в небольшой библиотеке было несколько шаблонов. Они использовались непосредственно в каждом случае, без явного указания заранее. Содержимое одного из шаблонных классов изменялось при переключении флага: некоторые поля данных просто не присутствовали в случае определения флага! Линкер ничего этого не заметил. (Так как класс был шаблонным, результирующие символы были слабыми.) Код использовал динамическое приведение, и класс, затронутый этой проблемой, наследовал от искаженного класса -> все пошло на юг.
Мой вопрос заключается в следующем: как бы вы защитились от такого рода проблем? Существуют ли какие-либо инструменты или решения для решения этой конкретной проблемы?
Будущая проверка
Я подумал о двух вещах и считаю, что на уровне объектных файлов невозможно построить защиту:
- 1: Сохранение опций, реализованных в виде символов препроцессора в некотором четко определенном месте, предпочтительно выделенных отдельным этапом сборки. Предоставьте скрипт проверки, который использует это для проверки всех определений компилятора и определений в пользовательском коде. Интегрируйте эту проверку в процесс сборки. Возможно, используйте расстояние Левенштейна или подобное для проверки орфографических ошибок. Дорого, и сценарий / решение может быть сложным. Возможна проблема с подобными флагами (но зачем их иметь?), Дополнительные файлы должны сопровождать код скомпилированной библиотеки. (Ну, может быть, с DWARF 2 это не соответствует действительности, но давайте предположим, что мы этого не хотим.)
- 2: Централизовать параметры сборки: дешево, опция настройки оставлена открытой (например, makefile.local), но создает монолитные чудовища, сильные связи проекта.
Я хотел бы пойти дальше и погасить несколько вероятных источников пламени, которые, возможно, вспыхивают у некоторых читателей: "не используйте символы препроцессора" здесь не вариант.
- Условная компиляция имеет свое место в высокопроизводительном коде, и выполнение всего с помощью шаблонов и enable_if-s приведет к ненужному усложнению. Хотя приведенное выше решение обычно нежелательно, оно может возникнуть в процессе разработки.
- Пожалуйста, предположите, что вы не можете контролировать ситуацию, предположите, что у вас есть устаревший код, и сделайте все возможное, чтобы заставить себя избежать побочных действий.
- Если это не сработает, обобщите в обнаружении несовместимости ABI, хотя это может слишком расширить область вопроса для SO.
Я знаю о:
- http://gcc.gnu.org/onlinedocs/libstdc++/manual/abi.html
- DT_SONAME не применяется.
- Другие схемы версий там также не применимы - они были разработаны для защиты пакета, который сам по себе не является неисправным.
- Смешивание C++ ABI для построения на основе устаревших библиотек
- Инструмент статического анализа для обнаружения разрывов ABI в C++
1 ответ
Если это имеет значение, не используйте регистр по умолчанию.
#ifdef YOUR_NORMAL_FLAG
// some code
#elsif YOUR_SPECIAL_FLAG
// some other code
#else
// in case of a typo, this is a compilation error
# error "No flag specified"
#endif
Это может привести к большому списку опций компилятора, если условная компиляция чрезмерно используется, но есть способы обойти это, например, определение конфигурационных файлов
flag=normal
flag2=special
которые анализируются сценариями сборки и генерируют параметры, и, возможно, могут проверять опечатки или могут быть проанализированы непосредственно из Makefile.