当前位置: 首页 > news >正文

PE文件之TLS

PE文件之TLS

是什么线程局部存储

线程局部存储(Thread Local Storage,TLS)是各线程独立的数据存储空间,使用TLS可以像修改自身局部变量一样修改进程的全局变量而不影响其它线程。这很好地解决了多线程程序设计中变量的同步问题。

TLS机制最重要的地方在于:用来向目标文件写入内容的代码在所有线程中都是一样的,而从TLS中取出的文件偏移和字节数却各不相同。

实现TLS比较常见的办法是在进程中建立一个全局表,通过线程的ID去查询相应的数据结构,因为每个线程的ID是不一样的,所以查到的数据也自然就不一样了。程序员最受益的地方是不需要加入任何同步代码,即可满足程序功能设计的要求,所有同步操作由操作系统在内部自行完成,这就是TLS技术。

TLS技术分为两种:

  • 动态线程局部存储技术
  • 静态线程局部存储技术

TLS技术的分类主要依据是:线程局部存储的数据所用空间在程序运行期,操作系统完成的是动态申请还是静态分配。

动态线程局部存储

Windows为每个进程维护了一个全局的TLS索引池(TlsAlloc分配),并为每个线程维护了一个私有的TLS槽位数组(TLS Slots)。这个数组在PE文件中没有固定的存储结构,而是在内存中动态生成。

动态TLS主要使用如下四个函数:

  • TlsAlloc:系统返回一个全局索引(如 0x2C)。这个索引在所有线程中代表同一个“槽位编号”,这是进程级别的
  • TlsSetValue(Index, pData):系统找到当前线程的TEB(Thread 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的步骤如下:

  1. 定位TLS目录:通过数据目录表第9项找到IMAGE_TLS_DIRECTORY
  2. 为每个线程创建TLS存储块:系统为进程的主线程创建一个私有的TLS内存块。当新线程被创建时,系统会:
    a. 分配一块新的内存。
    b. 将StartAddressOfRawData指向的模板数据完整拷贝到这块内存中(初始化)。
    c. 将SizeOfZeroFill指定大小的区域清零
  3. 初始化访问:线程通过一个非常高效的机制访问其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] 的方式直接寻址
存储位置索引是进程全局变量索引变量位于进程的数据段(如 .data),AddressOfIndex存储的是它的地址

TLS回调函数

程序可以通过PE文件的方式提供一个或多个TLS回调函数,用以支持对TLS数据进行附加的初始化和终止操作,这种操作类似于面向对象程序设计中的构造函数和析构函数。尽管回调函数通常不超过一个,但还是将其作为一个数组来实现,以便在需要时可以另外添加更多回调函数。如果回调函数超过一个,它们将会按照其地址在数组中出现的顺序被依次调用,一个双字的空指针表示这个数组的结尾。如果程序没有提供回调函数,则该列表可以为空,这时,这个数组就只有一个元素,即双字的0。

下面是TLS回调函数的定义:

可以看到,这和DllMain函数的定义是很像的。

http://www.jsqmd.com/news/522417/

相关文章:

  • libhv WebSocket服务端避坑指南:关于线程模型和对象生命周期的那些事儿
  • OpenMTP:突破macOS与Android文件传输壁垒的无缝解决方案
  • 2026年PVC塑料管评测:口碑供应商,你选对了吗?塑料管机构推荐分析综合实力与口碑权威评选 - 品牌推荐师
  • LangChain4j多模型动态切换+SpringBoot实战指南
  • 四川全屋定制费用多少钱,蒂莱斯高配零增项全包一口价 - 工业设备
  • 2026年东莞车贷逾期处理律师推荐:陈杰律师,房贷延期处理/信用卡逾期协商律师精选 - 品牌推荐官
  • 别再只盯着RGB了!搞懂HDMI里的YUV422和YUV420,选对线材和设置不花冤枉钱
  • Unity跨平台PDF交互全攻略:从UI到3D场景的加载、翻页与动态缩放
  • 栅极驱动芯片选型实战:从参数计算到型号匹配
  • 用Python实战NetworkX:手把手教你找出社交网络中的核心小圈子(附Bron-Kerbosch算法源码解析)
  • YOLO-Pose多分类改造:如何让你的模型识别更多物体关键点
  • 2026ADHD儿童学习困难治疗机构推荐指南 - 品牌排行榜
  • LoRA无感切换是啥?yz-bijini-cosplay新手必看的功能详解与实操
  • Gradio 6.5定制化UI开发:实时手机检测Web界面二次开发入门
  • Citra 3DS模拟器全场景应用指南:从痛点解决到体验升华
  • 3月防静电气泡袋供应商口碑分析,优质推荐来了,国内气泡袋企业优选品牌推荐与解析 - 品牌推荐师
  • 聊聊东莞网站建设服务商,靠谱的推荐几家 - mypinpai
  • Turbo Intruder:3大核心优势实现百万级请求的Web安全测试实战指南
  • 上海宠物口腔溃疡诊疗指南:精选专业医生推荐 - 品牌推荐师
  • 基于有人云物联网关与MQTT服务器实现PLC数据双向通信的实践指南
  • 从ifconfig到iproute2:现代Linux网络管理工具链迁移全攻略
  • LVGL V8实战:如何用btnmatrix打造高颜值键盘(附完整代码)
  • 工业机械臂轨迹跟踪实战:从动力学模型到精准焊接(附MATLAB仿真代码)
  • FlowState Lab提示词(Prompt)工程入门:如何描述你想要的波动
  • 终极指南:如何巧妙隐身玩转Riot游戏而不被打扰
  • Qwen3-0.6B-FP8应用场景:学生辅助学习、程序员代码解释、运营文案生成
  • 从安装到踩坑:Nacos 2.2.3在Windows本地开发环境的完整避坑指南
  • Step_Motor嵌入式步进电机控制库:轻量级运动规划与脉冲生成
  • Si5351A Arduino时钟库:面向RF应用的轻量级全功能驱动
  • translategemma-27b-it效果展示:中文短视频字幕图→多语种SRT字幕自动生成