PE文件之TLS
PE文件之TLS是什么线程局部存储线程局部存储Thread Local StorageTLS是各线程独立的数据存储空间使用TLS可以像修改自身局部变量一样修改进程的全局变量而不影响其它线程。这很好地解决了多线程程序设计中变量的同步问题。TLS机制最重要的地方在于用来向目标文件写入内容的代码在所有线程中都是一样的而从TLS中取出的文件偏移和字节数却各不相同。实现TLS比较常见的办法是在进程中建立一个全局表通过线程的ID去查询相应的数据结构因为每个线程的ID是不一样的所以查到的数据也自然就不一样了。程序员最受益的地方是不需要加入任何同步代码即可满足程序功能设计的要求所有同步操作由操作系统在内部自行完成这就是TLS技术。TLS技术分为两种动态线程局部存储技术静态线程局部存储技术TLS技术的分类主要依据是线程局部存储的数据所用空间在程序运行期操作系统完成的是动态申请还是静态分配。动态线程局部存储Windows为每个进程维护了一个全局的TLS索引池TlsAlloc分配并为每个线程维护了一个私有的TLS槽位数组TLS Slots。这个数组在PE文件中没有固定的存储结构而是在内存中动态生成。动态TLS主要使用如下四个函数TlsAlloc系统返回一个全局索引如 0x2C。这个索引在所有线程中代表同一个“槽位编号”这是进程级别的TlsSetValue(Index, pData)系统找到当前线程的TEBThread Environment Block定位到其中的TLS数组将 pData存入数组的第 Index个位置这是线程级别的TlsGetValue(Index)从当前线程的TLS数组中取出第 Index个值这是线程级别的TlsFree每个线程的TEB中有一个 ThreadLocalStoragePointer字段指向一个 LPVOID数组。TlsGetValue本质上就是一次数组寻址*(TEB-ThreadLocalStoragePointer Index)。静态线程局部存储在PE文件结构中静态线程局部存储特指通过编译器关键字如__declspec(thread)或__thread声明的、在编译链接时即确定存储布局并由PE文件自身的数据结构和系统加载器直接支持的线程局部变量机制。1. 实现核心PE文件中的静态TLS段当编译器发现__declspec(thread) int tls_var;这样的变量时它会进行特殊处理编译阶段将该变量放入一个特殊的节区通常命名为.tls或类似名称。链接阶段链接器会汇总所有此类变量在PE文件中创建或合并一个.tls 节区并生成一个IMAGE_TLS_DIRECTORY结构体。这个结构体的指针被放入PE头数据目录表的第9项IMAGE_DIRECTORY_ENTRY_TLS。2. 关键数据结构IMAGE_TLS_DIRECTORY这个结构体定义了静态TLS的“蓝图”包含以下关键字段StartAddressOfRawData / EndAddressOfRawData指向.tls 节区在文件中的起始和结束RVA。这块数据是TLS变量的初始化模板。AddressOfIndex指向存储TLS索引的位置通常是一个全局变量。AddressOfCallbacks指向TLS回调函数数组上一轮已详述。SizeOfZeroFill指定需要额外清零的TLS数据大小。3. 加载器的工作流程加载器在加载PE文件时处理静态TLS的步骤如下定位TLS目录通过数据目录表第9项找到IMAGE_TLS_DIRECTORY。为每个线程创建TLS存储块系统为进程的主线程创建一个私有的TLS内存块。当新线程被创建时系统会a. 分配一块新的内存。b. 将StartAddressOfRawData指向的模板数据完整拷贝到这块内存中初始化。c. 将SizeOfZeroFill指定大小的区域清零。初始化访问线程通过一个非常高效的机制访问其TLS变量。在x86/x64架构上通常通过FS/GS段寄存器中的一个固定偏移由加载器设置直接寻址到该线程的TLS存储块然后加上变量的编译时偏移量来访问。这比动态TLS的API调用快得多。动态TLS和静态TLS比较机制动态TLS (API)静态TLS (PE结构)全局索引DWORD g_tlsIndex TlsAlloc();DWORD tlsIndex;由AddressOfIndex指向索引获取程序员调用TlsAlloc()得到由系统加载器自动分配并写入AddressOfIndex指向的地址索引用途作为TlsGetValue(g_tlsIndex)的参数由编译器生成代码通过 FS/GS:[tlsIndex] 的方式直接寻址存储位置索引是进程全局变量索引变量位于进程的数据段如 .dataAddressOfIndex存储的是它的地址TLS回调函数程序可以通过PE文件的方式提供一个或多个TLS回调函数用以支持对TLS数据进行附加的初始化和终止操作这种操作类似于面向对象程序设计中的构造函数和析构函数。尽管回调函数通常不超过一个但还是将其作为一个数组来实现以便在需要时可以另外添加更多回调函数。如果回调函数超过一个它们将会按照其地址在数组中出现的顺序被依次调用一个双字的空指针表示这个数组的结尾。如果程序没有提供回调函数则该列表可以为空这时这个数组就只有一个元素即双字的0。下面是TLS回调函数的定义可以看到这和DllMain函数的定义是很像的。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2440305.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!