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 回调 的相关文章

  • 运行应用程序时.NET 3.5 JIT 不工作

    以下代码在 Visual Studio 内部运行该版本和在 Visual Studio 外部运行该版本时提供不同的输出 我正在使用 Visual Studio 2008 并面向 NET 3 5 我也尝试过 NET 3 5 SP1 在 Vis
  • C# 中直接从 URL 获取图像尺寸

    我正在尝试使用以下代码直接从网络上获取图片的尺寸 string image http www hephaestusproject com csharp3 png byte imageData new WebClient DownloadDa
  • 使用 GCHandle 将大型结构数组从 C# unity 脚本传递到 C++ dll 在 C++ 函数执行后崩溃

    我想从 C unity 脚本将结构数组传递给 c 本机插件 我做了如下操作 我可以访问数据 但我的应用程序在执行 c 函数后崩溃 我不知道为什么 C side StructLayout LayoutKind Sequential publi
  • 当 foreach 块的内容具有 Conditional 属性时,C# 编译器是否会对其进行优化?

    我正在工作中编写一些调试代码 我想知道我所做的是否会损害性能 让我们看一下代码 foreach var item in aCollection Debug WriteLine item Name 我知道 Debug 类使用 Conditio
  • 静态 OpenCV 库中未定义的引用

    我有一个使用 OpenCV 3 1 的 C 项目 并且使用共享库可以正常工作 但现在我想使用静态库 位于项目目录中的文件夹中 来编译它 因为我希望能够在未安装 OpenCV 的情况下导出它 如果需要还可以编辑和重新编译 这次我重新编译了 O
  • 尽管浮点数相同,但它们并不相等? [复制]

    这个问题在这里已经有答案了 下面的程序输出This No is not same 当两个数字相同时为什么会这样做 void main float f 2 7 if f 2 7 printf This No is same else prin
  • c 使用 lseek 以相反顺序复制文件

    我已经知道如何从一开始就将一个文件复制到另一个文件 但是我如何修改程序以按相反的顺序复制它 源文件应具有读取访问权限 目标文件应具有读写执行权限 我必须使用文件控制库 例如 FILE A File B should be ABCDEF FE
  • 打开位置设置页面或提示用户启用位置

    我一直在绞尽脑汁 徒劳地谷歌搜索 我正在尝试找到一种方法来提示用户通过直接进入设置页面或仅点击屏幕上的 是 来切换位置 我见过的所有代码似乎都不起作用 有人有有效的方法吗 一个详细的例子将不胜感激 谢谢 我对 Xamarin 开发非常陌生
  • PartialView Action 正在调用自身

    我有 MVC 应用程序 它用于从主视图 ProductMaster 将 ProductAreaGrid 列表显示为 PartialView 并且它将在局部视图内将 CreateProductArea 作为 PartialView 我的 Gr
  • 使用 catch all 字典属性将 json 序列化为对象

    我想使用 JSON net 反序列化为对象 但将未映射的属性放入字典属性中 是否可以 例如给定 json one 1 two 2 three 3 和 C 类 public class Mapped public int One get se
  • C# 反序列化过程中创建指向父对象的指针

    我有这样的课程 Serializable public class child public Parent parent Serializable public class Parent public List
  • List 或其他类型上的 string.Join

    我想将整数数组或列表转换为逗号分隔的字符串 如下所示 string myFunction List
  • 使用联合对 IP 地址进行多种解释?

    在工作中 我们使用以下构造来将 IP 地址解释为 4 字节数组或 32 位整数 union IPv4 std uint32 t ip std uint8 t data 4 这很好用 但是读完这本书的第 97 章 不要使用联合来重新解释表示
  • 模板定义中的友元函数

    我的问题有点相关this https stackoverflow com questions 1297609 overloading friend operator for template class one 我想重载某些类的运算符 te
  • 如何在 SQLite 中检查数据库是否存在 C#

    我目前正在用 C 编写一个应用程序 并使用 sqlite 作为嵌入式数据库 我的应用程序在启动时创建一个新数据库 但如何让它检查数据库是否存在 如果它确实存在 我如何让它使用它 如果不存在如何创建一个新数据库 这是我到目前为止所拥有的 pr
  • 按 Enter 继续

    这不起作用 string temp cout lt lt Press Enter to Continue cin gt gt temp cout lt lt Press Enter to Continue cin ignore 或更好 in
  • C# 和断点 - 这里有魔术师吗?

    我有这个 public static void ByLinkText string text for var i 0 i lt 50 i try Setup Driver FindElement By LinkText text Click
  • 在两个点之间创建一条曲线,每个点都具有标准化向量

    因此 我需要一种写入方法来在两点之间创建一条曲线 每个点都有一个指向任意方向的归一化向量 我一直在尝试设计这样一种方法 但一直无法理解数学 在这里 由于一张图片胜过一千个文字 这就是我所需要的 在图中 矢量垂直于红线 我相信向量需要进行相同
  • boost::spirit::qi::语法和可变参数模板

    我在使用可变参数模板定义语法时面临一个问题 我首先定义一些包含在某些结构中的简单语法 例如纬度 经度 如下所示 include
  • execlp() 系统调用输出错误

    这个非常简单的例子exec 系统调用 在这里 我试图打电话execlp 两次 但是 我没有得到例外的输出 它仅显示当前目录的第一次调用的输出 include

随机推荐

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

    我确信我错过了一些明显的东西 我有一个我打算在其中绘制文本的区域 我知道它 区域 的高度和宽度 我想知道宽度可以容纳多少个字符 单词 最好是字符 第二个问题 如果该行太长 我想绘制第二条线 所以我想我还需要获取文本的高度 包括它认为正确的垂
  • 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