PE格式第八講,TLS表(線程局部存儲)

            PE格式第八講,TLS表(線程局部存儲)

 

做者:IBinary
出處:http://www.cnblogs.com/iBinary/
版權全部,歡迎保留原文連接進行轉載:)數組

一丶複習線程相關知識

首先講解TLS的時候,須要複習線程相關知識,  (thread local storage )數據結構

1.瞭解經典同步問題函數

首先咱們先寫一段C++代碼,開闢兩個線程去跑,看看會不會出現同步問題.spa

看結果得知,結果並非正確的,形成同步的問題的緣由是兩個線程都對同一個變量進行訪問.操作系統

解決問題:線程

1.使用同步對象.  (自旋鎖 自加鎖 互斥體 事件  信號燈  臨界區.....等等均可以.)

這裏使用自加鎖解決(固然能夠用別的)設計

InterlockedIncrement  API 指針

 原型:code

LONG InterlockedIncrement(
  LPLONG volatile lpAddend   // variable to increment
);
只須要把全局變量的地址給它,強轉爲long * 類型便可.

使用以後結果是正確的htm

二丶何爲TLS  (Thread  local storage)

所謂TLS,意思就是指,每一個線程都有本身的空間,局部存儲,什麼意思?

好比上方咱們對一個g_dwNumber進行操做,那麼咱們就要使用同步對象,咱們不妨這樣去想,每一個線程,開闢一個空間

當對A線程進行操做的時候,操做的是A線程的g_dwNumber,當對B線程進行操做的時候,是對B線程的g_dwNumber進行操做.

其實很簡單,介紹一下TLS的API

總共4個

分別是:

TlsAlloc  分配線程局部存儲空間

TlsFree  釋放線程局部存儲空間

TlsGetValue 得到線程局部存儲空間裏面的值

TlsSetValue 設置線程局部存儲空間的值

三丶TLSAPI的使用

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表)

 

3.Tls的靜態使用(真正用法)

其實TLS真正的用法是靜態使用,操做系統已經幫你集成了語法了

看下用法,以及語法;

語法:

__declspec(thread) 類型  變量名

而後tls就會自動生成表了,操做系統幫你升成上面動態使用的代碼.(因此爲啥要理解動態使用)

用的時候仍是正常使用.

咱們的代碼都不用變的.

但其實彙編代碼仍是會編譯爲上面的動態使用.

若是變爲結構體,那麼是同樣的,只須要把類型變成結構體的類型便可.

四丶PE中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個成員: 索引的值,這個你能夠本身轉化查看.

五丶TLS結構體第四個成員,回調函數的數組指針

這個怎麼理解,是這樣的,還記到動態使用的時候,咱們不是在主線程中 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/版權全部,歡迎保留原文連接進行轉載:)

相關文章
相關標籤/搜索