PE格式第八讲,TLS表(线程局部存储)
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 : ;
DWORD Alignment : ;
DWORD Reserved1 : ;
} 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/
版权所有,欢迎保留原文链接进行转载:)
PE格式第八讲,TLS表(线程局部存储)的更多相关文章
- 第八讲,TLS表(线程局部存储)
一丶复习线程相关知识 首先讲解TLS的时候,需要复习线程相关知识, (thread local storage ) 1.了解经典同步问题 首先我们先写一段C++代码,开辟两个线程去跑,看看会不会出现 ...
- PE格式第四讲,数据目录表之导入表,以及IAT表
PE格式第四讲,数据目录表之导入表,以及IAT表 一丶IAT(地址表) 首先我们思考一个问题,程序加载的时候会调用API,比如我们以前写的标准PE 那么他到底是怎么去调用的? 他会Call 下边的Jm ...
- PE格式第七讲,重定位表
PE格式第七讲,重定位表 作者:IBinary出处:http://www.cnblogs.com/iBinary/版权所有,欢迎保留原文链接进行转载:) 一丶何为重定位(注意,不是重定位表格) 首先, ...
- PE格式第六讲,导出表
PE格式第六讲,导出表 请注意,下方字数比较多,其实结构挺简单,但是你如果把博客内容弄明白了,对你受益匪浅,千万不要看到字数多就懵了,其实字数多代表它重要.特别是第五步, 各种表中之间的关系. 作者: ...
- 《ArcGIS Engine+C#实例开发教程》第八讲 属性数据表的查询显示
原文:<ArcGIS Engine+C#实例开发教程>第八讲 属性数据表的查询显示 第一讲 桌面GIS应用程序框架的建立 第二讲 菜单的添加及其实现 第三讲 MapControl与Page ...
- 基于TLS(线程局部存储)的高效timelog实现
什么是timelog? 我们在分析程序性能的时候,会加入的一些logging信息记录每一部分的时间信息 timelog模块的功能就是提供统一的接口来允许添加和保存logging 我们正在用的timel ...
- 线程局部存储 TLS
C/C++运行库提供了TLS(线程局部存储),在多线程还未产生时,可以将数据与正在执行的线程关联.strtok()函数就是一个很好的例子.与它一起的还有strtok_s(),_tcstok_s()等等 ...
- Windows PE第九章 线程局部存储
线程局部存储(TLS) 这个东西并不陌生了,之前写过了一个关于这个的应用,利用静态TLS姿势实现代码段静态加密免杀或者所谓的加壳思路.地址在这:http://blog.csdn.net/u013761 ...
- PE格式第九讲,资源表解析
PE格式第九讲,资源表解析 一丶熟悉Windows管理文件的方法 首先,为什么标题是这个,主要是为了下边讲解资源方便,因为资源结构体很乱.如果直接拿出来讲解,那么就会很晕. 1.windows管理文件 ...
随机推荐
- 联合线程(案例顾客买蛋糕 :使用join()方法)
运行效果图:
- 团队作业8——Beta 阶段冲刺5th day
一.当天站立式会议 二.每个人的工作 (1)昨天已完成的工作(具体在表格中) 支付功能测试 (2)今天计划完成的工作(具体如下) 完善订单功能 (3)工作中遇到的困难(在表格中) 成员 昨天已完成的工 ...
- 201521123030《Java程序设计》第4周学习总结
1. 本周学习总结 1.1 尝试使用思维导图总结有关继承的知识点. 1.2 使用常规方法总结其他上课内容. 控制可见性的4个访问修饰符 private -- 仅对本类可见 public -- 对所有类 ...
- java201521123118《java程序设计》第3周总结
1. 本周学习 总结初学面向对象,会学习到很多碎片化的概念与知识.尝试学会使用思维导图将这些碎片化的概念.知识组织起来.请使用纸笔或者下面的工具画出本周学习到的知识点.截图或者拍照上传. 2. 书面作 ...
- backtrack 使用Tab键补全命令
刚安装个BackTrack5(基于Ubuntu)做安全测试,发现默认安全设置级别很高,连ssh服务默认都关闭,root下不能使用tab键补全命令,这个就真不方便了.原因是root的配置文件注释了三行脚 ...
- 201521123038 《Java程序设计》 第十二周学习总结
201521123038 <Java程序设计> 第十二周学习总结 1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结多流与文件相关内容. 2. 书面作业 将Student ...
- JDBC中的ResultSet无法多次循环的问题。
前几天碰见了一个很奇葩的问题,使我百思不得其解,今天就写一下我遇见的问题吧,也供大家参考,别和我犯同样的毛病. 首先说下jdbc,jdbc是java是一种用于执行SQL语句的Java API,从jdb ...
- 通过SDK和API获取阿里云RDS的监控数据
阿里云的RDS自带的监控系统获取数据不怎么直观,想要通过API获取数据通过zabbix显示,因为网上资料缺乏和其他一些原因,获取API签名很困难,但使用阿里云的SDK可以完美避开获取签名的步骤. 阿里 ...
- HttpServletRequest获取URL、URI
从Request对象中可以获取各种路径信息,以下例子: 假设请求的页面是index.jsp,项目是WebDemo,则在index.jsp中获取有关request对象的各种路径信息如下 import j ...
- js如何判断一个对象为空
今天碰到一个问题如何判断一个对象为空? 总结的方法如下: 1.使用jquery自带的$.isEmptyObject()函数. var data={}; console.log($.isEmptyObj ...

