Access violation on static initialization
Asked Answered
P

2

18

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.

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.

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.

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

The error message

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

Visual C# Console Application "Managed.exe"

Program.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 Class Library "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...\n";
    }

}; // class Singleton

} // namespace Native

The disassembly

    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)  

The registers

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

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)  

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.

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

Edit 2

Reported as bug through Microsoft Connect: Bug report

Parol answered 11/9, 2015 at 6:48 Comment(0)
P
31

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

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.

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.

Parol answered 25/9, 2015 at 6:55 Comment(4)
Thanks, this helped me. In my case I was upgrading a VS 2013 managed C++ project to VS 2015 on WIndows 7, but the managed code would crash before it ever got to my "main" method. The debugger didn't even show anything until I switched it to only debug Native code. That showed the crash had something to do with initialization code in "thread_safe_statics". And that brought me here. Once I switched to using the /Zc:threadSafeInit- option, my problem went away. Of course, that likely means I have something funny with some statics, but for now I'm just glad to have my project running. Thanks!Countryside
I also had this issue with binaries built with VS2017. /Zc:threadSafeInit- did resolve the crash when the static local object was about to be constructed on WinXP, however with this option it seems the object's constructor is not being called at all. At the very least, the vtable is not set up. Therefore, the only solution for me was to replace static local objects with static global objects.Isoelectronic
Thanks, it solved my problem. I was compiling a C++ dll with VS2017. The dll had a static object inside a function. Everything was working ok on Windows 10 but it was crashing on Windows XP when loading the library and calling the constructor of the static object for the first time. The dll was single-threaded and the error was the following: "Unable to load DLL. Invalid access to memory location".Work
Note that the preprocessor __cpp_threadsafe_static_init will not be #defined if your C++ code is compiled with /Zc:threadSafeInit- (introduced in VS 2017 15.9, see here). So you can write code for both scenarios.Gloat
D
-1

your constructor is NOT removed since its public. move it to the private section of your class declaration.

Down answered 11/9, 2015 at 7:14 Comment(4)
The access specification is not relevant to this bug. The same crash happens with a protected or private constructor. I left the constructor public to simplify this example. It was not meant to demonstrate singleton best practices.Parol
What happens if you move the static variable at class scope?Fabe
@SébastienCôté The crash is gone when moved to class scope.Parol
"magic statics" is a C++/11 feature supported for the first time in VS2015. Maybe you should stick to class-scope statics for the time being if you have the possibility to do so...Fabe

© 2022 - 2024 — McMap. All rights reserved.