ORC — это инструмент для поиска нарушений правила одного определения C++ в инструментальной цепочке OSX.
ORC — это игра на DWARF, которая является игрой на ELF. ORC — аббревиатура; в то время как O означает ODR, по иронии судьбы R и C представляют собой несколько (возможно, противоречивых) слов.
Существует множество статей о правиле одного определения (ODR), включая сам стандарт C++. Суть правила в том, что если символ определен в программе, его можно определить только один раз. Некоторым символам предоставляется исключение из этого правила, и их можно определять несколько раз. Однако эти символы должны определяться идентичными последовательностями токенов.
Обратите внимание, что некоторые настройки компилятора также могут влиять на последовательности токенов — например, включение или отключение RTTI может изменить определение символа (в данном случае vtable класса).
Любой символ, нарушающий вышеуказанное правило, является нарушением ODR (ODRV). В некоторых случаях компоновщик может обнаружить повторяющееся определение символа и выдать предупреждение или ошибку. Однако стандарт утверждает, что компоновщик не обязан это делать. Энди Джи хорошо это описывает:
из соображений производительности стандарт C++ требует, чтобы при нарушении правила одного определения в отношении шаблонов поведение было просто неопределенным. Поскольку компоновщику все равно, нарушения этого правила остаются без внимания. источник
Возможны нешаблонные ODRV, и компоновщик также может молчать о них.
ODRV обычно означает, что у вас есть символ, двоичная структура которого различается в зависимости от модуля компиляции, который его создал. Однако в соответствии с правилом, когда компоновщик встречает несколько определений, он может выбрать любое из них и использовать его в качестве двоичного макета для символа. Если выбранный макет не соответствует внутреннему двоичному макету символа в единице компиляции, поведение не определено.
Зачастую в таких сценариях отладчик бесполезен. Он также будет использовать одно определение символа для всей программы, и когда вы попытаетесь отладить ODRV, отладчик может выдать вам неверные данные или указать на место в файле, которое кажется неправильным. В конце концов, отладчик будет вам лгать, но молча не предложит никаких подсказок о том, в чем заключается основная проблема.
Как и все ошибки, исправление ODRV требует времени, так зачем же исправлять нарушение ODR в протестированном (и предположительно работающем) коде?
ORC — это инструмент, который выполняет следующее:
За исключением ошибок в инструменте, ORC не генерирует ложных срабатываний. Все, что он сообщает, является ODRV.
В настоящее время ORC не обнаруживает все возможные нарушения правила одного определения. Мы надеемся со временем расширить и улучшить то, что он может уловить. До тех пор это означает, что, хотя ORC является ценной проверкой, чистое сканирование не гарантирует, что программа не содержит ODRV.
ORC может найти:
Примечание о виртуальных таблицах: ORC обнаружит виртуальные методы, находящиеся в разных слотах. (Это отвратительная испорченная программа.) На этом этапе она не обнаружит класс, имеющий виртуальные методы, которые являются «надмножеством» повторяющегося класса, нарушающего ODR.
В дополнение к основным источникам ORC мы пытаемся предоставить множество примеров приложений, содержащих ODRV, которые инструмент должен перехватывать.
ORC изначально был задуман для macOS. Хотя его текущая реализация сосредоточена на этом, она не обязательно должна быть ограничена этой цепочкой инструментов.
ORC управляется cmake и создается с использованием типичных соглашений о сборке проекта, управляемого CMake:
mkdir build
cd build
cmake -GXcode ..
Существует несколько примеров приложений, в которые ORC интегрируется в целях тестирования. Их можно выбрать через всплывающее окно целей в Xcode.
ORC использует Tracy в качестве инструмента профилирования, и он включен по умолчанию. Чтобы отключить Трейси, укажите командную строку cmake следующим образом:
cmake .. -GXcode -DTRACY_ENABLE=OFF
Зависимость Tracy требуется, даже если профилирование отключено (оно будет скомпилировано во время выполнения). Обратите внимание, что этот параметр кэшируется, поэтому его необходимо явно OFF
или ON
. Повторный вызов командной строки с отсутствующим параметром приведет к использованию его предыдущего значения.
ORC можно вызвать непосредственно из командной строки или вставить в цепочку инструментов на этапе компоновщика. Результат не изменится; это просто вопрос удобства вашего рабочего процесса.
Этот режим полезен, если у вас есть команда компоновщика и ее аргументы, и вы хотите искать ODRV отдельно от фактической сборки.
Конфигурационный файл (см. ниже)
'forward_to_linker' = false
'standalone_mode' = false
Вам нужны аргументы командной строки ld
из XCode. Создайте с помощью Xcode (если вы не можете связать, ORC не поможет), скопируйте команду связывания и вставьте ее после вызова ORC. Что-то вроде:
/path/to/orc /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang++ -target ... Debug/lem_mac
(Это огромная командная строка, сокращенно здесь.)
ORC будет выполняться и регистрировать нарушения ODR на консоли.
Если у вас есть список файлов библиотеки для обработки ORC, он также может это сделать.
Конфигурационный файл (см. ниже)
'forward_to_linker' = false
'standalone_mode' = true
В этом режиме просто передайте список файлов библиотек в ORC для обработки.
Конфигурационный файл (см. ниже)
'forward_to_linker' = true
'standalone_mode' = false
Чтобы использовать ORC в проекте сборки Xcode, переопределите следующие переменные, указав полный путь к сценариям ORC:
"LIBTOOL": "/absolute/path/to/orc",
"LDTOOL": "/absolute/path/to/orc",
"ALTERNATE_LINKER": "/absolute/path/to/orc",
При наличии этих настроек Xcode должен использовать ORC в качестве инструмента как для libtool
, так и для ld
фаз сборки проекта. Благодаря настройке forward_to_linker
ORC вызовет соответствующий инструмент связывания для создания двоичного файла. Как только это завершится, ORC начнет сканирование.
Помимо других настроек, ORC можно настроить на выход или просто предупреждение при обнаружении ODRV.
ORC пройдёт по текущему каталогу в поисках файла конфигурации с именем:
.orc-config
или_orc-config
Если они найдены, многие переключатели могут управлять логикой ORC. Примеры см. в файле _orc_config
в репозитории. ORC предпочитает .orc-config
поэтому можно просто скопировать исходный _orc_config
и изменить значения локально в .orc-config
.
Например:
error: ODRV (structure:byte_size); conflict in `object`
compilation unit: a.o:
definition location: /Volumes/src/orc/extras/struct0/src/a.cpp:3
calling_convention: pass by value; 5 (0x5)
name: object
byte_size: 4 (0x4)
compilation unit: main.o:
definition location: /Volumes/src/orc/extras/struct0/src/main.cpp:3
calling_convention: pass by value; 5 (0x5)
name: object
byte_size: 1 (0x1)
structure:byte_size
известна как категория ODRV и подробно описывает, какой тип нарушения представляет эта ошибка. Затем выводятся две конфликтующие единицы компиляции вместе с информацией DWARF, которая привела к конфликту.
struct object { ... }
In ao:
и In main.o
находятся два несовпадающих объектных файла или архива. ODR, скорее всего, вызван неправильным совпадением настроек компиляции или #define при компиляции этих архивов. byte_size
— фактическое значение, вызывающее ошибку.
definition location: /Volumes/src/orc/extras/struct0/src/a.cpp:3
В какой строке и файле был объявлен объект. Итак, строка 3 файла a.cpp
в этом примере.
Для одной и той же версии ORC и одного и того же ввода ORC всегда будет записывать один и тот же вывод. Где «тот же» — это идентичный байт, и инструмент сравнения не покажет различий.
Достижение (и, вероятно, поддержание) согласованного вывода является на удивление сложной задачей в многопоточном приложении.
Однако имейте в виду, что это НЕ относится к различным версиям ORC. Изменения в ORC почти наверняка приведут к изменениям объема производства.
Также нет никакой гарантии, что «небольшое» изменение во входных файлах будет гарантировать «небольшое» изменение в выходных данных ORC. Такое поведение желательно и, вероятно, станет областью будущих улучшений.
orc_test
) Приложение для модульного тестирования предназначено для того, чтобы гарантировать, что ORC перехватывает то, что предназначено для перехвата. orc_test
представляет миниатюрную «систему сборки», позволяющую генерировать объектные файлы из известных источников для выявления известных нарушений ODR. Затем он обрабатывает объектные файлы, используя тот же механизм, что и инструмент командной строки ORC, и сравнивает результаты с ожидаемым списком отчетов ODRV.
Каждый модульный тест в батарее является дискретным и содержит:
odrv_test.toml
— TOML-файл высокого уровня, описывающий параметры теста.Как правило, одна проверка должна выявить одно нарушение ODR, но это возможно не во всех случаях.
Эти файлы являются стандартными исходными файлами C++. Их количество и размер должны быть очень небольшими – ровно настолько, чтобы вызвать предполагаемое ODRV.
odrv_test.toml
Файл настроек описывает тестовому приложению, какие источники необходимо скомпилировать, какие флаги компиляции следует использовать для теста и какие ODRV система должна отслеживать в результате связывания сгенерированных объектных файлов. ) вместе.
Источники тестов указываются директивой [[source]]
:
[[ source ]]
path = " one.cpp "
obj = " one "
flags = [
" -Dfoo=1 "
]
Поле path
описывает путь к файлу относительно odrv_test.toml
. Это единственное обязательное поле.
Поле obj
указывает имя создаваемого (временного) объектного файла. Если это имя опущено, будет использоваться псевдослучайное имя.
Поле flags
указывает флаги компиляции, которые будут использоваться специально для этой единицы компиляции. Используя это поле, можно повторно использовать один и тот же исходный файл с разными флагами компиляции для получения ODRV.
ODRV указываются директивой [[odrv]]
:
[[ odrv ]]
category = " subprogram:vtable_elem_location "
linkage_name = " _ZNK6object3apiEv "
Поле category
описывает конкретный тип нарушения ODR, которое тестовое приложение должно ожидать.
Поле linkage_name
описывает конкретный символ, вызвавший ODRV. В настоящее время он не используется, но будет применяться по мере развития тестового приложения.
Следующие флаги в настоящее время не используются или будут подвергаться серьезным изменениям по мере развития приложения модульного тестирования.
[compile_flags]
: серия флагов компиляции, которые следует применять к каждому исходному файлу в модульном тесте.
[orc_test_flags]
: ряд настроек времени выполнения, которые нужно передать тестовому приложению для этого теста.
[orc_flags]
: ряд настроек времени выполнения, которые нужно передать движку ORC для этого теста.