ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

.NET Runtime cDAC 数据契约解析:PlatformMetadata 契约的设计与实现

.NET Runtime cDAC 数据契约解析:PlatformMetadata 契约的设计与实现 .NET Runtime cDAC 数据契约解析PlatformMetadata 契约的设计与实现【免费下载链接】runtime.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.项目地址: https://gitcode.com/GitHub_Trending/runtime6/runtime导读本文深入剖析 .NET Runtime 仓库中 cDACData Contract Reader数据契约读取器体系下的PlatformMetadata契约。该契约是调试器与诊断工具跨平台解析目标进程的关键入口它对外暴露两个 API——GetPrecodeMachineDescriptor()与GetCodePointerFlags()分别描述目标平台 precode stub预编码桩的布局属性与代码指针code pointer的行为标志。阅读本文后你将掌握该契约的接口定义、Version 1 实现细节、VM 侧数据来源以及 PrecodeStubs 契约如何消费这些平台元数据完成 precode 识别与 MethodDesc 解析。背景契约在 cDAC 中的定位在 .NET Runtime 仓库中cDAC 是一套让诊断工具如 SOS、dotnet-dump在不依赖原生符号与特定运行时版本源码的前提下跨进程读取目标运行时内存的机制。核心思路是把读取什么数据、数据如何布局以**数据契约Data Contract**的形式形式化描述契约定义 API数据描述符Data Descriptor定义内存布局全局变量Global定义入口地址。相关设计文档集中在 docs/design/datacontracts 目录例如 PrecodeStubs.md 描述了 precode 相关的兄弟契约。PlatformMetadata正是这样一个契约它向调试端提供目标平台是什么、代码指针有什么特性、precode stub 的机器相关属性在哪里这一类跨切割面cross-cutting的平台元数据是所有依赖平台特性的诊断逻辑尤其是 precode 解析的公共数据源。契约 API 定义契约接口定义在 cDAC 托管侧抽象层IPlatformMetadata.cs。public enum CodePointerFlags : byte { HasArm32ThumbBit 0x1, HasArm64PtrAuth 0x2, } public interface IPlatformMetadata : IContract { static string IContract.Name { get; } nameof(PlatformMetadata); TargetPointer GetPrecodeMachineDescriptor() throw new NotImplementedException(); CodePointerFlags GetCodePointerFlags() throw new NotImplementedException(); }其中成员类型语义CodePointerFlags.HasArm32ThumbBit0x1目标进程运行在 arm32 上代码指针的低位携带 Thumb 位读取指令指针前需要将其屏蔽CodePointerFlags.HasArm64PtrAuth0x2目标进程使用 arm64e 指针认证pointer authentication代码指针中嵌入了 PAC 签名GetPrecodeMachineDescriptor()TargetPointer返回指向PrecodeMachineDescriptor结构的地址该结构描述平台特定的 precode stub 属性GetCodePointerFlags()CodePointerFlags返回描述代码指针行为的标志位契约文档中对该接口的正式描述见 PlatformMetadata.md契约的用途是 exposes properties that describe the target platform暴露描述目标平台的属性。Version 1 数据布局与实现数据描述符与全局变量契约 Version 1 依赖的数据布局来自文档中的生成表与 datadescriptor.inc 中的CDAC_TYPE_BEGIN(PlatformMetadata)区块一一对应Data DescriptorFieldTypeMeaningPlatformMetadataCodePointerFlagsuint8fields describing the behavior of target code pointersPlatformMetadataPrecodeMachineDescriptorpointerprecode stub-related platform specific propertiesGlobalTypeMeaningPlatformMetadatapointeraddress of the PlatformMetadata data该契约不依赖其他任何契约Contracts used:None是 cDAC 契约图中的基础节点。托管侧数据描述符类型定义在 Data/PlatformMetadata.cs[CdacType(nameof(DataType.PlatformMetadata))] internal sealed partial class PlatformMetadata : IDataPlatformMetadata { /// summaryAddress of the embedded PrecodeMachineDescriptor within this PlatformMetadata object./summary [FieldAddress] public partial TargetPointer PrecodeMachineDescriptor { get; } [Field] public partial byte CodePointerFlags { get; } }注意PrecodeMachineDescriptor使用[FieldAddress]特性它直接返回目标进程内嵌PrecodeMachineDescriptor对象的地址而非对象本身这样消费方可以用target.ProcessedData.GetOrAddData.PrecodeMachineDescriptor(...)按需惰性加载该对象。Version 1 契约实现Version 1 实现位于 Contracts/PlatformMetadata_1.csinternal readonly struct PlatformMetadata_1 : IPlatformMetadata { internal readonly Target _target; private readonly TargetPointer _cdacMetadataAddress; public PlatformMetadata_1(Target target) { _target target; _cdacMetadataAddress target.ReadGlobalPointer(Constants.Globals.PlatformMetadata); } TargetPointer IPlatformMetadata.GetPrecodeMachineDescriptor() _cdacMetadata.PrecodeMachineDescriptor; CodePointerFlags IPlatformMetadata.GetCodePointerFlags() (CodePointerFlags)_cdacMetadata.CodePointerFlags; }实现要点构造函数在创建契约实例时通过target.ReadGlobalPointer(Constants.Globals.PlatformMetadata)读取全局变量PlatformMetadata得到元数据对象的地址GetPrecodeMachineDescriptor()通过ProcessedData.GetOrAddData.PlatformMetadata(_cdacMetadataAddress)惰性获取解析后的PlatformMetadata数据对象再返回其中的PrecodeMachineDescriptor字段一个指针GetCodePointerFlags()将uint8原始字节强转成CodePointerFlags枚举。契约文档中给出了等价的参考实现逻辑TargetPointer GetPrecodeMachineDescriptor() { TargetPointer metadataAddress _target.ReadGlobalPointer(PlatformMetadata); return metadataAddress /* PlatformMetadata::PrecodeMachineDescriptor */ } CodePointerFlags GetCodePointerFlags() { TargetPointer metadataAddress _target.ReadGlobalPointer(PlatformMetadata); return (CodePointerFlags)_target.Readbyte(metadataAddress /*PlatformMetadata::CodePointerFlags*/); }两者完全一致都是先读全局指针定位元数据对象再按字段偏移读取目标内存。契约的全局变量与类型在 datadescriptor.inc 中注册为CDAC_GLOBAL_POINTER(PlatformMetadata, ::g_cdacPlatformMetadata)与CDAC_GLOBAL_CONTRACT(PlatformMetadata, c1)。VM 侧平台元数据从何而来契约读取的目标数据由运行时 VM 侧维护定义在 src/coreclr/vm/cdacplatformmetadata.hpp 与 src/coreclr/vm/cdacplatformmetadata.cpp。enum class CDacCodePointerFlags : uint8_t { None 0, HasArm32ThumbBit 0x1, HasArm64PtrAuth 0x2, }; struct CDacPlatformMetadata { PrecodeMachineDescriptor precode; CDacCodePointerFlags codePointerFlags; CDacPlatformMetadata() default; ... static void Init(); static void InitPrecodes(); }; GVAL_DECL(CDacPlatformMetadata, g_cdacPlatformMetadata);初始化逻辑按编译目标平台设置代码指针标志void CDacPlatformMetadata::Init() { #if defined(TARGET_ARM) (g_cdacPlatformMetadata)-codePointerFlags CDacCodePointerFlags::HasArm32ThumbBit; #elif defined(TARGET_ARM64) defined(TARGET_APPLE) // TODO set HasArm64PtrAuth if arm64e (g_cdacPlatformMetadata)-codePointerFlags CDacCodePointerFlags::None; #else (g_cdacPlatformMetadata)-codePointerFlags CDacCodePointerFlags::None; #endif } void CDacPlatformMetadata::InitPrecodes() { #ifndef FEATURE_PORTABLE_ENTRYPOINTS PrecodeMachineDescriptor::Init((g_cdacPlatformMetadata)-precode); #endif // !FEATURE_PORTABLE_ENTRYPOINTS }从源码结构看可以确认以下几点arm32TARGET_ARM下设置HasArm32ThumbBit因为 ARM 指令指针最低位是 Thumb 状态位arm64eTARGET_ARM64 TARGET_APPLE目前仍置为None代码中以 TODO 标注了若为 arm64e 则设置HasArm64PtrAuth即该标志位已预留但当前运行时尚未启用其他平台均为None0x0在启用FEATURE_PORTABLE_ENTRYPOINTS可移植入口点的构建中InitPrecodes()不填充 precode 描述符——这意味着该特性下 precode 布局由运行时统一管理无需平台特化描述。这两个初始化函数的调用时机位于 src/coreclr/vm/ceemain.cppCDacPlatformMetadata::Init()与CDacPlatformMetadata::InitPrecodes()分别在运行时启动序列的不同阶段被调用前者在第 628 行附近后者在第 836 行附近确保在 cDAC 可被诊断工具访问前g_cdacPlatformMetadata全局对象已就绪。VM 侧PrecodeMachineDescriptor的具体字段StubCodePageSize、StubPrecodeSize、FixupBytes、StubBytes、各 precode 类型字节等在 src/coreclr/vm/precode.cpp 中被 DAC 路径IsStubPrecodeByASM、IsFixupPrecodeByASM直接读取用于比对指令字节模式。cDAC 托管侧对应的数据对象定义在 Data/PrecodeMachineDescriptor.cs其中大部分字段使用[CustomInit]MaybeGetByte/MaybeGetBytes惰性读取——只有当目标类型信息DataType.PrecodeMachineDescriptor中实际存在该字段时才从目标内存读取否则返回null。这种设计保证了契约对不同运行时版本数据布局的容错字段缺失时消费方以null/0安全降级而不会崩溃。消费方PrecodeStubs 如何依赖 PlatformMetadataPlatformMetadata契约最大的消费方是PrecodeStubs契约。在 Contracts/PrecodeStubs_Common.cs 的构造函数中可以看到完整的依赖关系public PrecodeStubsCommon(Target target) { _target target; IPlatformMetadata pm target.Contracts.PlatformMetadata; TargetPointer descAddr pm.GetPrecodeMachineDescriptor(); MachineDescriptor target.ProcessedData.GetOrAddData.PrecodeMachineDescriptor(descAddr); _codePointerFlags pm.GetCodePointerFlags(); }即PrecodeStubs在构造时调用GetPrecodeMachineDescriptor()拿到PrecodeMachineDescriptor的地址通过GetOrAdd惰性解析该对象缓存为MachineDescriptor调用GetCodePointerFlags()缓存代码指针标志。随后这两个平台元数据被用于两个关键场景场景一代码指针归一化去 Thumb 位CodePointerReadableInstrPointer根据标志位把不可直接读取的代码指针转换成可读指令指针internal TargetPointer CodePointerReadableInstrPointer(TargetCodePointer codePointer) { if (_codePointerFlags.HasFlag(CodePointerFlags.HasArm32ThumbBit)) { return codePointer.AsTargetPointer ~1ul; } if (_codePointerFlags.HasFlag(CodePointerFlags.HasArm64PtrAuth)) { throw new NotImplementedException(CodePointerReadableInstrPointer for ARM64 with pointer authentication); } Debug.Assert(_codePointerFlags 0); return codePointer.AsTargetPointer; }arm32 目标屏蔽最低位 ~1ul得到真实的指令地址arm64e 指针认证当前实现直接抛NotImplementedException表明该路径尚未支持其他平台断言标志为 0 后原样返回。场景二precode 识别与 MethodDesc 提取Contracts/PrecodeStubs_1.cs 使用MachineDescriptor中的字节模式StubBytes/StubIgnoredBytes/FixupBytes/FixupIgnoredBytes与各 precode 类型字节通过逐字节比对识别指令地址处的 precode 类型Stub、PInvokeImport、Fixup、ThisPtrRetBuf、UMEntry、Interpreter、DynamicHelper再按StubCodePageSize定位 precode 之后的数据区从中读出MethodDesc、SecretParam等字段。这正是诊断工具从任意代码入口点回溯到方法元数据的核心路径。平台差异小结结合 VM 侧Init()逻辑与消费方处理PlatformMetadata所表达的平台差异可总结为目标平台CodePointerFlags对诊断逻辑的影响x86 / x64 / arm64非 AppleNone0代码指针可直接作为指令指针使用arm32TARGET_ARMHasArm32ThumbBit0x1读取指令前需 ~1ul屏蔽 Thumb 位arm64eTARGET_ARM64TARGET_APPLE预留HasArm64PtrAuth0x2当前为None未来需剥离 PAC 签名托管侧已预留分支但暂未实现此外FEATURE_PORTABLE_ENTRYPOINTS构建下InitPrecodes()不填充 precode 描述符PrecodeMachineDescriptor相关字段将保持空值消费方需按可移植入口点逻辑处理。总结PlatformMetadata是 .NET Runtime cDAC 契约体系中一处小而关键的平台抽象层VM 侧在 cdacplatformmetadata.cpp 维护g_cdacPlatformMetadata全局对象通过 datadescriptor.inc 注册类型与全局变量托管侧 IPlatformMetadata.cs 定义契约接口PlatformMetadata_1.cs 实现 Version 1下游 PrecodeStubs_Common.cs 与 PrecodeStubs_1.cs 消费其输出完成 precode 解析。理解这一契约是深入阅读 cDAC 其他平台相关诊断逻辑如代码版本、栈回溯的起点。【免费下载链接】runtime.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.项目地址: https://gitcode.com/GitHub_Trending/runtime6/runtime创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表