翼度科技»论坛 编程开发 .net 查看内容

GC终结标记 SuspendEE 是怎么回事

9

主题

9

帖子

27

积分

新手上路

Rank: 1

积分
27
一:背景

1. 讲故事

写这篇是起源于训练营里有位朋友提到了一个问题,在 !t -special 输出中有一个 SuspendEE 字样,这个字样在 coreclr 中怎么弄的?输出如下:
  1. 0:000> !t -special
  2. ThreadCount:      3
  3. UnstartedThread:  0
  4. BackgroundThread: 2
  5. PendingThread:    0
  6. DeadThread:       0
  7. Hosted Runtime:   no
  8.                                                                                                             Lock  
  9. DBG   ID     OSID ThreadOBJ           State GC Mode     GC Alloc Context                  Domain           Count Apt Exception
  10.    0    1     4ab0 000001CC44E5C490    2a020 Cooperative 0000000000000000:0000000000000000 000001cc44e520d0 -00001 MTA (GC)
  11.   11    2     19d8 000001CC44E84700    21220 Preemptive  0000000000000000:0000000000000000 000001cc44e520d0 -00001 Ukn (Finalizer)
  12.   12    3     6668 000001CC44ED4520    2b220 Preemptive  0000000000000000:0000000000000000 000001cc44e520d0 -00001 MTA
  13.           OSID Special thread type
  14.         0 4ab0 SuspendEE
  15.        10 3b6c DbgHelper
  16.        11 19d8 Finalizer
复制代码
哈哈,其实我特别能理解,很多人学了高级调试之后好奇心会爆棚,看啥都想探究底层,有一种技术上的重生,这篇我们就好好聊一聊。
二:WinDbg 分析

1. SuspendEE 标记是什么

这个单词全称为 Suspend Engine Execution, 即 冻结执行引擎 ,那冻结执行引擎的入口方法在哪里呢?这个考验着你对GC运作骨架图的认识,在 coreclr 源码中有一个骨架图,简化后如下:
  1.      GarbageCollectGeneration()
  2.      {
  3.          SuspendEE();
  4.          garbage_collect();
  5.          RestartEE();
  6.      }
  7.      
  8.      garbage_collect()
  9.      {
  10.          generation_to_condemn();
  11.          gc1();
  12.      }
复制代码
上面的 SuspendEE() 即 SOS 中的 SuspendEE 标记的入口函数,接下来我们深入探究下这个方法。
2. SuspendEE 到底做了什么

如果你仔细阅读过 SuspendEE() 方法的源代码,你会发现核心枚举变量是 ThreadType_DynamicSuspendEE,它起到了定乾坤的作用,参考代码如下:
  1. thread_local size_t t_ThreadType;
  2. void ThreadSuspend::SuspendEE(SUSPEND_REASON reason)
  3. {
  4.     // set tls flags for compat with SOS
  5.     ClrFlsSetThreadType(ThreadType_DynamicSuspendEE);
  6. }
  7. void ClrFlsSetThreadType(TlsThreadTypeFlag flag)
  8. {
  9.     t_ThreadType |= flag;
  10.     gCurrentThreadInfo.m_EETlsData = (void**)&t_ThreadType - TlsIdx_ThreadType;
  11. }
  12. enum PredefinedTlsSlots
  13. {
  14.     TlsIdx_ThreadType = 11 // bit flags to indicate special thread's type
  15. };
  16. enum TlsThreadTypeFlag // flag used for thread type in Tls data
  17. {
  18.     ThreadType_DynamicSuspendEE = 0x00000020,
  19. }
复制代码
从上面的代码中可以看到 t_ThreadType 是一个 C++ 级的线程本地存储,意味着每一个线程都有其备份,同时它也是 SuspendEE 标记的核心来源,如果 m_EETlsData 的第 11号 槽位为 0x20 的时候, SuspendEE 标记就会被成功打下,并且可以通过 gCurrentThreadInfo.m_EETlsData 变量去跟踪来源,有了这么多信息之后,接下来就可以代码验证了。
三:案例验证

1. 一段测试代码

代码非常简单,就是一个简单的手工 GC触发。
  1.     internal class Program
  2.     {
  3.         static void Main(string[] args)
  4.         {
  5.             Debugger.Break();
  6.             GC.Collect();
  7.             Console.ReadLine();
  8.         }
  9.     }
复制代码
接下来使用 windbg 在入口的 SuspendEE 方法上下断点 bp coreclr!ThreadSuspend::SuspendEE 观察,截图如下:

一旦将 ThreadType_DynamicSuspendEE=0x20 赋值之后,接下来用 windbg 去做个验证。
  1. 0:000> x coreclr!*gCurrentThreadInfo*
  2. 000001a1`668ee8c0 coreclr!gCurrentThreadInfo = struct ThreadLocalInfo
  3. 0:000> dx -id 0,0 -r1 (*((coreclr!ThreadLocalInfo *)0x1a1668ee8c0))
  4. (*((coreclr!ThreadLocalInfo *)0x1a1668ee8c0))                 [Type: ThreadLocalInfo]
  5.     [+0x000] m_pThread        : 0x1a166902e50 [Type: Thread *]
  6.     [+0x008] m_pAppDomain     : 0x1a166948b40 [Type: AppDomain *]
  7.     [+0x010] m_EETlsData      : 0x1a1668ee880 [Type: void * *]
  8. 0:000> dp 0x1a1668ee880
  9. 000001a1`668ee880  00000000`00000000 00000000`00000000
  10. 000001a1`668ee890  00000000`00000000 00000000`00000000
  11. 000001a1`668ee8a0  00000000`00000000 00000000`00000000
  12. 000001a1`668ee8b0  00000000`00000000 00000000`00000000
  13. 000001a1`668ee8c0  000001a1`66902e50 000001a1`66948b40
  14. 000001a1`668ee8d0  000001a1`668ee880 00000000`00000020
复制代码
从上面输出可以看到 000001a1668ee8d0+0x8 地址的内容已经被成功种下,相信这时候 !t -special 也能拿到标记了。
  1. 0:000> !t -special
  2.                                                                                                             Lock  
  3. DBG   ID     OSID ThreadOBJ           State GC Mode     GC Alloc Context                  Domain           Count Apt Exception
  4.    0    1     640c 000001A166902E50    2a020 Preemptive  000001A16B0094A8:000001A16B00A5B8 000001a166948b40 -00001 MTA (GC)
  5.   11    2     3e50 000001A16692B2D0    21220 Preemptive  0000000000000000:0000000000000000 000001a166948b40 -00001 Ukn (Finalizer)
  6.   12    3     6a24 000001A16699F8F0    2b220 Preemptive  0000000000000000:0000000000000000 000001a166948b40 -00001 MTA
  7.     OSID Special thread type
  8.         0 640c SuspendEE
  9.        10 76b0 DbgHelper
  10.        11 3e50 Finalizer
复制代码
那这个 0x20 什么时候被拿掉呢? 这个在源码中也能找到相应的答案,继续 go 运行,输出如下:
  1. void ClrFlsClearThreadType(TlsThreadTypeFlag flag)
  2. {
  3.     t_ThreadType &= ~flag;
  4. }
  5. 0:012> dp 0x1a1668ee880
  6. 000001a1`668ee880  00000000`00000000 00000000`00000000
  7. 000001a1`668ee890  00000000`00000000 00000000`00000000
  8. 000001a1`668ee8a0  00000000`00000000 00000000`00000000
  9. 000001a1`668ee8b0  00000000`00000000 00000000`00000000
  10. 000001a1`668ee8c0  000001a1`66902e50 000001a1`66948b40
  11. 000001a1`668ee8d0  000001a1`668ee880 00000000`00000000
复制代码
当然如果你去寻找 sos 的源码实现,也会找到相应的答案。
  1. HRESULT PrintSpecialThreads()
  2. {
  3.     ...
  4.     if (ThreadType & ThreadType_DynamicSuspendEE)
  5.     {
  6.         type += "SuspendEE ";
  7.     }
  8.     ...
  9.     return Status;
  10. }
复制代码
四:总结

挖掘这个标记的前世今生回头看其实还是挺有意思的,coreclr 居然新增了 m_EETlsData 字段来给 sos 做妥协,哈哈,这彰显了 sos 一等公民的地位。


来源:https://www.cnblogs.com/huangxincheng/p/18371228
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x

举报 回复 使用道具