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

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

 

做者:IBinary
出处:http://www.cnblogs.com/iBinary/
版权全部,欢迎保留原文连接进行转载:)数组

一丶复习线程相关知识

首先讲解TLS的时候,须要复习线程相关知识,  (thread local storage )数据结构

1.了解经典同步问题函数

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

看结果得知,结果并非正确的,形成同步的问题的缘由是两个线程都对同一个变量进行访问.操作系统

解决问题:线程

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

这里使用自加锁解决(固然能够用别的)设计

InterlockedIncrement  API 指针

 原型:code

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

使用以后结果是正确的htm

二丶何为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/版权全部,欢迎保留原文连接进行转载:)

相关文章
相关标签/搜索