ARTICLE DETAIL

资讯详情

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

.NET 运行时 Cross DAC 深度解析:如何用 Windows 调试工具分析 *nix 转储文件

.NET 运行时 Cross DAC 深度解析:如何用 Windows 调试工具分析 *nix 转储文件 .NET 运行时 Cross DAC 深度解析如何用 Windows 调试工具分析 *nix 转储文件【免费下载链接】runtime.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.项目地址: https://gitcode.com/GitHub_Trending/runtime6/runtime导读本文深入解析 .NET 运行时dotnet/runtime中的Cross DAC机制——一种交叉编译的调试器组件DAC它在 Windows 上编译执行却用于调试其它架构目标产生的进程转储。文章以 docs/design/features/cross-dac.md 为骨架结合src/coreclr下的真实源码crosscomp.h、daccess.h、CMake 构建脚本等系统讲解 Cross DAC 的设计约束、HOST/TARGET条件编译原则、跨平台类型布局的坑EMPTY_BASES与DAC_ALIGNAS、缺失类型补齐策略、目标侧栈展开libunwind 交叉编译以及构建入口。读完本文你将掌握 Cross DAC 的架构全貌理解为何类型布局一致是它的生命线并能据此在 Windows 主机上构建、调试针对 *nix 目标的 DAC/DBI。Cross DAC 是什么crossdac是一个交叉编译的 DACData Access Component。普通 DAC 运行在与目标进程相同的平台上而 Cross DAC 的特点是编译成在一种平台上执行调试不同架构/平台目标产生的转储。以当前仓库的落地形态为例现有 Cross DAC 全部满足三个条件见 cross-dac.md编译运行在Windows上同位数Target 与 Host 位数相同例如 x64 对 x64目标为*nix 变体Linux 等。其直接价值是开发者可以用 Windows 上的调试工具如 WinDbg / cdb SOS直接分析来自 *nix 进程的 dump 文件而无需把 dump 搬到对应架构的 Linux 机器上也无需在 Linux 上维护一套调试环境。设计约束与限制仅支持转储调试不支持活进程文档明确指出为了避免解决远程调用remoting与同步synchronization问题Cross DAC不支持 live process活动进程调试只支持 dump 调试。这是因为活动进程调试需要与目标进程建立实时通道并同步线程/内存状态跨平台远程化会引入大量复杂性与脆弱性而转储文件是静态快照DAC 只需要做只读的内存解析。DAC 必须与 Runtime 精确匹配与普通 DAC 一样每个 Cross DAC 必须与它的运行时runtime版本严格匹配——结构体布局、偏移、符号都必须一一对应。为了支撑这一点DAC 被索引在**符号服务器symbol server**上调试器可按需自动获取对应版本的 DAC。这也是为什么构建产物需要打包并上传到符号服务器见下文构建入口一节。条件代码选择HOST vs TARGETCross DAC 本质上是对 DAC 的 C 代码做简单交叉编译——同一份源码用不同的宏组合编译两次。关键是把HOST_*与TARGET_*宏配置成不同组合。语义上HOST指正在运行调试器的平台的架构/操作系统TARGET指产生代码转储的平台。文档给出的两条核心编码准则绝大多数代码应基于TARGET_*做条件编译。原因很直接我们希望 DAC 在交叉编译时行为与原生编译时保持一致——它解析的是目标平台的内存布局逻辑必须按目标平台走。只有涉及主机侧服务的代码才基于HOST条件编译。这类东西通常是文件 I/O 与内存分配——它们在调试器进程里执行必须使用主机平台的实现。在 src/coreclr/CMakeLists.txt 中可以看到交叉组件的编译开关是如何落地的if(CLR_CROSS_COMPONENTS_BUILD) add_definitions(-DCROSS_COMPILE) ... set(FEATURE_CROSSBITNESS 1) endif(CLR_CROSS_COMPONENTS_BUILD)CROSS_COMPILE宏会进一步联动到 src/coreclr/inc/crosscomp.h 中自动推导交叉编译状态。该文件开头的逻辑非常值得一读#if (!defined(HOST_64BIT) defined(TARGET_64BIT)) || (defined(HOST_64BIT) !defined(TARGET_64BIT)) #define CROSSBITNESS_COMPILE #ifndef CROSS_COMPILE #define CROSS_COMPILE #endif #endif #if defined(TARGET_UNIX) !defined(HOST_UNIX) !defined(CROSS_COMPILE) #define CROSS_COMPILE #endif也就是说只要 HOST 与 TARGET 的位数或操作系统不同CROSS_COMPILE就会被自动定义——这是让编译器替你找出所有该按 TARGET 条件化的代码这套策略的基础设施。初代实现策略让编译器抱怨文档记录了当初的实现方法论假设所有代码都应该基于TARGET条件化然后让编译器来报错。即先把代码统一按目标平台编译凡是编译不过的使用了主机平台才有的服务、类型、头文件就是漏网之鱼再逐个用HOST条件修正。这种编译器驱动的方式比人工逐行审计要可靠得多。类型布局Cross DAC 的生命线DAC 本质上是一个带辅助功能的内存解析工具。要让它在目标内存上读出正确的结构DAC 中类型的布局必须与运行时中对应类型的布局完全一致。然而C 标准并没有明确所有数据结构的内存布局规则由于从 C 演进而来大多数结构体布局直观易懂但较新、较复杂的结构体则不那么一致实验证明继承场景下布局会随编译器不同而变化。DAC 不支持通用多继承这简化了问题但它支持带空基类的多继承——而问题恰恰集中在这里。问题一空基类Empty Base Classgcc默认启用空基类优化EBO把多个空基类本应单独占用的 1 字节空间消除掉Windows 编译器默认不做这个优化为了保持二进制向后兼容Windows 编译器允许显式开启该优化我们的代码通过EMPTY_BASES宏来启用对应 MSVC 的__declspec(empty_bases)并且必须施加到每一个拥有多个基类或从这样的结构派生的结构上。在仓库中EMPTY_BASES被广泛应用在 CoreCLR 的基础容器与哈希结构上例如src/coreclr/inc/shash.h哈希表基类src/coreclr/inc/slist.h、src/coreclr/inc/sbuffer.h、src/coreclr/inc/sstring.h、src/coreclr/inc/sarray.hsrc/coreclr/vm/crossloaderallocatorhash.h这些都是典型从带空基类的类派生的场景漏掉一个EMPTY_BASES布局就会在 Windows 与 gcc 之间出现偏移差异DAC 读到的字段就会错位。问题二派生类首成员的填充复用Padding Reuse当基类以填充padding结尾时gcc编译器会复用这段 padding 来放置派生类的第一个成员——相当于在派生类中抹掉了基类的尾部填充Windows 编译器不会抹掉这段填充结果是同一类型在两平台上的派生部分布局不同。解决办法是使用DAC_ALIGNAS(a)宏放在派生类的第一个元素之前强制 gcc 把该成员对齐到指定值从而保留基类的 padding。参数a优先使用基类的类型名但如果编译器因为循环布局问题circular layout不允许用基类名a可以退而引用一个已知类型例如int64_t、int32_t、size_t等。DAC_ALIGNAS的正式定义在 src/coreclr/inc/daccess.h// For cross compilation, controlling type layout is important // We add a simple macro here which defines DAC_ALIGNAS to the C11 alignas operator // This helps force the alignment of the next member // For most cross compilation cases the layout of types simply works // There are a few cases (where this macro is helpful) which are not consistent across platforms: // - Base class whose size is padded to its align size. On Linux the gcc/clang // layouts will reuse this padding in the derived class for the first member // - Class with an vtable pointer and an alignment greater than the pointer size. // The Windows compilers will align the first member to the alignment size of the // class. Linux will align the first member to its natural alignment #define DAC_ALIGNAS(a) alignas(a)注释中还点出了第三种不一致场景带 vtable 指针且对齐要求大于指针大小的类——Windows 编译器会把首成员对齐到类的对齐大小而 Linux 只按自然对齐。DAC_ALIGNAS在仓库中有大量实际使用点例如 src/coreclr/inc/utilcode.hDAC_ALIGNAS(CHashTable)、src/coreclr/md/inc/metamodel.h、src/coreclr/md/inc/liteweightstgdb.h、src/coreclr/md/inc/recordpool.h、src/coreclr/md/inc/stgpool.h 等——元数据metadata模型是布局敏感的典型领域。用 DacCompareNativeTypes 定位布局问题文档作者编写并使用过DacCompareNativeTypes位于 dotnet/diagnostics 仓库的src/tests/DacCompareNativeTypes来发现和识别类型布局问题。其工作方式与经验要点工具比较 crude粗糙但确实完成了任务不要直接拿libcoreclr.so比较——它的符号太多速度非常慢加速技巧先比较dac库再比较dbi库的结构体布局。这能天然过滤掉大量无关数据结构不是所有差异都是真问题编译器会生成不同的调试数据与隐藏数据结构工具尽力忽略它们另外有些结构是 host-only 的预期就应不同通常在调试器里运行该工具以便查看工具保留的其它元数据如源文件与行号辅助判断差异来源。缺失/不同的类型crosscomp.h 的补位作用存在一些由 Target 定义、但在 Host 上缺失或不同的类型。此时需要在 src/coreclr/inc/crosscomp.h 中定义交叉编译类型。原设计文档以T_CRITICAL_SECTION为关键示例当时 host 与 target 都支持 critical section但 DAC 需要正确映射目标平台的数据结构因此需要定义一个目标的CRITICAL_SECTION类型并配套宏把需要目标定义的引用与可能需要主机定义的引用区分开同时还有防御性编程如T_CRITICAL_SECTION_VALIDATION_MESSAGE来校验这些结构体的准确性。需要说明的是在当前仓库的crosscomp.h中T_CRITICAL_SECTION这一具体符号已不复存在属于该设计笔记记载的历史做法但其为目标类型补齐宿主缺失定义的职责被完整继承并发扬光大。现在crosscomp.h为各种交叉组合如非 ARM 主机管理 ARM 目标、AMD64 主机管理 ARM64 / LoongArch64 / RISC-V64 目标定义了完整的T_CONTEXT、T_RUNTIME_FUNCTION、T_DISPATCHER_CONTEXT、T_KNONVOLATILE_CONTEXT_POINTERS系列类型并在主机与目标一致时回退为原生别名#define T_CONTEXT CONTEXT #define PT_CONTEXT PCONTEXT #define T_DISPATCHER_CONTEXT DISPATCHER_CONTEXT #define PT_DISPATCHER_CONTEXT PDISPATCHER_CONTEXT另一个值得一提的当代示例是DAC_MUTEX_MAX_SIZE与tgt_minipal_mutex同样位于crosscomp.h尾部因为 Crst 类型内嵌互斥锁为了跨 OS 编译 DAC必须保证互斥锁在目标侧有一致的字节大小。为此不同目标平台分别定义了DAC_MUTEX_MAX_SIZE例如 TARGET_LINUX 为 64、TARGET_APPLE 为 96、TARGET_WINDOWS 64 位为 40并用 union 让 DAC 构建时只保留目标平台的 padding 布局、不把主机的minipal_mutex数据布局带进来struct tgt_minipal_mutex final { union { #ifndef DACCESS_COMPILE minipal_mutex _mtx; #endif // This is unused padding to ensure struct size. alignas(void*) BYTE _dacPadding[DAC_MUTEX_MAX_SIZE]; }; };并且在非交叉编译时用static_assert校验DAC_MUTEX_MAX_SIZE不小于minipal_mutex的实际大小——这正是文档所说防御性编程确保结构体准确的当代版本。进程外栈展开交叉编译 libunwind要完整支持原生栈处理Cross DAC 还需要一个目标平台的 unwinder栈展开器。为此libunwind也被一并交叉编译。相关构建逻辑位于 src/coreclr/CMakeLists.txtadd_subdirectory(${CLR_SRC_NATIVE_DIR}/external/libunwind_extras ${CLR_ARTIFACTS_OBJ_DIR}/external/libunwind)即通过libunwind_extras子目录将 libunwind 编入交叉组件构建使 DAC 在 Windows 上运行时也能按照目标 *nix 平台的 unwinding 规则如 ARM64 的.xdata/RUNTIME_FUNCTION表解析目标进程的原生栈帧。crosscomp.h中定义的T_RUNTIME_FUNCTION、T_DISPATCHER_CONTEXT正是这类 unwind 元数据在目标平台的投影。DBI 一并交叉编译文档特别澄清术语文中DAC一词同时涵盖 DAC 与 DBIDebug Interface。两者其实都被交叉编译了。DBI 是调试器与 DAC 之间的托管侧接口层因此Cross DAC工程事实上是Cross DAC Cross DBI的完整调试组件交叉编译。构建入口与产出链路构建入口让 Windows 构建可以指定目标 OS主要的构建系统改动是为 Windows 构建增加设置 Target OS 的能力。在 src/coreclr/build-runtime.cmd 中新增-os参数解析if /i %1 -os (set __TargetOS%2shiftshiftgoto Arg_Loop)脚本默认__TargetOSwindows随后会把CLR_CMAKE_TARGET_OS、CLR_CMAKE_TARGET_ARCH等参数传给 CMake见同文件中的__ExtraCmakeArgs组装逻辑从而在 Windows 主机上产出针对 Linux 等目标的 DAC 组件。在 eng/Subsets.props 中定义了新的 subsetCrossDacPackSubsetName IncludeCrossDacPack OnDemandtrue DescriptionPackaging of cross OS DAC. Requires all assets needed to be present at a folder specified by $(CrossDacArtifactsDir). See Microsoft.CrossOsDiag.Private.CoreCLR.proj for details. /注意它是OnDemand按需的 subset并且依赖$(CrossDacArtifactsDir)目录中已存在的全部资产打包逻辑由Microsoft.CrossOsDiag.Private.CoreCLR.proj负责。此外文档提到官方构建official build也有相应改动设置这些标志、打包产物并上传到符号服务器——这正对应前文限制一节所说的DAC 按符号服务器索引、调试器按需获取。客户端调试器侧改动要消费新的 crossdacDAC 的各类客户端调试器宿主也需要相应改动——文档明确表示这些超出本文范围这里不再展开。总结Cross DAC 的技术要点一览主题关键要点仓库证据定位Windows 编译执行、同位数、调试 *nix dumpcross-dac.md限制仅 dump不支持 liveDAC 必须匹配 runtime符号服务器索引cross-dac.md条件编译默认按TARGET_*条件化仅主机服务I/O、分配按HOST_*crosscomp.h、CMakeLists.txt空基类EMPTY_BASES__declspec(empty_bases)施加到每个多基类/派生结构shash.h、slist.h、sstring.h 等派生首成员DAC_ALIGNAS(a)保留基类 paddinga优先基类名可退化为int64_t等daccess.h、utilcode.h缺失类型crosscomp.h定义T_CONTEXT/T_RUNTIME_FUNCTION等目标类型tgt_minipal_mutex统一互斥锁尺寸crosscomp.h栈展开libunwind 一并交叉编译CMakeLists.txt构建build-runtime.cmd -os指定目标 OSCrossDacPacksubset 打包上传符号服务器build-runtime.cmd、Subsets.propsCross DAC 是 .NET 调试体系里一块精巧的编译期工程 布局纪律拼图它不靠运行期魔法而是靠在交叉编译时严格执行类型布局与目标一致的纪律配合编译器驱动的条件化策略与DacCompareNativeTypes之类的布局体检工具最终让一套 Windows 调试工具链得以解析 *nix 世界的内存快照。对于想深入 CoreCLR 调试基础设施、或需要在 Windows 上分析 Linux dump 的开发者crosscomp.h、daccess.h与相关 CMake 构建脚本是最值得精读的入口。【免费下载链接】runtime.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.项目地址: https://gitcode.com/GitHub_Trending/runtime6/runtime创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表