Windows线程本地储存_TLS分析
一、何为TLS
ThreadLocalStore(TLS),线程本地存储。PE文件中一种特殊的数据存储方式,同时也是Windows操作系统提供的一种机制,允许每个线程拥有自己的数据区,而不是共享同一个全局变量。在多线程模式下,有些变量需要支持每个线程独享一份的功能,这种每个线程独享的变量会放到每个线程专有的存储区域,允许每个线程拥有自己单独的变量实例,简而言之,我们可以说每个线程都可以有自己独立的变量实例,而不会干扰其他线程,在多线程环境下,使用TLS来实现线程私有的数据存储,确保了数据的安全性和隔离性。
二、例子分析
2.1 线程数据
1 | |
代码中使用了__declspec(thread)去修饰变量,指示编译器为每个线程创建此变量的独立副本。这是使用TLS的最简单方式,编译器会自动处理TLS数据段的创建,运行后查看进程效果:
可以看到两个线程中,g_mydata的地址和数据都互不相同,且不干扰。
2.2 TLS回调
1 | |
这里解释一下代码:
4-10:这里使用条件编译来处理32位和64位的差异(注意区分下划线)。
/INCLUDE:__tls_used或/INCLUDE:_tls_used用于强制链接器包含TLS目录,即使没有引用它。/INCLUDE:__tls_callback或/INCLUDE:_tls_callback:强制链接器包含TLS回调函数表。(因为下边有注册TLS函数)
12~17:TLS回调函数的实现,三个参数与DllMain同对应。
19~25:这里开始创建TLS回调函数表:
EXTERN_C:确保使用C语言链接约定,因为C++编译后函数为别名,导致后续无法识别真正的TLS函数名。.CRT$XLB:特殊的段名,Windows加载器会识别此段中的TLS回调函数。_tls_callback[]:回调函数指针数组,以NULL(0)结尾。64位平台使用常量段(
const_seg),32位平台使用数据段(data_seg)
33~37:普通线程,调用printf输出线程ID。
39~46:main函数创建两个线程,创建前后间隔2秒,然后通过
getchar卡住程序。
运行代码看看效果:
Reason从MSDN给出的定义如下:
1 | |
因此可以得出结论:TLS回调执行的时机在线程创建时和结束时。由于main函数本身就是一个主线程,因此在main函数执行前,触发了一次TLS回调,但为什么main线程结束后,没有调用TLS?这个在后边分析会知道。
三、TLS分析
给TLS_CALLBACK1函数添加一个int3断点,然后使用windbg挂起。
断点触发后,查看堆栈。
发现顶层的来源为ntdll!LdrInitializeThunk,这个函数如果分析过NtCreateThread函数朋友就知道,这是线程函数的真正入口,并且还是一个APC函数。NtCreateThread调用后会构造TEB和其他一些列数据,最后插入APC进行触发线程执行。这里我们并不关心内核是怎么给APC插入的,我们只关心TLS是怎么被触发并调用的。