防止意外物体不兼容?

时间:2013-10-04 15:51:44

标签: c++ linker c-preprocessor symbol-table misspelling

TL; DR

在不同的编译单元中,控制条件编译的共享(可能是模板化的头)预处理器指令中的编译器参数拼写错误导致的二进制不兼容性保护?

实施例。

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的堆栈粉碎保护,以及一些更多的措施/方法/方法都失败了。

在彻底的破坏中,我的注意力转向了构建过程中最精细的细节,类似于:

  • 建立一个小型图书馆。
  • 构建一个大型库,使用小型库。
  • 构建大型库的测试套件。

仅当大型库被用作静态或动态库依赖项时(即动态链接器自动加载它,没有dlopen) 有问题吗?测试案例中,库的所有代码都包含在测试中,一切正常:这是最重要的线索。

“解决方案”

最后,事实证明这是最简单的事情:单个(!)拼写错误。

事实证明,编译标志与测试套件中的单个字符不同,而大型库:一个控制小型库行为的定义拼写错误。 关键信息morsel:小型库有一些模板。这些在每种情况下都直接使用,没有事先进行明确的实例化。当标志被切换时,其中一个模板化类的内容发生了变化:如果定义了标志,则某些数据字段根本不存在! 链接器没有注意到这一点。 (由于该类是模板化的,因此结果符号很弱。) 代码使用动态强制转换,受此问题影响的类继承自受损的类 - >事情向南发展。

我的问题如下:您如何防范此类问题?是否有解决此特定问题的工具或解决方案?

未来证明

我已经想到了两件事,并且相信不能在目标文件级别上构建保护:

  • 1:在一些定义明确的位置保存作为预处理程序符号实现的选项,最好通过单独的构建步骤提取。提供检查脚本,使用它来检查所有编译器定义,并在用户代码中定义。将此检查集成到构建过程中。可能使用Levenshtein距离或类似物来检查拼写错误。昂贵,脚本/解决方案可能变得复杂。类似标志可能存在问题(但为什么有它们?),附加文件必须伴随编译的库代码。 (好吧,也许使用DWARF 2,这是不真实的,但我们假设我们不希望这样。)
  • 2:集中构建选项:廉价,自定义选项保持开放(想想makefile.local),但会产生单一的怪物,强大的项目耦合。

我想继续前进并熄灭一些可能在某些读者中引起煽动的火焰余烬:“不要使用预处理器符号”这里不是一个选项。

  • 条件编译确实在高性能代码中占有一席之地,并且使用模板和enable_if-s执行所有操作会不必要地使事情过于复杂。虽然上述解决方案通常不是理想的,但它可以出现形成开发过程。
  • 请假设你无法控制情况,假设你有遗留代码,假设你可以强迫自己避免一边踩踏。
  • 如果不这样做,可以概括为ABI不兼容性检测,但这可能会使问题的范围过于夸大。

我知道:

1 个答案:

答案 0 :(得分:0)

如果重要,请没有默认情况。

#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中解析。