关于Windows中的TLS回调 [英] about TLS Callback in windows

查看:133
本文介绍了关于Windows中的TLS回调的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这是测试代码

#include "windows.h"
#include "iostream"
using namespace std;

__declspec(thread) int tls_int = 0;

void NTAPI tls_callback(PVOID, DWORD dwReason, PVOID)   
{
    tls_int = 1;
}

#pragma data_seg(".CRT$XLB")
PIMAGE_TLS_CALLBACK p_thread_callback = tls_callback;
#pragma data_seg()

int main()
{
    cout<<"main thread tls value = "<<tls_int<<endl;

    return 0;
}

使用多线程调试DLL(/MDd)构建 运行结果:主线程的tls值= 1

build with Multi-threaded Debug DLL (/MDd) run result : main thread tls value = 1

使用多线程调试(/MTd)构建 运行结果:主线程的tls值= 0

build with Multi-threaded Debug (/MTd) run result : main thread tls value = 0

看起来无法捕获使用MTd时创建的主线程

Looks like can not capture the main thread created when use the MTd

为什么?

推荐答案

虽然 Ofek Shilon 是正确的,因为代码缺少成分,但他的答案仅包含全部成分的一部分.可以在此处找到完整的解决方案依次取自此处

While Ofek Shilon is right that the code is missing an ingredient, his answer contains only part of the whole ingredient. Full working solution can be found here which is in turn taken from here.

有关其工作原理的解释,您可以参考此博客(假设我们正在工作VC ++编译器).

For explanation on how it works you may refer to this blog (assume we are working with VC++ compiler).

为方便起见,下面的代码发布了(请注意,同时支持x86和x64平台):

For convenience the code is posted below (note both x86 & x64 platforms are supported):

#include <windows.h>

// Explained in p. 2 below
void NTAPI tls_callback(PVOID DllHandle, DWORD dwReason, PVOID)
{
    if (dwReason == DLL_THREAD_ATTACH)
    {
        MessageBox(0, L"DLL_THREAD_ATTACH", L"DLL_THREAD_ATTACH", 0);
    }

    if (dwReason == DLL_PROCESS_ATTACH)
    {
        MessageBox(0, L"DLL_PROCESS_ATTACH", L"DLL_PROCESS_ATTACH", 0);
    }
}

#ifdef _WIN64
     #pragma comment (linker, "/INCLUDE:_tls_used")  // See p. 1 below
     #pragma comment (linker, "/INCLUDE:tls_callback_func")  // See p. 3 below
#else
     #pragma comment (linker, "/INCLUDE:__tls_used")  // See p. 1 below
     #pragma comment (linker, "/INCLUDE:_tls_callback_func")  // See p. 3 below
#endif

// Explained in p. 3 below
#ifdef _WIN64
    #pragma const_seg(".CRT$XLF")
    EXTERN_C const
#else
    #pragma data_seg(".CRT$XLF")
    EXTERN_C
#endif
PIMAGE_TLS_CALLBACK tls_callback_func = tls_callback;
#ifdef _WIN64
    #pragma const_seg()
#else
    #pragma data_seg()
#endif //_WIN64

DWORD WINAPI ThreadProc(CONST LPVOID lpParam) 
{
    ExitThread(0);
}

int main(void)
{
    MessageBox(0, L"hello from main", L"main", 0);
    CreateThread(NULL, 0, &ThreadProc, 0, 0, NULL);
    return 0;
}

绝对需要一些解释,因此让我们看一下代码中正在发生的事情.

Definitely some explanations are required so let us take a look on what's going on in the code.

  1. 如果我们想使用TLS回调,那么我们将明确地告诉编译器.这是通过包含变量_tls_used来完成的,该变量具有指向回调数组的指针(以空值结尾).对于此变量的类型,您可以参考Visual Studio附带的CRT源代码中的tlssup.c:

  1. If we want to use TLS callbacks then we are to tell the compiler about it explicitly. It is done with the including of the variable _tls_used which has a pointer to the callback array (null-terminated). For the type of this variable you may consult tlssup.c in CRT source code coming along with Visual Studio:

  • 默认情况下,对于VS 12.0,它位于以下位置:c:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\crt\src\
  • 默认情况下,对于VS 14.0,它位于以下位置:c:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\crt\src\vcruntime\
  • For VS 12.0 by default it lies here: c:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\crt\src\
  • For VS 14.0 by default it lies here: c:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\crt\src\vcruntime\

它是通过以下方式定义的:

It is defined in the following way:

#ifdef _WIN64

_CRTALLOC(".rdata$T") const IMAGE_TLS_DIRECTORY64 _tls_used =
{
        (ULONGLONG) &_tls_start,        // start of tls data
        (ULONGLONG) &_tls_end,          // end of tls data
        (ULONGLONG) &_tls_index,        // address of tls_index
        (ULONGLONG) (&__xl_a+1),        // pointer to call back array
        (ULONG) 0,                      // size of tls zero fill
        (ULONG) 0                       // characteristics
};

#else  /* _WIN64 */

_CRTALLOC(".rdata$T")
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) (&__xl_a+1), // pointer to call back array
        (ULONG) 0,                      // size of tls zero fill
        (ULONG) 0                       // characteristics
};

此代码初始化TLS目录条目所指向的IMAGE_TLS_DIRECTORY(64)结构的值.指向回调数组的指针是其字段之一.操作系统加载程序会遍历此数组,并调用指向的函数,直到遇到空指针为止.

This code initializes values for IMAGE_TLS_DIRECTORY(64) structure which is pointed to by the TLS directory entry. And pointer to call back array is one of its fields. This array is traversed by the OS loader and pointed functions are being called until null pointer is met.

有关PE文件中目录的信息,请查阅来自MSDN的此链接并搜索IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]的描述.

For info about directories in PE file consult this link from MSDN and search for a description of IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES].

x86注意:如您所见,x86& x64平台,但在为x86构建包含此名称时会添加其他_.这不是错字,而是链接程序功能,因此有效地命名了__tls_used.

x86 note: as you can see, the same name _tls_used is met in tlssup.c for both x86 & x64 platforms, but additional _ is added when including this name for x86 build. It's not a typo but linker feature, so effectively naming __tls_used is taken.

  1. 现在我们可以创建回调了.可以从winnt.h中的IMAGE_TLS_DIRECTORY(64)定义中获得其类型,其中有一个字段
  1. Now we are at the point of creating our callback. Its type can be obtained from the definition of IMAGE_TLS_DIRECTORY(64) which can be found in winnt.h, there is a field

对于x64:

ULONGLONG AddressOfCallBacks;  // PIMAGE_TLS_CALLBACK *;

,对于x86:

DWORD   AddressOfCallBacks;  // PIMAGE_TLS_CALLBACK *

回调的类型定义如下(同样来自winnt.h):

The type of callbacks is defined as follows (also from winnt.h):

typedef VOID
(NTAPI *PIMAGE_TLS_CALLBACK) (PVOID DllHandle, DWORD Reason, PVOID Reserved);

它与DllMain相同,并且可以处理相同的事件集:进程\线程附加\分离.

It is the same as for DllMain and it can handle the same set of events: process\thread attach\detach.

  1. 是时候注册回调了. 首先看一下tlssup.c中的代码:
  1. It is time to register callback. First of all take a look at the code from tlssup.c:

其中分配的部分:

_CRTALLOC(".CRT$XLA") PIMAGE_TLS_CALLBACK __xl_a = 0;

/* NULL terminator for TLS callback array.  This symbol, __xl_z, is never
 * actually referenced anywhere, but it must remain.  The OS loader code
 * walks the TLS callback array until it finds a NULL pointer, so this makes
 * sure the array is properly terminated.
 */

_CRTALLOC(".CRT$XLZ") PIMAGE_TLS_CALLBACK __xl_z = 0;

在命名PE部分时,了解$中的特殊内容非常重要,因此,来自名为对隐式TLS的编译器和链接器支持" :

It is very important to know what is special in $ when naming PE section, so the quote from the article named "Compiler and linker support for implicit TLS":

PE映像中的非标题数据被放置在一个或多个部分中, 是具有共同属性集的内存区域(例如 页面保护). __declspec(allocate(section-name))关键字 (特定于CL)告诉编译器要使用特定变量 放在最终可执行文件的特定部分中.编译器 此外,还支持连接名称相似的节 分成一个更大的部分.可以通过在前面加上一个前缀来激活此支持. 带有$字符的节名称,后跟任何其他文本.这 编译器将结果部分与 相同的名称,在$字符(包括)处被截断.

Non-header data in a PE image is placed into one or more sections, which are regions of memory with a common set of attributes (such as page protection). The __declspec(allocate("section-name")) keyword (CL-specific) tells the compiler that a particular variable is to be placed in a specific section in the final executable. The compiler additionally has support for concatenating similarly-named sections into one larger section. This support is activated by prefixing a section name with a $ character followed by any other text. The compiler concatenates the resulting section with the section of the same name, truncated at the $ character (inclusive).

编译器在以下情况下按字母顺序对各个部分进行排序 串联它们(由于在本节中使用了$字符 姓名).这意味着在内存中(在最终的可执行映像中), .CRT$XLB部分中的变量将位于 .CRT$XLA部分,但在.CRT$XLZ部分中的变量之前. C 运行时使用编译器的这个怪癖来创建null数组 终止到TLS回调的函数指针(已存储指针) 在.CRT$XLZ部分中为空终止符).因此,为了 确保声明的函数指针驻留在 限制_tls_used引用的TLS回调数组,它 必须放在.CRT$XLx形式的部分中.

The compiler alphabetically orders individual sections when concatenating them (due to the usage of the $ character in the section name). This means that in-memory (in the final executable image), a variable in the ".CRT$XLB" section will be after a variable in the ".CRT$XLA" section but before a variable in ".CRT$XLZ" section. The C runtime uses this quirk of the compiler to create an array of null terminated function pointers to TLS callbacks (with the pointer stored in the ".CRT$XLZ" section being the null terminator). Thus, in order to ensure that the declared function pointer resides within the confines of the TLS callback array being referenced by _tls_used, it is necessary place in a section of the form ".CRT$XLx".

实际上可能有2个以上的回调(我们实际上仅使用一个),并且我们可能想按顺序调用它们,现在我们知道了.只需将这些回调放在按字母顺序命名的部分中即可.

There may be actually 2+ callbacks (we will actually use only one) and we may want to call them in order, now we know how. Just place these callbacks in sections named in alphabetical order.

EXTERN_C被添加到禁止C ++样式的名称修饰和使用C样式的名称的方式.

EXTERN_C is added to forbid C++-style name mangling and use a C-style one.

constconst_seg用于x64版本的代码,因为否则它将无法正常工作,我不知道确切的原因,猜测可能是x86& CRT部分的访问权限不同; x64平台.

const and const_seg are used for x64 version of the code because otherwise it fails to work, I don't know exact reason for this, the guess may be that CRT sections' access rights are different for x86 & x64 platforms.

最后,我们将包含回调函数的名称,以使链接程序知道要添加到TLS回调数组中.有关用于x64构建的其他_的注释,请参见上面第1页的结尾.

And finally we are to include name of the callback function for linker to know it is to be added to the TLS call back array. For note about additional _ for x64 build see the end of p.1 above.

这篇关于关于Windows中的TLS回调的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆