1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
use crate::mem::ManuallyDrop;
use crate::ptr;
use crate::sync::atomic::AtomicPtr;
use crate::sync::atomic::Ordering::SeqCst;
use crate::sys::c;

pub type Key = c::DWORD;
pub type Dtor = unsafe extern "C" fn(*mut u8);

// 事实证明,与几乎所有内容一样,Windows 与 Unix 提供的功能非常接近,但略有不同! 对于 TLS,Windows 不提供 API 为 TLS 变量提供析构函数。
//
// 这最终对于实现非常关键,因此我们需要一种解决方法。
//
// 这里的解决方案最终有点晦涩难懂,但是请不要担心,互联网已经通知我 [1][2] 这个解决方案不是唯一的 (我也无法想到它! )。
// 关键思想是在某个位置插入一些 hook 以便在线程终止时运行任意代码。
// 有了这个,我们将能够运行任何我们喜欢的东西,包括所有 TLS 析构函数!
//
// 为了实现这一壮举,我们执行了许多线程,所有线程都包含在此模块中:
//
// * 所有 TLS 析构函数都由 *us* 而不是 windows 运行时跟踪。这意味着我们对每个已知的 TLS 密钥都有一个析构函数的列表。
// * 当线程退出时,我们遍历整个列表并为所有非空键运行 dtor。在这方面,这试图匹配 Unix 语义。
//
// 最终,这将产生使用列表的开销,到处都有一些锁,并且通常只会增加一些代码膨胀。
// 我们试图通过忘记没有析构函数的键来优化运行时,但这只能使我们走得更远。
//
// 有关更多细节和细节,请参见下面的代码部分!
//
// [1]: https://www.codeproject.com/Articles/8113/Thread-Local-Storage-The-C-Way
// [2]: https://github.com/ChromiumWebApps/chromium/blob/master/base
//                        /threading/thread_local_storage_win.cc#L42
//
//
//
//
//
//
//
//
//
//

// -------------------------------------------------------------------------
// 原生绑定
//
// 这部分只是 Windows 提供的原生函数的原始绑定。还有一些额外的调用来处理析构函数。
//

#[inline]
pub unsafe fn create(dtor: Option<Dtor>) -> Key {
    let key = c::TlsAlloc();
    assert!(key != c::TLS_OUT_OF_INDEXES);
    if let Some(f) = dtor {
        register_dtor(key, f);
    }
    key
}

#[inline]
pub unsafe fn set(key: Key, value: *mut u8) {
    let r = c::TlsSetValue(key, value as c::LPVOID);
    debug_assert!(r != 0);
}

#[inline]
pub unsafe fn get(key: Key) -> *mut u8 {
    c::TlsGetValue(key) as *mut u8
}

#[inline]
pub unsafe fn destroy(_key: Key) {
    rtabort!("can't destroy tls keys on windows")
}

#[inline]
pub fn requires_synchronized_create() -> bool {
    true
}

// -------------------------------------------------------------------------
// Dtor 注册
//
// Windows 没有对运行析构函数的原生支持,因此我们管理自己的析构函数列表以跟踪如何销毁密钥。
// 然后,我们稍后安装一个回调,以便在线程退出时被调用,并运行所有适当的析构函数。
//
// 当前不支持从该列表中注销。析构函数可以注册,但不能取消注册。这样做有多种简化原因,其中最大的原因是:
//
// 1. 当前,我们甚至不支持释放 TLS 密钥,因此正常操作不需要释放析构函数。
// 2. 我们没有时间知道可以注销析构函数,因为它总是可以被某个远程线程运行。
//
// 通常,进程具有一组静态已知的 TLS 密钥,该密钥非常小,并且无论如何,我们还是希望为整个进程保持活动状态的内存。
//
// 也许有一天我们可以将 `Box` 折叠成静态分配,扩展 `StaticKey` 结构体,使其不仅包含用于 TLS 密钥的插槽,而且还包含用于 windows 上的析构函数队列的插槽。
// 改天再优化!
//
//
//
//
//
//
//
//
//
//

static DTORS: AtomicPtr<Node> = AtomicPtr::new(ptr::null_mut());

struct Node {
    dtor: Dtor,
    key: Key,
    next: *mut Node,
}

unsafe fn register_dtor(key: Key, dtor: Dtor) {
    let mut node = ManuallyDrop::new(Box::new(Node { key, dtor, next: ptr::null_mut() }));

    let mut head = DTORS.load(SeqCst);
    loop {
        node.next = head;
        match DTORS.compare_exchange(head, &mut **node, SeqCst, SeqCst) {
            Ok(_) => return, // 没什么可丢弃的,我们已成功将节点添加到列表中
            Err(cur) => head = cur,
        }
    }
}

// -------------------------------------------------------------------------
// Magic (TM) 发生的地方
//
// 如果您正在查看此代码,并且想知道 "what is this doing?",您并不孤单! 我将尝试逐步解决此问题:
//
// # CRT $ XLB 怎么了?
//
// 对于 TLS 析构函数在 Windows 上运行的任何要求,我们必须能够在线程退出时运行 *something*。为此,我们将一个非常特殊的静态变量放置在一个非常特殊的位置。
// 如果以正确的方式对此进行编码,则内核的加载器显然足够好,以便在线程退出时运行我们的某些函数! 内核真好!
//
// 可以在上面的源 [1] 中找到很多详细信息,但是要点是,这是利用了 Microsoft 的 PE 格式 (可执行格式) 的功能,该功能目前尚未被任何编译器实际使用。
// 显然,这将转换为 ".CRT$XLB" 部分中在某些事件上运行的所有回调。
//
// 因此,毕竟,我们使用编译器的 #[link_section] 功能将回调指针放置到 magic 节中,以便最终被调用。
//
// # 这个回调是怎么回事?
//
// 指定的回调从... 接收许多参数。someone!
// (内核? 运行时? 我不太确定! ) 有一些事件被调用,但是我们目前仅对线程或进程 "detaches" (exits) 感兴趣。
// 进程部分发生在最后一个线程中,而线程部分发生在任何普通线程中。
//
// # 好的,运行所有这些析构函数是怎么回事?
//
// 随着时间的推移,这可能需要改进,但是此函数尝试使用 "poor man's" 析构函数回调系统。
// 有了要运行的内容的列表后,我们将遍历所有键,检查它们的值,然后运行析构函数 (如果值原来不是非 null 的话) (事先将它们设置为 null)。
// 我们循环执行几次以基本匹配 Unix 语义。
// 如果不久之后我们没有达到一个固定的点,那么我们就不可避免地泄漏了最有可能发生的事情。
//
// # 文章提到了关于 "/INCLUDE" 的怪异东西?
//
// 当然可以! 具体来说,我们在谈论这个引用:
//
//      Microsoft 运行时库通过定义 TLS 目录的内存映像并为其赋予特殊名称 `__tls_used` (英特尔 x86 平台) 或 `_tls_used` (其他平台) 来简化此过程。
//      链接器查找此内存映像,然后使用那里的数据创建 TLS 目录。
//      其他支持 TLS 并与 Microsoft 链接器一起使用的编译器必须使用相同的技术。
//
// 基本上,这意味着如果我们要支持我们的 TLS destructors/our hook,那么我们需要确保链接器不会省略该符号。否则它将忽略它,并且我们的回调函数也不会连接。
//
// 我们实际上并没有像本文提到的那样在这里使用 `/INCLUDE` 链接器标志,因为 Rust 编译器不会传播链接器标志,而是使用 shim 函数,该函数从符号地址执行易失性 1 字节加载以确保它坚持。
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//

#[link_section = ".CRT$XLB"]
#[allow(dead_code, unused_variables)]
#[used] // 我们不希望 LLVM 由于任何原因消除此符号,并且
// 当符号到达链接器时,链接器将接管
pub static p_thread_callback: unsafe extern "system" fn(c::LPVOID, c::DWORD, c::LPVOID) =
    on_tls_callback;

#[allow(dead_code, unused_variables)]
unsafe extern "system" fn on_tls_callback(h: c::LPVOID, dwReason: c::DWORD, pv: c::LPVOID) {
    if dwReason == c::DLL_THREAD_DETACH || dwReason == c::DLL_PROCESS_DETACH {
        run_dtors();
    }

    // 请参见上面的注释,以了解此操作。
    // 请注意,仅在 MSVC 上,我们就不需要在 GNU windows 上使用这种技巧。
    reference_tls_used();
    #[cfg(target_env = "msvc")]
    unsafe fn reference_tls_used() {
        extern "C" {
            static _tls_used: u8;
        }
        crate::intrinsics::volatile_load(&_tls_used);
    }
    #[cfg(not(target_env = "msvc"))]
    unsafe fn reference_tls_used() {}
}

#[allow(dead_code)] // 实际在上面
unsafe fn run_dtors() {
    let mut any_run = true;
    for _ in 0..5 {
        if !any_run {
            break;
        }
        any_run = false;
        let mut cur = DTORS.load(SeqCst);
        while !cur.is_null() {
            let ptr = c::TlsGetValue((*cur).key);

            if !ptr.is_null() {
                c::TlsSetValue((*cur).key, ptr::null_mut());
                ((*cur).dtor)(ptr as *mut _);
                any_run = true;
            }

            cur = (*cur).next;
        }
    }
}