首次抓到编译器 Bug

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

写代码这么多年,多次怀疑碰到了编译器 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 回帖
  • 技术

    到底什么才是技术呢?

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

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

    230 引用 • 477 回帖

相关帖子

欢迎来到这里!

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

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

推荐标签 标签

  • LeetCode

    LeetCode(力扣)是一个全球极客挚爱的高质量技术成长平台,想要学习和提升专业能力从这里开始,充足技术干货等你来啃,轻松拿下 Dream Offer!

    209 引用 • 72 回帖
  • Node.js

    Node.js 是一个基于 Chrome JavaScript 运行时建立的平台, 用于方便地搭建响应速度快、易于扩展的网络应用。Node.js 使用事件驱动, 非阻塞 I/O 模型而得以轻量和高效。

    139 引用 • 269 回帖
  • danl
    186 关注
  • Caddy

    Caddy 是一款默认自动启用 HTTPS 的 HTTP/2 Web 服务器。

    10 引用 • 54 回帖 • 184 关注
  • SQLServer

    SQL Server 是由 [微软] 开发和推广的关系数据库管理系统(DBMS),它最初是由 微软、Sybase 和 Ashton-Tate 三家公司共同开发的,并于 1988 年推出了第一个 OS/2 版本。

    21 引用 • 31 回帖 • 3 关注
  • Latke

    Latke 是一款以 JSON 为主的 Java Web 框架。

    71 引用 • 535 回帖 • 832 关注
  • 以太坊

    以太坊(Ethereum)并不是一个机构,而是一款能够在区块链上实现智能合约、开源的底层系统。以太坊是一个平台和一种编程语言 Solidity,使开发人员能够建立和发布下一代去中心化应用。 以太坊可以用来编程、分散、担保和交易任何事物:投票、域名、金融交易所、众筹、公司管理、合同和知识产权等等。

    34 引用 • 367 回帖
  • 面试

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

    326 引用 • 1395 回帖 • 1 关注
  • Swagger

    Swagger 是一款非常流行的 API 开发工具,它遵循 OpenAPI Specification(这是一种通用的、和编程语言无关的 API 描述规范)。Swagger 贯穿整个 API 生命周期,如 API 的设计、编写文档、测试和部署。

    26 引用 • 35 回帖 • 2 关注
  • Hexo

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

    22 引用 • 148 回帖 • 16 关注
  • uTools

    uTools 是一个极简、插件化、跨平台的现代桌面软件。通过自由选配丰富的插件,打造你得心应手的工具集合。

    7 引用 • 28 回帖
  • 强迫症

    强迫症(OCD)属于焦虑障碍的一种类型,是一组以强迫思维和强迫行为为主要临床表现的神经精神疾病,其特点为有意识的强迫和反强迫并存,一些毫无意义、甚至违背自己意愿的想法或冲动反反复复侵入患者的日常生活。

    15 引用 • 161 回帖 • 2 关注
  • 小薇

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

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

    35 引用 • 468 回帖 • 761 关注
  • HHKB

    HHKB 是富士通的 Happy Hacking 系列电容键盘。电容键盘即无接点静电电容式键盘(Capacitive Keyboard)。

    5 引用 • 74 回帖 • 523 关注
  • 钉钉

    钉钉,专为中国企业打造的免费沟通协同多端平台, 阿里巴巴出品。

    15 引用 • 67 回帖 • 262 关注
  • Dubbo

    Dubbo 是一个分布式服务框架,致力于提供高性能和透明化的 RPC 远程服务调用方案,是 [阿里巴巴] SOA 服务化治理方案的核心框架,每天为 2,000+ 个服务提供 3,000,000,000+ 次访问量支持,并被广泛应用于阿里巴巴集团的各成员站点。

    60 引用 • 82 回帖 • 619 关注
  • CSDN

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

    14 引用 • 155 回帖
  • Spring

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

    948 引用 • 1460 回帖 • 2 关注
  • Vim

    Vim 是类 UNIX 系统文本编辑器 Vi 的加强版本,加入了更多特性来帮助编辑源代码。Vim 的部分增强功能包括文件比较(vimdiff)、语法高亮、全面的帮助系统、本地脚本(Vimscript)和便于选择的可视化模式。

    29 引用 • 66 回帖 • 2 关注
  • CongSec

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

    1 引用 • 1 回帖 • 38 关注
  • OpenResty

    OpenResty 是一个基于 NGINX 与 Lua 的高性能 Web 平台,其内部集成了大量精良的 Lua 库、第三方模块以及大多数的依赖项。用于方便地搭建能够处理超高并发、扩展性极高的动态 Web 应用、Web 服务和动态网关。

    17 引用 • 51 关注
  • ZooKeeper

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

    61 引用 • 29 回帖 • 8 关注
  • Hibernate

    Hibernate 是一个开放源代码的对象关系映射框架,它对 JDBC 进行了非常轻量级的对象封装,使得 Java 程序员可以随心所欲的使用对象编程思维来操纵数据库。

    39 引用 • 103 回帖 • 726 关注
  • Gitea

    Gitea 是一个开源社区驱动的轻量级代码托管解决方案,后端采用 Go 编写,采用 MIT 许可证。

    5 引用 • 16 回帖 • 1 关注
  • gRpc
    11 引用 • 9 回帖 • 101 关注
  • 正则表达式

    正则表达式(Regular Expression)使用单个字符串来描述、匹配一系列遵循某个句法规则的字符串。

    31 引用 • 94 回帖
  • Vue.js

    Vue.js(读音 /vju ː/,类似于 view)是一个构建数据驱动的 Web 界面库。Vue.js 的目标是通过尽可能简单的 API 实现响应的数据绑定和组合的视图组件。

    268 引用 • 666 回帖