TLS线程局部存储

直接贴代码:

#include 
#include 
#include 

DWORD g_tlsUsedTime; 

UINT __stdcall ThreadFunc(LPVOID)
{
	TlsSetValue(g_tlsUsedTime, (LPVOID)GetTickCount());
	/* 给WaitForMultipleObjects()函数足够时间 */
	Sleep(2000); 
	/* printf()函数不可重入 */
	printf("Thread ID: %d, Start Time: %d \n", GetCurrentThreadId(), (DWORD)TlsGetValue(g_tlsUsedTime));
	return 0;
}

int main(int argc, char* argv[])
{
	g_tlsUsedTime = TlsAlloc();		// 全局变量定义成线程局部存储

	HANDLE h[10];
	for (int i = 0; i < 10; ++i)	// 十个线程间隔0.1秒开启
	{
		h[i] = (HANDLE)_beginthreadex(NULL, 0, ThreadFunc, NULL, 0, NULL);	
		Sleep(100);
	}
	WaitForMultipleObjects(10, h, TRUE, INFINITE);
	for (int j = 0; j < 10;++j)
	{
		CloseHandle(h[j]);
	}

	TlsFree(g_tlsUsedTime);
	system("pause");
	return 0;
}

把生成的可执行文件拖到PEview中观察,TLS Table为空,节区中也没有.tls节区,如图:

TLS线程局部存储_第1张图片

后来知道在程序中主动使用TlsAlloc( )、TlsSetValue( )、TlsGetValue( )、TlsFree( )函数,这种叫动态线程局部存储,生成的pe文件中不会有tls这些东西,使用静态线程局部存储pe文件中就会有tls这中东西。

拿《逆向工程核心原理》第45章的Tlstest.exe文件做例子,源代码:

#include 
#pragma comment(linker, "/INCLUDE:__tls_used")

void print_console(char* szMsg)
{
    HANDLE hStdout = GetStdHandle(STD_OUTPUT_HANDLE);
    WriteConsoleA(hStdout, szMsg, strlen(szMsg), NULL, NULL);
}

void NTAPI TLS_CALLBACK1(PVOID DllHandle, DWORD Reason, PVOID Reserved)
{
    char szMsg[80] = {0,};
    wsprintfA(szMsg, "TLS_CALLBACK1() : DllHandle = %X, Reason = %d\n", DllHandle, Reason);
    print_console(szMsg);
}

void NTAPI TLS_CALLBACK2(PVOID DllHandle, DWORD Reason, PVOID Reserved)
{
    char szMsg[80] = {0,};
    wsprintfA(szMsg, "TLS_CALLBACK2() : DllHandle = %X, Reason = %d\n", DllHandle, Reason);
    print_console(szMsg);
}

#pragma data_seg(".CRT$XLX")
    PIMAGE_TLS_CALLBACK pTLS_CALLBACKs[] = { TLS_CALLBACK1, TLS_CALLBACK2, 0 };
#pragma data_seg()

DWORD WINAPI ThreadProc(LPVOID lParam)
{
    print_console("ThreadProc() start\n");
    print_console("ThreadProc() end\n");
    return 0;
}

int main(void)
{
    HANDLE hThread = NULL;
    print_console("main() start\n");
    hThread = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);
    WaitForSingleObject(hThread, 60*1000);
    CloseHandle(hThread);
    print_console("main() end\n");
    return 0;
}

代码作用:向操作系统注册2个tls回调函数,在main函数中创建线程,创建的线程结束后主线程退出。

先看pe文件中的TLS Table

TLS线程局部存储_第2张图片

指向的是IMAGE_TLS_DIRECTORY32结构体,大小0x18:

struct _IMAGE_TLS_DIRECTORY32 
{
    DWORD   StartAddressOfRawData;   // tls数据起始地址
    DWORD   EndAddressOfRawData;    // tls数据结束地址
    DWORD   AddressOfIndex;            // PDWORD 索引位置
    DWORD   AddressOfCallBacks;        // 向操作系统注册的回调函数数组指针
    DWORD   SizeOfZeroFill;       // 据说一直是0
    DWORD   Characteristics;           // 据说一直是0  
} IMAGE_TLS_DIRECTORY32;

在ida中观察:

IMAGE_TLS_DIRECTORY32结构体内容:

TLS线程局部存储_第3张图片

tls数据存放地址(都是0,就是没使用数据):

tls回调函数指针数组(有2个函数,数组以空结束):

再看tls节区头(如果使用了数据部分,tls节区在镜像中的大小应该很大了):

TLS线程局部存储_第4张图片

镜像中tls节区只有2个字节大小(没用数据部分),文件中tls节区大小0x200(是为了文件对齐)

 

TlsTest.exe输出截图:

TLS线程局部存储_第5张图片

至于为什么没有主线程分离,尚不清楚,估计是主进程分离的时候已经,操作系统已经实现了主线程分离。

注册的tls回调函数是顺序执行,不会出现函数1调用了,函数2没有调用的情况。

 

 

你可能感兴趣的:(PE结构)