CLR 调试体系结构
公共语言运行时 (CLR) 调试 API 专门用作操作系统内核的一部分。 在非托管代码中,当程序生成异常时,内核将暂停执行进程,并使用 Win32 调试 API 将异常信息传递给调试器。 CLR 调试 API 可以为托管代码提供相同功能。 当托管代码生成异常时,CLR 调试 API 将暂停执行进程,并将异常信息传递给调试器。
进程体系结构
CLR 调试 API 包括以下两个主要组件:
调试 DLL,始终加载到与正在调试的程序相同的进程中。 运行时控制器负责与 CLR 进行通信并对正在运行托管代码的线程进行执行控制和检查。
调试器接口,加载到与正在调试的程序不同的进程中。 调试器接口负责代表调试器与运行时控制器进行通信。 它还负责处理来自正在调试的进程的 Win32 调试事件,要么处理这些事件,要么将这些事件传递给非托管代码调试器。 调试器接口是 CLR 调试 API 中唯一具有公开 API 的部件。
CLR 调试 API 不支持跨计算机或跨进程的远程使用;也就是说,使用该 API 的调试器必须从其自己的进程内执行此操作,如下面的 API 体系结构示意图所示。 此图显示了 CLR 调试 API 的不同组件所在的位置以及它们与 CLR 和调试器的交互方式。
CLR 调试 API 体系结构
托管代码调试器
可以构建一个只支持托管代码的调试器。 通过使用“软附加”机制,CLR 调试 API 使这种调试器能够根据需要附加到进程。 软附加到进程的调试器随后可以从该进程中分离出来。
线程同步
CLR 调试 API 具有与进程体系结构有关的相互冲突的要求。 一方面,将调试逻辑与正在调试的程序保持在相同进程中的原因很多,而且也很有说服力。 例如,数据结构复杂,经常要通过函数而不是通过固定内存布局来处理它们。 直接调用函数(而不是从进程外尝试对数据结构进行解码)要容易得多。 将调试逻辑保持在同一进程中的另一个原因是消除了进程间的通信开销,从而提高了性能。 最后,CLR 调试的一项重要功能就是能够在调试对象所在的进程中运行用户代码,很明显,这需要与调试对象进程进行一些协作。
另一方面,CLR 调试必须与非托管代码调试共存,后者只能从外部进程中正确执行。 此外,进程外调试器比进程内调试器更加安全,因为在进程外调试器中最大程度地减小了调试器的操作与调试对象进程之间的相互影响。
由于存在这些相互冲突的要求,所以 CLR 调试 API 会将各种方法的一些内容组合在一起。 主要的调试接口位于进程外,并且与本机 Win32 调试服务共存。 但是,CLR 调试 API 添加了与调试对象进程同步的功能以便能够在用户进程中安全地运行代码。 为了执行此同步操作,API 将与操作系统和 CLR 进行协作以便在进程中的所有线程不会中断操作的位置处暂停这些线程并使运行时处于不相干的状态。 然后,调试器可以在特殊的线程中运行代码,该线程可以检查运行时的状态并根据需要调用用户代码。
当托管代码执行断点指令或生成异常时,将通知运行时控制器。 此组件将确定正在执行托管代码的线程以及正在执行非托管代码的线程。 通常,在运行托管代码的线程达到可以安全挂起的状态之前,将允许这些线程继续执行。 例如,它们可能必须完成正在进行的垃圾回收。 当托管代码线程达到安全状态时,所有线程都将被挂起。 然后,调试器接口会通知调试器已经收到了断点或异常。
当非托管代码执行断点指令或生成异常时,调试器接口组件将通过 Win32 调试 API 接收通知。 此通知将传递给非托管调试器。 如果调试器确定需要执行同步(例如,为了能够检查托管代码堆栈帧),则调试器接口必须首先重新启动停止的调试对象进程,然后通知运行时控制器执行同步。 然后,当同步已完成时将会通知调试器接口。 此同步对于非托管调试器是透明的。
在同步进程期间,不得执行生成断点指令或异常的线程。 为了便于执行此规定,调试器接口在线程的筛选器链中放置了一个特殊的异常筛选器来控制线程。 当重新启动线程时,线程将进入异常筛选器,异常筛选器会将线程交给运行时控制器控制。 该继续处理异常(或该取消异常)时,筛选器会将控制权返还给线程的常规异常筛选器链或者返回正确的结果以继续执行。
在极少数情况下,生成本机异常的线程可能拥有重要的锁,只有先打开这些锁,然后才能完成运行时同步。 (通常,这些锁将为低级别库锁,例如 malloc 堆上的锁。)在这些情况下,同步操作一定会超时并且将会失败。 这也将导致需要同步的某些操作失败。
进程中的帮助器线程
每个 CLR 进程中只使用一个调试器帮助器线程以确保 CLR 调试 API 正常运行。 此帮助器线程负责处理由调试 API 提供的许多检查服务,以及在某些情况下协助线程同步。 您可以使用 ICorDebugProcess::GetHelperThreadID 方法识别帮助器线程。
与 JIT 编译器交互
为了使调试器能够调试实时 (JIT) 编译的代码,CLR 调试 API 必须能够将 Microsoft 中间语言 (MSIL) 版本的函数中的信息映射到本机版本的函数中。 此信息包括代码中的序列点以及局部变量位置信息。 在 .NET Framework 1.0 和 1.1 版中,只有当运行时处于调试模式时,才会产生此信息。 在 .NET Framework 2.0 中,始终会产生此信息。
另外,可以对 JIT 编译的代码进行高度优化。 优化(例如公共子表达式消除、函数内联展开、循环展开、代码检查等)可能会导致函数的 MSIL 代码与被调用执行的本机代码之间的相互关系丢失。 因此,这些主动代码优化方法将会严重影响 JIT 编译器提供正确映射信息的能力。 所以,在调试模式下运行运行时时,JIT 编译器将不会执行某些优化。 此限制使调试器能够准确地确定所有局部变量和参数的源行映射和位置。
调试模式
CLR 调试 API 提供了以下两种特殊的调试模式:
“编辑并继续”模式。 在此情况下,运行时将以不同方式运行以便以后能够更改代码。 这是因为某些运行时数据结构的布局必须不同以便支持“编辑并继续”。 因为这对性能有负面影响,所以除非想使用“编辑并继续”功能,否则请不要使用此模式。
调试模式。 此模式使 JIT 编译器能够忽略优化。 因此,它可以使执行的本机代码与高级语言源代码更加匹配。 除非需要,否则请不要使用此模式,因为这种模式也对性能有负面影响。
如果在“编辑并继续”模式外调试程序,则不支持“编辑并继续”功能。 如果在调试模式外调试程序,则将仍然支持大多数调试功能,但优化可能会引起异常行为。 例如,单步执行看来像是在方法中的行与行之间随机跳转,内联方法可能未在堆栈跟踪中出现。
如果调试器在运行时初始化自己之前获取了进程控制权,则调试器可以通过 CLR 调试 API 以编程方式启用“编辑并继续”模式和调试模式。 这足以能够达到许多目的。 但是,附加到已经运行了一段时间(例如在 JIT 调试期间)的进程的调试器将无法启动这些模式。
为了帮助处理这些问题,可以独立于调试器在 JIT 模式或调试模式下运行程序。 有关启用调试的方法的信息,请参见调试、跟踪和分析。
JIT 优化可能使应用程序的可调试性降低。 CLR 调试 API 使用经过优化的 JIT 编译代码来启用堆栈帧和局部变量检查。 单步执行虽然受支持,但可能不精确。 您可以运行一个程序来指示 JIT 编译器禁用所有 JIT 优化以产生可调试的代码。
CLR 调试体系结构的更多相关文章
- CLR 调试概述
利用公共语言运行时 (CLR) 调试 API,工具供应商可以编写调试器来调试运行于 CLR 环境中的应用程序. 要调试的代码可为 CLR 支持的任何代码种类.CLR 调试 API 主要是使用非托管代码 ...
- CLR调试时的sos.dll/clr.dll/mscorwks.dll/mscordacwks.dll等动态库的版本对应
大家都知道,在调试托管代码时,一定会加载到sos/clr/mscorwks/mscordacwks这些动态库,才能够很好的完成我们的调试工作,那么他们的版本对应关系是怎样的呢,特别是clr.dll/m ...
- WinDbg常用命令系列---.cordll (控制CLR调试)
.cordll (控制CLR调试) 简介 .cordell命令控制托管代码调试和Microsoft.NET公共语言运行库(CLR). 使用形式 .cordll [Options] 参数 Options ...
- CLR调试报错“Visual Studio远程调试监视器 (MSVSMON.EXE) 的 64 位版本无法调试 32 位进程或 32 位转储。请改用 32 位版本”的解决
Win7 64位电脑上进行visual studio的数据库项目的CLR存储过程进行调试时,报错: ---------------------------Microsoft Visual Studio ...
- clr调试扩展和DAC
SOS.DLL.SOSEX.DLL这两个就是用来对.NET程序在Windows调试工具中起到翻译作用的调试器扩展.简单讲就是,这两个组件是.NET项目组专门开发出来用来对.NET应用程序进行方便调试用 ...
- windbg调试托管代码 .Net clr
现在很多的程序都是多语言混合编程的,比如我司的产品,就是用C++/.net clr混合编制的.那么当我们调试这样的程序时,一定要注意,比如有时我们只看到c++的栈和名称,而.net clr的代码确看不 ...
- 远程debug调试java代码
远程debug调试java代码 日常环境和预发环境遇到问题时,可以用远程调试的方法本地打断点,在本地调试.生产环境由于网络隔离和系统稳定性考虑,不能进行远程代码调试. 整体过程是通过修改远程服务JAV ...
- 使用java远程调试技术监控代码运行
JAPA介绍 JPDA(Java Platform Debugger Architecture)是 Java 平台调试体系结构的缩写,通过 JPDA 提供的 API,开发人员可以方便灵活的搭建 Jav ...
- tomcat如何正确的开启远程调试功能
在日常开发中,有时需要对远程服务器上的应用进行远程调试,对于tomcat,要进行远程调试其实很简单,只需要在启动tomcat时开启jpda服务即可. 什么是JPDA呢? JPDA(JavaPlatfo ...
随机推荐
- delphi xe6 JSON 测试
System.JSON ISuperJSOn mORMETJSON QJSON 测试 我在测试时发现系统自带的JSON 占用内存大一但多了就会出现内存泄漏的问题 我用的Flst< ...
- 【Rust】使用cargo创建项目及cargo源的替换
---------------------------------参考文档------------------------------- https://rustlang-cn.org/office/ ...
- 【2】【典型一维动态规划】【剑指offer+leetcode53】连续子数组的最大和
HZ偶尔会拿些专业问题来忽悠那些非计算机专业的同学.今天测试组开完会后,他又发话了:在古老的一维模式识别中,常常需要计算连续子向量的最大和,当向量全为正数的时候,问题很好解决.但是,如果向量中包含负数 ...
- C语言--线性表
#include<stdio.h>#include<stdlib.h>#include<string.h>#define LIST_SIZE 100#define ...
- 2019 用友网络java面试笔试题 (含面试题解析)
本人3年开发经验.18年年底开始跑路找工作,在互联网寒冬下成功拿到阿里巴巴.今日头条. 用友网络等公司offer,岗位是Java后端开发,最终选择去了 用友网络. 面试了很多家公司,感觉大部分公司考察 ...
- Vue v-bind与v-model的区别
v-bind 缩写 : 动态地绑定一个或多个特性,或一个组件 prop 到表达式. 官网举例 <!-- 绑定一个属性 --> <img v-bind:src=" ...
- redux reducer笔记
踩坑一,reducer过于抽象 reducer写得没那么抽象也不会有人怪你的.^_^ reducer其实只有一个,由不同的reducer composition出来的.所以, reducer的父作用域 ...
- ABAP-System Functions
ABAP_CALLSTACK ABAP_CRC64 ABAP_PRECOMPILED_HEADER_USAGE ABSTOR_TEST AB_CALL_LITL_CHECK AB_CALL_STACK ...
- ipc$ 空连接 net use
常用命令 [xxx]表示的内容,需要根据自己的需求更改 //建立空连接 > net use \\127.0.0.1\ipc$ //删除连接 > net use \\127.0.0.1\ip ...
- linux技能点七 shell
shell脚本:定义,连接符,输入输出流,消息重定向,命令的退出状态,申明变量,运算符,控制语句 定义:linux下的多命令操作文件 连接符: ::用于命令的分隔符,命令会从左往右依次执行 & ...