首次抓到编译器 Bug

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

写代码这么多年,多次怀疑碰到了编译器 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 引用 • 3453 回帖 • 201 关注
  • C++

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

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

    到底什么才是技术呢?

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

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

    221 引用 • 473 回帖

相关帖子

欢迎来到这里!

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

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

推荐标签 标签

  • CSDN

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

    14 引用 • 155 回帖 • 1 关注
  • Telegram

    Telegram 是一个非盈利性、基于云端的即时消息服务。它提供了支持各大操作系统平台的开源的客户端,也提供了很多强大的 APIs 给开发者创建自己的客户端和机器人。

    5 引用 • 35 回帖
  • JetBrains

    JetBrains 是一家捷克的软件开发公司,该公司位于捷克的布拉格,并在俄国的圣彼得堡及美国麻州波士顿都设有办公室,该公司最为人所熟知的产品是 Java 编程语言开发撰写时所用的集成开发环境:IntelliJ IDEA

    18 引用 • 54 回帖
  • 宕机

    宕机,多指一些网站、游戏、网络应用等服务器一种区别于正常运行的状态,也叫“Down 机”、“当机”或“死机”。宕机状态不仅仅是指服务器“挂掉了”、“死机了”状态,也包括服务器假死、停用、关闭等一些原因而导致出现的不能够正常运行的状态。

    13 引用 • 82 回帖 • 52 关注
  • OpenResty

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

    17 引用 • 47 关注
  • CSS

    CSS(Cascading Style Sheet)“层叠样式表”是用于控制网页样式并允许将样式信息与网页内容分离的一种标记性语言。

    197 引用 • 547 回帖
  • JVM

    JVM(Java Virtual Machine)Java 虚拟机是一个微型操作系统,有自己的硬件构架体系,还有相应的指令系统。能够识别 Java 独特的 .class 文件(字节码),能够将这些文件中的信息读取出来,使得 Java 程序只需要生成 Java 虚拟机上的字节码后就能在不同操作系统平台上进行运行。

    180 引用 • 120 回帖 • 1 关注
  • C++

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

    107 引用 • 153 回帖 • 3 关注
  • 小说

    小说是以刻画人物形象为中心,通过完整的故事情节和环境描写来反映社会生活的文学体裁。

    28 引用 • 108 回帖
  • etcd

    etcd 是一个分布式、高可用的 key-value 数据存储,专门用于在分布式系统中保存关键数据。

    5 引用 • 26 回帖 • 526 关注
  • 博客

    记录并分享人生的经历。

    273 引用 • 2388 回帖
  • 分享

    有什么新发现就分享给大家吧!

    247 引用 • 1792 回帖 • 7 关注
  • Log4j

    Log4j 是 Apache 开源的一款使用广泛的 Java 日志组件。

    20 引用 • 18 回帖 • 32 关注
  • 友情链接

    确认过眼神后的灵魂连接,站在链在!

    24 引用 • 373 回帖 • 1 关注
  • Google

    Google(Google Inc.,NASDAQ:GOOG)是一家美国上市公司(公有股份公司),于 1998 年 9 月 7 日以私有股份公司的形式创立,设计并管理一个互联网搜索引擎。Google 公司的总部称作“Googleplex”,它位于加利福尼亚山景城。Google 目前被公认为是全球规模最大的搜索引擎,它提供了简单易用的免费服务。不作恶(Don't be evil)是谷歌公司的一项非正式的公司口号。

    49 引用 • 192 回帖
  • LeetCode

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

    209 引用 • 72 回帖 • 1 关注
  • ngrok

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

    7 引用 • 63 回帖 • 622 关注
  • 微软

    微软是一家美国跨国科技公司,也是世界 PC 软件开发的先导,由比尔·盖茨与保罗·艾伦创办于 1975 年,公司总部设立在华盛顿州的雷德蒙德(Redmond,邻近西雅图)。以研发、制造、授权和提供广泛的电脑软件服务业务为主。

    8 引用 • 44 回帖
  • gRpc
    11 引用 • 9 回帖 • 61 关注
  • Ngui

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

    7 引用 • 9 回帖 • 388 关注
  • 房星科技

    房星网,我们不和没有钱的程序员谈理想,我们要让程序员又有理想又有钱。我们有雄厚的房地产行业线下资源,遍布昆明全城的 100 家门店、四千地产经纪人是我们坚实的后盾。

    6 引用 • 141 回帖 • 584 关注
  • OpenShift

    红帽提供的 PaaS 云,支持多种编程语言,为开发人员提供了更为灵活的框架、存储选择。

    14 引用 • 20 回帖 • 624 关注
  • 知乎

    知乎是网络问答社区,连接各行各业的用户。用户分享着彼此的知识、经验和见解,为中文互联网源源不断地提供多种多样的信息。

    10 引用 • 66 回帖
  • Flume

    Flume 是一套分布式的、可靠的,可用于有效地收集、聚合和搬运大量日志数据的服务架构。

    9 引用 • 6 回帖 • 621 关注
  • 链书

    链书(Chainbook)是 B3log 开源社区提供的区块链纸质书交易平台,通过 B3T 实现共享激励与价值链。可将你的闲置书籍上架到链书,我们共同构建这个全新的交易平台,让闲置书籍继续发挥它的价值。

    链书社

    链书目前已经下线,也许以后还有计划重制上线。

    14 引用 • 257 回帖
  • Android

    Android 是一种以 Linux 为基础的开放源码操作系统,主要使用于便携设备。2005 年由 Google 收购注资,并拉拢多家制造商组成开放手机联盟开发改良,逐渐扩展到到平板电脑及其他领域上。

    334 引用 • 323 回帖
  • Scala

    Scala 是一门多范式的编程语言,集成面向对象编程和函数式编程的各种特性。

    13 引用 • 11 回帖 • 124 关注