做者:IBinary
出處:http://www.cnblogs.com/iBinary/
版權全部,歡迎保留原文連接進行轉載:)數組
首先講解TLS的時候,須要複習線程相關知識, (thread local storage )數據結構
1.瞭解經典同步問題函數
首先咱們先寫一段C++代碼,開闢兩個線程去跑,看看會不會出現同步問題.spa
看結果得知,結果並非正確的,形成同步的問題的緣由是兩個線程都對同一個變量進行訪問.操作系統
解決問題:線程
這裏使用自加鎖解決(固然能夠用別的)設計
InterlockedIncrement API 指針
原型:code
LONG InterlockedIncrement( LPLONG volatile lpAddend // variable to increment );
只須要把全局變量的地址給它,強轉爲long * 類型便可.
使用以後結果是正確的htm
所謂TLS,意思就是指,每一個線程都有本身的空間,局部存儲,什麼意思?
好比上方咱們對一個g_dwNumber進行操做,那麼咱們就要使用同步對象,咱們不妨這樣去想,每一個線程,開闢一個空間
當對A線程進行操做的時候,操做的是A線程的g_dwNumber,當對B線程進行操做的時候,是對B線程的g_dwNumber進行操做.
其實很簡單,介紹一下TLS的API
總共4個
分別是:
TlsAlloc 分配線程局部存儲空間
TlsFree 釋放線程局部存儲空間
TlsGetValue 得到線程局部存儲空間裏面的值
TlsSetValue 設置線程局部存儲空間的值
1.首先是TlsAlloc的使用
DWORD TlsAlloc(VOID); 函數原型
調用一次TlsAlloc則會分配4個字節的空間,無論你在哪裏調用,若是在main裏面(主線程)中調用,那麼當你建立線程的時候
線程會默認有4個字節的控件
返回值是一個索引, 這個索引是查FS寄存器數組的值固然,這個一會講解.只須要知道,當咱們爲每個線程申請了4個字節的空間
那麼索引是同樣的,可是索引操做的數據是不同的
好比 你申請的索引是1
那麼在A線程中,操做1索引的時候,那麼操做的是A線程的,那麼若是在B線程操做索引1的時候,那麼操做的是B線程的數據
舉例子:
好比有個電話號碼是 12345678
中國: 12345678
外國: 12345678 (把電話號碼看作是索引)
咱們知道,電話號碼是同樣的,可是你打這個電話的時候,人是不同的
好比我在中國打123456 那麼接聽人是張三
我在外國打123456 那麼接聽人是李四
其中張三李四就是表達了對同一數據的不一樣操做.看下代碼
再好比:
咱們使用tlsAlloc申請了4個字節的空間
索引就是nindex (看作是g_dwNumber);
那麼訪問不一樣線程的索引,那麼索引裏面的值是不一樣的.
1.Tls的動態使用方法,設置全局變量
動態使用就是PE中不創建TLS表格了,一樣完成同步
首先,咱們爲每一個線程開闢了4個字節的空間
而後返回一個索引(這個索引看作是g_dwNumber,其實這個索引是去數組裏面去取出成員來,好比如今是第1個,那麼去數組裏面取出第一項來,當作g_dwNumber)
TlsSetValue(索引,設置的值)
這樣寫其實就是根據索引找到數組裏面的值,設置一下.
TlsGetValue(索引)則是根據下標索引,去數組裏面取出g_dwNumber的值.
而後下方從新設置回去了.在1索引的位置,設置了g_dwNumber的值.
若是對齊數據結構不理解,能夠看下手工寫的圖
AThread (當前索引爲1)
數組: [0][1][2][3]..... 數組首地址: 00401000
BThread (當前索引爲1)
數組: [0][1][2][3]..... 數組首地址: 00402000
其實每一個線程能夠理解爲索引雖然同樣,可是在數組裏面取出來的值是不同的.
好比A線程的索引爲1,裏面的成員是A線程的g_dwNumber 好比如今它的值是5
如今切換到了B線程了,那麼仍是根據索引去找值,可是數組不一樣了,因此再次找1找的則是B數組的g_dwNumber了.
其實API的做用就至關於你手工的去給數組第幾個元素賦值,取值.等等.
只不過這個是操做系統封裝的數組,因此給你提供API
按照咱們的寫法,可能會下面那樣作,僞代碼,便於理解
AThread[1] = 0;
DWORD g_dwNumber = AThread[1];
printf(g_dwNumber);
AThread[1] = g_dwNumber++;
替換成API則是
TlsSetValue(索引,值)
TlsGetValue(索引);
如今看下那張圖,那麼已經實現了同步.線程也切換了,操做的就是本身的數據.
2.動態使用Tls之結構體的設置
上面咱們說的是數組裏面設置的是全局變量,如今咱們要設置一下結構體了.
結構體實際上是同樣的,咱們讓數組裏面存指針就行.
好比看下方代碼:
很簡單
1.咱們定義一個p指針,指向了一塊new的內存
2.初始化的時候,設置數組索引的當前索引的值爲p的指針
3.從索引中得到p指針
4.修改p指向的m_dwCount的值
注意,這裏由於p是一個指針,咱們修改的只是它空間成員變量的值,因此不用從新再設置回去了.
到了如今感受TLS是否是有點難用了.其實使用TLS 比使用任何同步對象都快,就至關於沒同步的時候的速度.
可是TLS的真正的語法不是這樣用的.(上面是動態使用不會生成TLS表)
其實TLS真正的用法是靜態使用,操做系統已經幫你集成了語法了
看下用法,以及語法;
語法:
__declspec(thread) 類型 變量名
而後tls就會自動生成表了,操做系統幫你升成上面動態使用的代碼.(因此爲啥要理解動態使用)
用的時候仍是正常使用.
咱們的代碼都不用變的.
但其實彙編代碼仍是會編譯爲上面的動態使用.
若是變爲結構體,那麼是同樣的,只須要把類型變成結構體的類型便可.
瞭解了上方的原理了,那麼若是讓你設計表格你要怎麼設計?
1.咱們全局變量初始化爲0了,那麼咱們確定有地方存儲了這個全局變量的數據 ,因此我會設計一段分爲存儲這個值.
2.咱們經常使用的nindex索引,那麼我覺着也要存儲一下
廢話不說了,看下真是的結構體
ypedef struct _IMAGE_TLS_DIRECTORY32 { DWORD StartAddressOfRawData; TLS初始化數據的起始地址 DWORD EndAddressOfRawData; TLS初始化數據的結束地址 兩個正好定位一個範圍,範圍放初始化的值 DWORD AddressOfIndex; TLS 索引的位置 DWORD AddressOfCallBacks; Tls回調函數的數組指針 DWORD SizeOfZeroFill; 填充0的個數 union { DWORD Characteristics; 保留 struct { DWORD Reserved0 : 20; DWORD Alignment : 4; DWORD Reserved1 : 8; } DUMMYSTRUCTNAME; } DUMMYUNIONNAME; } IMAGE_TLS_DIRECTORY32;
首先介紹前兩個成員,
起始地址 結束地址 定位了一個範圍,那麼這個範圍內存放的就是初始化的值(注意只有靜態使用纔有TLS表)也就是上方咱們定義的g_dwNumber = 0;存放了0,可是由於0很差看,這裏我從新賦值爲12345678 代碼不貼了.
咱們查看下PE定位一下Tls的位置.
注意,由於我是VS2015編寫的程序,隨機基址懶得去了,直接在PE中修改了,把文件頭的文件屬性修改了便可.
之前是02,如今改爲03便可.
首先查看下數據目錄的第9項
得出RVA = 000176FC
查看下模塊首地址. 首地址是 00400000
看下屬於哪一個節
命中在.rdata節,RVA = 00016000
上面的RVA減去如今的RVA = 偏移
000176FC - 00016000 = 16FC
節中的文件偏移 + 偏移 = 文件中的位置.
文件偏移是下方的第二個成員
5400 + 16FC = 6AFC
查看6AFC定位Tls表的位置.
前面兩個成員分別指向的是
0041B000 0041B208的位置 結束地址 - 起始地址 = 範圍.
尋找起始地址的FA
時間關係,這裏命中的節是 Rva = 001B000
那麼轉爲文件偏移
FA = 8400h直接計算出來了
起始地址是8400h 那麼+208就是8608 ,那麼8400h 到8608的位置就存放的初始值,如今已經看到上圖畫出來的12345678了(小尾方式讀取)
第3個成員: 索引的值,這個你能夠本身轉化查看.
這個怎麼理解,是這樣的,還記到動態使用的時候,咱們不是在主線程中 TlsAlloc 和TlsFree嗎
如今咱們能夠註冊回調函數,操做系統會調用這個回調函數.
怎麼註冊?
關鍵字: 加段,必須添加到特定的段中
首先先看下回調的函數原型.
typedef VOID (NTAPI *PIMAGE_TLS_CALLBACK) (PVOID DllHandle, DWORD Reason,PVOID Reserved );
PIMAGE_TLS_CALLBACK 其中這個回調是從結構體中第四個成員裏面,註釋獲得的
首先咱們本身寫一個
請看註釋,其實這裏纔是真正的申請和釋放,注意,這個回調函數操做系統會從問價那種讀取地址,而後執行一遍,沒有申請內存,因此這裏面能夠藏代碼的.
注意,雖然回調咱們寫了,可是要讓操做系統調用,那麼咱們須要添加一個特定的節.
語法:
#pragma data_seg(".CRT$XLB") 其中關於.CRT$XLB 爲何是這個節,我發下鏈接看雪論壇的,本身看下吧,很簡單了.https://bbs.pediy.com/thread-108015.htm
/*中間寫代碼,定義函數回調數組*/
PIMAGE_TLS_CALLBACK ary[] = {MyTlsCallBack,0}; //0結尾,那麼操做系統就會在文件中找到這個位置,調用一下這個回調.若是多個,裏面能夠寫多個,0結尾便可.
#pragma data_seg();
發現1已經成功彈出來了,那麼如今結構體的第四個成員,就是指向這個數組首地址的.PE加載的時候,會默認調用,而後依次執行一遍..
請注意,只會在文件中存儲,若是你跑到內存中查看,這個地址是沒有的.
太晚了,快4點了,剩下的字節明天說.
做者:IBinary
出處:http://www.cnblogs.com/iBinary/版權全部,歡迎保留原文連接進行轉載:)