静态初始化时的访问冲突

2021-12-22 00:00:00 visual-c++ c++ c++-cli

我正在使用 Windows 7 上的 Visual Studio 2015 开发一个应用程序.该应用程序有一个 C# 前端、一个 C++ CLR 包装器和 C++ 本机代码.

I am working on an application with Visual Studio 2015 on Windows 7. The application has a C# frontend, a C++ CLR wrapper and C++ native code.

在使用 C++ 本机代码在函数范围内初始化静态变量时,我的应用程序因访问冲突而崩溃.但仅在 Windows Server 2003 Enterprise SP2 上,而不在 Windows 7 或 Windows Server 2012 上.我知道 Windows Server 2003 不再受支持,但我必须针对该平台再坚持几个月,而 Visual Studio 2015 提供了一个平台工具集来定位

My application crashes with an access violation while initializing a static variable at function scope with C++ native code. But only on Windows Server 2003 Enterprise SP2 and not on Windows 7 or Windows Server 2012. I know Windows Server 2003 is out of support, but I have to target that platform for a few additional months and Visual Studio 2015 provides a platform toolset to target it.

我创建了一个可重现的小示例,您可以在最后找到.

I created a small reproducible example which you find at the end.

崩溃只发生在涉及的所有三个部分(C#、C++ CLR、C++).如果我删除任何内容,崩溃就会消失.

The crash only happens with all three parts involved (C#, C++ CLR, C++). If I remove any, the crash is gone.

崩溃仅发生在定义了自定义构造函数的情况下.如果我删除构造函数,崩溃就会消失.

The crash only happens with a custom constructor defined. If I remove the constructor, the crash is gone.

我不是汇编专家,但对我来说,崩溃似乎是由检查是否需要静态初始化的代码引起的.甚至没有调用构造函数.

I am no assembly expert, but for me it looks like the crash is caused by the code which checks if the static initialization is required. The constructor is not even called.

我的问题是:为什么它会在 Windows Server 2003 上崩溃?我是否遗漏了一些重要的项目设置?

My question is: Why does it crash on Windows Server 2003? Am I missing some important project setting?

Unhandled exception at 0x1000167E (Native.dll) in Managed.exe.dmp: 0xC0000005: Access violation reading location 0x00000000.

Visual C# 控制台应用程序Managed.exe"

程序.cs

// Target framework: .NET Framework 4
// Platform target: x86

using System;

namespace Managed
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.Write("Press enter to start test...");
            Console.ReadLine();

            Native.Wrapper wrapper = new Native.Wrapper();
            Console.WriteLine("Test was successful");

            Console.Write("Press enter to exit...");
            Console.ReadLine();
        }
    }
}

Visual C++ CLR 类库Native.dll"

Wrapper.hpp

#pragma once

namespace Native
{

public ref class Wrapper
{
public:
    Wrapper();

}; // public ref class Wrapper

} // namespace Native

Wrapper.cpp

// Platform Toolset: Visual Studio 2015 - Windows XP (v140_xp)
// Common Language Runtime Support: Common Language Runtime Support (/clr)
// .NET Target Framework Version: v4.0
// Warning Level: Level4
// Treat Warnings As Errors: Yes (/WX)
// Precompiled Header: Not Using Precompiled Headers
// SubSystem: Console (/SUBSYSTEM:CONSOLE)
// Optimization: Disabled (/Od)

#pragma once

#include "Wrapper.hpp"
#include "Caller.hpp"

namespace Native
{

Wrapper::Wrapper()
{
    Caller* caller = new Caller();
    delete caller;
}

} // namespace Native

Caller.hpp

#pragma once

namespace Native
{

class Caller
{
public:
    Caller();

}; // class Caller

} // namespace Native

Caller.cpp

// Platform Toolset: Visual Studio 2015 - Windows XP (v140_xp)
// Common Language Runtime Support: No Common Language RunTime Support
// Warning Level: Level4
// Treat Warnings As Errors: Yes (/WX)
// Precompiled Header: Not Using Precompiled Headers
// SubSystem: Console (/SUBSYSTEM:CONSOLE)
// Optimization: Disabled (/Od)

#include "Caller.hpp"
#include "Singleton.hpp"

namespace Native
{

Caller::Caller()
{
    Singleton::GetInstance()->DoSomething();
}

} // namespace Native

Singleton.hpp

#pragma once

#include <iostream>

namespace Native
{

class Singleton
{
public:
    Singleton() // !!! remove constructor to prevent crash !!!
    { }

    static Singleton* GetInstance()
    {
        static Singleton Instance; // !!! crashes here !!!
        return &Instance;
    }

    void DoSomething()
    {
        std::wcout << L"Doing something...
";
    }

}; // class Singleton

} // namespace Native

拆解

    static Singleton* GetInstance()
    {
10001650  push        ebp  
10001651  mov         ebp,esp  
10001653  push        0FFFFFFFFh  
10001655  push        10006A8Ch  
1000165A  mov         eax,dword ptr fs:[00000000h]  
10001660  push        eax  
10001661  mov         eax,dword ptr ds:[1001B334h]  
10001666  xor         eax,ebp  
10001668  push        eax  
10001669  lea         eax,[ebp-0Ch]  
1000166C  mov         dword ptr fs:[00000000h],eax  
        static Singleton Instance;
10001672  mov         eax,dword ptr ds:[1001B5D0h]  
10001677  mov         ecx,dword ptr fs:[2Ch]  
1000167E  mov         edx,dword ptr [ecx+eax*4] // !!! access violation here !!!
10001681  mov         eax,dword ptr ds:[1001B5A4h]  
10001686  cmp         eax,dword ptr [edx+4]  
1000168C  jle         Native::Singleton::GetInstance+79h (100016C9h)  

寄存器

EAX?=?00000000 EBX?=?00000000 ECX?=?00000000 EDX?=?006A0003 ESI?=?001647C8
EDI?=?0012F3BC EIP?=?1000167E ESP?=?0012F394 EBP?=?0012F3A4 EFL?=?00010282 

编辑 1

在没有发生崩溃的本地调试时,程序集可以看到更多的符号:

Edit 1

While debugging locally where the crash does not happen, a few more symbols are visible with the assembly:

    static Singleton* GetInstance()
    {
0FBD1650  push        ebp  
0FBD1651  mov         ebp,esp  
0FBD1653  push        0FFFFFFFFh  
0FBD1655  push        offset __ehhandler$?GetInstance@Singleton@Native@@SAPAV12@XZ (0FBD86BCh)  
0FBD165A  mov         eax,dword ptr fs:[00000000h]  
0FBD1660  push        eax  
0FBD1661  mov         eax,dword ptr [___security_cookie (0FBF03CCh)]  
0FBD1666  xor         eax,ebp  
0FBD1668  push        eax  
0FBD1669  lea         eax,[ebp-0Ch]  
0FBD166C  mov         dword ptr fs:[00000000h],eax  
        static Singleton Instance;
0FBD1672  mov         eax,dword ptr [__tls_index (0FBF0668h)]  
0FBD1677  mov         ecx,dword ptr fs:[2Ch]  
0FBD167E  mov         edx,dword ptr [ecx+eax*4]  
0FBD1681  mov         eax,dword ptr [TSS0<`template-parameter-2',Native::Singleton::tInstance,Native::Singleton * * const volatile,void,int, ?? &> (0FBF063Ch)]  
0FBD1686  cmp         eax,dword ptr [edx+4]  
0FBD168C  jle         Native::Singleton::GetInstance+79h (0FBD16C9h)  

符号 __tls_index 似乎属于某个线程本地存储(从名称猜测).这与使用线程本地存储作为参考实现中的性能优化.崩溃时,线程本地存储返回0.

The symbol __tls_index seems to belong to some thread local store (guessed from the name). This matches with Magic statics which uses thread local store as a performance optimization in the reference implementation. When crashing, the thread local store returns 0.

这可能是 Windows Server 2003 上管理和初始化线程本地存储的运行时环境的错误吗?

Could this be a bug with the runtime environment on Windows Server 2003 which manages and initializes the thread local store?

通过 Microsoft Connect 报告为错误:错误报告

Reported as bug through Microsoft Connect: Bug report

推荐答案

这是 Microsoft 在我的 错误报告在 Microsoft Connect:

This is the answer of Microsoft as posted to my bug report at Microsoft Connect:

Windows Server 2003 和 Windows XP 在动态加载使用线程本地存储的 DLL(通过 LoadLibrary)时存在问题,当静态本地已经初始化时,线程安全静态在内部使用它来提供高效执行.由于这些系统不受支持,因此极不可能为这些系统创建补丁来添加这种支持,就像 Vista 和更新的操作系统中存在的那样,并且我们不愿意惩罚受支持操作系统上的性能来提供这种支持旧的不受支持的功能.

Windows Server 2003 and Windows XP have problems with dynamically loading a DLL (via LoadLibrary) that uses thread-local storage, which is what thread-safe statics use internally to provide efficient execution when the static local has already been initialized. As these systems are out of support, it is extremely unlikely for a patch to be created for those systems to add this support as is present in Vista and newer OSes, and we are reluctant to penalize the performance on in-support OSes to provide this functionality to the old out-of-support ones.

要解决此问题,您可以使用/Zc:threadSafeInit- 禁用线程安全初始化代码,这将避免线程局部变量.当然,这样做初始化代码会恢复到 VS2013 模式并且不是线程安全的,所以这个选项只有在你不依赖局部静态的线程安全时才可行.

To work around the issue you can use /Zc:threadSafeInit- to disable the thread-safe initialization code and this will avoid the thread-local variable. Of course by doing so the initialization code reverts back to the VS2013 mode and is not thread-safe, so this option is only viable if you don't rely on the thread-safety of local statics.

相关文章