-
The Thread Local Storage CRT implementation is located at
C:/Program Files/Microsoft Visual Studio/2022/Community/VC/Tools/MSVC/14.39.33218/crt/src/vcruntime/tlssup.cpp. -
The TLS callback symbol must be registered in the
CRT$XL?section, where?is an arbitrary letter excludingAandZ. We choseCRT$XLFfor our symbol. -
const_seganddata_segare specific to MSVC and used to register symbols in specific sections of the object file.
-
On x64, using
data_segresults in these warnings, soconst_segmust be used.LIBCMT.lib(exe_main.obj) : warning LNK4078: multiple '.CRT' sections found with different attributes (40400040) LIBCMT.lib(initializers.obj) : warning LNK4254: section '.CRT' (C0000040) merged into '.rdata' (40000040) with different attributes -
If
const_segmust be used, then the variable must beconst. -
I couldn't find a good reason not to use
const_seginstead ofdata_segon x86. All the examples usedata_segon x86. Historical artifact? C++ quirks? -
The attribute
__declspec(allocate("section-name"))seems unnecessary when usingdata_segorconst_seg, but only needed when declaring a new section with thesectionpragma.A warning is raised on x64 and the callback doesn't fire if the
sharedattribute ofsectionis used. -
In C++,
constvariables don't have external linkage. However, one cannot define anextern constvariable, which is why the snippet is common:typedef void (*proc)(void *); void f(void *arg) { ... } extern const proc _f; const proc _f = &f;
At the same time, in C++, the exported symbol should use C mangling rules, so this becomes:
extern "C" const proc _f;
Does the symbol retains external linkage with
extern "C"?IIUC external linkage can be forced with (see
/INCLUDE(Force Symbol References)):#pragma comment (linker, "/INCLUDE:_f")but for the case of C, comparing the output of
dumpbin /ALLwith and without this directive doesn't seem to show a relevant difference. It's possible that the directive is still needed in case of whole program optimization. -
The loader and the linker need to know the symbol describing the callback array. It is named
_tls_used, but symbols are prefixed with an additional underscore on x86, and it becomes__tls_used. We could always re-declare it withextern, but that could be bad if its type changes. We can use a linker directive instead:#if defined _M_IX86 #pragma comment (linker, "/INCLUDE:__tls_used") #elif defined _M_X64 #pragma comment (linker, "/INCLUDE:_tls_used") #endif
-
It seems that GCC only needs the
sectionon the TLS callback:#if defined __GNUC__ __attribute__((__section__(".CRT$XLF"))) #endif
-
Needs testing on arm and arm64 (don't forget to
make cleanbetween tests):# Do that in x86, x86_64. arm, arm64 envs make CC=cl && ./main.exe make CC=clang-cl && ./main.exe make main-gcc.exe CC=x86_64-w64-mingw32-gcc AR=x86_64-w64-mingw32-ar && ./main-gcc.exe make main-gcc.exe CC=i686-w64-mingw32-gcc AR=i686-w64-mingw32-ar && ./main-gcc.exe
-
References