gpt4 book ai didi

c++ - 关于 Windows 中的 TLS 回调

转载 作者:塔克拉玛干 更新时间:2023-11-02 23:24:44 26 4
gpt4 key购买 nike

这是测试代码

#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 value = 1

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

貌似无法捕获使用MTd时创建的主线程

为什么?

最佳答案

虽然 Ofek Shilon 是正确的,代码缺少一个成分,但他的回答只包含整个成分的一部分。可以找到完整的工作解决方案 here这又取自 here .

有关其工作原理的说明,您可以引用 this blog (假设我们正在使用 VC++ 编译器)。

为方便起见,代码贴在下面(注意同时支持 x86 和 x64 平台):

#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;
}

编辑:

肯定需要一些解释,所以让我们看看代码中发生了什么。

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

    • 对于 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\

定义如下:

#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) 结构的值。指向回调数组的指针是它的字段之一。该数组由 OS 加载程序遍历,并调用指向的函数,直到遇到空指针。

有关 PE 文件中目录的信息,请参阅 this link from MSDN并搜索 IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES] 的描述。

x86 注意事项:如您所见,对于 x86 和 x64 平台,tlssup.c 中会遇到相同的名称 _tls_used,但额外的 _ 在为 x86 构建包含此名称时添加。这不是拼写错误,而是链接器功能,因此有效地命名了 __tls_used

  1. 现在我们要创建回调了。它的类型可以从IMAGE_TLS_DIRECTORY(64)的定义中得到,在winnt.h中有一个字段

对于 x64:

ULONGLONG AddressOfCallBacks;  // PIMAGE_TLS_CALLBACK *;

对于 x86:

DWORD   AddressOfCallBacks;  // PIMAGE_TLS_CALLBACK *

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

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

它与 DllMain 相同,并且可以处理同一组事件:process\thread attach\detach。

  1. 是时候注册回调了。首先看一下 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 部分时了解 $ 中的特殊之处非常重要,因此引用了名为 "Compiler and linker support for implicit TLS" 的文章:

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).

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 个以上的回调(我们实际上只使用一个),我们可能想按顺序调用它们,现在我们知道怎么做了。只需将这些回调放在按字母顺序命名的部分中。

EXTERN_C 被添加以禁止 C++ 风格的名称改组并使用 C 风格的名称改组。

constconst_seg 用于 x64 版本的代码,否则它无法工作,我不知 Prop 体原因,猜测可能是CRT 部分的访问权限对于 x86 和 x64 平台是不同的。

最后我们要包含回调函数的名称,以便链接器知道它要添加到 TLS 回调数组中。有关 x64 构建的附加 _ 的说明,请参阅上面 p.1 的末尾。

关于c++ - 关于 Windows 中的 TLS 回调,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14538159/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com