排错实战 | 当编译器"吃掉"函数声明:一次由宏冲突引发的离奇编译错误

摘要

在编译 .NET Runtime 源码研究 GC 机制时,我遇到了一个离奇的编译错误:函数 __asan_handle_no_return 的声明被编译器报错为”类函数宏的调用”。通过将源文件预处理输出到中间文件,我发现这个函数声明竟然变成了 void ;——它被”吃掉”了!追踪发现,在 utils.h 中定义了一个同名宏,当 __SANITIZE_ADDRESS__ 宏未定义时,该宏被展开为空,导致函数声明被意外删除。这个案例再次证明:宏命名冲突是编译错误的常见陷阱,而预处理输出文件是诊断这类问题的利器。同时,FileLocator 这类文件搜索工具在源码分析中不可或缺。

初遇错误

https://github.com/dotnet/runtime 下载源代码到本地,打开 powershell 执行 .\build.cmd -vs coreclr.sln -a x64 -c Debug,一切顺利的话会生成对应的 sln 文件。打开 sln 文件,重新生成解决方案,就遇到了下面的错误。

compile-error

从图中看,这就是一个很简单的函数声明啊,这还能出错?如果仔细看提示,类函数宏的调用。可以猜想这个错误跟宏有关系。难道又是一个宏冲突导致的问题?不管怎么样,还是老一套,把对应的源文件输出到中间文件,可以参考之前的文章

初步调查

错误工程是 minipal_sanitizer_support,出错的源文件是 sansupport.c。把 sansupport.c 经过预处理的文件输出到中间文件中,设置如下图:

preprocess-to-file-and-keep-comments

设置好后,在 sansupport.c 上右键,编译,编译完成后,会生成一个名为 sansupport.i 的中间文件,打开此文件,并搜索 __asan_handle_no_return,仅发现了一条无关紧要的记录(在注释中出现),如下图:

search-keyword-and-find-one-useless

呦吼,直接消失了?有点意思,搜索一下它上面的函数 __asan_addr_is_in_fake_stack。搜到了,而且有了意外收获 —— 目标函数变成了 void ;,如下图:

search-previous-function-and-find-target-function-missing

至此,已经可以基本确定是宏导致的了,在某处一定有一个与目标函数重名的宏,接下来该怎么办呢?还是搜。

找出真凶

清楚 FileLocator 在下载的 .net runtime 源码目录下搜 __asan_handle_no_return,发现在 utils.h 中定义了这个宏。打开 utils.h 查看,果然发现 146 行的宏定义,如下图:

macro-defined-in-utils-h

根据此处的逻辑可知,应该是 126 行的宏(HAS_ADDRESS_SANITIZER)没定义,导致了这个错误。而 126 行用到的宏又是由 91~107 行的宏定义决定的。如下图:

macro-HAS_ADDRESS_SANITIZER-definition

显然是没定义 __SANITIZE_ADDRESS__ 宏导致的。知道原因了,简单粗暴的加上一句 #define __SANITIZE_ADDRESS__,再次编译,果然顺利通过了。

至于为什么编译失败,我就不关心了。因为我有更感兴趣的事情要处理 —— 查看 gc 相关的逻辑。本篇总结就水到这里啦。

总结

  • 遇到奇怪的编译错误不要慌,秘密武器就是输出到中间文件,查看编译器眼中的文件到底长什么样
  • 一定要有自己顺手的一套工具,搜索文件内容强烈推荐 FileLocator
BianChengNan wechat
扫描左侧二维码关注公众号,扫描右侧二维码加我个人微信:)
0%