直接贴代码:
#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节区,如图:
后来知道在程序中主动使用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
指向的是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数据存放地址(都是0,就是没使用数据):
tls回调函数指针数组(有2个函数,数组以空结束):
再看tls节区头(如果使用了数据部分,tls节区在镜像中的大小应该很大了):
镜像中tls节区只有2个字节大小(没用数据部分),文件中tls节区大小0x200(是为了文件对齐)
TlsTest.exe输出截图:
至于为什么没有主线程分离,尚不清楚,估计是主进程分离的时候已经,操作系统已经实现了主线程分离。
注册的tls回调函数是顺序执行,不会出现函数1调用了,函数2没有调用的情况。