日韩成人免费在线_国产成人一二_精品国产免费人成电影在线观..._日本一区二区三区久久久久久久久不

當(dāng)前位置:首頁 > 科技  > 軟件

聊一聊 C# 的線程本地存儲TLS到底是什么

來源: 責(zé)編: 時間:2024-01-02 17:28:07 239觀看
導(dǎo)讀一:背景1. 講故事有朋友在后臺留言讓我說一下C#的 ThreadStatic 線程本地存儲是怎么玩的?這么說吧,C#的ThreadStatic是假的,因為C#完全是由CLR(C++)承載的,言外之意C#的線程本地存儲,用的就是用C++運行時提供的 __declspec(th

一:背景

1. 講故事

有朋友在后臺留言讓我說一下C#的 ThreadStatic 線程本地存儲是怎么玩的?這么說吧,C#的ThreadStatic是假的,因為C#完全是由CLR(C++)承載的,言外之意C#的線程本地存儲,用的就是用C++運行時提供的 __declspec(thread) 或 __thread 來虛構(gòu)的一套玩法,這一篇我們就來簡單聊一聊。war28資訊網(wǎng)——每日最新資訊28at.com

二:C# 的線程本地存儲

1. 虛構(gòu)在哪里

在 C# 中使用ThreadStatic就可以將變量和線程進行綁定,參考代碼如下:war28資訊網(wǎng)——每日最新資訊28at.com

internal class Program    {        [ThreadStatic]        public static int num = 10;        static void Main(string[] args)        {            Console.WriteLine($"num={num}");            Debugger.Break();        }    }

在 CLR 中如何將 num 與 Thread 綁定呢?研究過 CLR 源碼的朋友應(yīng)該知道是用 ThreadLocalInfo 的,參考代碼如下:war28資訊網(wǎng)——每日最新資訊28at.com

#ifdef _MSC_VER__declspec(selectany) __declspec(thread) ThreadLocalInfo gCurrentThreadInfo;#elseEXTERN_C __thread ThreadLocalInfo gCurrentThreadInfo;#endifstruct ThreadLocalInfo{    Thread* m_pThread;    AppDomain* m_pAppDomain; // This field is read only by the SOS plugin to get the AppDomain    void** m_EETlsData; // ClrTlsInfo::data};

上面的 m_pThread 就是 C# Thread 在 CLR 層面的承載,怎么去驗證呢?可以把代碼跑起來,然后用 windbg 驗證一下。war28資訊網(wǎng)——每日最新資訊28at.com

0:000> dt coreclr!gCurrentThreadInfo   +0x000 m_pThread        : 0x000001e3`506c5fa0 Thread   +0x008 m_pAppDomain     : 0x000001e3`506ba9b0 AppDomain   +0x010 m_EETlsData      : 0x000001e3`506aa360  -> (null) 0:000> !tThreadCount:      3UnstartedThread:  0BackgroundThread: 2PendingThread:    0DeadThread:       0Hosted Runtime:   no                                                                                                            Lock   DBG   ID     OSID ThreadOBJ           State GC Mode     GC Alloc Context                  Domain           Count Apt Exception   0    1     2e04 000001E3506C5FA0    2a020 Preemptive  000001E3521DCE80:000001E3521DD4A8 000001e3506ba9b0 -00001 MTA    6    2     4ef8 000001E3506F1A30    21220 Preemptive  0000000000000000:0000000000000000 000001e3506ba9b0 -00001 Ukn (Finalizer)    7    3     3550 000001E3726A0AE0    2b220 Preemptive  0000000000000000:0000000000000000 000001e3506ba9b0 -00001 MTA

從卦中可以清楚的看到 m_pThread=0x000001e3506c5fa0 就是我們的主線程,最后的 num 就是放在與之關(guān)聯(lián)的 ThreadLocalModule 中,這個比較簡單,關(guān)注下匯編代碼就好了,下面的 rax 就是 ThreadLocalModule。war28資訊網(wǎng)——每日最新資訊28at.com

00007ffb`218d2c2c 48b9b07b9921fb7f0000 mov rcx,7FFB21997BB0h00007ffb`218d2c36 ba04000000      mov     edx,400007ffb`218d2c3b e8001fb55f      call    coreclr!JIT_GetSharedNonGCThreadStaticBase (00007ffb`81424b40)00007ffb`218d2c40 8b4820          mov     ecx,dword ptr [rax+20h]00007ffb`218d2c43 894dfc          mov     dword ptr [rbp-4],ecx0:000> dp rax+0x20 L100000294`d0539790  abababab`0000000a

CLR層面用了太多的高層虛構(gòu)來玩了一套線程本地存儲,其實最核心的還要理解再下一層的 __declspec(selectany) ,接下來聊聊這玩意是怎么玩的。war28資訊網(wǎng)——每日最新資訊28at.com

2. __declspec(selectany) 是怎么玩的

在Windows層面的術(shù)語中,有兩種 TLS 技術(shù)。war28資訊網(wǎng)——每日最新資訊28at.com

  • 動態(tài)TLS

借助 Windows 提供的 TlsAlloc, TlsSetValue 之類的方法來實現(xiàn),并且存放在線程 _TEB.TlsSlots 的槽位中,參考代碼如下:war28資訊網(wǎng)——每日最新資訊28at.com

0:000> dt 0x000000f4f0ca6000 ntdll!_TEB   +0x000 NtTib            : _NT_TIB   ...   +0x1480 TlsSlots         : [64] (null)    ...
  • 靜態(tài)TLS

C#的線程本地存儲用的就是靜態(tài)TLS,也就是在編譯時就已經(jīng)聲明好的,在 PE 文件里面有一個 .tls 節(jié)點,這個節(jié)點的數(shù)據(jù)會被每個線程在heap堆上copy一份,存放在 _TEB.ThreadLocalStoragePointer 來指向的指針數(shù)組中,參考代碼如下:war28資訊網(wǎng)——每日最新資訊28at.com

0:000> dt 0x000000f4f0ca6000 ntdll!_TEB   +0x000 NtTib            : _NT_TIB   +0x058 ThreadLocalStoragePointer : 0x00000294`d0536ab0 Void   ...

動態(tài)的TLS我就不介紹了,這里著重說一下靜態(tài)的TLS。war28資訊網(wǎng)——每日最新資訊28at.com

3. 靜態(tài)TLS詳解

為了方便講解,先上一段測試代碼。war28資訊網(wǎng)——每日最新資訊28at.com

#include <windows.h>#include <stdio.h>#include <limits.h>__declspec(thread) int i = INT_MAX;__declspec(thread) int j = INT_MAX;int main() { int num1 = i; int num2 = j; printf("i=%d,j=%d", num1, num2);}

上面的 i,j 值在編譯時就已經(jīng)放到了 PE 頭的 .tls 節(jié),可以用 PPEE 觀察下對象頭。war28資訊網(wǎng)——每日最新資訊28at.com

圖片war28資訊網(wǎng)——每日最新資訊28at.com

從卦中可以看到 .tls 占用了 0x400 字節(jié)大小,并且用 WinHex 真的觀察到了 i,j 的值,挺有意思。war28資訊網(wǎng)——每日最新資訊28at.com

在內(nèi)存中TLS區(qū)比這個還小一點,可以觀察一下 DIRECTORY_ENTRY_TLS 節(jié)的 StartAddressOfRawData 和 EndAddressOfRawData 字段,這也是每個線程copy的原始內(nèi)存區(qū)域,可以看到只有 0x20D ,大概少了一半,截圖如下:war28資訊網(wǎng)——每日最新資訊28at.com

圖片war28資訊網(wǎng)——每日最新資訊28at.com

有了這些前置知識,接下來觀察內(nèi)存中的地址,在運行之前先把 ASLR 關(guān)掉,匯編代碼參考如下:war28資訊網(wǎng)——每日最新資訊28at.com

//int num1 = i;   14 00411895 a1b4a14100      mov     eax,dword ptr [ConsoleApplication2!_tls_index (0041a1b4)]   14 0041189a 648b0d2c000000  mov     ecx,dword ptr fs:[2Ch]   14 004118a1 8b1481          mov     edx,dword ptr [ecx+eax*4]   14 004118a4 8b8208010000    mov     eax,dword ptr [edx+108h]   14 004118aa 8945f8          mov     dword ptr [ebp-8],eax   //int num2 = j;   15 004118ad a1b4a14100      mov     eax,dword ptr [ConsoleApplication2!_tls_index (0041a1b4)]   15 004118b2 648b0d2c000000  mov     ecx,dword ptr fs:[2Ch]   15 004118b9 8b1481          mov     edx,dword ptr [ecx+eax*4]   15 004118bc 8b8204010000    mov     eax,dword ptr [edx+104h]   15 004118c2 8945ec          mov     dword ptr [ebp-14h],eax

可以看到每一句大概會生成 5 行匯編代碼,我們簡單分析下。war28資訊網(wǎng)——每日最新資訊28at.com

  • ConsoleApplication2!_tls_index (0041a1b4)

這個值就是 PE 頭的 AddressOfIndex 值,可以再回頭觀察下,里面存的就是 tls 索引,當(dāng)前是 0 ,參考如下:war28資訊網(wǎng)——每日最新資訊28at.com

0:000> dp 0041a1b4 L10041a1b4  00000000
  • fs:[2Ch]

在用戶態(tài)層面上 fs 指向的是當(dāng)前線程的 TEB 結(jié)構(gòu),其中的 2C 偏移指的就是 ThreadLocalStoragePointer 結(jié)構(gòu),windbg 觀察如下:war28資訊網(wǎng)——每日最新資訊28at.com

0:000> dg fs                                  P Si Gr Pr LoSel    Base     Limit     Type    l ze an es ng Flags---- -------- -------- ---------- - -- -- -- -- --------0053 002bc000 00000fff Data RW Ac 3 Bg By P  Nl 000004f30:000> dt 0x002bc000 ntdll!_TEB   +0x000 NtTib            : _NT_TIB   +0x01c EnvironmentPointer : (null)    +0x020 ClientId         : _CLIENT_ID   +0x028 ActiveRpcHandle  : (null)    +0x02c ThreadLocalStoragePointer : 0x00664400 Void   ...
  • edx,dword ptr [ecx+eax*4]

這句匯編是一個數(shù)組操作,翻譯成 C 就是 ThreadLocalStoragePointer[tls]。war28資訊網(wǎng)——每日最新資訊28at.com

0:000> dp 0x00664400 L100664400  00664448

這里要提醒的是:上面的 00664448 所在的 heap 位置其實就是 PE 頭里的 StartAddressOfRawData~EndAddressOfRawData內(nèi)存區(qū)域的 copy,截圖如下:war28資訊網(wǎng)——每日最新資訊28at.com

圖片war28資訊網(wǎng)——每日最新資訊28at.com

  • eax,dword ptr [edx+108h]

這句話的意思就是在 數(shù)組元素1 這個結(jié)構(gòu)上偏移108的位置存放著我們的 num 值,用 windbg 觀察之后果然就是的。war28資訊網(wǎng)——每日最新資訊28at.com

0:000> dp 00664448+0x108 L100664550  7fffffff

三:總結(jié)

C# 屬于一種業(yè)務(wù)高層抽象的語言,它的很多底層被C++再次隔離了,想要理解本篇的TLS,還得需要往下一層一層的擊穿,作為C#程序員太難了。war28資訊網(wǎng)——每日最新資訊28at.com

本文鏈接:http://www.www897cc.com/showinfo-26-56404-0.html聊一聊 C# 的線程本地存儲TLS到底是什么

聲明:本網(wǎng)頁內(nèi)容旨在傳播知識,若有侵權(quán)等問題請及時與本網(wǎng)聯(lián)系,我們將在第一時間刪除處理。郵件:2376512515@qq.com

上一篇: 用Go實現(xiàn)一個帶緩存的REST API服務(wù)端

下一篇: 利用Go傳統(tǒng)RPC和gRPC框架分別實現(xiàn)一個RPC服務(wù)端

標(biāo)簽:
  • 熱門焦點
Top 主站蜘蛛池模板: 关岭| 大余县| 遵义市| 浦东新区| 且末县| 军事| 彩票| 酒泉市| 永仁县| 大埔县| 广灵县| 和顺县| 大英县| 辽阳市| 仲巴县| 辽宁省| 巢湖市| 新宁县| 伊通| 孟州市| 若羌县| 剑河县| 怀安县| 开鲁县| 塔河县| 政和县| 拉萨市| 临湘市| 达尔| 汽车| 绥宁县| 左权县| 湘阴县| 富蕴县| 昭平县| 秦皇岛市| 溆浦县| 独山县| 西青区| 岳阳县| 贞丰县|