线程局部存储(TLS)
这个东西并不陌生了,之前写过了一个关于这个的应用,利用静态TLS姿势实现代码段静态加密免杀或者所谓的加壳思路。地址在这:http://blog.csdn.net/u013761036/article/details/53967943今天就简单的整理下TLS的相关概念和常规应用。一开始说了一大堆的Windows的进程与线程啥啥啥的概念和原理,这里直接省略。
什么是线程局部存储?
线程局部存储(Thread Local Storage,TLS)很好的解决了多线程设计中变量同步问题,比如你写一个exe里面有N个线程,你可以放弃使用TLS,因为你对自己设计的程序有比较全面的把握。你清楚自己设计的进程里总共有多少个线程,每个线程使用了哪些数据结构,内存空间申请、释放都在你的掌控之下,全局变量的访问全部都采用了同步技术,那是没问题的。如果你是一个DLL开发者,你无法确定调用这个DLL的素质程序里到底有多少个线程,每个线程的数据是如何定义的,这时,可以考虑使用线程据存储技术。
TLS分为静态和动态两种:
动态TLS,主要是使用这几个API TlsAlloc ,TlsGetValue,TlsSetValue,TlsFree
下面是微软MSDN上的一个例子,看下理解下:
#include <windows.h> #include <stdio.h> #define THREADCOUNT 4 DWORD dwTlsIndex; VOID ErrorExit(LPSTR); VOID CommonFunc(VOID) { LPVOID lpvData; // Retrieve a data pointer for the current thread. lpvData = TlsGetValue(dwTlsIndex); if ((lpvData == 0) && (GetLastError() != ERROR_SUCCESS)) ErrorExit("TlsGetValue error"); // Use the data stored for the current thread. printf("common: thread %d: lpvData=%lx\n", GetCurrentThreadId(), lpvData); Sleep(5000); } DWORD WINAPI ThreadFunc(VOID) { LPVOID lpvData; // Initialize the TLS index for this thread. lpvData = (LPVOID) LocalAlloc(LPTR, 256); if (! TlsSetValue(dwTlsIndex, lpvData)) ErrorExit("TlsSetValue error"); printf("thread %d: lpvData=%lx\n", GetCurrentThreadId(), lpvData); CommonFunc(); // Release the dynamic memory before the thread returns. lpvData = TlsGetValue(dwTlsIndex); if (lpvData != 0) LocalFree((HLOCAL) lpvData); return 0; } int main(VOID) { DWORD IDThread; HANDLE hThread[THREADCOUNT]; int i; // Allocate a TLS index. if ((dwTlsIndex = TlsAlloc()) == TLS_OUT_OF_INDEXES) ErrorExit("TlsAlloc failed"); // Create multiple threads. for (i = 0; i < THREADCOUNT; i++) { hThread[i] = CreateThread(NULL, // default security attributes 0, // use default stack size (LPTHREAD_START_ROUTINE) ThreadFunc, // thread function NULL, // no thread function argument 0, // use default creation flags &IDThread); // returns thread identifier // Check the return value for success. if (hThread[i] == NULL) ErrorExit("CreateThread error\n"); } for (i = 0; i < THREADCOUNT; i++) WaitForSingleObject(hThread[i], INFINITE); TlsFree(dwTlsIndex); return 0; } VOID ErrorExit (LPSTR lpszMessage) { fprintf(stderr, "%s\n", lpszMessage); ExitProcess(0); }
静态TLS
静态线程局部存储是操作系统提供的另外一种线程与数据绑定的技术。它与动态TLS的区别在于,通过静态线程局部存储指定的数据无需使用专门的API函数,随意在易用性上会更好一些。
静态线程局部存储预先将变量定义在PE文件内部,一般使用.tls节存储,对于相关API的调用由操作系统来完成。这种方式的有点就是从高级语言程序员角度来看更简单了。这种实现方式使得TLS数据的定义与初始化就像程序中使用普通的静态变量那样。
对静态TLS变量的定义不需要想动态线程局部存储一样,调用相关API,只需要做如下声明即可:
_declspec(thread) int tlsFlag=1;
为了支持这种编程模式。PE中的.tls节会包含一下信息:
初始化数据
用于每个线程初始化和终止的回调函数
TLS索引
可执行代码访问静态TLS数据一般需要经过一下几个步骤:
1.在链接的时候,连接器设置TLS目录中的AddressOfIndex字段。这个字段指向一个位置,在这个位置保存程序用到的TLS索引。
2.当创建线程是,加载器通过将线程环境块TEB的地址放入FS寄存器来传递线程的TLS数组地址。距TEB开头0x2c的位置处的字段ThreadLocalStoragePointer指向TLS数组。
3.加载器将TLS索引值保存到AddressOfIndex字段指向的位置处。
4.可执行代码获取TLS索引以及TLS数组的位置。
5.可执行代码将索引乘以4,并将该值作为这个数组内的偏移来使用。通过以上方法获取给定程序和模块的TLS数据区的地址。每个线程拥有他自己的TLS数据区,但这对于线程是透明的,它并不需要知道怎为单个线程分配数据的。
6.单个的TLS数据对象都位于TLS数据区的某个固定偏移处,因此可以用这种方式访问。
静态的TLS主要的应用是TLS回调函数。
关于静态TLS的代码相关就直接去开头我说的那个网址去看吧,里面我写了一个代码内存加密的代码例子。有静态加载TLS的姿势。