首次抓到编译器 Bug

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

写代码这么多年,多次怀疑碰到了编译器 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思源笔记

    1062 引用 • 3455 回帖 • 149 关注
  • C++

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

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

    到底什么才是技术呢?

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

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

    229 引用 • 476 回帖

相关帖子

欢迎来到这里!

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

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

推荐标签 标签

  • Hexo

    Hexo 是一款快速、简洁且高效的博客框架,使用 Node.js 编写。

    22 引用 • 148 回帖 • 8 关注
  • Rust

    Rust 是一门赋予每个人构建可靠且高效软件能力的语言。Rust 由 Mozilla 开发,最早发布于 2014 年 9 月。

    59 引用 • 22 回帖 • 7 关注
  • 爬虫

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

    106 引用 • 275 回帖
  • Sublime

    Sublime Text 是一款可以用来写代码、写文章的文本编辑器。支持代码高亮、自动完成,还支持通过插件进行扩展。

    10 引用 • 5 回帖
  • C

    C 语言是一门通用计算机编程语言,应用广泛。C 语言的设计目标是提供一种能以简易的方式编译、处理低级存储器、产生少量的机器码以及不需要任何运行环境支持便能运行的编程语言。

    86 引用 • 165 回帖
  • MySQL

    MySQL 是一个关系型数据库管理系统,由瑞典 MySQL AB 公司开发,目前属于 Oracle 公司。MySQL 是最流行的关系型数据库管理系统之一。

    694 引用 • 537 回帖
  • Office

    Office 现已更名为 Microsoft 365. Microsoft 365 将高级 Office 应用(如 Word、Excel 和 PowerPoint)与 1 TB 的 OneDrive 云存储空间、高级安全性等结合在一起,可帮助你在任何设备上完成操作。

    5 引用 • 34 回帖
  • NetBeans

    NetBeans 是一个始于 1997 年的 Xelfi 计划,本身是捷克布拉格查理大学的数学及物理学院的学生计划。此计划延伸而成立了一家公司进而发展这个商用版本的 NetBeans IDE,直到 1999 年 Sun 买下此公司。Sun 于次年(2000 年)六月将 NetBeans IDE 开源,直到现在 NetBeans 的社群依然持续增长。

    78 引用 • 102 回帖 • 712 关注
  • 面试

    面试造航母,上班拧螺丝。多面试,少加班。

    326 引用 • 1395 回帖
  • Pipe

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

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

    134 引用 • 1127 回帖 • 108 关注
  • IPFS

    IPFS(InterPlanetary File System,星际文件系统)是永久的、去中心化保存和共享文件的方法,这是一种内容可寻址、版本化、点对点超媒体的分布式协议。请浏览 IPFS 入门笔记了解更多细节。

    20 引用 • 245 回帖 • 232 关注
  • 微信

    腾讯公司 2011 年 1 月 21 日推出的一款手机通讯软件。用户可以通过摇一摇、搜索号码、扫描二维码等添加好友和关注公众平台,同时可以将自己看到的精彩内容分享到微信朋友圈。

    133 引用 • 796 回帖
  • Facebook

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

    4 引用 • 15 回帖 • 443 关注
  • Spring

    Spring 是一个开源框架,是于 2003 年兴起的一个轻量级的 Java 开发框架,由 Rod Johnson 在其著作《Expert One-On-One J2EE Development and Design》中阐述的部分理念和原型衍生而来。它是为了解决企业应用开发的复杂性而创建的。框架的主要优势之一就是其分层架构,分层架构允许使用者选择使用哪一个组件,同时为 JavaEE 应用程序开发提供集成的框架。

    947 引用 • 1460 回帖 • 1 关注
  • V2EX

    V2EX 是创意工作者们的社区。这里目前汇聚了超过 400,000 名主要来自互联网行业、游戏行业和媒体行业的创意工作者。V2EX 希望能够成为创意工作者们的生活和事业的一部分。

    16 引用 • 236 回帖 • 242 关注
  • ZooKeeper

    ZooKeeper 是一个分布式的,开放源码的分布式应用程序协调服务,是 Google 的 Chubby 一个开源的实现,是 Hadoop 和 HBase 的重要组件。它是一个为分布式应用提供一致性服务的软件,提供的功能包括:配置维护、域名服务、分布式同步、组服务等。

    61 引用 • 29 回帖 • 12 关注
  • OAuth

    OAuth 协议为用户资源的授权提供了一个安全的、开放而又简易的标准。与以往的授权方式不同之处是 oAuth 的授权不会使第三方触及到用户的帐号信息(如用户名与密码),即第三方无需使用用户的用户名与密码就可以申请获得该用户资源的授权,因此 oAuth 是安全的。oAuth 是 Open Authorization 的简写。

    36 引用 • 103 回帖 • 36 关注
  • 深度学习

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

    43 引用 • 44 回帖 • 2 关注
  • Thymeleaf

    Thymeleaf 是一款用于渲染 XML/XHTML/HTML5 内容的模板引擎。类似 Velocity、 FreeMarker 等,它也可以轻易的与 Spring 等 Web 框架进行集成作为 Web 应用的模板引擎。与其它模板引擎相比,Thymeleaf 最大的特点是能够直接在浏览器中打开并正确显示模板页面,而不需要启动整个 Web 应用。

    11 引用 • 19 回帖 • 394 关注
  • Swift

    Swift 是苹果于 2014 年 WWDC(苹果开发者大会)发布的开发语言,可与 Objective-C 共同运行于 Mac OS 和 iOS 平台,用于搭建基于苹果平台的应用程序。

    34 引用 • 37 回帖 • 559 关注
  • PWL

    组织简介

    用爱发电 (Programming With Love) 是一个以开源精神为核心的民间开源爱好者技术组织,“用爱发电”象征开源与贡献精神,加入组织,代表你将遵守组织的“个人开源爱好者”的各项条款。申请加入:用爱发电组织邀请帖
    用爱发电组织官网:https://programmingwithlove.stackoverflow.wiki/

    用爱发电组织的核心驱动力:

    • 遵守开源守则,体现开源&贡献精神:以分享为目的,拒绝非法牟利。
    • 自我保护:使用适当的 License 保护自己的原创作品。
    • 尊重他人:不以各种理由、各种漏洞进行未经允许的抄袭、散播、洩露;以礼相待,尊重所有对社区做出贡献的开发者;通过他人的分享习得知识,要留下足迹,表示感谢。
    • 热爱编程、热爱学习:加入组织,热爱编程是首当其要的。我们欢迎热爱讨论、分享、提问的朋友,也同样欢迎默默成就的朋友。
    • 倾听:正确并恳切对待、处理问题与建议,及时修复开源项目的 Bug ,及时与反馈者沟通。不抬杠、不无视、不辱骂。
    • 平视:不诋毁、轻视、嘲讽其他开发者,主动提出建议、施以帮助,以和谐为本。只要他人肯努力,你也可能会被昔日小看的人所超越,所以请保持谦虚。
    • 乐观且活跃:你的努力决定了你的高度。不要放弃,多年后回头俯瞰,才会发现自己已经成就往日所仰望的水平。积极地将项目开源,帮助他人学习、改进,自己也会获得相应的提升、成就与成就感。
    1 引用 • 487 回帖 • 4 关注
  • CSDN

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

    14 引用 • 155 回帖
  • CongSec

    本标签主要用于分享网络空间安全专业的学习笔记

    1 引用 • 1 回帖 • 37 关注
  • 开源中国

    开源中国是目前中国最大的开源技术社区。传播开源的理念,推广开源项目,为 IT 开发者提供了一个发现、使用、并交流开源技术的平台。目前开源中国社区已收录超过两万款开源软件。

    7 引用 • 86 回帖
  • ngrok

    ngrok 是一个反向代理,通过在公共的端点和本地运行的 Web 服务器之间建立一个安全的通道。

    7 引用 • 63 回帖 • 655 关注
  • Kafka

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

    36 引用 • 35 回帖 • 3 关注
  • SOHO

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

    7 引用 • 55 回帖 • 3 关注