chi*_*yer 6 c++ thread-local-storage
这是测试代码
#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;
}
Run Code Online (Sandbox Code Playgroud)
使用多线程调试DLL(/ MDd)运行结果:主线程tls值= 1
使用多线程调试(/ MTd)运行结果:主线程tls值= 0
看起来无法捕获使用MTd时创建的主线程
为什么?
虽然Ofek Shilon对代码缺少一种成分是正确的,但他的回答只包含整个成分的一部分.这里可以找到完整的工作解决方案,而这里又是从这里开始的.
有关它如何工作的解释,您可以参考此博客(假设我们正在使用VC++编译器).
为方便起见,代码发布在下面(请注意,支持x86和x64平台):
#include <windows.h>
// Explained in p. 2 below
void NTAPI tls_callback(PVOID DllHandle, DWORD dwReason, PVOID)
{
if (dwReason == DLL_THREAD_ATTACH)
{
MessageBox(0, L"DLL_THREAD_ATTACH", L"DLL_THREAD_ATTACH", 0);
}
if (dwReason == DLL_PROCESS_ATTACH)
{
MessageBox(0, L"DLL_PROCESS_ATTACH", L"DLL_PROCESS_ATTACH", 0);
}
}
#ifdef _WIN64
#pragma comment (linker, "/INCLUDE:_tls_used") // See p. 1 below
#pragma comment (linker, "/INCLUDE:tls_callback_func") // See p. 3 below
#else
#pragma comment (linker, "/INCLUDE:__tls_used") // See p. 1 below
#pragma comment (linker, "/INCLUDE:_tls_callback_func") // See p. 3 below
#endif
// Explained in p. 3 below
#ifdef _WIN64
#pragma const_seg(".CRT$XLF")
EXTERN_C const
#else
#pragma data_seg(".CRT$XLF")
EXTERN_C
#endif
PIMAGE_TLS_CALLBACK tls_callback_func = tls_callback;
#ifdef _WIN64
#pragma const_seg()
#else
#pragma data_seg()
#endif //_WIN64
DWORD WINAPI ThreadProc(CONST LPVOID lpParam)
{
ExitThread(0);
}
int main(void)
{
MessageBox(0, L"hello from main", L"main", 0);
CreateThread(NULL, 0, &ThreadProc, 0, 0, NULL);
return 0;
}
Run Code Online (Sandbox Code Playgroud)
编辑:
绝对需要一些解释,所以让我们来看看代码中发生了什么.
如果我们想要使用TLS回调,那么我们将明确告诉编译器.它由变量的包含完成,该变量_tls_used具有指向回调数组的指针(以null结尾).对于此变量的类型,您可以参考tlssup.c随Visual Studio一起提供的CRT源代码:
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\它按以下方式定义:
#ifdef _WIN64
_CRTALLOC(".rdata$T") const IMAGE_TLS_DIRECTORY64 _tls_used =
{
(ULONGLONG) &_tls_start, // start of tls data
(ULONGLONG) &_tls_end, // end of tls data
(ULONGLONG) &_tls_index, // address of tls_index
(ULONGLONG) (&__xl_a+1), // pointer to call back array
(ULONG) 0, // size of tls zero fill
(ULONG) 0 // characteristics
};
#else /* _WIN64 */
_CRTALLOC(".rdata$T")
const IMAGE_TLS_DIRECTORY _tls_used =
{
(ULONG)(ULONG_PTR) &_tls_start, // start of tls data
(ULONG)(ULONG_PTR) &_tls_end, // end of tls data
(ULONG)(ULONG_PTR) &_tls_index, // address of tls_index
(ULONG)(ULONG_PTR) (&__xl_a+1), // pointer to call back array
(ULONG) 0, // size of tls zero fill
(ULONG) 0 // characteristics
};
Run Code Online (Sandbox Code Playgroud)
此代码初始化IMAGE_TLS_DIRECTORY(64)TLS目录条目指向的结构的值.指向回调数组的指针是其中的一个字段.OS加载程序遍历此数组,并且在满足空指针之前调用指向的函数.
有关PE文件中目录的信息,请参阅MSDN中的此链接并搜索其描述IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES].
x86注意:正如您所看到的,x86和x64平台都_tls_used满足相同的名称tlssup.c,但_在为x86 build包含此名称时会添加其他名称.它不是拼写错误而是链接器功能,因此可以有效地命名__tls_used.
IMAGE_TLS_DIRECTORY(64)找到winnt.h,有一个字段对于x64:
ULONGLONG AddressOfCallBacks; // PIMAGE_TLS_CALLBACK *;
Run Code Online (Sandbox Code Playgroud)
对于x86:
DWORD AddressOfCallBacks; // PIMAGE_TLS_CALLBACK *
Run Code Online (Sandbox Code Playgroud)
回调的类型定义如下(也来自winnt.h):
typedef VOID
(NTAPI *PIMAGE_TLS_CALLBACK) (PVOID DllHandle, DWORD Reason, PVOID Reserved);
Run Code Online (Sandbox Code Playgroud)
它与for相同DllMain,它可以处理同一组事件:process\thread attach\detach.
tlssup.c:分配的部分:
_CRTALLOC(".CRT$XLA") PIMAGE_TLS_CALLBACK __xl_a = 0;
/* NULL terminator for TLS callback array. This symbol, __xl_z, is never
* actually referenced anywhere, but it must remain. The OS loader code
* walks the TLS callback array until it finds a NULL pointer, so this makes
* sure the array is properly terminated.
*/
_CRTALLOC(".CRT$XLZ") PIMAGE_TLS_CALLBACK __xl_z = 0;
Run Code Online (Sandbox Code Playgroud)
在$命名PE部分时,了解什么是特殊的非常重要,因此引用文章名为"隐式TLS的编译器和链接器支持":
PE映像中的非标头数据被放置在一个或多个部分中,这些部分是具有共同属性集(例如页面保护)的存储器区域.的
__declspec(allocate(“section-name”))关键字(CL-特异性)告诉编译器,特定变量是被放置在一个特定的部分中的最后的可执行文件.编译器还支持将类似命名的节连接成一个更大的节.通过在节名称前加上$字符后跟任何其他文本来激活此支持.编译器将结果部分与同名部分连接起来,在$字符(包括)处截断.连接时,编译器按字母顺序对各个部分进行排序(由于在部分名称中使用了$字符).这意味着在内存中(在最终的可执行映像中),该
“.CRT$XLB”部分中的变量将在该“.CRT$XLA”部分中的变量之后 但在“.CRT$XLZ”部分中的变量之前.C运行时使用编译器的这个怪癖来创建一个到TLS回调的空终止函数指针数组(指针存储在该“.CRT$XLZ”部分中作为空终止符).因此,为了确保声明的函数指针位于被引用的TLS回调数组的范围内,_tls_used必须放在表单的一部分中“.CRT$XLx“.
实际上可能有2个以上的回调(我们实际上只使用一个),我们可能想按顺序调用它们,现在我们知道如何.只需将这些回调放在按字母顺序命名的部分中.
EXTERN_C 添加到禁止C++风格的名称修改并使用C风格的名称.
const并且const_seg用于x64版本的代码,因为否则它无法工作,我不知道确切的原因,猜测可能是CRT部分的访问权限对于x86和x64平台是不同的.
最后,我们要为链接器包含回调函数的名称,以便知道它将被添加到TLS回调数组中.有关_x64 build的附加说明,请参阅上面的第1页末尾.
您还必须显式添加符号 __tls_used。这样你的代码应该可以工作:
#pragma comment(linker,"/include:__tls_used")
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
8303 次 |
| 最近记录: |