下面是测试代码:
#include "windows.h"
#include "iostream"
using namespace std;
__declspec(thread) int tls_int = 0;
void NTAPI tls_callback(PVOID, DWORD dwReason, PVOID)
{
tls_int = 1;
}
#pragma data_seg(".CRT$XLB")
PIMAGE_TLS_CALLBACK p_thread_callback = tls_callback;
#pragma data_seg()
int main()
{
cout<<"main thread tls value = "<<tls_int<<endl;
return 0;
}
字符串
使用多线程DLL(/MDd)生成运行结果:main thread tls value = 1
使用多线程编译器(/MTd)生成运行结果:main thread tls value = 0
看起来我无法捕获使用MTd时创建的主线程。
为什么会这样呢?
3条答案
按热度按时间z31licg01#
虽然Ofek Shilon是正确的,代码缺少一个成分,但他的答案只包含整个成分的一部分。
有关它如何工作的解释,您可以参考this blog(假设我们使用VC++编译器)。
为了方便起见,代码发布在下面(注意,支持x86和x64平台):
字符串
编辑:
肯定需要一些解释,所以让我们看看代码中发生了什么。
1.如果我们想使用TLS回调,那么我们要明确地告诉编译器。这是通过包含变量
_tls_used
来完成的,变量_tls_used
有一个指向回调数组的指针(null终止)。对于这个变量的类型,你可以在CRT源代码中查阅tlssup.c
,它是沿着Visual Studio一起提供的:c:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\crt\src\
c:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\crt\src\vcruntime\
其定义如下:
型
这段代码为TLS目录条目指向的
IMAGE_TLS_DIRECTORY(64)
结构提供了值。回调数组的指针是它的字段之一。这个数组被OS加载器遍历,并被调用,直到遇到空指针。IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]
的描述。*_tls_used
在x86和x64平台的tlssup.c
中也有相同的名称,但是在x86版本中包含这个名称时会添加额外的_
。这不是一个错别字,而是链接器特性,所以有效地命名为__tls_used
。*1.现在我们开始创建回调函数了。它的类型可以从
winnt.h
中的IMAGE_TLS_DIRECTORY(64)
定义中获得,其中有一个字段对于x64:
型
对于x86:
型
回调的类型定义如下(也来自
winnt.h
):型
它与
DllMain
相同,可以处理相同的事件集:process\thread attach\detach。1.现在是时候注册回调了。首先看一下
tlssup.c
的代码:其中分配的部分:
型
在命名PE部分时,了解
$
的特殊之处非常重要,因此引用了命名为"Compiler and linker support for implicit TLS"的文章:PE图像中的非标题数据被放置到一个或多个部分中,它们是具有一组公共属性的内存区域(如页面保护)。
__declspec(allocate(“section-name”))
关键字(CL特定的)告诉编译器一个特定的变量将被放置在最终可执行文件的特定部分。编译器还支持类似的连接-命名的节合并成一个更大的节。这种支持是通过在节名前面加上$
字符,后跟任何其他文本来激活的。编译器将得到的节与同名的节连接起来,在$
字符处截断(包括)。编译器在连接各个节时按顺序排列各个节(由于在节名中使用了$字符)。这意味着在内存中(在最终的可执行映像中),
“.CRT$XLB”
部分中的变量将在“.CRT$XLA”
部分中的变量之后,但在“.CRT$XLZ”
部分中的变量之前。C运行时使用编译器的这种怪癖来创建一个指向TLS回调的null终止函数指针数组(存储在“.CRT$XLZ”
部分的指针是null终止符)。因此,为了确保声明的函数指针驻留在_tls_used
引用的TLS回调数组的范围内,必须将其放置在“.CRT$XLx“
形式的部分中。实际上可能有2个以上的回调函数(我们实际上只使用一个),我们可能想按顺序调用它们,现在我们知道如何调用了。只需将这些回调函数按字母顺序命名。
添加
EXTERN_C
以禁止C++风格的名称mangling,并使用C风格的名称mangling。const
和const_seg
用于x64版本的代码,因为否则它无法工作,我不知道确切的原因,猜测可能是CRT部分的访问权限对于x86和x64平台不同。最后,我们要包括回调函数的名称,以便链接器知道它将被添加到TLS回调数组中。有关x64构建的额外
_
的注意事项,请参阅上文第1页的末尾。6bc51xsx2#
您还必须显式添加符号__tls_used。使用此符号,您的代码应该可以工作:
字符串
yduiuuwa3#
如果使用隐式TLS变量(
__declspec(thread)
),则不需要使用#pragma comment(linker,"/include:__tls_used")
。