IDE: Visual Studio 2015
Version: 10.0.0.2255
Target: Island (Windows 32-bit)
Description:
It seems that globals (file scope variables) are lazy initialized (this kinda makes sense given that Main
isn’t called). The problem is that as far as I can see the initialization isn’t thread-safe.
For example, code similar to:
import rtl
struct SomeStruct {
var s: String
init() {
s = "Hello, World!"
}
}
var aStruct = SomeStruct()
@DllExport
@CallingConvention(CallingConvention.Stdcall)
@SymbolName("_Foo")
func fgdgdf() -> Bool {
MessageBox(nil, "A Message", "Foo", 0)
MessageBox(nil, aStruct.s.FirstChar, "Foo", 0)
return true
}
results in something like:
SilverDLL1!SilverDLL1.<Global>::fgdgdf:
613d4470 55 push ebp
613d4471 89e5 mov ebp,esp
613d4473 833d40a33861ff cmp dword ptr [SilverDLL1!GC_n_smashed+0x8 (6138a340)],0FFFFFFFFh
613d447a 7405 je SilverDLL1!SilverDLL1.<Global>::fgdgdf+0x11 (613d4481)
613d447c e80fffffff call SilverDLL1!SilverDLL1.<Global>::get_aStruct+0x30 (613d4390)
613d4481 6a00 push 0
Note the third instruction.
There’s some problem with the symbols (worthy of a separate post later perhaps) or WinDbg, but SilverDLL1!GC_n_smashed+0x8
is actually SilverDLL1!@_aStruct
(according to the WinDbg ln
command too).
This patter of comparing to 0xFFFFFFFF and jumping to get_aStruct
happens all the time. The problem is that no one guarantees that the first access to a global comes before multiple threads are spawned. If multiple thread are present and are executing this code bad things may happen.
In an EXE this can be circumvented by forcing access to all the globals before starting threads, although this is not completely true1, but a DLL doesn’t control thr program flow. It gets loaded by someone else, perhaps after lots of threads already exist, and its exports may be immediately called by them.
C++ statics had a similar problem (being initialized the first time a function is called, not thread-safe in most implementation). This WG21 paper by a Google employee proposed an algorithm (“A Fast Implementation of Synchronized Initialization”) and it’s actualyl being used by MSVC for a couple of years now. Or you can implement in any other way you want.
1 Windows may start threads on its own for stuff like RPC, and actually since Windows 10 the loader is parallelized and thus every process starts with a bunch of threads for loading all the static imports.