资讯动态

.NET runtime Profiler API 破坏性变更详解:代码版本化、ApplyMetadata 与 ReJIT on Attach 的三条记录

发布时间:2026/9/17 15:12:57 来源:尧图企业网站定制
.NET runtime Profiler API 破坏性变更详解代码版本化、ApplyMetadata 与 ReJIT on Attach 的三条记录【免费下载链接】runtime.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.项目地址: https://gitcode.com/GitHub_Trending/runtime6/runtime本文基于 .NET runtime 仓库中的官方变更记录 Profiler Breaking Changes.md逐条剖析 Profiler API 至今为止的三处破坏性变更Code Versioning代码版本化对 JIT 事件模型的影响、ICorProfilerInfo7::ApplyMetadata引入的 GC 约束以及 ReJIT on attach 后“被 ReJIT 的方法永不内联”的新语义。读完本文Profiler 作者可以明确知道每一处变更的原因、可观察的行为差异、受影响的回调与 API以及应当如何调整自己的 Profiler 实现以兼容。一、这份文档是什么Profiler API 的破坏性变更台账runtime 仓库将 Profiler 相关设计文档集中在 docs/design/coreclr/profiling/ 目录下。其中 Profiler Breaking Changes.md 的定位很明确随着时间推移runtime 需要不断修改 Profiler API该文档就是这些破坏性变更breaking changes的官方记录原文只有一句总述Over time we will need to modify the Profiler APIs, this document will serve as a record of any breaking changes.目前台账中登记了三条记录Code Versioning 功能引入的破坏性变更详情见仓库中的 code-versioning-profiler-breaking-changes.md为支持在模块加载后追加新类型与新方法ICorProfilerInfo7::ApplyMetadata现在可能触发 GC且不能在 GC 无法发生的场景例如ICorProfilerCallback::RootReferences回调中调用为支持 attach 后的 ReJIT被 ReJIT 的方法从此不再被内联永久生效。由于内联被阻止也不会再收到ICorProfilerCallback::JITInlining回调。以下三节按台账顺序逐条展开并结合仓库源码与关联设计文档补充实现层面的细节。二、变更一Code Versioning 打破了“一个方法只 JIT 一次”的隐含假设2.1 背景FunctionID 与 JIT 事件的 1:1 关系被打破Code Versioning 破坏性变更的完整说明在 docs/design/features/code-versioning-profiler-breaking-changes.md。该文档解释了根本原因代码版本化——尤其是它用于分层编译tiered compilation时——会让 runtime 比过去调用 JIT 的次数更多。历史上FunctionID与单个JITCompilationFinished事件之间是 1:1 关系对使用 ReJIT API 的 Profiler 来说1:1 关系建立在(FunctionID, ReJITID)对与单个[Re]JITCompilationFinished事件之间。[Re]JITCompilationStarted事件通常也是 1:1 的但并不保证在所有情况下成立。而分层编译会在单个方法上潜在地多次调用 JIT直接打破了这些不变量。2.2 Profiler 作者大概率会观察到的行为变化原变更文档列出了 5 条“大概率可见”的行为变化这里完整继承并逐条解释JIT 事件变多JITCompilation*事件以及潜在的 ReJIT 编译事件会比以前更多。依赖“每个方法只收到一次编译完成事件”的 Profiler 逻辑需要按(FunctionID, ReJITID)甚至多个版本代码体来建立状态机。事件线程变化这些 JIT 事件可能来自后台工作线程background worker thread该线程可能与最终运行 JIT 代码的线程不同。依赖“在发起线程的回调上下文中访问线程局部状态”的 Profiler 需要注意线程切换。ICorProfilerInfo4::GetCodeInfo3只返回第一个 JIT 代码体对给定的(FunctionID, rejitID)对只返回首个 JIT 代码体的信息后续的代码体需要新的 API 才能获取。ICorProfilerInfo4::GetILToNativeMapping2同样只覆盖第一个 JIT 代码体IL 到原生代码的映射查询同理后续代码体的映射需要新 API 支持。JITCompilationStarted中提供的 IL 会被验证在JITCompilationStarted回调中提供的 IL现在会像ModuleLoadFinished中提供的 IL 一样被验证。这意味着之前“只要运行时不真正执行就不会报错”的宽松路径不再存在。2.3 小概率但确实可能遇到的变化原变更文档还列了两条更隐蔽的情况同样值得 Profiler 作者知晓如果分层编译在一个已经通过RequestReJIT插桩并 JIT 过的方法上未能发布更新的代码体Profiler 可能会收到一个报告该问题的 rejit 错误回调。官方说明这种情况只应在 OOM 或进程内存损坏时发生。ReJITCompilationFinished的触发时机被调整为略早新代码体生成之后、更新旧 JIT 代码以修改控制流之前。这带来一个很小但真实存在的可能性在 OOM 或进程内存损坏时rejit 错误回调可能出现在ReJITCompilationFinished之后。作者在该文档末尾也提醒行为变化可能还有其他未想到的变体如果进一步测试或代码评审发现新问题会持续补充到此文档——这正体现了该文档作为“活台账”的性质。三、变更二ICorProfilerInfo7::ApplyMetadata可能触发 GC且不能在RootReferences中调用3.1 动机attach 之后还要改元数据这条变更记录的上下文是“允许在模块加载后添加新类型和新方法”的工作。传统上Profiler 作者的指导是在ICorProfilerCallback::ModuleLoadFinished回调中做元数据改写。但当 Profiler 不是在模块加载时 attach 的这正是 ReJIT on attach 场景这条指导就失效了。为此runtime 现在允许在任意时刻进行一组特定的元数据修改只要随后调用ICorProfilerInfo7::ApplyMetadata提交即可。合法的修改清单见 ReJIT on Attach.md包括DefineUserString、DefineTypeRefByName、DefineMemberRefDefineTypeDefDefineMethod——仅限新类型上的方法或现有类型上的非虚方法DefineNestedType——仅限新类型DefineCustomAttribute、DefinePinvokeMap、DefineModuleRefDefineField——仅限新类型DefineEvent、DefineMethodImpl仅限新类型、DefineMethodSpec同时文档明确了两类绝对非法的修改向现有类型添加虚方法、向现有类型添加字段。未列入合法或非法清单的修改属于未测试状态不应假定其可以工作。3.2 代价ApplyMetadata可能触发 GC允许在任意时机提交上述元数据修改意味着 runtime 内部可能需要执行 GC例如元数据表的更新涉及需要回收或重新分配的内部结构。由此产生的破坏性约束是ApplyMetadata不能在 GC 无法发生的上下文中调用典型例子就是ICorProfilerCallback::RootReferences回调。为什么RootReferences是典型受限场景从 Profiler 接口的定义来看RootReferences的设计目的就是让 Profiler 建立完整的对象引用图runtime 调用它并传入根对象信息Profiler 再顺着引用继续展开。回调本身的契约是受限的——接口定义中明确指出RootReferences调用期间返回的 ObjectID 并不有效Profiler 不应试图在回调中检查对象见 corprof.idl 中RootReferences的文档注释。从源码结构看这类构建引用图的回调运行在 GC 被挂起的约束上下文里因此任何可能分配、可能触发 GC 的 API 都不允许在其中执行。如果你的 Profiler 需要修改元数据必须在RootReferences之类的受限回调之外例如普通回调或自有线程的消息处理中调用ApplyMetadata。四、变更三ReJIT on attach 后被 ReJIT 的方法永不内联也没有JITInlining回调4.1 背景与关键 APIICorProfilerInfo10::RequestReJITWithInlinersProfiler 作者长期以来希望能在 attach 之后再 ReJIT 已编译的方法这在桌面 .NET 上因非平凡的技术原因从来不可行但 CoreCLR 的若干变更使其变为可能。为此引入了ICorProfilerInfo10::RequestReJITWithInliners接口声明见 corprof.idlHRESULT RequestReJITWithInliners( [in] DWORD dwRejitFlags, [in] ULONG cFunctions, [in, size_is(cFunctions)] ModuleID moduleIds[], [in, size_is(cFunctions)] mdMethodDef methodIds[]);它概念上与ICorProfilerInfo4::RequestReJIT工作方式相同但会自动 ReJIT 历史上内联过目标方法的任何方法。dwRejitFlags取值来自以下枚举见 corprof.idltypedef enum { // ReJITted methods will be prevented from being inlined COR_PRF_REJIT_BLOCK_INLINING 0x1, // This flag controls whether the runtime will call GetReJITParameters // on methods that are ReJITted because they inline a method that was requested // for ReJIT COR_PRF_REJIT_INLINING_CALLBACKS 0x2 } COR_PRF_REJIT_FLAGS;调用方必须设置COR_PRF_REJIT_BLOCK_INLINING。COR_PRF_REJIT_INLINING_CALLBACKS则控制当某个方法是因为“它内联了被请求 ReJIT 的方法”而被 ReJIT 时是否对它调用ICorProfilerCallback4::GetReJITParameters默认不设置收不到这类回调而对显式请求的方法始终会收到。4.2 破坏性语义内联被“永久”阻止这是台账第三条记录的核心被 ReJIT 的方法从此不再被内联ever。实现方式是 runtime 现在全局阻止被 ReJIT 的方法被内联——即使它是通过旧的ICorProfilerInfo4::RequestReJIT而非新 API发起的。Profiler 不再需要监视 JIT 回调来手动阻止内联发生。相应地由于内联被阻止不会再有ICorProfilerCallback::JITInlining回调。如果你的 Profiler 之前依赖JITInlining回调来识别内联关系、或据此更新插桩这条路径对 ReJIT 过的方法将彻底消失。解除方式当方法通过ICorProfilerInfo4::RequestRevert被还原时未来的 JIT 会再次对其进行内联。但要注意RequestRevert的坑还原时激活的是原始原生代码其中包含内联进去的未 ReJIT 的旧 IL。例如方法 A 内联了方法 B两者都被 ReJIT 后行为正常但若随后只还原 A 而保留 B 的 ReJITA 的原始代码会把 B 的原始未修改IL 重新内联回来。因此官方建议想“还原”某个方法时不如再次对它调用RequestReJIT并在GetReJITParameters中提供原始 IL避免推理内联序列。已知限制未被解除可收集类型collectible与动态方法dynamic methods仍不可 ReJIT。即使你不直接对它们调用RequestReJIT只要你想 ReJIT 的方法 A 曾被内联进可收集/动态方法 B就仍没有办法让 B 调用更新后的 A。4.3 与变更记录第二、三条的联动这三条记录实际上是同一演进方向的不同侧面attach 后插桩能力增强ReJIT on attach→ 需要 attach 后改元数据ApplyMetadata合法化随之而来 GC 约束→ 底层 JIT 事件模型本身也在变化Code Versioning。Profiler 作者做兼容性改造时应把三者放在一起考虑事件数量与线程模型变了第二节、元数据修改的时机与 GC 约束变了第三节、内联与JITInlining回调行为变了第四节。五、相关文档与源码索引变更台账原文docs/design/coreclr/profiling/Profiler Breaking Changes.mdCode Versioning 破坏性变更详情docs/design/features/code-versioning-profiler-breaking-changes.mdReJIT on attach含ApplyMetadata合法修改清单docs/design/coreclr/profiling/ReJIT on Attach.mdProfiler 接口定义RequestReJITWithInliners、COR_PRF_REJIT_FLAGS、RootReferences契约src/coreclr/inc/corprof.idl更多 Profiler 背景阅读Profiler Attach on CoreCLR.md、IL Rewriting Basics.md、davbr 博客存档ReJIT - The Basics.md【免费下载链接】runtime.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.项目地址: https://gitcode.com/GitHub_Trending/runtime6/runtime创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价