https://www.bpsend.net/thread-407-1-1.html

当一个全局变量,所有的线程都会同时访问这个全局变量,其实就是访问同一块内存,有时我们希望所有的内存访问同一块内存,它们的值是不一样的,同一个线程里面是同一个值,不同线程里面是不同的值.

// Project1.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
// #include <iostream>
using namespace std; int g_n = 99; void  TestFunc1() {  
    g_n = 88;
} void  Thread1() 
{
    TestFunc1(); } void  Thread2() {
    TestFunc1();
}
int main()
{
    cout << "Hello World!\n";
}

例如上面代码, 线程1 Thread1 和线程 Thread2 同时运行,访问 函数 TestFunc1 ,但是里面全局变量的值是相互独立,互不影响的,系统提供了2种方式, 显式TLS 和隐式TLS

显式TLS - thread local storage

线程局部存储

由四个api负责

TlsAlloc: 判断数组中哪个元素可以使用,将该元素分配并保留,返回值是数组下标。 TlsSetValue:给数组元素赋值 TlsGetValue:获取数组元素的值 TlsFree: 释放索引 *Set和Get:元素值可以是4字节的值,也可以是指针。比如存结构体类型的TLS,在SetValue之前new个内存, 将指针存入元素;读取的是结构体指针,使用时强转。

TlsAlloc 返回的索引不是无限大的,最基础的需求是64个, 说明线程多的话,索引值可能相同,相同的话也不会存到同一块内存,每个线程都是独立的,该变量的是存入到 TEB 的 E10 位置处

fs:[18] 保存的是teb的信息

隐式TLS

变量

●语法:__declspec(thread) 类型 变量名 = xx。对这些变量的访问与普通变量一样,编译器负责处理其中细节分配索引和初始化等,生成动态使用时的代码。

●静态使用TLS变量需要在main之前(入口代码之前)申请数组下标并初始化元素为0。如果在main 中申请,在main之前执行代码时用到TLS变量来不及申请。所以只能记录在PE中,加载完PE立刻申请 TLS。

●每开辟一条新的线程,系统都会拷贝一份TLS变量副本到该线程中。作用域限制在各个线程内部。

●各个线程中TLS变量的初始值取决于申请TLS变量时赋的初始值。

●静态创建的TLS变量不能用于DLL动态库中。

#include <Windows.h>
#include <iostream>
#include <thread>
using namespace std; int g_n = 99; void  TreadFunc() {
    g_n = 999999;
    cout << g_n << endl;
} int main()
{
    thread t(TreadFunc);
    t.join();
    cout << g_n << endl;
    cout << "Hello World!\n";
}

可以看到全局变量的值被改了,那么怎么变成线程的局部变量呢 变量前面 __declspec(thread)

#include <Windows.h>
#include <iostream>
#include <thread>
using namespace std; __declspec(thread) int g_n = 99;   //设为线程局部函数 void  TreadFunc() {
    g_n = 999999;
    cout << g_n << endl;
} int main()
{
    thread t(TreadFunc);
    t.join();
    cout << g_n << endl;
    cout << "Hello World!\n";
}

可以看到此时局部变量的值没有被修改

  • _tls_index的值一般为 0

fs:[2C] 处,TEB结构体的2C处,先从2C处取首地址,然后根据index计算偏移,将3333333存储到该位置处。tls_index 发现是一个固定地址,猜测在文件中。

TLS 表

PE头里面的 TLS 表,使用隐式TLS的时候才存在该表,数据目录第 9(下标) 项

IMAGE_TLS_DIRECTORY32

// IMAGE_TLS_DIRECTORY32:IMAGE_DIRECTORY_ENTRY_TLS ,在数据目录的[9],24字节
// 隐式加载才会建此表
typedef struct _IMAGE_TLS_DIRECTORY32 {
 DWORD   StartAddressOfRawData; // TLS初始化数据的起始地址 VA,需要重定位
 DWORD   EndAddressOfRawData;    // TLS初始化数据的结束地址 VA,需要重定位
                                 // 1和2字段两者相减=定位放初始化值的范围
 DWORD   AddressOfIndex;         // TLS 索引的位置
 DWORD   AddressOfCallBacks;     // Tls回调函数的数组指针 也是 VA
 DWORD   SizeOfZeroFill;     // 填充0的个数
 union {
     DWORD Characteristics;    // 保留 
     struct {
         DWORD Reserved0 : 20;
         DWORD Alignment : 4;
         DWORD Reserved1 : 8;
     } DUMMYSTRUCTNAME;
 } DUMMYUNIONNAME; } IMAGE_TLS_DIRECTORY32;
typedef IMAGE_TLS_DIRECTORY32 * PIMAGE_TLS_DIRECTORY32;

  • StartAddressOfRawData 和 EndAddressOfRawData 决定一个范围,创建线程的时候拷贝该范围数据到TEB结构体中。

回调函数

  • 线程创建结束以及进程创建结束时调,类似DllMain。
  • 当用户选择使用自己编写的回调函数时,在应用程序初始化阶段,系统将要调用一个由用户编写的回调函数以完成相应的初始化以及其他的一些初始化工作。此调用将在程序真正开始执行到入口点之前就完成,以保证程序执行的正确性。
  • 当同步的代码量不大的时候,TLS可以提高性能,TLS是C标准。
#include <Windows.h>
#include <iostream>
#include <thread>
using namespace std; __declspec(thread) int g_n = 99; void  TreadFunc() {
    g_n = 999999;
    cout << g_n << endl;
} VOID NTAPI TlsCallBack(
    PVOID DllHandle,
    DWORD Reason,
    PVOID Reserved
)
{
    switch (Reason) // 调用原因
    {
    case DLL_PROCESS_ATTACH:
        printf("tid:%08X DLL_PROCESS_ATTACH\r\n", GetCurrentThreadId());
        break;
    case DLL_PROCESS_DETACH:
        printf("tid:%08X DLL_PROCESS_DETACH\r\n", GetCurrentThreadId());
        break;
    case DLL_THREAD_ATTACH:
        printf("tid:%08X DLL_THREAD_ATTACH\r\n", GetCurrentThreadId());
        break;
    case DLL_THREAD_DETACH:
        printf("tid:%08X DLL_THREAD_DETACH\r\n", GetCurrentThreadId());
        break;
    }
}
//======== 强制生成tls表 ========
#pragma comment(linker, "/include:__tls_used")    //启用tls
#pragma data_seg(".CRT$XLY")
PIMAGE_TLS_CALLBACK callbacks[] = { 
    TlsCallBack,
    TlsCallBack,
    TlsCallBack,
};
#pragma data_seg() //======== 强制生成tls表 ========
int main()
{
    thread(TreadFunc).join();
    thread(TreadFunc).join();
    cout << g_n << endl;
    cout << "Hello World!\n";
}
  • .CRT$XLH:以$作为分割,.CRT表示启用C运行时库,XL固定,H(B-Y任意字符)
  • #pragma data_seg(".CRT$XLY") 申请新节,链接器安排节的时候会将节名相似的节安排到一起,并且排序的时候会按照ASCLL码排序。

pragma data_seg(".CRT$XLA")

pragma data_seg(".CRT$XLB")

pragma data_seg(".CRT$XLC")

pragma data_seg(".CRT$XLD")

pragma data_seg(".CRT$XLE")

内存排序顺序:ABCDE。

  __xl_a 00401000
00000000
c0 22 41 00 c0 22 41 00 c0 22 41 00 00 00 00 00
00 00 00 00  extern const IMAGE_TLS_DIRECTORY _tls_used =
{
        (ULONG)(ULONG_PTR) &_tls_start, // start of tls data
        (ULONG)(ULONG_PTR) &_tls_end,   // end of tls data
        (ULONG)(ULONG_PTR) &_tls_index, // address of tls_index
        (ULONG)(ULONG_PTR) 00401004, // pointer to call back array
        (ULONG) 0,                      // size of tls zero fill
        (ULONG) 0                       // characteristics
};

应用

因为线程回调的时机很早,每次线程创建之前他就会调一下,进程启动前也会调一下,因此很早的时候被用来做反调试,是配合堆标志来做反调试的,堆在调试状态和非调试状态的初始值是不一样的,跑起来时,先在stl中申请一块堆,检查堆里面的初始值,看看是不是调试状态的初始值,如果是调试状态的初始值,说明是调试器. PEB 里面有一个标志位,如果改值位TRUE ,每次申请堆,里面堆的初始值是调试状态的初始值,如果是FLASE,就是非调试状态的初始值, 直接改这个标志位的值是没用的,因为会影响后面一系列的标志 ,如果真想改 需要吧内核 里面的 CreateProcess hook掉在他给标志的时候改成自己的值就可以

WindowsPE文件格式入门10.TLS表的更多相关文章

  1. 无废话ExtJs 入门教程四[表单:FormPanel]

    无废话ExtJs 入门教程四[表单:FormPanel] extjs技术交流,欢迎加群(201926085) 继上一节内容,我们在窗体里加了个表单.如下所示代码区的第28行位置,items:form. ...

  2. 一文快速入门分库分表中间件 Sharding-JDBC (必修课)

    书接上文 <一文快速入门分库分表(必修课)>,这篇拖了好长的时间,本来计划在一周前就该写完的,结果家庭内部突然人事调整,领导层进行权利交接,随之宣布我正式当爹,紧接着家庭地位滑落至第三名, ...

  3. Cesium入门10 - 3D Tiles

    Cesium入门10 - 3D Tiles Cesium中文网:http://cesiumcn.org/ | 国内快速访问:http://cesium.coinidea.com/ 我们团队有时把Ces ...

  4. PE格式第八讲,TLS表(线程局部存储)

    PE格式第八讲,TLS表(线程局部存储) 作者:IBinary出处:http://www.cnblogs.com/iBinary/版权所有,欢迎保留原文链接进行转载:) 一丶复习线程相关知识 首先讲解 ...

  5. 10. InnoDB表空间加密

    10. InnoDB表空间加密 InnoDB支持存储在单独表空间中的表的数据加密 .此功能为物理表空间数据文件提供静态加密. 详细信息见官方文档

  6. Farseer.net轻量级ORM开源框架 V1.x 入门篇:表的数据操作

    导航 目   录:Farseer.net轻量级ORM开源框架 目录 上一篇:Farseer.net轻量级ORM开源框架 V1.x 入门篇:表实体类映射 下一篇:Farseer.net轻量级ORM开源框 ...

  7. Farseer.net轻量级ORM开源框架 V1.x 入门篇:表实体类映射

    导航 目   录:Farseer.net轻量级ORM开源框架 目录 上一篇:Farseer.net轻量级ORM开源框架 V1.x 入门篇:数据库上下文 下一篇:Farseer.net轻量级ORM开源框 ...

  8. 第八讲,TLS表(线程局部存储)

    一丶复习线程相关知识 首先讲解TLS的时候,需要复习线程相关知识,  (thread local storage ) 1.了解经典同步问题 首先我们先写一段C++代码,开辟两个线程去跑,看看会不会出现 ...

  9. JavaScript基础入门10

    目录 JavaScript 基础入门10 正则表达式 为什么使用正则表达式? 正则表达式的应用场景 如何创建一个正则表达式 基础语法 具有特殊意义的转义字符 量词 字符类 贪婪模式 练习 邮箱验证 中 ...

  10. 04. Web大前端时代之:HTML5+CSS3入门系列~HTML5 表单

    Web大前端时代之:HTML5+CSS3入门系列:http://www.cnblogs.com/dunitian/p/5121725.html 一.input新增类型: 1.tel:输入类型用于应该包 ...

随机推荐

  1. Linux - Centos操作系统iso文件下载

    CENTOS VERSION DOWNLOAD LINK CentOS 8.5(2111) Download CentOS 8.4(2105) Download CentOS 8.3(2011) Do ...

  2. Docker - 在docker中部署Nginx

    1.docker search 查找ngix 2.docker pull下载镜像 3.查看镜像列表 4.docker run启动容器 5.测试nginx容器是否启动成功 1.docker search ...

  3. halcon中是怎么实现半导体/Led中的GoldenDie的检测方法的 基于局部可变形模板匹配 variation_model模型

    原文作者:aircraft 原文地址:https://www.cnblogs.com/DOMLX/p/18739196 这篇简单介绍一下halcon中的print_check_single_chars ...

  4. 当懒惰遇上AI:我如何用Coze让大模型帮我整理2.5万字课程笔记

    能写代码绝不动手,能用AI绝不写代码 -- AI粉嫩特攻队信条 通过本文学会打造这个AI工具,只有一个要求:识字且会上网! 一个小困扰 有朋友最近在上一位大佬的线上直播课程,感叹道: "老师 ...

  5. ChatBI≠NL2SQL:关于问数,聊聊我踩过的坑和一点感悟

    "如果说数据是新时代的石油,智能问数就是能让普通人也能操作的智能钻井平台." 这里是**AI粉嫩特攻队!** ,这段时间真的太忙了,不过放心,关于从零打造AI工具的coze实操下篇 ...

  6. 自动化-Yaml文件读取函数封装

    1.文件布局 打开文件修改读取方式为w load函数加载文件 class ReadConfiYaml: def __init__(self,yaml_file): self.yaml_file=yam ...

  7. 查看、安装python指定版本的包、安装卸载第三方模块

    python安装/卸载第三方包 (1)安装第三方包: 安装指令pip install xxx (xxx,需安装的包名) 安装特定版本的package:通过使用==, >=, <=, > ...

  8. Oracle12c 数据库 警告日志

    目录 一:查看警告日志文件的位置 二:警告日志内容 三:告警日志监控: 方案1: 方案2: 方案3: 正文 回到顶部 一:查看警告日志文件的位置 Oracle 12c环境下查询,alert日志并不在b ...

  9. 有限Abel群的结构(1)

    版权申明:本文为博主窗户(Colin Cai)原创,欢迎转帖.如要转贴,必须注明原文网址 https://www.cnblogs.com/Colin-Cai/p/18774816.html 作者:窗户 ...

  10. 在 Hugging Face Spaces 上使用 Gradio 免费运行 ComfyUI 工作流

    简介 在本教程中,我将逐步指导如何将一个复杂的 ComfyUI 工作流转换为一个简单的 Gradio 应用程序,并讲解如何将其部署在 Hugging Face Spaces 的 ZeroGPU 无服务 ...