通过AddressSanitizer来发现实际产品的Bug

ASan,你值得拥有

感谢Aaron Gorenstein授权发布这篇文章。

从最近发布的Visual Studio 2019 v16.9开始,用于MSVC的ASan(AddressSanitizer)组件就正式可以使用了。 我们已经展示了如何通过它寻找代码中的Bug,甚至在产品级别的代码(例如EASTL),我们也可以用上它。今天的这篇文章中,我们将会展示我们是如何使用ASan找到了MSVC编译器内部的一个Bug的。

想法很简单:ASan很善于查找代码中的Bug,而且我们也总是对编译器中的Bug十分感兴趣。就像你可以在你的工程中启用ASan并执行测试一样,我们在我们的MSVC编译器项目上也启用了ASan,测试发现,还真的找到了一个Bug。

启用ASan并编译代码

在我们的构建系统中启用ASan非常容易。在通用构建场景中,我们已经文档化了如何启用ASan的具体步骤。在我们的案例中,我在构建指令cl.exe上添加了/fsanitize=address开关,另外,我们旧的但在不断演进的构建系统还需要一些额外的手动配置来指定我们需要的扩展库的位置信息。

这样就可以了。现在,我可以构建二进制文件了,也就是c2.dll,就和之前一样。但是,在生成的二进制文件内部,有大量的ASan监控代码被插入到了二进制代码中,以查找那些潜在的Bug。接下来,我就可以和以前一样执行一系列的内部测试流程,看看会不会有任何问题。

找到Bug

我们的测试套件中大概有4000个单独的C++文件,包含了真实世界的代码,手工测试代码,性能指标测试以及回归测试。我们有一个内部开发的只能从命令行访问的测试程序。当执行它时,我们几乎通过了所有的测试项目,但是有一个出现了失败,通过查看日志,我发现如下的诊断信息:

通过AddressSanitizer来发现实际产品的Bug

具体来说,我有如下的发现:

1) 被报告的错误是”stack-buffer-underflow”:ASan可以找到有关栈和堆方面的问题。

2) 请注意这一行”stack of thread T3″。在c2.dll中,我们会并行运行很多线程,例如有T1,T2等。ASan可以处理这种多线程的代码场景。

最重要的是:ASan从来没有误报的情况。这个错误报告确实是我们代码里的一个Bug,因此我已经知道修复它的方式了。

幸运的是,触发此错误的输入是一个单个文件。我可以简单地重复命令行执行来重现这个Bug。下图展示了我是如何触发这个Bug的:

通过AddressSanitizer来发现实际产品的Bug

我去除了一些输出,但是在上图中,我们还是可以看到ASan的命令行诊断信息。我可以利用这个信息(上图中的堆栈)来分析这个Bug。但我个人还是喜欢在完整的IDE中来分析和调试问题。通过下面这个命令行,我可以重现这个ASan问题,同时将它附加在调试器中。

通过AddressSanitizer来发现实际产品的Bug

启动附加二进制文件之后的调试器,我看到了如下的景象:

通过AddressSanitizer来发现实际产品的Bug

IDE可以提供关于这个Bug的更加丰富的信息。你可以看到,这个ASan问题被报告为一个异常,并指明了具体出现在代码的哪一行,另外还有我熟悉的调试堆栈,以及其他的信息。另外,在输出窗口我们也可以看到一些很有帮助的信息。

大家可以猜到Bug藏在哪里吗?
提示:”sz”看起来是代表一个”size”。然后,再回忆一下Asan的报告:”stack buffer underflow”。

修复Bug

通过检查sz变量,我们可以更加清楚的明白问题的原因:MscIsFloatOrVectorConstant函数如果找到则返回内容长度,否则返回0。这个Bug场景中,它返回了0,我们在函数的局部结构体vval中发生了下溢出。修复方法也比较直接:我们在第16828行添加了一个判断代码。这个修复已经被集成到正式代码中,并将包含在即将发布的v16.10中。

这个特定的错误不太可能在真实世界出现:堆栈将需要以正确的方式存储一些垃圾值(以通过第16831行的条件)。但是,从理论上讲,此错误(更广泛地说,就是像它一样的错误)可能会导致代码中的优化不正确。 那是编译器可能遇到的最严重的错误之一:silent-bad-codegen。 我很高兴能找到这个错误,我也很高兴能够与你分享ASan可以很容易地修正此类错误。

结论

我们通常不会写有关修复编译器中的错误的博客文章,但我们想表达的是,ASan能帮助你轻松有效地发现并修复错误:
> 我们定制的命令行驱动构建系统只需几行更改即可集成build-with-ASan选项。
> 构建完成后,就可以无缝测试二进制文件:我运行了典型的内部开发循环测试套件。
> 一旦发现问题,就可以无缝重复我们IDE调试器中的步骤,将我直接指向要检查的源代码行。
> 确切的源代码行,再加上ASan能够描述问题的能力(堆栈下溢),使调查迅速而轻松。不需要很长的时间的分析:当然,我仍然必须确认并实际解决该问题,但是与传统的错误修复程序相比,Bug分析的大部分工作都是很快速的。

我希望ASan的高效率,有效性和简单性可以打动正在阅读本文的你。而且,对我来说,最引人注目的是:ASan发现了一个内存冲突,而且此冲突尚未在我们的程序中表现出不良的行为。它可以体现出来,但是在这里,我们无需花费更多,更间接的代码分析就能确定并解决它,并且希望它不会影响到我们的客户,这是太Biang了。

那就,试试看呗!

最后

Microsoft Visual C++团队的博客是我非常喜欢的博客之一,里面有很多关于Visual C++的知识和最新开发进展。大浪淘沙,如果你对Visual C++这门古老的技术还是那么感兴趣,则可以经常去他们那(或者我这)逛逛。
本文来自:《Finding Bugs with AddressSanitizer: MSVC Compiler》

通过AddressSanitizer来发现实际产品的Bug

上一篇:05 Java的class文件的组成介绍


下一篇:JVM_06 类加载与字节码技术(类文件结构)