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

作者:IBinary
出处:http://www.cnblogs.com/iBinary/
版权所有,欢迎保留原文链接进行转载:)

一丶复习线程相关知识

首先讲解TLS的时候,需要复习线程相关知识,  (thread local storage )

1.了解经典同步问题

首先我们先写一段C++代码,开辟两个线程去跑,看看会不会出现同步问题.

看结果得知,结果并不是正确的,造成同步的问题的原因是两个线程都对同一个变量进行访问.

解决问题:

1.使用同步对象.  (自旋锁 自加锁 互斥体 事件  信号灯  临界区.....等等都可以.)

这里使用自加锁解决(当然可以用别的)

InterlockedIncrement  API

原型:

LONG InterlockedIncrement(LPLONG volatile lpAddend   // variable to increment
);只需要把全局变量的地址给它,强转为long * 类型即可.

使用之后结果是正确的

二丶何为TLS  (Thread  local storage)

所谓TLS,意思就是指,每个线程都有自己的空间,局部存储,什么意思?

比如上方我们对一个g_dwNumber进行操作,那么我们就要使用同步对象,我们不妨这样去想,每个线程,开辟一个空间

当对A线程进行操作的时候,操作的是A线程的g_dwNumber,当对B线程进行操作的时候,是对B线程的g_dwNumber进行操作.

其实很简单,介绍一下TLS的API

总共4个

分别是:

TlsAlloc  分配线程局部存储空间

TlsFree  释放线程局部存储空间

TlsGetValue 获得线程局部存储空间里面的值

TlsSetValue 设置线程局部存储空间的值

三丶TLSAPI的使用

1.首先是TlsAlloc的使用

DWORD TlsAlloc(VOID);  函数原型

调用一次TlsAlloc则会分配4个字节的空间,不管你在哪里调用,如果在main里面(主线程)中调用,那么当你创建线程的时候线程会默认有4个字节的控件返回值是一个索引, 这个索引是查FS寄存器数组的值当然,这个一会讲解.只需要知道,当我们为每一个线程申请了4个字节的空间那么索引是一样的,但是索引操作的数据是不一样的比如 你申请的索引是1那么在A线程中,操作1索引的时候,那么操作的是A线程的,那么如果在B线程操作索引1的时候,那么操作的是B线程的数据举例子:比如有个电话号码是  12345678中国: 12345678外国: 12345678 (把电话号码看做是索引)我们知道,电话号码是一样的,但是你打这个电话的时候,人是不一样的比如我在中国打123456 那么接听人是张三我在外国打123456 那么接听人是李四其中张三李四就是表达了对同一数据的不同操作.看下代码再比如:我们使用tlsAlloc申请了4个字节的空间索引就是nindex (看做是g_dwNumber);那么访问不同线程的索引,那么索引里面的值是不同的.

1.Tls的动态使用方法,设置全局变量动态使用就是PE中不建立TLS表格了,同样完成同步
首先,我们为每个线程开辟了4个字节的空间然后返回一个索引(这个索引看做是g_dwNumber,其实这个索引是去数组里面去取出成员来,比如现在是第1个,那么去数组里面取出第一项来,当做g_dwNumber)TlsSetValue(索引,设置的值)这样写其实就是根据索引找到数组里面的值,设置一下.TlsGetValue(索引)则是根据下标索引,去数组里面取出g_dwNumber的值.然后下方重新设置回去了.在1索引的位置,设置了g_dwNumber的值.

如果对齐数据结构不理解,可以看下手工写的图

AThread (当前索引为1)   数组: [0][1][2][3].....  数组首地址: 00401000
BThread (当前索引为1)   数组: [0][1][2][3]..... 数组首地址: 00402000其实每个线程可以理解为索引虽然一样,但是在数组里面取出来的值是不一样的.比如A线程的索引为1,里面的成员是A线程的g_dwNumber 比如现在它的值是5现在切换到了B线程了,那么还是根据索引去找值,但是数组不同了,所以再次找1找的则是B数组的g_dwNumber了.其实API的作用就相当于你手工的去给数组第几个元素赋值,取值.等等.只不过这个是操作系统封装的数组,所以给你提供API按照我们的写法,可能会下面那样做,伪代码,便于理解AThread[1] = 0;DWORD g_dwNumber = AThread[1];printf(g_dwNumber);AThread[1] = g_dwNumber++;替换成API则是TlsSetValue(索引,值)TlsGetValue(索引);

现在看下那张图,那么已经实现了同步.线程也切换了,操作的就是自己的数据.2.动态使用Tls之结构体的设置上面我们说的是数组里面设置的是全局变量,现在我们要设置一下结构体了.结构体其实是一样的,我们让数组里面存指针就行.比如看下方代码:

很简单

1.我们定义一个p指针,指向了一块new的内存

2.初始化的时候,设置数组索引的当前索引的值为p的指针

3.从索引中获得p指针

4.修改p指向的m_dwCount的值

注意,这里因为p是一个指针,我们修改的只是它空间成员变量的值,所以不用重新再设置回去了.

到了现在感觉TLS是不是有点难用了.其实使用TLS 比使用任何同步对象都快,就相当于没同步的时候的速度.

但是TLS的真正的语法不是这样用的.(上面是动态使用不会生成TLS表)

3.Tls的静态使用(真正用法)

其实TLS真正的用法是静态使用,操作系统已经帮你集成了语法了

看下用法,以及语法;

语法:

__declspec(thread) 类型  变量名

然后tls就会自动生成表了,操作系统帮你升成上面动态使用的代码.(所以为啥要理解动态使用)

用的时候还是正常使用.

我们的代码都不用变的.

但其实汇编代码还是会编译为上面的动态使用.

如果变为结构体,那么是一样的,只需要把类型变成结构体的类型即可.

四丶PE中TLS表的设计

了解了上方的原理了,那么如果让你设计表格你要怎么设计?

1.我们全局变量初始化为0了,那么我们肯定有地方存储了这个全局变量的数据 ,所以我会设计一段分为存储这个值.

2.我们常用的nindex索引,那么我觉着也要存储一下

废话不说了,看下真是的结构体

ypedef struct _IMAGE_TLS_DIRECTORY32 {DWORD   StartAddressOfRawData;    TLS初始化数据的起始地址DWORD   EndAddressOfRawData;      TLS初始化数据的结束地址  两个正好定位一个范围,范围放初始化的值DWORD   AddressOfIndex;              TLS 索引的位置DWORD   AddressOfCallBacks;          Tls回调函数的数组指针
    DWORD   SizeOfZeroFill;         填充0的个数union {DWORD Characteristics;      保留struct {DWORD Reserved0 : 20;DWORD Alignment : 4;DWORD Reserved1 : 8;} DUMMYSTRUCTNAME;} DUMMYUNIONNAME;} IMAGE_TLS_DIRECTORY32;

首先介绍前两个成员,

起始地址  结束地址 定位了一个范围,那么这个范围内存放的就是初始化的值(注意只有静态使用才有TLS表)也就是上方我们定义的g_dwNumber = 0;存放了0,但是因为0不好看,这里我重新赋值为12345678 代码不贴了.

我们查看下PE定位一下Tls的位置.

注意,因为我是VS2015编写的程序,随机基址懒得去了,直接在PE中修改了,把文件头的文件属性修改了即可.

以前是02,现在改成03即可.

首先查看下数据目录的第9项

得出RVA = 000176FC

查看下模块首地址. 首地址是 00400000

看下属于哪个节

命中在.rdata节,RVA = 00016000

上面的RVA减去现在的RVA = 偏移

000176FC - 00016000 = 16FC

节中的文件偏移 + 偏移 = 文件中的位置.

文件偏移是下方的第二个成员

5400 + 16FC = 6AFC

查看6AFC定位Tls表的位置.

前面两个成员分别指向的是

0041B000  0041B208的位置  结束地址 - 起始地址 = 范围.

寻找起始地址的FA

时间关系,这里命中的节是 Rva = 001B000

那么转为文件偏移

FA = 8400h直接计算出来了

起始地址是8400h 那么+208就是8608 ,那么8400h 到8608的位置就存放的初始值,现在已经看到上图画出来的12345678了(小尾方式读取)

第3个成员: 索引的值,这个你可以自己转化查看.

五丶TLS结构体第四个成员,回调函数的数组指针

这个怎么理解,是这样的,还记到动态使用的时候,我们不是在主线程中 TlsAlloc 和TlsFree吗

现在我们可以注册回调函数,操作系统会调用这个回调函数.

怎么注册?

关键字: 加段,必须添加到特定的段中

首先先看下回调的函数原型.

typedef VOID
(NTAPI *PIMAGE_TLS_CALLBACK) (PVOID DllHandle, DWORD Reason,PVOID Reserved );
PIMAGE_TLS_CALLBACK 其中这个回调是从结构体中第四个成员里面,注释得到的

首先我们自己写一个

请看注释,其实这里才是真正的申请和释放,注意,这个回调函数操作系统会从问价那种读取地址,然后执行一遍,没有申请内存,所以这里面可以藏代码的.

注意,虽然回调我们写了,但是要让操作系统调用,那么我们需要添加一个特定的节.

语法:

#pragma data_seg(".CRT$XLB")  其中关于.CRT$XLB 为什么是这个节,我发下连接看雪论坛的,自己看下吧,很简单了.https://bbs.pediy.com/thread-108015.htm

/*中间写代码,定义函数回调数组*/

PIMAGE_TLS_CALLBACK ary[] = {MyTlsCallBack,0}; //0结尾,那么操作系统就会在文件中找到这个位置,调用一下这个回调.如果多个,里面可以写多个,0结尾即可.

#pragma data_seg();

发现1已经成功弹出来了,那么现在结构体的第四个成员,就是指向这个数组首地址的.PE加载的时候,会默认调用,然后依次执行一遍..

请注意,只会在文件中存储,如果你跑到内存中查看,这个地址是没有的.

太晚了,快4点了,剩下的字节明天说.

作者:IBinary
出处:http://www.cnblogs.com/iBinary/
版权所有,欢迎保留原文链接进行转载:)

转载于:https://www.cnblogs.com/iBinary/p/7697355.html

PE格式第八讲,TLS表(线程局部存储)相关推荐

  1. PE格式第七讲,重定位表

    PE格式第七讲,重定位表 作者:IBinary 出处:http://www.cnblogs.com/iBinary/ 版权所有,欢迎保留原文链接进行转载:) 一丶何为重定位(注意,不是重定位表格) 首 ...

  2. OpenGL ES EGL TLS(线程局部存储) G3D

    1. 什么是EGL EGL是用来管理绘图表面的(Drawing surfaces),并且提供了如下的机制 (1) 与本地窗口系统进行通信 (2) 查找绘图表面可用的类型和配置信息 (3) 创建绘图表面 ...

  3. PE格式系列_0x03:输入表原理+IAT填充流程

    文章目录 Q1:函数真实地址? Q2:函数调用和真实地址之间桥梁? Q3:PE文件记录DLL信息? 1.数据结构 part 1.整体分析 part 2.局部分析 2.数据结构总结 3. 加载流程 Q4 ...

  4. GraphPad Prism的八种数据表格式

    GraphPad Prism的数据表格式将决定可以制作何种图表以及可以执行何种分析.选择数据表格式可以让GraphPad Prism创建一张适合于您数据的数据表,并且可以轻松创建您想要的图表类型并执行 ...

  5. Linux内存机制浅见——从内存布局到线程局部存储TLS

    先记 最近在重新分析SGX的源码<再回顾sgx_create_enclave>,一路发现,我还需要重新思考ELF文件格式(我在<SGX初始化中ElfParser::run_parse ...

  6. Windows PE第九章 线程局部存储

    线程局部存储(TLS) 这个东西并不陌生了,之前写过了一个关于这个的应用,利用静态TLS姿势实现代码段静态加密免杀或者所谓的加壳思路.地址在这:http://blog.csdn.net/u013761 ...

  7. java 线程局部存储,转载boost::thread简要分析(3):线程局部存储及其它

    多线程编程中还有一个重要的概念:Thread Local Store(TLS,线程局部存储),在boost中,TLS也被称作TSS,Thread Specific Storage. boost::th ...

  8. 内核知识第八讲,PDE,PTE,页目录表,页表的内存管理

    内核知识第八讲,PDE,PTE,页目录表,页表的内存管理 一丶查看GDT表. 我们通过WinDbg + 虚拟机可以进行双机调试.调试一下看下GDT表 我们知道,GDT表中.存储的是存储段信息. 保存了 ...

  9. PE 格式详解与试验

    PE 格式详解与试验 可执行文件结构分析 DOS头 文件头 可选头 PE RVA 地址与文件地址转换 块表 Section Header 导入表 Data Directory 基址重定位 reloc ...

  10. PE格式详细讲解4 - 系统篇04|解密系列

    PE格式详细讲解4 - 系统篇04 让编程改变世界 Change the world by program   到此为止,小甲鱼和大家已经学了许多关于 DOS header 和 PE header 的 ...

最新文章

  1. 证明矩阵添加一列(行),则其秩或不变,或增加1
  2. 3D打印机控制板 Ramps 1.4的原理图
  3. Docker selenium自动化 - windows版docker的安装与运行环境检测
  4. I2C通信读写数据过程
  5. Golang结构体与面向对象
  6. PHP仿百度实现弹窗登录效果,js仿百度登录页实现拖动窗口效果
  7. 大牛熬夜整理,京东网易滴滴,天津农行软开java面试题合集,大牛已于上个月入职华为!
  8. 微信小程序制作天气查询系统
  9. java json设置编码_java-JSON字符编码
  10. netstat 中state详解
  11. 青龙面板 Bot配置教程
  12. Android 启用/禁用蓝牙传输文件功能(不能影响蓝牙耳机听歌)
  13. 安卓开发常用的adb命令
  14. 网络嗅探之一 --- 原理篇
  15. 武汉地铁站点最短路径搜索的实现(一)——Dijkstra算法(资料收集)
  16. 国税总局发票查验平台——Excel批量查验自动截图保存助手
  17. JavaJDK下载安装与环境配置(Windows 10 超详细的图文版教程 )
  18. 如何建立自己的认知体系?
  19. c\c++语言删除数组中重复元素
  20. 查看计算机的操作系统,查看电脑操作系统版本(适用于Windows OS)

热门文章

  1. 冲突等价(ConflictEquivalence) 可串行化调度(Serializable Schedules)
  2. 区块链 共识机制研究的重要定理有哪些
  3. java 遍历 Map 的六种方式 学习笔记
  4. dh参数逆运动学_干货 | 运动学好像够用了,我们为什么还需要动力学
  5. android使用的数据,在 Android 应用中使用数据 - Xamarin | Microsoft Docs
  6. java正则卡号4位一空格_银行卡号每隔4位插入空格
  7. java中常用的类——Object类
  8. 列举微型计算机的主要性能指标,计算机基础考试纲.ppt
  9. 邮件服务器搬家,企业邮箱怎么“搬家”
  10. linux通用内核,重新编译内核 - Linux通用系统优化大全_Linux教程_Linux公社-Linux系统门户网站...