Windows PE第九章 线程局部存储
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 線程局部存儲(chǔ)(TLS)
? ? 這個(gè)東西并不陌生了,之前寫過了一個(gè)關(guān)于這個(gè)的應(yīng)用,利用靜態(tài)TLS姿勢實(shí)現(xiàn)代碼段靜態(tài)加密免殺或者所謂的加殼思路。地址在這:http://blog.csdn.net/u013761036/article/details/53967943今天就簡單的整理下TLS的相關(guān)概念和常規(guī)應(yīng)用。一開始說了一大堆的Windows的進(jìn)程與線程啥啥啥的概念和原理,這里直接省略。
什么是線程局部存儲(chǔ)?
? ? 線程局部存儲(chǔ)(Thread?Local?Storage,TLS)很好的解決了多線程設(shè)計(jì)中變量同步問題,比如你寫一個(gè)exe里面有N個(gè)線程,你可以放棄使用TLS,因?yàn)槟銓?duì)自己設(shè)計(jì)的程序有比較全面的把握。你清楚自己設(shè)計(jì)的進(jìn)程里總共有多少個(gè)線程,每個(gè)線程使用了哪些數(shù)據(jù)結(jié)構(gòu),內(nèi)存空間申請(qǐng)、釋放都在你的掌控之下,全局變量的訪問全部都采用了同步技術(shù),那是沒問題的。如果你是一個(gè)DLL開發(fā)者,你無法確定調(diào)用這個(gè)DLL的素質(zhì)程序里到底有多少個(gè)線程,每個(gè)線程的數(shù)據(jù)是如何定義的,這時(shí),可以考慮使用線程據(jù)存儲(chǔ)技術(shù)。
?
TLS分為靜態(tài)和動(dòng)態(tài)兩種:
動(dòng)態(tài)TLS,主要是使用這幾個(gè)API?TlsAlloc?,TlsGetValue,TlsSetValue,TlsFree
下面是微軟MSDN上的一個(gè)例子,看下理解下:
#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); }靜態(tài)TLS
? ? 靜態(tài)線程局部存儲(chǔ)是操作系統(tǒng)提供的另外一種線程與數(shù)據(jù)綁定的技術(shù)。它與動(dòng)態(tài)TLS的區(qū)別在于,通過靜態(tài)線程局部存儲(chǔ)指定的數(shù)據(jù)無需使用專門的API函數(shù),隨意在易用性上會(huì)更好一些。
? ? 靜態(tài)線程局部存儲(chǔ)預(yù)先將變量定義在PE文件內(nèi)部,一般使用.tls節(jié)存儲(chǔ),對(duì)于相關(guān)API的調(diào)用由操作系統(tǒng)來完成。這種方式的有點(diǎn)就是從高級(jí)語言程序員角度來看更簡單了。這種實(shí)現(xiàn)方式使得TLS數(shù)據(jù)的定義與初始化就像程序中使用普通的靜態(tài)變量那樣。
? ? 對(duì)靜態(tài)TLS變量的定義不需要想動(dòng)態(tài)線程局部存儲(chǔ)一樣,調(diào)用相關(guān)API,只需要做如下聲明即可:
_declspec(thread)?int?tlsFlag=1;
?????為了支持這種編程模式。PE中的.tls節(jié)會(huì)包含一下信息:
初始化數(shù)據(jù)
用于每個(gè)線程初始化和終止的回調(diào)函數(shù)
TLS索引
可執(zhí)行代碼訪問靜態(tài)TLS數(shù)據(jù)一般需要經(jīng)過一下幾個(gè)步驟:
1.在鏈接的時(shí)候,連接器設(shè)置TLS目錄中的AddressOfIndex字段。這個(gè)字段指向一個(gè)位置,在這個(gè)位置保存程序用到的TLS索引。
2.當(dāng)創(chuàng)建線程是,加載器通過將線程環(huán)境塊TEB的地址放入FS寄存器來傳遞線程的TLS數(shù)組地址。距TEB開頭0x2c的位置處的字段ThreadLocalStoragePointer指向TLS數(shù)組。
3.加載器將TLS索引值保存到AddressOfIndex字段指向的位置處。
4.可執(zhí)行代碼獲取TLS索引以及TLS數(shù)組的位置。
5.可執(zhí)行代碼將索引乘以4,并將該值作為這個(gè)數(shù)組內(nèi)的偏移來使用。通過以上方法獲取給定程序和模塊的TLS數(shù)據(jù)區(qū)的地址。每個(gè)線程擁有他自己的TLS數(shù)據(jù)區(qū),但這對(duì)于線程是透明的,它并不需要知道怎為單個(gè)線程分配數(shù)據(jù)的。
6.單個(gè)的TLS數(shù)據(jù)對(duì)象都位于TLS數(shù)據(jù)區(qū)的某個(gè)固定偏移處,因此可以用這種方式訪問。
靜態(tài)的TLS主要的應(yīng)用是TLS回調(diào)函數(shù)。
關(guān)于靜態(tài)TLS的代碼相關(guān)就直接去開頭我說的那個(gè)網(wǎng)址去看吧,里面我寫了一個(gè)代碼內(nèi)存加密的代碼例子。有靜態(tài)加載TLS的姿勢。
?
總結(jié)
以上是生活随笔為你收集整理的Windows PE第九章 线程局部存储的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Windows PE 第八章 延迟加载导
- 下一篇: Windows PE 第十章 加载配置