线程本地存储记录

线程本地存储(TLS)的一些总结

一、静态线程局部存储

1.MinGW编译器下

代码:

#include<iostream>
#include<thread>
#include<windows.h>
#include<mutex>
using namespace std;

mutex mu;
__thread int num = 0;//使用关键字__thread修改的变量


void test(int i)
{
	num+=i;
	
	mu.lock();
	cout<<"thread "<<this_thread::get_id()<<" num = "<<num<<endl;
	mu.unlock();
}

int main()
{
	clock_t t1 = clock();
	thread th[5];
	for(int i=0;i<5;i++)
	{
		th[i] = thread(test,i);
	}
	for(int i=0;i<5;i++)
	{
		th[i].join();
	}
	cout<<"finish main num = "<<num<<endl;
	clock_t t2 = clock();
	cout<<"use time = "<<(t2-t1)<<endl;
	system("pause");
}

静态存储主要依靠关键字 __thread修饰变量。

运行结果:
image

2.msvc编译器下

代码类似,只是前面的修饰符变化而已。

代码:

#include<iostream>
#include<thread>
#include<windows.h>
#include<mutex>
using namespace std;

mutex mu;
//_thread int num = 0;
_declspec (thread) int num = 0;


void test(int i)
{
	num += i;

	mu.lock();
	cout << "thread " << this_thread::get_id() << " num = " << num << endl;
	mu.unlock();
	//Sleep(1000);
}

int main()
{
	clock_t t1 = clock();
	thread th[5];
	for (int i = 0; i<5; i++)
	{
		th[i] = thread(test, i);
	}
	for (int i = 0; i<5; i++)
	{
		th[i].join();
	}
	cout << "finish main num = " << num << endl;
	clock_t t2 = clock();
	cout << "use time = " << (t2 - t1) << endl;
	system("pause");
	return 0;
}

运行结果就不贴了,运行结果基本一直,除了线程id为全局id之外。

二、动态线程局部存储

代码:

#include<iostream>
#include<thread>
#include<windows.h>
#include<mutex>
using namespace std;

mutex mu;
__declspec (thread) int num = 0;
DWORD index = 0;
void test(int i)
{
	num+=i;
	int count = 7*i;
	TlsSetValue(index,(LPVOID)count);//此函数必须在自己的线程之内操作,才能改变自己的槽
    //这里举个简单的例子,就一个int和LPVOID是一样大的。更小的数据,比如char之类的,直接存储就好了。大一些的就只能存储指针。
	mu.lock();
  
	cout<<"thread "<<this_thread::get_id()<<" num = "<<num<<endl;
	cout<<(int)TlsGetValue(index)<<endl;//在自己的线程之内使用,才有意义。
    __asm {
		mov	eax, DWORD PTR FS:[0x18]//TEB环境块
		mov eax, DWORD PTR DS :[eax + 0xE14] //因为index为1,所以是0XE10+0x4
		mov count,eax
	}
	cout << "count = " << count << endl;//操作线程存储槽和函数是一样的
	mu.unlock();
}

int main()
{
	index = TlsAlloc();
	clock_t t1 = clock();
	thread th[5];
	for(int i=0;i<5;i++)
	{
		th[i] = thread(test,i);
	}
	for(int i=0;i<5;i++)
	{
		th[i].join();
	}
	cout<<"finish main num = "<<num<<endl;
	clock_t t2 = clock();
	cout<<"use time = "<<(t2-t1)<<endl;
	TlsFree(index);
	system("pause");
}

image

直接操作线程的TLS存储槽和使用这几个函数是一样的效果。

三、TLS回调函数。

代码来源于网上。需要注意的是debug版本的exe可以看见回调函数输出到控制台,release版本的看不见。

参考至本篇博客:https://www.cnblogs.com/dliv3/p/6489629.html

代码:

#include <windows.h>

//告知连接器使用TLS
#pragma comment(linker, "/INCLUDE:__tls_used")

void print_console(char* szMsg)
{
	HANDLE hStdout = GetStdHandle(STD_OUTPUT_HANDLE);
	//先于主线程调用执行的TLS回调函数中使用printf可能会发生Runtime Error,可直接调用WriteConsole API
	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);
}
/*
注册TLS函数
.CRT$XLX的作用
CRT表示使用C Runtime 机制
X表示表示名随机
L表示TLS Callback section
X也可以换成B~Y任意一个字符
*/
#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");
	system("pause");
	return 0;
}

image

回调函数标准写法:

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)

回调函数执行原因:

#define DLL_PROCESS_ATTACH 1 	//进程启动
#define DLL_THREAD_ATTACH 2		//线程启动
#define DLL_THREAD_DETACH 3		//线程结束
#define DLL_PROCESS_ATTACH 0	//进程结束

四:WindowsPE权威指南关于线程本地存储的实验。

代码:

	.386
	.model flat,stdcall
	option casemap:none

include windows.inc
include user32.inc
includelib user32.lib
include kernel32.inc
includelib kernel32.lib

	.data
szText db 'HelloWorldPE',0,0,0,0

;所谓构造,就是自己指定0x18字节的数据,作为TLS目录表 IMAGE_TLS_DIRECTORY32
TLS_DIRR dd offset Tls1
		 dd offset Tls2
		 dd offset Tls3
		 dd offset TlsCallBack
		 dd 0
		 dd 0
Tls1     dd 0
Tls2 	 dd 0
Tls3 	 dd 0
TlsCallBack dd	offset TLS
			dd 0
			dd 0
	.data?
TLSCalled db ?
	.code
start:
	invoke ExitProcess,NULL
	RET
TLS:
cmp byte ptr [TLSCalled],1 ;因为回调函数会多次调用,和上面Cpp的调用结果一样,这里只是为了在创建的时候有信息框弹出。
je @exit
mov byte ptr [TLSCalled],1
invoke MessageBox,NULL,addr szText,NULL,MB_OK

@exit:
RET
	end start

这么编译,PE的数据目录表本来是没有TLS表的,所以需要我们手动修改exe

image

从.text节往上数,然后修改即可。

修改完毕,再运行就有信息框了。

posted @ 2022-03-27 17:40  念秋  阅读(28)  评论(0编辑  收藏  举报