-
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 excludingA
andZ
. We choseCRT$XLF
for our symbol. -
const_seg
anddata_seg
are specific to MSVC and used to register symbols in specific sections of the object file.
-
On x64, using
data_seg
results in these warnings, soconst_seg
must 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_seg
must be used, then the variable must beconst
. -
I couldn't find a good reason not to use
const_seg
instead ofdata_seg
on x86. All the examples usedata_seg
on x86. Historical artifact? C++ quirks? -
The attribute
__declspec(allocate("section-name"))
seems unnecessary when usingdata_seg
orconst_seg
, but only needed when declaring a new section with thesection
pragma.A warning is raised on x64 and the callback doesn't fire if the
shared
attribute ofsection
is used. -
In C++,
const
variables don't have external linkage. However, one cannot define anextern const
variable, 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 /ALL
with 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
section
on the TLS callback:#if defined __GNUC__ __attribute__((__section__(".CRT$XLF"))) #endif
-
Needs testing on arm and arm64 (don't forget to
make clean
between 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