.NET是托管运行时 。 这意味着它包含为您控制程序的高级功能(从Introduction到Common Language Runtime(CLR),2007年 ):
运行时提供了许多功能,因此可以很方便地将它们分为以下几类:
- 影响他人设备的主要功能 。 这些包括:
- 垃圾收集;
- 确保内存访问和类型系统安全性;
- 对编程语言的高级支持。
- 附加功能 -在主要功能的基础上工作。 许多有用的程序都没有它们。 这些功能包括:
- 使用AppDomains隔离应用程序
- 应用程序保护和沙箱隔离。
- 所有运行时都需要其他功能 ,但是它们不使用基本的CLR功能。 这些功能反映了创建完整编程环境的愿望。 这些包括:
- 版本控制;
- 调试/分析;
- 确保互动。
可以看出,尽管调试和概要分析不是主要功能或附加功能,但它们在列表中却是因为“ 希望创建一个完善的编程环境 ”。

文章的其余部分描述了Core CLR中存在哪些监视 , 可观察性和自省功能,它们为何有用以及环境如何提供这些功能。
诊断程序
首先,看看CLR为我们提供的诊断信息。 传统上,Windows事件跟踪(ETW)已用于此目的。
CLR提供有关许多事件的信息。 它们与:
- 垃圾收集(GC);
- JIT编译;
- 模块和应用程序领域;
- 使用线程并在阻塞时发生冲突;
- 以及许多其他
例如,这里在AppDomain加载过程中发生了一个事件 ,这里事件与引发异常相关 ,这里与垃圾收集器的内存分配周期有关 。
性能观察
如果要查看与.NET应用程序相关的跟踪系统(ETW)中的事件,建议您使用出色的PerfView工具,并从这些培训视频或PerfView:Ultimate .NET Performance Tool的此演示开始。 PerfView被认为可以提供宝贵的信息。 例如,Microsoft工程师经常使用它来分析性能 。
通用基础设施
如果名称不清楚,则ETW中的事件跟踪仅在Windows下可用,这与.NET Core的跨平台世界不太匹配。 您可以使用PerfView分析Linux下的性能 (使用LTTng)。 但是,此称为PerfCollect的命令行工具仅收集数据。 分析功能和丰富的用户界面(包括firegraphs )目前仅在Windows解决方案中可用。
但是,如果您仍然想在Linux下分析.NET性能,则还有其他方法:
上面的第二个链接引发了对.NET Core中正在使用的新EventPipe基础结构的讨论(除了EventSources和EventListeners)。 它的开发目标可以在“ 跨平台性能监控设计”文档中找到。 在较高的层次上,此基础架构将创建一个单一的位置,CLR将在该位置发送与诊断和性能有关的事件。 然后,这些事件将被重定向到一个或多个记录器,例如,可能包括ETW,LTTng和BPF。 将根据运行CLR的操作系统或平台来确定所需的记录器。 有关各种日志记录技术的优缺点的详细说明,请参见.NET跨平台性能和事件设计 。
通过性能监控项目和相关的“ EventPipe”问题来监控EventPipes的进度。
未来计划
最后,计划创建一个Performance Profiling Controller ,它具有以下任务:
控制器必须管理配置基础结构,并以简单和跨平台的方式显示负责性能诊断的.NET组件生成的性能数据。
根据计划,控制器应通过HTTP服务器提供以下功能 ,并从EventPipes基础结构接收所有必要的数据:
REST API
- 原则1:简单分析:在时间段X内分析运行时并返回跟踪。
- 原则1:高级分析:开始跟踪(以及配置)
- 原则1:高级分析:完全跟踪(此调用的答案将是跟踪本身)。
- 原理2:获取与所有EventCounter或特定EventCounter相关的统计信息。
HTML可浏览页面
- 原则1:流程中所有托管代码堆栈的文本表示。
- 原理2:显示EventCounters计数器的当前状态(可能带有历史记录)。
- 提供现有计数器及其值的概述。
- 未解决的问题:我认为没有必要的公共API来计算EventCounters。
我真的很想看看性能分析控制器(PPC)会发生什么。 我认为,如果将它内置到CLR中,它将为.NET带来很多好处。 这样的功能存在于其他运行时中 。
剖析
CLR拥有的另一个有效工具是性能分析API。 (主要)供第三方工具使用,以较低级别连接到运行时。 您可以从此评论中了解有关该API的更多信息,但在较高的层次上,可以使用它来进行以下情况下激活的回调:
- 与垃圾收集器有关的事件;
- 抛出异常;
- 程序集的加载/卸载;
- 还有更多 。
来自BOTR Profiling API页面的图像-概述
此外,它还具有其他有效功能。 首先,您可以设置在每次执行.NET方法时调用的处理程序,无论是在环境本身中还是在用户代码中。 这些回调称为Enter / Leave处理程序。 这是如何使用它们的一个很好的例子 。 但是,为此,您需要了解不同OS和CPU体系结构的调用约定 ,这并不总是那么容易 。 另外,请记住,分析API是一个COM组件,只能从C / C ++代码访问,而不能从C#/ F#/ VB.NET访问。
其次,分析器可以使用SetILFunctionBody()API在JIT编译之前重写任何.NET方法的IL代码。 该API确实非常有效。 它是许多APM .NET工具的基础。 您可以从我的文章如何模拟密封的类和静态方法以及相关代码中了解有关它的更多信息。
ICorProfiler API
事实证明,分析API可以正常工作,运行时环境中应该有各种各样的技巧。 只需查看附件上的“ 允许rejit”页面上的讨论即可 (有关ReJIT的更多信息,请参见ReJIT:操作指南 )。
您可以在\ vm \ inc \ corprof.idl中找到所有概要分析API接口和回调的完整定义(请参见接口描述语言 )。 它分为2个逻辑部分。 一部分是Profiler-> Runtime Environment(EE)接口,称为ICorProfilerInfo
:
// , ICorProfilerInfo*, // . , DLL // , // .
这在以下文件中实现:
另一个主要部分是回调Runtime-> Profiler,它们被归类为ICorProfilerCallback
接口:
// // ICorProfilerCallaback* . // , EEToProfInterfaceImpl.
这些回调在以下文件中实现:
最后,值得注意的是,性能分析API可能不适用于所有运行.NET Core的操作系统和体系结构。 这是一个示例: Linux上的ELT调用存根问题 。 有关更多信息,请参见CoreCLR Profiler API的状态 。
剖析v。 侦错
作为一个小题外话,我必须说分析和调试仍然有些重叠。 因此,了解在.NET运行时的上下文中提供哪些不同的API(从CLR调试vs. CLR分析获得 )很有用。
CLR中调试和性能分析之间的区别
侦错
开发人员对调试的理解不同。 例如,我在Twitter上问“如何调试.NET程序”,并得到了许多 不同的答案 。 同时,答案的确包含了很多工具和方法,因此我建议您仔细研究一下。 感谢#LazyWeb
我认为调试的所有本质中最好的体现了以下信息:
CLR提供了广泛的调试相关功能列表。 但是,为什么需要这些资金? 在这篇很棒的文章中至少提到了三个原因为什么托管调试与本机调试不同? :
- 可以在硬件级别抽象调试非托管代码,但是必须在IL代码级别抽象调试托管代码。
- 调试托管代码需要大量信息,这些信息在执行前不可用。
- 托管代码调试器必须与垃圾收集器(GC)协调
因此,为了易于使用,CLR应该提供称为ICorDebug
的高级调试API。 下图显示了一般的调试场景(来源:BOTR):
ICorDebug API
各种组件的实现原理和说明摘自CLR调试,简介如下:
.Net中的所有调试支持都在DLL库(我们称为The Dac)的顶部实现。 该文件(通常称为mscordacwks.dll
)是我们的公共调试API( ICorDebug
)和两个私有调试API(SOS-Dac API和IXCLR)的结构元素。
在理想的世界中,每个人都将使用我们的公共API ICorDebug
。 但是, ICorDebug
缺少工具开发人员需要的许多功能。 这是我们试图解决的问题。 但是,这些改进仅在CL.v.next中存在,而在CLR的早期版本中不存在。 实际上,只有在CLR v4发行时, ICorDebug
API中才会出现故障转储调试支持。 在CLR v2中使用故障转储进行调试的每个人将根本无法应用ICorDebug
。
(有关更多信息,请参见SOS和ICorDebug)
实际上, ICorDebug
API分为70多个接口。 我不会全部介绍它们,但会显示它们可以分为哪些类别。 有关更多信息,请参见发布此列表的ICorDebug分区。
- 顶层 :ICorDebug + ICorDebug2-完美充当ICorDebugProcess对象集合的顶层接口。
- 回调 :托管代码调试事件通过方法发送到调试器实现的回调对象。
- 流程 :这组接口代表工作代码,并包括与事件相关的API。
- 代码/类型检查 :通常用于静态PE图像,但是有一些方便的方法可以处理真实数据。
- 执行控制 :能够监视线程的进度。 实际上,这意味着可以设置断点(F9)并逐步执行代码(F11代码输入,F10代码旁路,S + F11代码退出)。 ICorDebug执行控制功能仅适用于托管代码。
- 线程+调用堆栈 : 调用堆栈是调试器实现的检查功能的基础。 使用以下接口来进行调用堆栈的工作。 ICorDebug仅支持调试托管代码,因此,您可以跟踪仅托管代码的堆栈。
- 对象检查 : 对象检查是API的一部分,可让您查看调试代码中的变量值。 对于每个接口,我都提供了MVP方法,在我看来,该方法应该简要描述此接口的用途。
与分析API一样,调试API支持级别因操作系统和处理器体系结构而异。 例如,截至2018年8月,仍然没有用于诊断和调试托管代码的Linux ARM解决方案。 有关Linux支持的更多信息,请参阅Microsoft上的带有LLDB的Linux上的.NET Core调试和Microsoft的Diagnostics存储库,其目的是简化Linux的.NET程序的调试。
最后,如果您想了解ICorDebug
API在C#中的外观,请看一下CLRMD库中的包装器,包括所有可用的回调(有关CLRMD的更多信息,将在本文后面讨论)。
SOS和DAC
在BOTR页面上详细讨论了数据访问组件(DAC)。 本质上,它提供了对CLR数据结构的进程外访问,以便可以从另一个进程中读取其中的信息。 因此,调试器(通过ICorDebug
)或扩展名“ Son of Strike”(SOS)可以访问运行中的CLR实例或内存转储并查找例如:
- 所有正在运行的线程;
- 托管堆对象
- 有关该方法的完整信息,包括机器代码;
- 当前堆栈跟踪。
一个小的题外话 :如果您想找出这些奇怪的名字的来源,并从.NET历史中汲取一点教训,请在Stack Overflow上查看此答案 。
SOS命令的完整列表令人印象深刻 。 如果将其与WinDBG一起使用,则可以非常低地了解程序和CLR内部发生的情况。 若要查看所有内容的实现方式,请看!HeapStat
,该!HeapStat
显示.NET GC使用的各种堆的大小的描述:
(图片来自SOS:即将发布的版本具有一些新命令-HeapStat)
下面的代码流显示了SOS和DAC如何协同工作:
- SOS完整团队
!HeapStat
( 链接 ) - 与Workstation GC配合使用的
!HeapStat
中的SOS代码(链接) - SOS函数
GCHeapUsageStats(..)
,它执行工作中最困难的部分( 链接 ) - 共享的
DacpGcHeapDetails
数据DacpGcHeapDetails
,其中包含指向GC堆中主要数据的指针,例如段,位掩码和各个世代( 参考 )。 - 填充
DacpGcHeapDetails
结构的DAC GetGCHeapStaticData
函数( 链接 ) - 共享的
DacpHeapSegmentData
数据DacpHeapSegmentData
,其中包含有关单个GC堆段的信息( 链接 ) - DAC
GetHeapSegmentData(..)
,它填充DacpHeapSegmentData
结构( 链接 )
第三方调试器
自从Microsoft发布调试API以来,第三方开发人员便能够使用ICorDebug
接口。 这是我设法找到的列表:
内存转储
我们将要讨论的最后一件事是内存转储,它可以从工作系统获得并在其外部进行分析。 .NET运行时始终支持在Windows下转储内存 。 现在,.NET Core已成为跨平台的工具,出现了在其他OS上执行相同任务的工具。
使用内存转储时,有时很难获得正确的,匹配的SOS和DAC文件版本。 幸运的是,Microsoft最近发布了dotnet symbol
CLI工具,该工具:
可以为任何特定的核心转储,小型转储或任何受支持平台(包括ELF,MachO,Windows DLL,PDB和Portable)的文件下载调试所需的所有文件(特定coreclr模块的字符集,模块,SOS和DAC文件) PDB
最后,如果您要对内存转储进行一些分析,建议您看一下微软几年前发布的出色的CLR MD库。 我已经写了它的功能。 简而言之,使用该库,您可以通过直观的C#API处理内存转储,其中包含的类提供对ClrHeap,GC根目录,CLR线程,堆栈框架等的访问。 实际上,CLR MD可以实现大多数(如果不是全部)SOS命令。
您可以从这篇文章中了解它的工作原理:
ClrMD托管库是调试API的包装,该调试API仅在CLR中供内部使用。 尽管这些API对于诊断非常有效,但我们不以公开的,已记录的发行版的形式支持它们,因为它们的使用非常复杂且与CLR实现的其他功能密切相关。 ClrMD通过围绕这些底层调试API提供易于使用且易于管理的包装器来解决此问题。
