Windows 中的 TLS 回调

2023-11-28

这是测试代码:

#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) 构建 运行结果:main thread tls value = 1

使用多线程调试 (/MTd) 进行构建 运行结果:main thread tls value = 0

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

为什么会这样呢?


While 奥菲克·希隆代码中缺少一种成分是正确的,他的答案只包含整个成分的一部分。可以找到完整的工作解决方案here这又取自here.

有关其工作原理的解释,您可以参考这个博客(假设我们正在使用 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;
}

EDIT:

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

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

    • 对于 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
};

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

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

x86注意:如你所见,同名_tls_used满足于tlssup.c适用于 x86 和 x64 平台,但额外_在 x86 构建中包含此名称时添加。这不是拼写错误,而是链接器功能,因此有效命名__tls_used被采取。

  1. 现在我们正在创建回调。其类型可以从定义中获得IMAGE_TLS_DIRECTORY(64)可以在以下位置找到:winnt.h,有一个字段

for 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并且它可以处理同一组事件:进程\线程附加\分离。

  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 部分时,因此引用了名为“编译器和链接器支持隐式 TLS”:

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

编译器按字母顺序对各个部分进行排序 连接它们(由于在该部分中使用了 $ 字符 姓名)。这意味着在内存中(在最终的可执行映像中), 变量在“.CRT$XLB”部分将位于变量之后“.CRT$XLA”节但在变量之前“.CRT$XLZ”部分。 C 运行时使用编译器的这个怪癖来创建一个 null 数组 终止的函数指针指向 TLS 回调(指针存储在 在里面“.CRT$XLZ”部分是空终止符)。因此,为了 确保声明的函数指针位于 所引用的 TLS 回调数组的范围_tls_used, 它 是表格某个部分的必要位置“.CRT$XLx“.

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

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

const and const_seg用于 x64 版本的代码,否则它将无法工作,我不知道确切的原因,猜测可能是 x86 和 x64 平台的 CRT 部分的访问权限不同。

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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Windows 中的 TLS 回调 的相关文章

  • 向进度条添加百分比文本 C#

    我有一个方法可以显示进程栏何时正在执行以及何时成功完成 我工作得很好 但我想添加一个百分比 如果完成 则显示 100 如果卡在某个地方 则显示更少 我在网上做了一些研究 但我无法适应我正在寻找的解决方案 这是我的代码 private voi
  • 未提供参数时如何指定 C# System.Commandline 行为?

    在我的控制台应用程序中 当未提供控制台参数时 将执行我指定列表 在本例中为参数 3 的任何处理程序 调用该处理程序时 布尔参数设置为 false 但对我来说 根本不调用它更有意义 如何防止这种情况发生并显示帮助文本 using System
  • 提交后禁用按钮

    当用户提交付款表单并且发布表单的代码导致 Firefox 中出现重复发布时 我试图禁用按钮 去掉代码就不会出现这个问题 在firefox以外的任何浏览器中也不会出现这个问题 知道如何防止双重帖子吗 System Text StringBui
  • ClickOnce 应用程序错误:部署和应用程序没有匹配的安全区域

    我在 IE 中使用 FireFox 和 Chrome 的 ClickOnce 应用程序时遇到问题 它工作正常 异常的详细信息是 PLATFORM VERSION INFO Windows 6 1 7600 0 Win32NT Common
  • C中的malloc内存分配方案

    我在 C 中尝试使用 malloc 发现 malloc 在分配了一些内存后浪费了一些空间 下面是我用来测试 malloc 的一段代码 include
  • 错误:表达式不产生值

    我尝试将以下 C 代码转换为 VB NET 但在编译代码时出现 表达式不产生值 错误 C Code return Fluently Configure Mappings m gt m FluentMappings AddFromAssemb
  • 复制目录内容

    我想将目录 tmp1 的内容复制到另一个目录 tmp2 tmp1 可能包含文件和其他目录 我想使用C C 复制tmp1的内容 包括模式 如果 tmp1 包含目录树 我想递归复制它们 最简单的解决方案是什么 我找到了一个解决方案来打开目录并读
  • 使用 Newtonsoft 和 C# 反序列化嵌套 JSON

    我正在尝试解析来自 Rest API 的 Json 响应 我可以获得很好的响应并创建了一些类模型 我正在使用 Newtonsoft 的 Json Net 我的响应中不断收到空值 并且不确定我的模型设置是否正确或缺少某些内容 例如 我想要获取
  • 如何创建包含 IPv4 地址的文本框? [复制]

    这个问题在这里已经有答案了 如何制作一个这样的文本框 我想所有的用户都见过这个并且知道它的功能 您可以使用带有 Mask 的 MaskedTestBox000 000 000 000 欲了解更多信息 请参阅文档 http msdn micr
  • 使用接口有什么好处?

    使用接口有什么用 我听说它用来代替多重继承 并且还可以用它来完成数据隐藏 还有其他优点吗 哪些地方使用了接口 程序员如何识别需要该接口 有什么区别explicit interface implementation and implicit
  • 如何使用 LINQ2SQL 连接两个不同上下文的表?

    我的应用程序中有 2 个数据上下文 不同的数据库 并且需要能够通过上下文 B 中的表的右连接来查询上下文 A 中的表 我该如何在 LINQ2SQL 中执行此操作 Why 我们正在使用 SaaS 产品来跟踪我们的时间 项目等 并希望向该产品发
  • 在 Visual Studio 2010 中从 Fortran 调用 C++ 函数

    我想从 Fortran 调用 C 函数 为此 我在 Visual Studio 2010 中创建了一个 FORTRAN 项目 之后 我将一个 Cpp 项目添加到该 FORTRAN 项目中 当我要构建程序时出现以下错误 Error 1 unr
  • 我可以使用 moq Mock 来模拟类而不是接口吗?

    正在经历https github com Moq moq4 wiki Quickstart https github com Moq moq4 wiki Quickstart 我看到它 Mock 一个接口 我的遗留代码中有一个没有接口的类
  • 如何在 Xaml 文本中添加电子邮件链接?

    我在 Windows Phone 8 应用程序中有一些大文本 我希望其中有电子邮件链接 例如 mailto 功能 这是代码的一部分
  • Azure 辅助角色“请求输入之一超出范围”的内部异常。

    我在辅助角色中调用 CloudTableClient CreateTableIfNotExist 方法 但收到一个异常 其中包含 请求输入之一超出范围 的内部异常 我做了一些研究 发现这是由于将表命名为非法表名引起的 但是 我尝试为我的表命
  • 为什么 gcc 抱怨“错误:模板参数 '0' 的类型 'intT' 取决于模板参数”?

    我的编译器是gcc 4 9 0 以下代码无法编译 template
  • System.IO.FileNotFoundException:找不到网络路径。在 Windows 7 上使用 DirectoryEntry 对象时出现异常

    我正在尝试使用 DirectoryEntry 对象连接到远程 Windows 7 计算机 这是我的代码 DirectoryEntry obDirEntry new DirectoryEntry WinNT hostName hostName
  • C++ 条件编译

    我有以下代码片段 ifdef DO LOG define log p record p else define log p endif void record char data 现在如果我打电话log hello world 在我的代码中
  • 我的班级应该订阅自己的公共活动吗?

    我正在使用 C 3 0 遵循标准事件模式我有 public event EventHandler
  • 如何从 ODBC 连接获取可用表的列表?

    在 Excel 中 我可以转到 数据 gt 导入外部数据 gt 导入数据 然后选择要使用的数据源 然后在提供登录信息后 它会给我一个表格列表 我想知道如何使用 C 以编程方式获取该列表 您正在查询什么类型的数据源 SQL 服务器 使用权 看

随机推荐

  • 适合特定宽度的字符串长度

    我确信我错过了一些明显的东西 我有一个我打算在其中绘制文本的区域 我知道它 区域 的高度和宽度 我想知道宽度可以容纳多少个字符 单词 最好是字符 第二个问题 如果该行太长 我想绘制第二条线 所以我想我还需要获取文本的高度 包括它认为正确的垂
  • AutoIt 类似于 Java 的 GUI 自动化工具 [关闭]

    Closed 此问题正在寻求书籍 工具 软件库等的推荐 不满足堆栈溢出指南 目前不接受答案 我需要对我的模块插入的软件进行自动化 UI 测试 我无权访问主机的代码 所以我需要像 AutoIt 这样的东西 由于 AutoIt 不能与 Swin
  • ArraySegment 类有什么用?

    我刚刚遇到ArraySegment
  • 标记未出现在传单中的连续世界上

    当我设置选项时continuousWorld true标记不会显示在克隆图块上 仅显示在主世界上 这是设计好的行为吗 可能是其他选项 我没有注意到 来显示这些标记的存在 UPD My aim to repeat markers on eve
  • 如何使用python进行坐标仿射变换?第2部分

    我有与这里描述的相同的问题 如何使用python进行坐标仿射变换 我试图使用所描述的方法 但由于某些原因我会收到错误消息 我对代码所做的更改是替换主系统和辅助系统点 我通过使用不同的原点创建了辅助坐标点 在我正在研究这个主题的实际情况中 测
  • 在 gevent 中,如何转储所有正在运行的 greenlet 的堆栈跟踪?

    出于调试目的 我想迭代所有 greenlet 并获取它们的跟踪记录 如何使用 gevent 做到这一点 基本上 我想做的 gevent 相当于this 您可以使用gc模块迭代堆上的所有对象并搜索 greenlet Greenlets 将堆栈
  • 删除 Google Apps 脚本文档服务中的内容

    如何刷新 Google Apps 脚本文档服务中的文档 我是否需要循环遍历所有类型的元素 例如段落 图像 表格并在小时候删除它们 有没有更简单的方法来删除文档正文中的所有内容 谢谢你 根据文档 the Document setText应该可
  • 如何标准化图像颜色?

    在他们的论文中描述维奥拉 琼斯物体检测框架 Viola 和 Jones 提出的鲁棒实时人脸检测 据说 用于训练的所有示例子窗口均已标准化为方差 最大限度地减少不同照明条件的影响 我的问题是 他们使用什么样的工具来标准化图像 我不是在寻找 V
  • 无法将 Jinja2 模板包含到 Pyinstaller 分发中

    我有一个使用 Jinja2 模板的 python 脚本 我正在尝试使用 Pyinstaller 创建一个单文件夹发行版 在 Jinja 中 我让程序通过使用PackageLoader班级 下面的代码显示它指向我的templates下的文件夹
  • 使用 tSQLt 对 SSIS 包进行单元测试

    我真的很喜欢 tsqlt 来测试过程和函数 但真的希望能够执行 SSIS 包并利用 FakeTable 和 AssertEquals 来确定 SSIS 包是否做了它应该做的事情 有没有人探索过这条路径 是否可以通过 tsqlt 包装您的测试
  • 为列名添加前缀

    当阅读以下内容时helpfile应该可以在列名中添加前缀 colnames x do NULL TRUE prefix col 以下内容对我不起作用 我在这里做错了什么 m2 lt cbind 1 1 4 colnames m2 do NU
  • 使用 chrome.tabs 与 browser.tabs 实现浏览器兼容性

    我正在将 Chrome 扩展程序移植到 Firefox 根据 MDN 有一个浏览器选项卡chrome应该支持的API However browser不是 Chrome 稳定对象 同时chrome tabs在 Firefox 中工作得很好 更
  • rdtsc,循环次数过多

    include
  • CSS 显示属性上的转换

    我目前正在设计一个 CSS 大型下拉 菜单 基本上是一个常规的纯 CSS 下拉菜单 但包含不同类型的内容 眼下 CSS 3 过渡似乎不适用于 display 属性 也就是说 你不能从display none to display block
  • Android:如何使用非字符串选择参数查询 SQLiteDatabase?

    有没有直接查询的方法SQLiteDatabase选择参数不是String types 特别是 如果 arg 是byte type 我能找到的最接近的是SQLiteDatabase compileStatement 它返回一个SQLiteSt
  • 使用 ARC 时的条件编译

    有没有办法询问编译器是否打开了 ARC 然后根据该值进行条件编译 例如 我有一个协议 protocol ProtocolA required void protocolMethodOne optional void protocolMeth
  • 使用 Nokogiri 和 Ruby 从 html 文档获取链接和 href 文本?

    我正在尝试使用 nokogiri gem 提取页面上的所有 url 及其链接文本 并将链接文本和 url 存储在哈希中 a href foo Foo a a href bar Bar a 我想回来 Foo gt foo Bar gt bar
  • Python - 在 Pandas DataFrame 中取消嵌套单元格

    假设我有DataFrame df a b c v f 3 4 5 v 2 6 v f 4 5 我想制作这个df a b c v f 3 v f 4 v f 5 v 2 6 v f 4 v f 5 我知道如何在 R 中进行这种转换 使用tid
  • 如何在 Flutter 中传递 HTTP post 请求中的标头?

    当我调试应用程序时 收到 415 错误 不支持的媒体类型 我知道我缺少在帖子查询中传递标题 我已经使用地图来传递数据 请帮助我如何传递标题 或者请为我提供一个使用 JSON 在 Flutter 中注册 注册的示例 import dart a
  • Windows 中的 TLS 回调

    这是测试代码 include windows h include iostream using namespace std declspec thread int tls int 0 void NTAPI tls callback PVOI