0x00 線程局部存儲
線程局部存儲,它實現了線程內局部變量的存儲訪問。該技術下定義的變量能被同一個線程內部的各個函數調用,同時杜絕了其他線程對這些變量的訪問。
(線程局部存儲設計Windows進程和線程,所以在研究TLS之前,要先了解windows進程和線程相關的內容。)
作用:TLS解決了多線程程序設計中同步變量問題。
實現:解決同步變量的問題,也就是幾個線程共用一個變量X,TLS的解決方法是,每個線程,開闢一個空間當對A線程進行操作的時候,操作的是A線程的X,當對B線程進行操作的時候,是對B線程的X進行操作。
具體實現就是在進程中建立一個全局表,通過現成的ID去查詢相應的數據結構,因爲每個線程的ID是不一樣的,所以查到的數據也自然不一樣了。
實現的方法有倆種:
- 動態線程局部存儲技術
- 靜態線程局部存儲技術
0x01 動態線程局部存儲技術
主要通過四個函數TlsAlloc( )、TlsSetValue( )、TlsGetValue( )、TlsFree( )來實現,同時生成的PE文件中沒有.tls表。
TlsAlloc( ):分配線程局部存儲空間/索引,該進程任何線程都可以通過該索引來存儲和檢索線程中的值。
TlsFree( ): 釋放線程局部存儲空間/索引。
TlsGetValue( ): 獲得線程局部存儲空間裏面的值,按索引取值。
TlsSetValue( ): 設置線程局部存儲空間的值,按索引存儲。
如下圖所示,線程1對進程索引3操作,操作的是線程1的內容;線程2對進程的索引3操作,操作的也只是線程2的內容。
測試用例:
有倆個線程對同一個變量進行操作,發現他們互不影響,並且運行到最後,變量__Number還是最初分配的1,沒有被線程改變。
附上代碼:
#include"TLSDynamic.h"
/*
動態tls其實就是爲每個線程創建一個與其關聯的內存塊,這個內存塊可以當數組使用TlsAlloc可以分配一個沒有用過的索引給你,讓你在裏面寫入東西
*/
DWORD WINAPI ThreadProcedure(LPVOID parameter);
DWORD __Number = 0;//動態使用(存放索引)
int _tmain(int argc, TCHAR** argv, TCHAR* envp[])
{
setlocale(LC_ALL, "Chinese-simplified");
_tprintf(_T("main start\r\n"));
__Number = TlsAlloc();//使用之前先分配一個索引
_tprintf(_T("__Number=%d\r\n"), __Number);
HANDLE ThreadHandle[2];
//TLS中的變量單獨存在於每個獨立的線程當中,每個線程中對該變量的操作都不會影響到其他線程中的TLS變量。
for (int i = 0; i < 2; i++)
{
ThreadHandle[i] = CreateThread(NULL, 0, ThreadProcedure, NULL, 0, NULL);
}
//2個線程
WaitForMultipleObjects(2, ThreadHandle, true, INFINITE);
_tprintf(_T("__Number=%d\r\n"), __Number);
TlsFree(__Number);
_tprintf(_T("動態Tls End\r\n"));
_tprintf(_T("Input AnyKey To Exit\r\n"));
_gettchar();
return 0;
}
DWORD WINAPI ThreadProcedure(LPVOID parameter)
{
//獲得tls的空間,然後對齊。
TlsSetValue(__Number, 0);//分別在 空間的某個索引處儲存某個數據
for (int i = 0; i < 10; i++)
{
int n = (int)TlsGetValue(__Number);
Sleep(1);
_tprintf(_T("PID=%d __Number=%d\n"), GetCurrentThreadId(), n);
//設置tls局部空間的值
TlsSetValue(__Number, (LPVOID)(++n));//分別在 空間的某個索引處儲存某個數據
}
return 0;
}
0x02 靜態線程局部存儲技術
靜態方法會用聲明__declspec (thread) int xx = 1;這樣的方式來創建。需要注意的是靜態創建的TLS變量不能用於DLL動態庫中。靜態方法預先將變量定義儲存在PE的.tls表中。
PE文件的.tls節中會包括:初始化數據、用於每個線程初始化和終止的回調函數、TLS索引。
0x03 TLS表結構解析
Tls表在數據目錄的第十位,通常一個包含了TLS表的程序,它就會擁有.tls段,這個段裏面保存了變量和回調函數的數據,但是TLS表本身的結構體一般存在於.rdata段內。
typedef struct _IMAGE_TLS_DIRECTORY32
{
DWORD StartAddressOfRawData; // TLS初始化數據的起始地址
DWORD EndAddressOfRawData;// TLS初始化數據的結束地址 兩個正好定位一個範圍,範圍放初始化的值
PDWORD AddressOfIndex;// TLS 索引的位置
PIMAGE_TLS_CALLBACK *AddressOfCallBacks;// Tls回調函數的數組指針
DWORD SizeOfZeroFill;// 填充0的個數
DWORD Characteristics;
} IMAGE_TLS_DIRECTORY32
其中AddressOfCallBacks回調函數的數組指針比較重要,我們可以自己註冊回調函數進行使用,回調函數在程序入口點代碼之前執行。(嘿嘿,可以用來做反調試)。