ORC 是一款用于查找 OSX 工具链上违反 C++ 单一定义规则的工具。
ORC 是 DWARF 的翻版,而 DWARF 则是 ELF 的翻版。 ORC 是一个缩写词; O代表 ODR,讽刺的是, R和C代表多个(可能是冲突的)单词。
有很多关于单一定义规则 (ODR) 的文章,包括 C++ 标准本身。该规则的要点是,如果一个符号在程序中定义,则只允许定义一次。某些符号被授予此规则的例外,并且允许被多次定义。但是,这些符号必须由相同的标记序列定义。
请注意,某些编译器设置也会影响令牌序列 - 例如,启用或禁用 RTTI 可能会改变符号的定义(在本例中为类的 vtable)。
任何违反上述规则的符号均属于 ODR 违规 (ODRV)。在某些情况下,链接器可能会捕获重复的符号定义并发出警告或错误。然而,标准规定链接器不需要这样做。 Andy G 描述得很好:
出于性能原因,C++ 标准规定,如果违反了模板的“单一定义规则”,则该行为将是未定义的。由于链接器不在乎,因此违反此规则的行为不会发生。来源
非模板 ODRV 是可能的,并且链接器也可能同样对它们保持沉默。
ODRV 通常意味着您有一个符号,其二进制布局因构建它的编译单元而异。然而,由于该规则,当链接器遇到多个定义时,可以自由选择其中任何一个并将其用作符号的二进制布局。当所选布局与编译单元中符号的内部二进制布局不匹配时,行为未定义。
通常,调试器在这些场景中毫无用处。它也将为整个程序使用符号的单个定义,当您尝试调试 ODRV 时,调试器可能会提供错误的数据,或者指向文件中看起来不正确的位置。最后,调试器似乎在对您撒谎,但默默地不提供有关根本问题是什么的任何线索。
与所有错误一样,ODRV 需要时间来修复,那么为什么要修复经过测试(并且可能有效)的代码中的 ODR 违规呢?
ORC 是一个执行以下操作的工具:
除非工具中存在错误,否则 ORC 不会产生误报。它报告的任何内容都是 ODRV。
目前,ORC 并未检测到所有可能违反单一定义规则的情况。我们希望随着时间的推移,能够扩展和改进它所能捕获的内容。在那之前,这意味着虽然 ORC 是一项有价值的检查,但干净的扫描并不能保证程序没有 ODRV。
ORC 可以找到:
关于 vtable 的注释:ORC 将检测位于不同槽中的虚拟方法。 (这是一种令人讨厌的损坏程序。)此时,它不会检测到具有虚拟方法的类,该虚拟方法是违反 ODR 的重复类的“超集”。
除了主要的 ORC 源之外,我们还尝试提供一系列示例应用程序,其中包含该工具应捕获的 ODRV。
ORC 最初是在 macOS 上构思的。虽然其当前的实现集中于此,但它不必局限于该工具链。
ORC 由 cmake 管理,并使用 CMake 管理项目的典型构建约定进行构建:
mkdir build
cd build
cmake -GXcode ..
为了测试目的,ORC 集成到了一些示例应用程序中。这些可以通过 Xcode 中的目标弹出窗口进行选择。
ORC 使用 Tracy 作为其选择的分析工具,并且默认情况下处于启用状态。要禁用 Tracy,请指定 cmake 命令行,如下所示:
cmake .. -GXcode -DTRACY_ENABLE=OFF
即使禁用分析,也需要 Tracy 依赖项(它将在运行时编译出来)。请注意,此选项已缓存,因此您必须显式将其OFF
或ON
。在缺少选项的情况下重新运行命令行调用将导致使用其先前的值。
ORC 可以直接从命令行调用,或者在链接器步骤中插入到工具链中。输出不变;这只是您工作流程中的便利问题。
如果您有链接器命令及其参数,并且想要与实际构建分开搜索 ODRV,则此模式非常有用。
配置文件(见下文)
'forward_to_linker' = false
'standalone_mode' = false
您需要来自 XCode 的ld
命令行参数。使用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
要在 Xcode 构建项目中使用 ORC,请使用 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
是不匹配的 2 个目标文件或存档。 ODR 可能是由于编译这些档案时编译或 #define 设置不匹配造成的。 byte_size
是导致错误的实际值。
definition location: /Volumes/src/orc/extras/struct0/src/a.cpp:3
该对象是在哪一行和文件中声明的。本例中是a.cpp
的第 3 行。
对于相同版本的 ORC 和相同的输入,ORC 将始终写入相同的输出。其中“相同”是字节相同,并且 diff 工具不会显示任何差异。
在高度多线程的应用程序中,实现(并可能保持)一致的输出非常具有挑战性。
但请记住,这不适用于不同版本的 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 引擎进行此测试的一系列运行时设置。