恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
.NET 运行时 Profiling API 可分析性实现指南:从契约(Contracts)到回调/Info 接口的落地实践
首页
资讯中心
/
.NET 运行时 Profiling API 可分析性实现指南:从契约(Contracts)到回调/Info 接口的落地实践
.NET 运行时 Profiling API 可分析性实现指南:从契约(Contracts)到回调/Info 接口的落地实践
发布时间:2026/9/16 19:23:18
.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 两个方向上的接口给出了看似相反、实则自洽的建议ICorProfilerCallbackCLR → Profiler 的回调倾向于选择更“宽松”permissive即激进的契约组合。这给了 profiler 在回调内最大的灵活性——它可以在回调期间调用哪些 ICorProfilerInfo 方法取决于回调本身的契约有多宽松。ICorProfilerInfoProfiler → 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*直接转成AppDomainID、ClassID等。这一点在源码中有直接印证——proftoeeinterfaceimpl.cpp 中的MethodDescToFunctionID与FunctionIdToMethodDesc就是两个极简的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 传回一个伪造的 IDCLR 会直接访问违例AV。这是预期行为CLR 不会为了校验查找而去哈希 ID它假设 profiler 清楚自己在做什么。记住简单校验是必须的但“信任 profiler”是性能取舍下的既定设计。三、ICorProfilerCallbackCLR 向 profiler 通知事件的回调ICorProfilerCallback接口由 CLR 回调 profiler用于通知其感兴趣的事件。每个回调都被 EE 中的一个**薄包装方法wrapper**包裹该包装负责定位 profiler 对ICorProfilerCallback及其后续版本ICorProfilerCallback2、ICorProfilerCallback3……的实现并调用其对应方法。3.1 事件订阅机制SetEventMask 与 CORProfiler* 内联函数Profiler 通过调用ICorProfilerInfo::SetEventMask()/SetEventMask2()并设置对应标志位来订阅事件。Profiling API 保存这些选择并通过一组专门的**内联函数CORProfiler***向 CLR 暴露——这些函数对事件标志位做按位掩码mask判断。事件标志的枚举定义位于 src/coreclr/inc/corprof.idl例如COR_PRF_MONITOR_NONE 0x00000000COR_PRF_MONITOR_FUNCTION_UNLOADS 0x00000001COR_PRF_MONITOR_CLASS_LOADS 0x00000002COR_PRF_MONITOR_MODULE_LOADS 0x00000004COR_PRF_MONITOR_ASSEMBLY_LOADS 0x00000008COR_PRF_MONITOR_APPDOMAIN_LOADS 0x00000010COR_PRF_MONITOR_JIT_COMPILATION 0x00000020COR_PRF_MONITOR_EXCEPTIONS 0x00000040COR_PRF_MONITOR_GC 0x00000080COR_PRF_MONITOR_OBJECT_ALLOCATED 0x00000100在整个 CLR 代码库中你会看到大量“散落各处”的、以标志位为条件的回调调用。原文档给出的典型样板如下例如模块加载事件{ // check if profiler set flag BEGIN_PROFILER_CALLBACK(CORProfilerTrackModuleLoads()); // call the ProfControlBlock wrapper around the profilers 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_CALLBACK与END_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_profStatus、g_profControlBlock.pProfInterface使用无锁、volatile 的每线程计数器与状态读取以保证在不加锁的前提下安全判断“当前是否有 profiler、是否在跟踪某类事件”。BEGIN_PROFILER_CALLBACK与END_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));关键要点必须显式指定throws、triggers、mode、take_lock 的值并且回调上还必须有ASSERT_NO_EE_LOCKS_HELD()后者仅回调需要。这保证了我们能持续维护面向 profiler 编写者的文档准确性。每个契约必须有自己的注释能用“首选值”就注释// 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 参数、且该参数不一定等于当前 ThreadID的ICorProfilerCallback方法。使用时必须把 ThreadID 作为宏的第一个参数传入宏会用你的 ThreadID 而非GetThread()来断言“该 ThreadID 当前是否仍允许回调”即尚未对该 ThreadID 发出过ThreadDestroyed()。四、ICorProfilerInfoprofiler 反向调用 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 函数是那些任何时候无论在不在回调内调用都安全的函数。这类函数相对较少通常是劫持式采样 profilerhijacking sampling profiler如 Visual Studio profiler想在某个采样点内调用的函数。关键要求标记为异步的 Info 函数必须能从任何可能的调用栈上执行。一个线程可能正持有任意数量的锁自旋锁、ThreadStore 锁、OS 堆锁等时被打断然后被 profiler 强制通过一个异步 Info 函数重入运行时——这极易造成死锁或数据损坏。异步 Info 函数确保自身安全有两条路径极度简单不取锁、不触发 GC、不访问可能不一致的数据等或者必要时做足前置检查在函数顶部有充分的检查确保锁、数据结构等处于安全状态后再继续。这通常包括询问“当前线程是否正处于 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 在更多场景下调用更安全如果参数或返回值是 ObjectIDMODE_COOPERATIVE是合理的否则强烈首选MODE_ANYCANNOT_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_ASYNCPROFILER_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_NOTRIGGER与MODE_ANY。CANNOT_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。新增回调时在 corprof.idl 的ICorProfilerCallback接口中声明方法在eetoprofinterfaceimpl.*中实现对应的包装器方法套用本文“回调契约样板”NOTHROW; GC_TRIGGERS; MODE_PREEMPTIVE; CAN_TAKE_LOCK;ASSERT_NO_EE_LOCKS_HELD()并紧跟CLR_TO_PROFILER_ENTRYPOINT入口宏在 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 函数时在 corprof.idl 的ICorProfilerInfo接口中声明方法在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.hProfControlBlock、g_profControlBlock、DoOneProfilerIteration、EvacuationCounterHolder以及BEGIN_PROFILER_CALLBACK/END_PROFILER_CALLBACK的语义说明都集中在此新增回调时建议先通读其头部注释。src/coreclr/vm/profdetach.cpp 与 profdetach.h与“回调期间 pin 住 profiler 防止 detach”的机制相关可帮助理解BEGIN_PROFILER_CALLBACK的 pin 行为。六、实战核对清单在为一个新 CLR 特性接入 Profiling API 时可对照以下清单逐项自检回调方向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())包裹。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_NOTRIGGERMODE_ANY并对锁定/数据一致性做前置检查参考DoStackSnapshot的 forbid suspend 区域检查模式。性能底线校验只做“简单输入验证”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),仅供参考