天天看點

TLS 處理

TLS(Thread Local Storage,線程局部存儲)是一種便利的程式設計機制。我們通常不使用,是以并不太關心。但是要壓縮的原程式可能會用到它。事實上,Delphi 總是使用它,如果我們打算支援 Delphi 程式,最好相容它。

TLS 基本上是通過 API 實作。大緻過程是,你配置設定一個“ Index(索引)”并存儲在一個全局變量中。通過這個 Index 獲得針對每個線程的一個雙字值。通常使用這個值儲存一個為每個線程配置設定好的記憶體塊的指針。人們認為這樣很乏味,一個特殊機制的出現使得實作它更容易些。是以,你可以這樣寫代碼:

TLS 處理

__declspec ( thread ) int tls_int_value = 0;

TLS 處理

每個線程可以通過名稱通路它獨特的執行個體,就像通路其他變量一樣。我不知道這種 TLS 形式是否有官方名稱,是以我叫它“簡化 TLS”。這種機制與作業系統相容,并且 PE 檔案中有對應的結構。這些結構包含在資料目錄的一個塊中:

TLS 處理

origdirinfo[IMAGE_DIRECTORY_ENTRY_TLS].VirtualAddress

TLS 處理

問題是, 處理這資訊發生在由 OS 在每個線程的建立時刻,在執行到線程開始位址之前。(這句翻譯的拗口,原文 The problem is that the processing of this information happens by the OS on the creation of every thread prior to execution being passed to the thread start address. )這通常不牽涉到我們,除了至少一個線程在我們解壓資料之前被執行:我們的線程!我們必須設定一個僞 TLS 處理區段捕獲 OS 在我們開始之前做的事情,然後在最後一步把這個資訊傳遞給原程式。

為此,我在外部加殼器外部全局結構添加了2個項目:

TLS 處理

GlobalExternVars

TLS 處理

{

TLS 處理

//(other stuff we previously described)

TLS 處理

IMAGE_TLS_DIRECTORY tls_original;

TLS 處理

IMAGE_TLS_DIRECTORY tls_proxy;

TLS 處理

};

TLS 處理

加殼器将會在運作期複制原始資料到 tls_orginal 為我們所用。tls_proxy 幾乎是一個精确的副本,除了2個項目将會被修改:

TLS 處理

tls_proxy.AddressOfIndex

TLS 處理

tls_proxy.AddressOfCallBacks

在這個塊中我們将要初始化 AddressOfIndex 指向一個正常的全局雙字變量,并且我們将初始化 AddressOfCallBacks 指向一個函數指針數組。它是一個線程建立時将會被調用的函數指針清單。使用者使用它定義 TLS 對象的初始化。唉,我沒見過一個編譯器使用它。此外,在 9x 下,這些函數不會被調用。盡管如此,我們還是要支援它以防萬一哪天它會被使用。我們令 AddressOfCallbacks 指向一個2個成員的數組,一個試我們将要執行的函數指針,另一個是 NULL 作為清單結束符。

設定一個全局雙字存儲 TLS slot(槽?)

TLS 處理

DWORD TLS_slot_index;

TLS 處理

TLS 回調函數必須是這種原型:

TLS 處理

extern "C" void NTAPI TLS_callback ( PVOID DllHandle, DWORD Reason, PVOID Reserved );

TLS 處理

當然還要添加兩個邏輯标志表示是否可以安全地調用原來的回調函數,和是否延期調用。這樣初始化它們:

TLS 處理

bool safe_to_callback_tls = false;

TLS 處理

bool delayed_tls_callback = false;

TLS 處理

再提供一些變量儲存延遲調用的資料:

TLS 處理

PVOID TLS_dll_handle = NULL;

TLS 處理

DWORD TLS_reason = 0;

TLS 處理

PVOID TLS_reserved = NULL;

TLS 處理

編寫回調函數:

TLS 處理

extern "C" void NTAPI TLS_callback ( PVOID DllHandle, DWORD Reason, PVOID Reserved )

TLS 處理
TLS 處理

        if ( safe_to_callback_tls )

TLS 處理

        {

TLS 處理

                PIMAGE_TLS_CALLBACK* ppfn = g_pkrdat.m_tlsdirOrig.AddressOfCallBacks;

TLS 處理

                if ( ppfn )

TLS 處理

                {

TLS 處理

                        while ( *ppfn )

TLS 處理

                        {

TLS 處理

                        (*ppfn) ( DllHandle, Reason, Reserved );

TLS 處理

                        ++ppfn;

TLS 處理

                        }

TLS 處理

                }

TLS 處理
TLS 處理

        }

TLS 處理

        else

TLS 處理
TLS 處理

                delayed_tls_callback = true;

TLS 處理

                TLS_dll_handle = DllHandle;

TLS 處理

                TLS_reason = Reason;

TLS 處理

                TLS_reserved = Reserved;

TLS 處理
TLS 處理

}

TLS 處理

這樣會為 OS 提供一個存儲 slot 資訊的地方,我們稍候恢複它,并且如果調用了我們的回調函數我們将捕獲參數,在解壓縮之後調用原來的回調函數。否則會出錯因為 0S 會在我們有機會解壓縮之前做這件事情。解壓縮之後,我們把參數傳遞給原來的回調函數。

最後一步是這樣的:

TLS 處理

void FinalizeTLSStuff()

TLS 處理
TLS 處理

        if ( origdirinfo[IMAGE_DIRECTORY_ENTRY_TLS].VirtualAddress != 0 )

TLS 處理
TLS 處理

                *gev.tls_original.AddressOfIndex = TLS_slot_index;

TLS 處理

                void* TLS_data;

TLS 處理
TLS 處理

                __asm

TLS 處理
TLS 處理

                mov ecx, DWORD PTR TLS_slot_index;

TLS 處理

                mov edx, DWORD PTR fs:[02ch]

TLS 處理

                mov ecx, DWORD PTR [edx+ecx*4]

TLS 處理

                mov pvTLSData, ecx

TLS 處理
TLS 處理
TLS 處理

                int size = gev.tls_original.EndAddressOfRawData - gev.tls_original.StartAddressOfRawData;

TLS 處理
TLS 處理

                memcpy ( pvTLSData, (void*) gev.tls_original.StartAddressOfRawData, size );

TLS 處理

                memset ( (void*) gev.tls_original.EndAddressOfRawData, 0,

TLS 處理

                gev.tls_original.SizeOfZeroFill );

TLS 處理
TLS 處理
TLS 處理

        safe_to_callback_tls = true;

TLS 處理

        if ( delayed_tls_callback )

TLS 處理
TLS 處理

                TLSCallbackThunk ( TLS_dll_handle TLS_reason TLS_reserved );

TLS 處理
TLS 處理