首次抓到编译器 Bug

本贴最后更新于 2592 天前,其中的信息可能已经事过境迁

写代码这么多年,多次怀疑碰到了编译器 Bug,但最终却总是自己的责任,这次终于抓到了一个真正的编译器 Bug,记录一下。

最近公司的一个项目的编译环境升级到了 VS2013,完成后发现注册调试版本的一个 DLL (foo.dll)时触发了下面的断言:

ATLASSERT(pComModule->m_hInstTypeLib != NULL)

这里,pComModule 指向的 _AtlComModule 定义如下:

#pragma managed(push, off) __declspec(selectany) CAtlComModule _AtlComModule; #pragma managed(pop)

在 ATL 源码中,可以看到 CAtlComModule 的构造函数对 m_hInstTypeLib 进行了赋值。但奇怪的是,调试发现这个构造函数根本没被调用过,所以 m_hInstTypeLib 一直是 NULL,最终触发了上面的断言。

经过调查,发现了以下情况:

  1. foo.dll 是一个既有原生代码和又有托管代码的混合型项目,它的大多数 .cpp 文件是原生代码,但有 3 个文件是托管代码。因为 _AtlComModule 定义在 atlbase.h 中,而后者被 stdafx.h 包含了,所以,每一个编译后的 .obj 文件里都会有它的一个实例。但因为定义它时使用了 __declspec(selectany),所以链接器会只留下一个并把其他都丢掉。

  2. 对于动态链接库中的全局变量,编译器会自动生成代码,保证它们的初始化在调用 DllMain 之前完成(这也就保证了在调用 DllRegisterServer 时它们已经完成初始化),但这一点仅限于原生代码。对于托管代码,为避免可能发生的死锁(参见这篇文档),编译器在调用 DllMain 之前,不会调用任何托管代码,所以,托管代码中的全局变量无法在调用 DllMain 之前初始化,它们的初始化是在首次调用托管代码之前完成的。

  3. COM Dll 注册过程是先加载 Dll 文件(调用 DllMain),然后调用 DllRegisterServer 完成注册。默认情况下,这个过程不涉及任何托管代码的调用。

把以上几点综合在一起,问题就清楚了:如果链接器选择了托管代码中的 _AtlComModule,注册就会失败;反之,如果选择了原生代码中的实例,注册就可以成功(定义 _AtlComModule 时的 managed off 指令在这里并没有什么作用,因为它只能保证生成原生代码来调用构造函数,但只要编译选项包含了 /clr,这段代码本身还是需要从托管代码中调用)。但问题是,链接器的选择毫无规律可循,所以,这个锅只能它背了。

目前,这个问题只出现在程序的 Debug 版本中,但并没有证据显示它不会出现在 Release 版本中。而且,除了 _AtlComModule,ATL 中还有很多使用了 __declspec(selectany) 的全局变量,它们也可能引发类似的问题。

作为临时的解决方案,可以使用下面的方法来保证所有全局变量都能在被使用前完成初始化。

1. 在任意一个被编译为托管代码的 .cpp 文件中,添加下面的函数:

// DO NOT DELETE THIS FUNCTION!!! // This function is to ensure CLR is loaded and all global variables are initialized // before calling: DllRegisterServer / DllUnregisterServer / DllGetClassObject // If it is removed, calling to above functions may fail void ManagedEnsureClrLoaded() { // calling to GetTickCount() is only to prevent compiler optimization from removing // this function, you're free to do other things as your wish ::GetTickCount(); }

2. 仿照下面的方式修改 module class 的定义(这里以 CFooModule 为例)。需要注意的是,如果 Dll 还有其它导出的函数,也需要在它里面调用一下 NativeEnsureClrLoaded

// Add this function void NativeEnsureClrLoaded() { void ManagedEnsureClrLoaded(); static bool loaded = false; if( !loaded ) // no need locks, run twice does not cause a logical error { ManagedEnsureClrLoaded(); loaded = true; } } [module(dll, uuid = "{E6915FF1-AAAA-CCCC-BBBB-E4AEFB2C67CB}",name = "Foo", helpstring = "Foo 1.0 Type Library",resource_name = "IDR_Foo")] class CFooModule { public: // Override CAtlDllModuleT members HRESULT DllRegisterServer( _In_ BOOL bRegTypeLib = TRUE ) throw() { NativeEnsureClrLoaded(); // call NativeEnsureClrLoaded return __super::DllRegisterServer( bRegTypeLib ); } HRESULT DllUnregisterServer( _In_ BOOL bUnRegTypeLib = TRUE ) throw() { NativeEnsureClrLoaded(); // call NativeEnsureClrLoaded return __super::DllUnregisterServer( bUnRegTypeLib ); } HRESULT DllGetClassObject( _In_ REFCLSID rclsid, _In_ REFIID riid, _COM_Outptr_ LPVOID* ppv ) throw() { NativeEnsureClrLoaded(); // call NativeEnsureClrLoaded return __super::DllGetClassObject( rclsid, riid, ppv ); } };

最后,吐槽一下微软的技术支持,把问题报给他们之后各种拖延,基本没有主动的状态更新,这个问题的调试他们也一点忙没帮上。找到原因后,发了好几封邮件才终于确认是编译器(链接器)的 Bug,但表示不会在 VS2013 上修复了,不知道 VS2015 有没有指望。

  • B3log

    B3log 是一个开源组织,名字来源于“Bulletin Board Blog”缩写,目标是将独立博客与论坛结合,形成一种新的网络社区体验,详细请看 B3log 构思。目前 B3log 已经开源了多款产品:SymSoloVditor思源笔记

    1063 引用 • 3455 回帖 • 162 关注
  • C++

    C++ 是在 C 语言的基础上开发的一种通用编程语言,应用广泛。C++ 支持多种编程范式,面向对象编程、泛型编程和过程化编程。

    107 引用 • 153 回帖 • 1 关注
  • 技术

    到底什么才是技术呢?

    88 引用 • 179 回帖 • 4 关注
  • Windows

    Microsoft Windows 是美国微软公司研发的一套操作系统,它问世于 1985 年,起初仅仅是 Microsoft-DOS 模拟环境,后续的系统版本由于微软不断的更新升级,不但易用,也慢慢的成为家家户户人们最喜爱的操作系统。

    227 引用 • 476 回帖 • 1 关注

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...

推荐标签 标签

  • 深度学习

    深度学习(Deep Learning)是机器学习的分支,是一种试图使用包含复杂结构或由多重非线性变换构成的多个处理层对数据进行高层抽象的算法。

    54 引用 • 44 回帖
  • 旅游

    希望你我能在旅途中找到人生的下一站。

    95 引用 • 901 回帖 • 2 关注
  • SEO

    发布对别人有帮助的原创内容是最好的 SEO 方式。

    35 引用 • 200 回帖 • 31 关注
  • QQ

    1999 年 2 月腾讯正式推出“腾讯 QQ”,在线用户由 1999 年的 2 人(马化腾和张志东)到现在已经发展到上亿用户了,在线人数超过一亿,是目前使用最广泛的聊天软件之一。

    45 引用 • 557 回帖 • 1 关注
  • Laravel

    Laravel 是一套简洁、优雅的 PHP Web 开发框架。它采用 MVC 设计,是一款崇尚开发效率的全栈框架。

    20 引用 • 23 回帖 • 740 关注
  • Hadoop

    Hadoop 是由 Apache 基金会所开发的一个分布式系统基础架构。用户可以在不了解分布式底层细节的情况下,开发分布式程序。充分利用集群的威力进行高速运算和存储。

    89 引用 • 122 回帖 • 618 关注
  • SOHO

    为成为自由职业者在家办公而努力吧!

    7 引用 • 55 回帖 • 1 关注
  • 运维

    互联网运维工作,以服务为中心,以稳定、安全、高效为三个基本点,确保公司的互联网业务能够 7×24 小时为用户提供高质量的服务。

    150 引用 • 257 回帖 • 2 关注
  • 国际化

    i18n(其来源是英文单词 internationalization 的首末字符 i 和 n,18 为中间的字符数)是“国际化”的简称。对程序来说,国际化是指在不修改代码的情况下,能根据不同语言及地区显示相应的界面。

    8 引用 • 26 回帖 • 1 关注
  • Docker

    Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的操作系统上。容器完全使用沙箱机制,几乎没有性能开销,可以很容易地在机器和数据中心中运行。

    494 引用 • 930 回帖
  • CSDN

    CSDN (Chinese Software Developer Network) 创立于 1999 年,是中国的 IT 社区和服务平台,为中国的软件开发者和 IT 从业者提供知识传播、职业发展、软件开发等全生命周期服务,满足他们在职业发展中学习及共享知识和信息、建立职业发展社交圈、通过软件开发实现技术商业化等刚性需求。

    14 引用 • 155 回帖
  • 小薇

    小薇是一个用 Java 写的 QQ 聊天机器人 Web 服务,可以用于社群互动。

    由于 Smart QQ 从 2019 年 1 月 1 日起停止服务,所以该项目也已经停止维护了!

    35 引用 • 468 回帖 • 763 关注
  • Sillot

    Insights(注意当前设置 master 为默认分支)

    汐洛彖夲肜矩阵(Sillot T☳Converbenk Matrix),致力于服务智慧新彖乄,具有彖乄驱动、极致优雅、开发者友好的特点。其中汐洛绞架(Sillot-Gibbet)基于自思源笔记(siyuan-note),前身是思源笔记汐洛版(更早是思源笔记汐洛分支),是智慧新录乄终端(多端融合,移动端优先)。

    主仓库地址:Hi-Windom/Sillot

    文档地址:sillot.db.sc.cn

    注意事项:

    1. ⚠️ 汐洛仍在早期开发阶段,尚不稳定
    2. ⚠️ 汐洛并非面向普通用户设计,使用前请了解风险
    3. ⚠️ 汐洛绞架基于思源笔记,开发者尽最大努力与思源笔记保持兼容,但无法实现 100% 兼容
    29 引用 • 25 回帖 • 118 关注
  • CAP

    CAP 指的是在一个分布式系统中, Consistency(一致性)、 Availability(可用性)、Partition tolerance(分区容错性),三者不可兼得。

    12 引用 • 5 回帖 • 631 关注
  • 安装

    你若安好,便是晴天。

    132 引用 • 1184 回帖 • 2 关注
  • Ngui

    Ngui 是一个 GUI 的排版显示引擎和跨平台的 GUI 应用程序开发框架,基于
    Node.js / OpenGL。目标是在此基础上开发 GUI 应用程序可拥有开发 WEB 应用般简单与速度同时兼顾 Native 应用程序的性能与体验。

    7 引用 • 9 回帖 • 400 关注
  • 爬虫

    网络爬虫(Spider、Crawler),是一种按照一定的规则,自动地抓取万维网信息的程序。

    106 引用 • 275 回帖 • 1 关注
  • Solo

    Solo 是一款小而美的开源博客系统,专为程序员设计。Solo 有着非常活跃的社区,可将文章作为帖子推送到社区,来自社区的回帖将作为博客评论进行联动(具体细节请浏览 B3log 构思 - 分布式社区网络)。

    这是一种全新的网络社区体验,让热爱记录和分享的你不再感到孤单!

    1441 引用 • 10069 回帖 • 496 关注
  • OpenCV
    15 引用 • 36 回帖 • 5 关注
  • Facebook

    Facebook 是一个联系朋友的社交工具。大家可以通过它和朋友、同事、同学以及周围的人保持互动交流,分享无限上传的图片,发布链接和视频,更可以增进对朋友的了解。

    4 引用 • 15 回帖 • 446 关注
  • 书籍

    宋真宗赵恒曾经说过:“书中自有黄金屋,书中自有颜如玉。”

    78 引用 • 396 回帖
  • ReactiveX

    ReactiveX 是一个专注于异步编程与控制可观察数据(或者事件)流的 API。它组合了观察者模式,迭代器模式和函数式编程的优秀思想。

    1 引用 • 2 回帖 • 182 关注
  • Gzip

    gzip (GNU zip)是 GNU 自由软件的文件压缩程序。我们在 Linux 中经常会用到后缀为 .gz 的文件,它们就是 Gzip 格式的。现今已经成为互联网上使用非常普遍的一种数据压缩格式,或者说一种文件格式。

    9 引用 • 12 回帖 • 164 关注
  • Sandbox

    如果帖子标签含有 Sandbox ,则该帖子会被视为“测试帖”,主要用于测试社区功能,排查 bug 等,该标签下内容不定期进行清理。

    432 引用 • 1250 回帖 • 596 关注
  • frp

    frp 是一个可用于内网穿透的高性能的反向代理应用,支持 TCP、UDP、 HTTP 和 HTTPS 协议。

    20 引用 • 7 回帖
  • 自由行
  • Kafka

    Kafka 是一种高吞吐量的分布式发布订阅消息系统,它可以处理消费者规模的网站中的所有动作流数据。 这种动作(网页浏览,搜索和其他用户的行动)是现代系统中许多功能的基础。 这些数据通常是由于吞吐量的要求而通过处理日志和日志聚合来解决。

    36 引用 • 35 回帖 • 1 关注