惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

G
GRAHAM CLULEY
S
SegmentFault 最新的问题
MyScale Blog
MyScale Blog
F
Fortinet All Blogs
T
Tor Project blog
P
Privacy & Cybersecurity Law Blog
博客园 - 司徒正美
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
C
Check Point Blog
Hacker News - Newest:
Hacker News - Newest: "LLM"
PCI Perspectives
PCI Perspectives
N
News | PayPal Newsroom
aimingoo的专栏
aimingoo的专栏
The Hacker News
The Hacker News
AWS News Blog
AWS News Blog
N
News and Events Feed by Topic
O
OpenAI News
博客园 - Franky
M
MIT News - Artificial intelligence
H
Heimdal Security Blog
Hacker News: Ask HN
Hacker News: Ask HN
L
LINUX DO - 热门话题
Know Your Adversary
Know Your Adversary
S
Security @ Cisco Blogs
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
H
Hacker News: Front Page
TaoSecurity Blog
TaoSecurity Blog
The Cloudflare Blog
C
Cyber Attacks, Cyber Crime and Cyber Security
MongoDB | Blog
MongoDB | Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
L
LangChain Blog
人人都是产品经理
人人都是产品经理
C
CERT Recently Published Vulnerability Notes
Google DeepMind News
Google DeepMind News
罗磊的独立博客
博客园 - 【当耐特】
C
Cisco Blogs
K
Kaspersky official blog
Cisco Talos Blog
Cisco Talos Blog
IT之家
IT之家
Recent Announcements
Recent Announcements
Cyberwarzone
Cyberwarzone
博客园_首页
阮一峰的网络日志
阮一峰的网络日志
Y
Y Combinator Blog
AI
AI
爱范儿
爱范儿
S
Schneier on Security
Google DeepMind News
Google DeepMind News

博客园 - Flier Lu

[草稿] 自动下载完整的.NET源代码 Atlas 实现机制浅析 [3] Atlas 实现机制浅析 [2] Atlas 实现机制浅析 [1] [草稿] Managed COM Apartment - ExecutionContext [1] ASP.NET 2.0 异步页面原理浅析 [1] 也评《应用框架的设计与实现——.NET平台》 咬文嚼字之 Java 版 关注企业级服务自身的可管理性 享受 .NET 时代的 COM+ 开发 感受 COM+ 事务性 在 CLR 1.1 中获取 IL 函数体信息 GDS [1] 使用Google桌面搜索构建企业搜索方案 基于透明代理的内部类访问抽象 [2] 自动类型转换与封装 基于透明代理的内部类访问抽象 [1] 类型与调用的封装 [TIPS] 正确判断当前用户角色 使用 FishEye 直观了解 CVS 情况 在 .NET 中获取 AD 上帐号密码过期时间 用WinDbg探索CLR世界 [10] 透明代理实现原理浅析 - 静态结构
CLR 调试支持机制原理浅析 [1] 整体结构 [草稿]
Flier Lu · 2005-08-28 · via 博客园 - Flier Lu

0:003> ~
   0  Id: 1040.1fbc Suspend: 1 Teb: 7ffdd000 Unfrozen
   1  Id: 1040.1fc4 Suspend: 1 Teb: 7ffdc000 Unfrozen
   2  Id: 1040.1e64 Suspend: 1 Teb: 7ffdb000 Unfrozen
.  3  Id: 1040.1e14 Suspend: 1 Teb: 7ffda000 Unfrozen

0:003> !threads
ThreadCount: 2
UnstartedThread: 0
BackgroundThread: 1
PendingThread: 0
DeadThread: 0
                             PreEmptive   GC Alloc               Lock     
       ID ThreadOBJ    State     GC       Context       Domain   Count APT Exception
  0  1fbc 0015e890      6020 Enabled  00bfe82c:00bffff4 00158ef8     0 STA
  2  1e64 00151530      b220 Enabled  00000000:00000000 00158ef8     0 MTA (Finalizer)

    

    可以看到正如 Yun Jin 所说,除了与主线程 (ID:1fbc) 和 Finalizer 线程 (ID:1e64) 对应的 Native 线程外,还有两个 (ID: 1fc4, 1e14) Native 线程。
用 ~* 命令查看线程的详细情况如下:

0:003> ~*
   0  Id: 1040.1fbc Suspend: 1 Teb: 7ffdd000 Unfrozen
      Start: *** WARNING: Unable to verify checksum for image00400000
*** ERROR: Module load completed but symbols could not be loaded for image00400000
image00400000+0x61de (004061de) 
      Priority: 0  Priority class: 32
   1  Id: 1040.1fc4 Suspend: 1 Teb: 7ffdc000 Unfrozen
      Start: KERNEL32!BaseThreadStartThunk (7c82b5bb) 
      Priority: 0  Priority class: 32
   2  Id: 1040.1e64 Suspend: 1 Teb: 7ffdb000 Unfrozen
      Start: KERNEL32!BaseThreadStartThunk (7c82b5bb) 
      Priority: 2  Priority class: 32
.  3  Id: 1040.1e14 Suspend: 1 Teb: 7ffda000 Unfrozen
      Start: ntdll!DbgUiRemoteBreakin (7c975e73) 
      Priority: 0  Priority class: 32

      其中线程 (ID: 1e14) 是 WinDbg 调试用,可以暂且不记。另外一个线程 (ID: 1fc4) 就是我们的目标:调试支持线程。用 sysinternals 的 Process Explorer 可以查看其运行时堆栈如下:


...
KERNEL32.dll!WaitForMultipleObjects+0x18
mscorwks.dll!DebuggerRCThread::MainLoop+0x90
mscorwks.dll!DebuggerRCThread::ThreadProc+0x68
mscorwks.dll!DebuggerRCThread::ThreadProcStatic+0xb
KERNEL32.dll!BaseThreadStart+0x34

      在大致了解了其运行时状况后,我们来看看实际的实现方法。

      在 《用WinDbg探索CLR世界 [2] 线程》 一文中曾介绍过 CLR 初始化 EE (Execute Engine) 的步骤。通过 CoInitializeEE -> TryEEStartup -> EEStartup 的调用,最终由 EEStartup 函数 (vm\ceemain.cpp:206) 完成实际的初始化工作。
EEStartup 函数会在完成最基本的初始化工作,如启动 IPC 引擎后;在建立任何 EE 线程之前,初始化调试服务:

      因为 CLR 的调试支持依赖于基于 IPC 的 Notify 机制,因此在初始化调试支持之前,需要先完成对 IPC 和 Notify 服务的支持。
Notify 服务本质上就是一块全局的共享内存块,其 Section 在 Rotor 中名称为 ROTORSvcEventQueue,在 .NET 1.1 中名称为 CORSvcEventQueue。通过这块共享内存,CLR 会维护一个 ServiceEventBlock 类 (clr/src/inc/corsvcpriv.h:70),提供跨进程一级的消息队列机制。这个内存块会由一个独立的服务进程维护,负责对 CLR 启动和服务停止等事件进行分发。如果此全局服务存在,NotifyService 函数会在队列中现有事件已经分发完毕后,构造一个类型为 runtimeStarted 的 ServiceEvent 提交给它,以通告 CLR 正在启动。通过这个机制可以完成一些非常有趣的功能,但因为不涉及此次讨论的主体,暂且放下回头有空专门写文章讨论。

      负责初始化调试器支持的 InitializeDebugger 函数 (vm/ceemain.cpp:1868) 主要负责构造调试器对象和初始化调试器接口。InitializeDebugger 函数及其相关伪代码如下:

      首先调用 EEDbgInterfaceImpl::Init 函数 (vm/eedbginterfaceimpl.h:53) 构造面向外部调试器的 EE 调试接口,并存储在全局变量 g_pEEDbgInterfaceImpl 中。
然后调用 CorDBGetInterface 函数 (debug/ee/debugger.cpp:130) 构造调试器 Debugger 类 (debug/ee/debugger.h:631) 的实例,并存储于全局变量 g_pDebugger 中。而 CLR 内部则通过通用保存全局变量 g_pDebugInterface 中的 DebugInterface 抽象类 (vm/dbginterface.h:34) 的指针使用此实例。
最后在 Debugger 构造成功后,调用其 Startup 方法启动调试器。

      如果使用者通过 CLR 的 Host API 接口 ICorRuntimeHost,获取配置接口 ICorConfiguration,并设置调试线程控制接口 IDebuggerThreadControl,则 InitializeDebugger 函数还需要针对此接口做特殊处理。
主要工作包括调用 CorHost::RefreshDebuggerSpecialThreadList 函数 (vm/corhost.cpp:654) 更新特殊线程列表,避免调试器针对这些线程进行处理,不过貌似没有直接使用到此机制。

      通过上面的分析我们可以知道,CLR 的调试支持机制实际上是分为两个层面的。

      EEDbgInterfaceImpl 通过 facade 模式,将 EE 内部对调试所需功能的支持集成到一个 EEDebugInterface 接口 (vm/eedbginterface.h:55)。
      Debugger 则通过实现 DebugInterface 抽象类的功能,将 ICorDebugInfo 接口 (inc/corinfo.h:1168) 暴露给最终的调试功能使用者。

      EEDbgInterfaceImpl 的实现基本上是对 EE 内部对象和功能的调用,自身没有数据,因此这儿暂时不讨论。
Debugger 则负责向外部调试机制使用者提供接口,其 Debugger::Startup 方法 (debug/ee/debugger.cpp:513) 完成对调试环境的启动工作。
      Debugger::Startup 方法的主要工作包括:

      1.初始化多线程安全的调试堆
      2.建立、初始化并启动调试控制线程 DebuggerRCThread
      3.创建一组调试用 Event/Mutex 内核对象
      4.创建并初始化用于枚举 appdomain 的 IPC 共享内存控制块

      此外对于在调试器中启动 CLR 的情况,将做特殊处理。

      伪代码大致如下:

      对 Debugger 对象的调试堆来说,功能上就是为基础的 gmallocHeap 堆,增加了多线程安全的线程同步支持,避免因为并行使用调试接口造成内存分派上的问题。而 debugger.h 中通过重新定义全局 new/delete 操作符的方式,将 DebuggerRCThread 等对象创建的内存分派,都接管到调试堆中。代码如下:

      创建的调试用内核对象基本上是为 CLR 和调试接口各准备了一套,其中面向调试接口的一套内核对象,命名方式遵循 "CorDB + IPC + 对象名 + _ + 进程 ID" 的模式,并且将其安全属性通过 IPCManager 设置为只有同一帐号才能进行控制。如果在 Rotor 下则前缀 CorDB 改为 RotorDB,如:

      通过分析上述代码可以知道,CLR 的调试支持机制,实际上在运行时是由进程内的 DebuggerRCThread 线程,与进程外的调试器线程,通过一组 CorDBIPCxxx_xxx 事件进行通讯来完成的。就好比在调试支持初始化并启动后,如果 CLR 在调试器中启动,则触发 m_debuggerAttachedEvent 来通知调试器可以进行调试。这种机制与 Native WIN32 API 的调试支持机制基本相同,都是基于事件的触发调试模型。只不过 Native 的调试器是由内核一级的调试子系统来完成事件的分发和处理工作,而 CLR 因为不希望涉及内核态操作,自行通过调试控制线程完成类似的机制。因此我们如果希望进一步深入了解调试支持,就可以简化为对调试事件的支持与分析。

      而调试支持线程 DebuggerRCThread 的构造与初始化,基本上就是对其内部结构的处理。其中值得注意的是调试控制线程中,为 InProc/OutOfProc 两部分调试支持机制,各维护了一个 DebuggerIPCControlBlock 控制块结构,并维护了各部分的调试相关基础信息。对 InProc 这部分的信息,Init 方法会自行初始化;而 OutOfProc 部分的信息,则通过 IPCManager,在调试器 attach 的时候从外部进程获取。

      这一小节我们了解了 CLR 调试支持机制的整体结构,下一节我们继续分析调试支持线程是如何处理各种相关事件的。

to be continue...