.NET 运行时 Profiling API 可分析性实现指南:从契约(Contracts)到回调/Info 接口的落地实践
2026/9/16 19:23:13 网站建设 项目流程

.NET 运行时 Profiling API 可分析性实现指南:从契约(Contracts)到回调/Info 接口的落地实践

【免费下载链接】runtime.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.项目地址: https://gitcode.com/GitHub_Trending/runtime6/runtime

导读

本文基于 .NET 运行时(dotnet/runtime)仓库中的 docs/design/coreclr/botr/profilability.md 编写,面向需要为 CLR 新特性接入 Profiling API(即让新功能“可分析”)的运行时开发工程师。文章系统讲解 Profiling API 的两大接口族(ICorProfilerCallback 回调与 ICorProfilerInfo 信息函数)背后的设计哲学、契约(Contract)体系、同步/异步语义,并结合仓库中 corprof.idl、eetoprofinterfaceimpl.cpp、proftoeeinterfaceimpl.cpp 等真实源码给出可照抄的契约样板与修改路径。读完本文,你将掌握:如何为回调包装器与 Info 函数挑选正确的契约组合、为什么回调与 Info 的契约偏好完全相反、同步与异步 Info 函数的区别与安全要求,以及新增一个可分析特性时需要改动哪些文件。

一、设计哲学:契约(Contracts)的总体思路

在深入讨论 Profiling API 该用哪些契约之前,必须先理解整个 CLR 在契约使用上的总体哲学。这一哲学贯穿了“默认契约运动”(default contracts movement):鼓励 CLR 中的绝大多数代码都具备应对“激进行为”(如抛异常 THROWS、触发 GC_TRIGGERS)的能力

基于这一前提,文档对 Profiling API 两个方向上的接口给出了看似相反、实则自洽的建议:

  • ICorProfilerCallback(CLR → Profiler 的回调):倾向于选择更“宽松”(permissive,即激进)的契约组合。这给了 profiler 在回调内最大的灵活性——它可以在回调期间调用哪些 ICorProfilerInfo 方法,取决于回调本身的契约有多宽松。
  • ICorProfilerInfo(Profiler → CLR 的信息函数):恰恰相反,倾向于“严格”(restrictive)而非宽松。原因很直白:我们希望 profiler 能在尽可能多的调用点安全地调用这些函数,包括那些契约比较严格的回调(例如某些不得已必须是GC_NOTRIGGER的回调)。

这两个方向并不矛盾:整个 CLR 的默认契约哲学鼓励绝大多数函数宽松化,而ICorProfilerInfo恰恰属于那一小部分必须严格的特殊调用路径的“根”。因为 profiler 可能正在运行时的“敏感时刻”(delicate times)反向调用 CLR,我们希望这些调用尽量“无侵入”。它们不是 CLR 的主流函数,而是少数需要格外小心的特殊路径。

因此,通用指导原则是:能使用默认契约就用默认契约;但凡是源自 profiler(即从 ICorProfilerInfo 出发)的调用路径,其契约必须显式写出,并且比默认契约更严格。

二、性能 vs 易用性:为什么 CLR 几乎不做 ID 校验

理想情况下性能和易用性两者兼得,但如果必须取舍,优先保证性能。Profiling API 被定位为 CLR 与 profiling DLL 之间的一层轻量、薄薄的在进程内(in-process)桥梁。profiler 编写者是极少数、且大多是相当资深的开发者,因此:

  • CLR 只做简单的输入校验,例如检查 NULL 指针、检查被请求检查的类是否已初始化、检查“平行参数”的一致性(如数组指针参数非 NULL 时其 size 参数必须非零)。
  • 但仅此而已。以 profiler 的 ID 为例:所有 ID 都只是 C++ EE 对象实例指针的强制类型转换(如AppDomain*MethodTable*直接转成AppDomainIDClassID等)。这一点在源码中有直接印证——proftoeeinterfaceimpl.cpp 中的MethodDescToFunctionIDFunctionIdToMethodDesc就是两个极简的reinterpret_cast双向转换,没有任何查表或哈希校验:
static FunctionID MethodDescToFunctionID(MethodDesc * pMD) { LIMITED_METHOD_CONTRACT; return reinterpret_cast< FunctionID > (pMD); } MethodDesc *FunctionIdToMethodDesc(FunctionID functionID) { LIMITED_METHOD_CONTRACT; MethodDesc *pMethodDesc = reinterpret_cast< MethodDesc* >(functionID); _ASSERTE(pMethodDesc != NULL); return pMethodDesc; }

如果 profiler 传回一个伪造的 ID,CLR 会直接访问违例(AV)。这是预期行为:CLR 不会为了校验查找而去哈希 ID,它假设 profiler 清楚自己在做什么。记住:简单校验是必须的,但“信任 profiler”是性能取舍下的既定设计。

三、ICorProfilerCallback:CLR 向 profiler 通知事件的回调

ICorProfilerCallback接口由 CLR 回调 profiler,用于通知其感兴趣的事件。每个回调都被 EE 中的一个**薄包装方法(wrapper)**包裹:该包装负责定位 profiler 对ICorProfilerCallback(及其后续版本ICorProfilerCallback2ICorProfilerCallback3……)的实现,并调用其对应方法。

3.1 事件订阅机制:SetEventMask 与 CORProfiler* 内联函数

Profiler 通过调用ICorProfilerInfo::SetEventMask()/SetEventMask2()并设置对应标志位来订阅事件。Profiling API 保存这些选择,并通过一组专门的**内联函数(CORProfiler*)**向 CLR 暴露——这些函数对事件标志位做按位掩码(mask)判断。事件标志的枚举定义位于 src/coreclr/inc/corprof.idl,例如:

  • COR_PRF_MONITOR_NONE = 0x00000000
  • COR_PRF_MONITOR_FUNCTION_UNLOADS = 0x00000001
  • COR_PRF_MONITOR_CLASS_LOADS = 0x00000002
  • COR_PRF_MONITOR_MODULE_LOADS = 0x00000004
  • COR_PRF_MONITOR_ASSEMBLY_LOADS = 0x00000008
  • COR_PRF_MONITOR_APPDOMAIN_LOADS = 0x00000010
  • COR_PRF_MONITOR_JIT_COMPILATION = 0x00000020
  • COR_PRF_MONITOR_EXCEPTIONS = 0x00000040
  • COR_PRF_MONITOR_GC = 0x00000080
  • COR_PRF_MONITOR_OBJECT_ALLOCATED = 0x00000100

在整个 CLR 代码库中,你会看到大量“散落各处”的、以标志位为条件的回调调用。原文档给出的典型样板如下(例如模块加载事件):

{ // check if profiler set flag BEGIN_PROFILER_CALLBACK(CORProfilerTrackModuleLoads()); // call the ProfControlBlock wrapper around the profiler's callback implementation // which pins the profiler in DoOneProfilerIteration via EvacuationCounterHolder (&g_profControlBlock)->ModuleLoadStarted((ModuleID) this); // unpins the profiler after completing the callback END_PROFILER_CALLBACK(); }

这段代码就是散落在代码库各处的“调用点”形态。它所调用的函数(此处是ModuleLoadStarted())才是我们封装 profiler 回调实现的包装器(对应ICorProfilerCallback::ModuleLoadStarted())。所有包装器都集中在同一个文件 src/coreclr/vm/eetoprofinterfaceimpl.cpp(及其头文件 eetoprofinterfaceimpl.h)中,后续小节给出的契约指导针对的正是这些包装器,而不是上面这段调用包装器的示例代码。

3.2 BEGIN_PROFILER_CALLBACK / END_PROFILER_CALLBACK 宏的语义

BEGIN_PROFILER_CALLBACK宏会求值其传入的表达式:

  • 若表达式为 TRUE:执行BEGIN_PROFILER_CALLBACKEND_PROFILER_CALLBACK之间的代码,并通过ProfControlBlock包装器将 profiler钉在内存中(pinned),意味着 profiler 在回调期间无法从进程中分离(detach)。
  • 若表达式为 FALSE:跳过两者之间的全部代码。

关于这两个宏的底层机制,src/coreclr/vm/profilinghelper.cpp 中的注释给出了清晰的实现级说明:

  • ProfControlBlock回调包装器会调用DoOneProfilerIteration,其核心工作之一是在栈上压入一个EvacuationCounterHolder,用于持有ProfilerInfo——这既保证回调执行期间 profiler 不会被卸载(防止 use-after-free),也保证了并发/附加(attach)场景下的安全(见 profilinghelper.cpp 中EvacuationCounterHolder holder(pProfilerInfo);的实际用法)。
  • 一旦退出DoOneProfilerIteration代码块,evacuation counter 便递减,profiler 恢复可分离状态。
  • 状态访问方面,g_profControlBlock(如g_profControlBlock.curProfStatus.m_profStatusg_profControlBlock.pProfInterface)使用无锁、volatile 的每线程计数器与状态读取,以保证在不加锁的前提下安全判断“当前是否有 profiler、是否在跟踪某类事件”。

BEGIN_PROFILER_CALLBACKEND_PROFILER_CALLBACK宏的完整定义见代码库中的头文件(profilinghelper.h/eetoprofinterfaceimpl.h一带),在动手写新回调前请先阅读其定义处的注释。

3.3 回调包装器的契约样板

每个回调包装器顶部都必须有一段“公共样板”,原文档给出的标准示例为:

CONTRACTL { // Yay! NOTHROW; // Yay! GC_TRIGGERS; // Yay! MODE_PREEMPTIVE; // Yay! CAN_TAKE_LOCK; } CONTRACTL_END; CLR_TO_PROFILER_ENTRYPOINT((LF_CORPROF, LL_INFO10, "**PROF: useful logging text here.\n"));

关键要点:

  1. 必须显式指定throws、triggers、mode、take_lock 的值,并且回调上还必须有ASSERT_NO_EE_LOCKS_HELD()(后者仅回调需要)。这保证了我们能持续维护面向 profiler 编写者的文档准确性。
  2. 每个契约必须有自己的注释:能用“首选值”就注释// Yay!,这样后来复制粘贴这段代码的人知道什么是最好的;若无法使用首选值,则注释原因。

3.4 回调契约的首选值表

首选值原因细节
NOTHROW允许从任何 CLR 上下文发出回调。由于 Info 函数也应是NOTHROW,这对 profiler 不算为难注意:如果 profiler 从回调里调用了一个THROWS的 Info 函数,即使 profiler 用 try/catch 包住了调用,你仍会得到 throws 契约违例——因为契约系统看不到 profiler 的 try/catch。因此,在真正调用进 profiler 之前,需要插入一个作用域限定的CONTRACT_VIOLATION(ThrowsViolation)
GC_TRIGGERS给 profiler 调用 Info 函数的最大灵活性如果回调发生在敏感时刻,保护所有对象引用容易出错或显著降低性能,则使用GC_NOTRIGGER(并务必注释原因!)
MODE_PREEMPTIVE(如可能);否则MODE_COOPERATIVEMODE_PREEMPTIVE给 profiler 调用 Info 函数的最大灵活性(除非因 ObjectID 必须 coop);同时MODE_PREEMPTIVE是整个 EE 中受青睐的“默认”契约,强制回调处于抢占模式能推动 EE 其他地方也使用抢占模式如果向 profiler 传递 ObjectID 参数,MODE_COOPERATIVE是合理的。否则指定MODE_PREEMPTIVE。回调的调用方本应已经处于抢占模式;若不是,请重新思考原因,并尽可能把调用方改成抢占模式;否则需要在调用回调前使用GCX_PREEMP()
CAN_TAKE_LOCK给 profiler 调用 Info 函数的最大灵活性无更多补充
ASSERT_NO_EE_LOCKS_HELD()给 profiler 调用 Info 函数更大的灵活性,因为它确保没有任何 Info 会重取锁或乱序取锁(既然根本没有锁可“重取”或破坏顺序)这其实不是契约,但契约块是放置它的方便之处,以免遗忘。与契约一样,若无法指定,请注释原因

补充说明:回调上无需指定EE_THREAD_NOT_REQUIRED/EE_THREAD_REQUIRED。GC 回调本来就不能指定 “REQUIRED”(可能根本不存在 EE Thread),而且这两个契约只在 Info 函数(profiler → CLR 方向)上才有意义。

3.5 入口宏(Entrypoint macros)

契约之后,应放一个入口宏。它负责:日志记录、在 EE Thread 对象上标记“当前处于回调中”、移除栈保护(stack guard)、执行若干断言。有几种变体可选:

CLR_TO_PROFILER_ENTRYPOINT

这是首选且最常用的宏。若必须使用其他变体,必须注释原因

*_FOR_THREAD_*命名的变体用于带 ThreadID 参数、且该参数不一定等于当前 ThreadIDICorProfilerCallback方法。使用时必须把 ThreadID 作为宏的第一个参数传入,宏会用你的 ThreadID 而非GetThread()来断言“该 ThreadID 当前是否仍允许回调”(即尚未对该 ThreadID 发出过ThreadDestroyed())。

四、ICorProfilerInfo:profiler 反向调用 CLR 的入口

ICorProfilerInfo接口由 profiler 用于调用 CLR。其实现位于 src/coreclr/vm/proftoeeinterfaceimpl.cpp(及头文件 proftoeeinterfaceimpl.h、内联文件 proftoeeinterfaceimpl.inl),该文件头部(第 26-74 行)对入口宏的种类与适用场景有权威注释。

4.1 同步(Synchronous)与异步(Asynchronous)分类

每个 Info 调用都被划分为同步异步两类:

  • 同步函数必须从回调(Callback)内部调用;
  • 异步函数则随时调用都安全。
同步 Info 函数

绝大多数 Info 调用都是同步的:只有 profiler 正执行在某个 Callback 内部时,调用同步 Info 函数才是合法的。换句话说,栈上必须有一个ICorProfilerCallback,才能合法调用同步 Info 函数。

该约束通过 EE Thread 对象上的一个**位(bit)**来追踪:发出回调时置位,回调返回时复位;调用同步 Info 函数时测试该位——若未置位则拒绝该调用。

**没有 EE Thread 的线程:**由于上述位依赖 EE Thread 对象,只有“拥有 EE Thread 对象的线程”上的 Info 调用才会被强制检查同步性。任何在非 EE Thread 线程上的 Info 调用都被直接视为合法。这通常没问题,因为主要是 EE Thread 线程会累积出复杂、难以重入的上下文;而且最终保证正确性仍是 profiler 的责任(如前所述,出于性能原因,Profiling API 历来将正确性检查保持在最低限度)。profiler 在非 EE Thread 线程上发起 Info 调用的典型场景有两类:

  • 在做 server GC 的线程上、于 GC 回调期间发起的 Info 调用;
  • 在 profiler 自建线程(例如采样线程 sampling thread,栈上没有 CLR 代码)上发起的 Info 调用。

Enter / Leave 钩子:如果 profiler 请求了 enter/leave 钩子并使用快路径(fast path,即由 JIT 代码直接函数调用到 profiler、中间不经过任何 profiling API 代码),那么从 enter/leave 钩子内调用任何 Info 函数都会被视作异步调用。这也是务实的做法:profiling API 代码没有机会运行(为了性能),自然也没机会在 EE Thread 上置“正在执行回调”的位。这意味着,从快路径 enter/leave 钩子中,profiler 只能调用异步安全的 Info 函数。这通常可以接受——一个对性能敏感到要求 enter/leave 直接函数调用的 profiler,大概率本来就不会在钩子里调用任何 Info 函数。

替代方案是:profiler 设置一个请求参数/返回值信息的标志,这会强制 profiling API 的一个 C 函数介入,为 profiler 的 Enter/Leave 钩子准备参数/返回值信息。设置了该标志时,profiling API 会在这个准备信息的 C 函数内部置上 EE Thread 位,从而允许 profiler 在其 Enter/Leave 钩子内调用同步Info 函数。

异步 Info 函数

异步 Info 函数是那些任何时候(无论在不在回调内)调用都安全的函数。这类函数相对较少,通常是劫持式采样 profiler(hijacking sampling profiler,如 Visual Studio profiler)想在某个采样点内调用的函数。

关键要求:标记为异步的 Info 函数必须能从任何可能的调用栈上执行。一个线程可能正持有任意数量的锁(自旋锁、ThreadStore 锁、OS 堆锁等)时被打断,然后被 profiler 强制通过一个异步 Info 函数重入运行时——这极易造成死锁或数据损坏。异步 Info 函数确保自身安全有两条路径:

  1. 极度简单:不取锁、不触发 GC、不访问可能不一致的数据等;或者
  2. 必要时做足前置检查:在函数顶部有充分的检查,确保锁、数据结构等处于安全状态后再继续。
    • 这通常包括询问“当前线程是否正处于 forbid suspend thread 区域(禁止挂起线程区域)内”,若是则返回错误退出——不过这并非在所有情况下都足够的检查。
    • DoStackSnapshot就是复杂异步函数的范例:它通过组合多种检查(包括上述 forbid suspend thread 区域判断)来决定继续执行还是放弃。

4.2 Info 函数的契约样板

每个 Info 函数顶部也必须有一段公共样板,原文档给出的标准示例:

CONTRACTL { // Yay! NOTHROW; // Yay! GC_NOTRIGGER; // Yay! MODE_ANY; // Yay! EE_THREAD_NOT_REQUIRED; // Yay! CANNOT_TAKE_LOCK; } CONTRACTL_END; PROFILER_TO_CLR_ENTRYPOINT_SYNC((LF_CORPROF, LL_INFO1000, "**PROF: EnumModuleFrozenObjects 0x%p.\n", moduleID));

注意:这些首选值大部分与回调的首选值相反!如果感到困惑,请重读本文第一节的设计哲学——回调要宽松、Info 要严格,二者互补。

4.3 Info 函数契约的首选值表

首选值原因细节
NOTHROW让 profiler 更容易调用;profiler 不需要自己写 try/catch如果你的被调函数都是NOTHROW,就用NOTHROW;否则,与其自己设 try/catch,不如直接标成THROWS——profiler 很可能可以通过在多个 Info 调用间共享一个 try 块来做得更高效
GC_NOTRIGGER让 profiler 在更多场景下调用更安全要尽力避免触发 GC。如果某个 Info 函数可能触发(例如加载一个尚未加载的类型),要尽可能提供一种让 profiler 指定“不走上触发路径”的方式(例如可设为 FALSE 的fAllowLoad参数),并按条件写入契约
MODE_ANY让 profiler 在更多场景下调用更安全如果参数或返回值是 ObjectID,MODE_COOPERATIVE是合理的;否则强烈首选MODE_ANY
CANNOT_TAKE_LOCK让 profiler 在更多场景下调用更安全确保你的被调函数不取锁;如果必须取锁,精确注释取了哪些锁
可选:EE_THREAD_NOT_REQUIRED允许 profiler 从 GC 回调以及 profiler 自建线程(如采样线程)调用该 Info 函数这些契约目前尚未被强制执行,留空也没问题。如果你比较确信该 Info 函数不需要(也不会调用需要)当前 EE Thread,可以写上EE_THREAD_NOT_REQUIRED作为日后线程契约强制执行时的提示

下面是一个“不那么 Yay”的真实感示例,展示每个契约都要注释原因的写法:

CONTRACTL { // ModuleILHeap::CreateNew throws THROWS; // AppDomainIterator::Next calls AppDomain::Release which can destroy AppDomain, and // ~AppDomain triggers, according to its contract. GC_TRIGGERS; // Need cooperative mode, otherwise objectId can become invalid if (GetThreadNULLOk() != NULL) { MODE_COOPERATIVE; } // Yay! EE_THREAD_NOT_REQUIRED; // Generics::GetExactInstantiationsFromCallInformation eventually // reads metadata which causes us to take a reader lock. CAN_TAKE_LOCK; } CONTRACTL_END;

这个示例与仓库现状高度吻合:在 proftoeeinterfaceimpl.cpp 中你能看到大量真实契约块,例如NonGenericTypeHandleToClassID使用NOTHROW; GC_NOTRIGGER; MODE_ANY;的组合(第 333-338 行),CoCreateProfiler则因需要加载资源字符串、写事件日志而声明THROWS; GC_TRIGGERS; MODE_ANY; CAN_TAKE_LOCK;(见 eetoprofinterfaceimpl.cpp)。这些都是“首选值”之外的按原因注释的活样本。

4.4 Info 函数的入口宏

契约之后应放入口宏。它负责日志记录;若是同步函数,还会查阅回调状态标志以强制确认它确实是在同步上下文中被调用。根据 Info 函数是同步、异步、还是只能在 Initialize 回调内调用,选用三者之一:

  • PROFILER_TO_CLR_ENTRYPOINT_SYNC(典型选择)
  • PROFILER_TO_CLR_ENTRYPOINT_ASYNC
  • PROFILER_TO_CLR_ENTRYPOINT_CALLABLE_ON_INIT_ONLY

在 proftoeeinterfaceimpl.cpp 中可以找到这些宏的实际定义:PROFILER_TO_CLR_ENTRYPOINT_ASYNC/_SYNC分别展开为带kP2EENone标志的_ASYNC_EX/_SYNC_EX变体,而_EX变体支持kP2EEAllowableAfterAttach等标志,用于控制该入口在 profiler 附加(attach)之后是否仍被允许;PROFILER_TO_CLR_ENTRYPOINT_CALLABLE_ON_INIT_ONLY内部则展开为异步宏并附带“仅限初始化”约束。

异步 Info 函数的额外负担:如前所述,异步 Info 方法很罕见且负担更重。上述首选契约在异步场景下“更加首选”,其中两条是硬性要求GC_NOTRIGGERMODE_ANYCANNOT_TAKE_LOCK在异步函数中比同步函数更受青睐,但并非总能满足——无法满足时请参考本文“异步 Info 函数”一节的应对方案(前置检查 + 失败返回)。

五、需要修改的文件清单

新增或修改方法的位置相当直白,代码审查(code inspection)即可摸清。以下是必须动工的几处:

5.1 corprof.idl —— 定义所有接口与类型

所有 Profiling API 接口与类型都定义在 src/coreclr/inc/corprof.idl。先到这里定义你的新类型和新方法。该文件是唯一的 IDL 事实来源,包含ICorProfilerCallback全系列、ICorProfilerInfo全系列、事件标志枚举(如前述COR_PRF_MONITOR_*,corprof.idl)以及COR_PRF_*各种枚举与结构体。新增事件标志时也要在此处枚举中登记。

5.2 EEToProfInterfaceImpl.* —— profiler 回调的包装器

ICorProfilerCallback实现的包装位于 src/coreclr/vm/eetoprofinterfaceimpl.cpp(配套 eetoprofinterfaceimpl.h 与 eetoprofinterfaceimpl.inl)。新增回调时:

  1. 在 corprof.idl 的ICorProfilerCallback接口中声明方法;
  2. eetoprofinterfaceimpl.*中实现对应的包装器方法,套用本文“回调契约样板”(NOTHROW; GC_TRIGGERS; MODE_PREEMPTIVE; CAN_TAKE_LOCK;+ASSERT_NO_EE_LOCKS_HELD())并紧跟CLR_TO_PROFILER_ENTRYPOINT入口宏;
  3. 在 CLR 中需要通知 profiler 的位置,用BEGIN_PROFILER_CALLBACK(CORProfilerTrackXxx())+(&g_profControlBlock)->XxxStarted(...)+END_PROFILER_CALLBACK()的模式触发回调。

该文件还承载了 profiler 加载逻辑(例如CoCreateProfiler通过FakeCoCreateInstanceEx创建 profiler 实例并取得ICorProfilerCallback2,见 eetoprofinterfaceimpl.cpp),是整个回调方向的枢纽。

5.3 ProfToEEInterfaceImpl.* —— ICorProfilerInfo 的实现

ICorProfilerInfo的实现位于 src/coreclr/vm/proftoeeinterfaceimpl.cpp(配套 proftoeeinterfaceimpl.h 与 proftoeeinterfaceimpl.inl)。新增 Info 函数时:

  1. 在 corprof.idl 的ICorProfilerInfo接口中声明方法;
  2. proftoeeinterfaceimpl.*中实现,套用本文“Info 契约样板”(NOTHROW; GC_NOTRIGGER; MODE_ANY; EE_THREAD_NOT_REQUIRED; CANNOT_TAKE_LOCK;),并按函数的同步/异步/仅初始化属性选择对应入口宏(PROFILER_TO_CLR_ENTRYPOINT_SYNC/_ASYNC/_CALLABLE_ON_INIT_ONLY)。

5.4 辅助设施(可参考)

  • src/coreclr/vm/profilinghelper.cpp 与 profilinghelper.h:ProfControlBlockg_profControlBlockDoOneProfilerIterationEvacuationCounterHolder以及BEGIN_PROFILER_CALLBACK/END_PROFILER_CALLBACK的语义说明都集中在此,新增回调时建议先通读其头部注释。
  • src/coreclr/vm/profdetach.cpp 与 profdetach.h:与“回调期间 pin 住 profiler 防止 detach”的机制相关,可帮助理解BEGIN_PROFILER_CALLBACK的 pin 行为。

六、实战核对清单

在为一个新 CLR 特性接入 Profiling API 时,可对照以下清单逐项自检:

  1. 回调方向(ICorProfilerCallback)

    • 在 corprof.idl 中声明新回调方法(必要时新增COR_PRF_MONITOR_*事件标志);
    • 在 eetoprofinterfaceimpl.cpp 中实现包装器;
    • 契约块含NOTHROW; GC_TRIGGERS; MODE_PREEMPTIVE; CAN_TAKE_LOCK;ASSERT_NO_EE_LOCKS_HELD(),每个契约有注释(能 “Yay!” 就 “Yay!”,否则注释原因);
    • 紧跟CLR_TO_PROFILER_ENTRYPOINT(带 ThreadID 参数的方法改用*_FOR_THREAD_*变体);
    • 调用点使用BEGIN_PROFILER_CALLBACK(CORProfilerTrackXxx())包裹。
  2. Info 方向(ICorProfilerInfo)

    • 在 corprof.idl 中声明新 Info 方法;
    • 在 proftoeeinterfaceimpl.cpp 中实现;
    • 契约块含NOTHROW; GC_NOTRIGGER; MODE_ANY;(必要时EE_THREAD_NOT_REQUIRED; CANNOT_TAKE_LOCK;),每个契约有注释;
    • 按同步/异步/仅初始化选择PROFILER_TO_CLR_ENTRYPOINT_SYNC/_ASYNC/_CALLABLE_ON_INIT_ONLY
    • 若标为异步,硬性满足GC_NOTRIGGER+MODE_ANY,并对锁定/数据一致性做前置检查(参考DoStackSnapshot的 forbid suspend 区域检查模式)。
  3. 性能底线

    • 校验只做“简单输入验证”(NULL 指针、并行参数一致性等),不做 ID 哈希校验——ID 就是指针强转,信任 profiler 是该 API 的既定性能设计。

遵循以上契约组合与文件分布,你新增的可分析特性就能与既有 Profiling API 保持一致的行为、文档与性能特征,也便于后续开发者通过注释理解每个契约选择的权衡。

【免费下载链接】runtime.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.项目地址: https://gitcode.com/GitHub_Trending/runtime6/runtime

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询