Intel 06H家族MCE错误码增量解码实战:从MCi_STATUS到故障定位 凌晨三点被叫起来处理服务器重启的人应该都见过这样一行日志mce: [Hardware Error]: Machine check: Bank 4: b800000000020000后面往往还跟着 TSC、CPUID、微码版本之类的信息。新人看到这个的第一反应是翻手册第二反应是懵——因为同一串十六进制在不同 CPU 型号上可能代表完全不同的硬件故障。我在系统软件这一行干了十几年几乎每年都会遇到几次因为机器检查错误Machine Check Exception简称 MCE引发的线上事故。处理这类问题的第一步从来不是急着换硬件而是先把 CPU 通过这串值想告诉你的话读出来。这篇文章就聚焦其中最容易让人犯迷糊的一个概念06H 处理器家族用于机器检查的机器错误码也就是 Intel 文档里经常出现的 Machine Check Error Codes 和 Incremental Decoding Information。我尽量用实际操作的语言讲清楚两件事这串错误码到底怎么编码的以及拿到错误码之后如何一步步“增量解码”出真实故障。它适合三类人看被 MCE 日志折磨过的服务器运维、写固件或内核的工程师、以及想在实验室里复现和验证硬件错误的同学。1. Machine Check 机制先弄明白 CPU 在“汇报”什么1.1 从 #MC 异常说起CPU 内部有大量用于错误检测的硬件单元比如缓存的一致性校验、总线事务监控、微码校验、内存控制器的 ECC 逻辑等。它们一旦发现异常就会触发一个机器检查异常编号 18也就是常说的 #MCMachine Check Exception。#MC 异常发生后处理器会把当时的错误信息记录在一组专门的寄存器里这整套机制叫 Machine Check Architecture缩写是 MCA。MCA 的核心思路其实很朴素CPU 在自己的“工作日志”里记一条事故报告然后通过异常通知操作系统去读取。报告内容包括错误发生在哪个监测点对应哪个 Bank、错误的严重程度、错误码、发生错误的地址以及一些额外的补充信息。操作系统或底层固件把这些寄存器读出来再依据 CPU 厂商提供的解码规则翻译成工程师能看懂的语言这才算完成一次完整的错误处理流程。这里面有几个概念必须分清楚MCG (Machine Check Global) 寄存器组负责全局状态和能力汇报典型的是 IA32_MCG_CAP能力和 IA32_MCG_STATUS当前状态。MCi (Machine Check per Bank) 寄存器组每个监测点对应一组寄存器包括 IA32_MC0_CTL、IA32_MC0_STATUS、IA32_MC0_ADDR、IA32_MC0_MISC后续 Bank 依此类推。每一组描述一个错误源。操作系统在响应 #MC 异常时核心动作就是遍历所有 Bank把 MCi_STATUS 寄存器读出来检查有效性标志位然后根据内容决定是记录日志、尝试恢复还是直接停机。这也是为什么我们看所有 MCE 日志时总能看到“Bank N”这个字段——它表示 CPU 的哪一个监测点报了警。1.2 MCi_STATUS 的结构状态位 错误码MCi_STATUS 是一个 64 位寄存器也是整个解码过程的信息源。它分两大部分高位的状态标志以及低 32 位的错误编码。我把最关键的状态位整理成一张速查表位域名称含义bit 63VALVALID值为 1 表示本 Bank 记录有效可以解码bit 62OVEROVERFLOW值为 1 表示有多个错误旧的被覆盖了bit 61UCUNCORRECTED1 表示错误未被纠正bit 60ENERROR ENABLED1 表示该错误对应的上报是使能的bit 59MISCV1 表示 IA32_MC0_MISC 寄存器里有补充信息bit 58ADDRV1 表示 IA32_MC0_ADDR 寄存器里有错误地址bit 57PCCPROCESSOR CONTEXT CORRUPT1 表示处理器上下文可能已损坏bit 56SSIGNALING1 表示这是通过机器检查异常信号上报的bit 55ARACTION REQUIRED1 表示软件必须采取行动bits 31:16MCA Error Code16 位机器检查错误码bits 15:0Model-Specific Error Code16 位模型相关错误码这里最容易误读的就是 bits 31:16 和 bits 15:0 的这一组也就是错误码字段。前者是通用错误码后者是某个具体 CPU 实现特有的错误码。很多运维朋友拿到的日志只有 Bank 号和 Status没注意 MCi_MISC 和 MCi_ADDR 是否有效就急着判断故障这是不对的。正确顺序一定是从标志位开始一层一层往下读。1.3 为什么读出来的十六进制“不是人话”理论上只要对照 Intel 开发手册 SDM 的 Machine-Check Error Codes 表就能把 16 位错误码翻译成人话。但实际做起来困难不少同一段错误码在 Family 06H 的老型号和新型号上语义可能完全不同一些简单的内部错误码比如 0x0005 Internal Parity Error如果不结合 CPU Model、Bank 编号以及低 16 位模型相关错误码根本无法判断到底是缓存、TLB、微码还是互连总线出了问题。这就引出了“增量解码”的必要性。Intel 文档在给 06H 处理器家族描述机器错误码时专门有“Incremental Decoding Information”这样的小节目的就是告诉工程师解码不是一步到位的而是由粗到细、逐级加上上下文信息最终收敛到具体故障单元。后面我会专门用一整节解释这个解码路径。2. 06H 处理器家族解码前先看清平台2.1 Family 06H 指的不是“酷睿第六代”先破除一个最常见的误解。这里的“06H”是 CPUID 指令返回的 Family 编号跟我们日常说的“酷睿第六代”完全是两码事。从 Pentium ProP6开始一直到后来的 Core 2、Nehalem、Sandy Bridge、SkylakeIntel 的 CPUID Family 值一直是 06HFamily 编号的十六进制写法就是 0x06。所以“06H 处理器家族”是一棵巨大的家族树里面横跨了二十多年的产品代次。Linux 下可以用 lscpu 或者直接读 /proc/cpuinfo 查到 CPU 的完整签名。比如一行常见的信息PROCESSOR 0:206a7 TIME ... SOCKET 0 APIC 4 microcode 1f这里的0x206a7是一个 CPUID Signature翻译过来就是 Family 6、Model 0x2A、Stepping 7对应的是 Sandy Bridge 架构的一颗处理器。换句话说它确实属于 06H 家族但要真正查它的机器检查错误码表必须精确到 Model 甚至 Stepping。2.2 06H 家族内部的 MCA 差异比想象中大既然 Family 06H 覆盖这么多代产品它的 Machine Check 架构自然也是“大同小异细节千差万别”。大方向是通用的都有 MCG 寄存器组、都有多个 Bank、错误码都按照 simple/compound 的方式组织。但具体到每个 ModelBank 数量不同。老的 P6 可能只有几个 Bank现代的 Ice Lake 平台 Bank 数量明显更多因为错误源变多了。错误源类型不同。比如 Nehalem 引入了集成内存控制器的错误报告Sandy Bridge 增加了 interconnectQPI相关 Bank再往后又有 PCIe Root Port、UPI、M2I 等新的报告源。模型相关错误码bits 15:0不同。即使通用错误码相同模型相关字段能补充的信息也完全不一样。这也是为什么网上很多“MCE 错误码大全”之类的文章不靠谱——它们往往只列了某个年代的通用表拿到新一代 CPU 上直接套最后的结论经常是错的。2.3 文档为什么专门给 06H 家族写一节“增量解码”Intel SDM 在描述 Machine Check Architecture 时正文先给一套通用解码规则然后在讲到具体处理器家族时又单独给出“Incremental Decoding Information”或者类似标题的小节并且标注适用范围是 Family 06H。这样安排的动机很简单通用错误码只能回答“错误属于哪一个大类”剩下的细节必须依赖处理器实现无法在通用层面展开。“增量”这个词我的理解是信息粒度逐步增加。初次解码时你手里只有 MCi_STATUS 里那 16 位通用错误码这是一级信息。要继续定位就要加上 CPU Model、Bank 编号、低 16 位模型相关错误码可能还要读 MCi_MISC、MCi_ADDR甚至深入寄存器看位域。这些“后面加进来的信息”就是增量拿增量信息去细化错误定位就是增量解码。后面我会用实际例子把这条路径走一遍。3. 增量解码信息到底在解什么3.1 错误码的第一级简单错误码 vs 复合错误码解码的第一步永远是判断 MCA Error Code 属于简单错误码还是复合错误码因为这两种码的解析方式完全不同。判定方法也很直接看错误码的最高几位是不是全 0。简单错误码是指那些结构相对固定、一眼就能查表的错误典型值包括MCA Error Code含义常见平台0x0000No error无错误记录0x0001Unclassified无法分类的错误0x0002Microcode ROM Parity Error微码 ROM 奇偶校验错误0x0003External Bus Error外部总线错误0x0004FRC Error冗余时钟/功能冗余检查错误0x0005Internal Parity Error内部奇偶校验错误0x0006Internal Timer Error内部定时器错误这些值在不同文档版本里可能会微调完整的表必须以对应处理器版本的 SDM 为准。我上面列的是出现频率最高、也是老工程师脑子里烙得最深的一组。复合错误码则复杂得多它本身还携带错误类型、事务类型、参与方等子字段。比如典型的 Bus and Interconnect Error高 5 位用来表示这是一个总线和互连类错误剩余位会进一步描述“谁发起的请求”“谁响应的”“目标是多少”“读还是写”“数据还是指令”“内存还是 IO”等细节。这种复合错误码的出现说明 CPU 已经识别到一个相对具体的错误场景接下来的工作就是把各个子字段逐一解出来。3.2 什么是“增量”从大类到具体单元的逐级收敛用查字典来类比简单错误码相当于你直接翻到正文某个词条看到就结束了。复合错误码相当于你先查到“目录”知道这个词条属于“计算机系统”这个大类接下来还得去“计算机系统的硬件错误”这一节里找详细解释最后再根据附加信息定位到具体一行——这就是“增量”的感觉。增量解码的完整路径我是这样走的读 MCG_CAP确认平台支持哪些能力比如是否有扩展状态、是否支持 Local MCE(LMCE) 等。读 MCi_STATUS先看 VAL、UC、EN、PCC、AR 这些标志位判断错误的严重性与是否可恢复。看 MCA Error Code 的高位判断是简单码还是复合码。如果是复合码拆解错误类型、参与方、事务类型子字段得到“大类 场景”的信息。再用 bits 15:0 的模型相关错误码结合当前 CPU 的 Model、Stepping、Bank 编号去查 SDM 中对应 Model 的“Incremental Decoding”表。所谓的“增量解码信息”在我理解里就是把第 4 步和第 5 步衔接起来的那部分文档内容以及你实际需要收集的全部上下文信息。它不是一个特殊寄存器而是一套解码方法论。3.3 增量解码需要收集哪些“上下文”我在实际排查时发现很多人只盯着 Status 寄存器里的错误码忽略了上下文信息导致解码结果模棱两可。要正确定位一个 06H 家族的机器检查错误至少要收集下面这些CPU 的 Family / Model / Stepping 编号。这个直接决定你该翻哪年代的错误码表。Bank 编号。不同 Bank 负责不同的错误源同一个错误码在内存控制器 Bank 和核心缓存 Bank 上含义不同。MCi_MISC 是否有效。有效时里面可能有地址类型、错误地址级别等信息。MCi_ADDR 是否有效。有效时记录的是故障地址能辅助判断是内存、缓存还是其他单元。MCG_CAP 中的支持标志。有些错误码只有在特定扩展能力开启时才会被记录。微码版本。CPU 微码更新后某些 model-specific 错误码的解释会发生变化查旧表格容易得出错误结论。这七要素缺一不可。特别是微码版本我踩过坑一个老的 Xeon 平台如果微码没有更新错误码的某些位根本无法触发更新微码后同样的错误码会被解释成更深一层的内存控制器故障。所以看到陌生错误码时先问一句这台机器的 BIOS 和微码是不是最新4. 实操手把手解码一组错误码4.1 从日志中找回现场正常生产环境中MCE 日志会被 mcelog 或 rasdaemon 采集然后写入系统日志。我建议每个维护服务器的人都提前把这些工具部署好不要等出事了才去翻 dmesg。假设某天你看到一条日志mce: [Hardware Error]: Machine check: Bank 4: b800000000020000 mce: [Hardware Error]: TSC 1295a21c1e83 mce: [Hardware Error]: PROCESSOR 0:206a7 TIME 1700000000 SOCKET 0 APIC 4 microcode 1f这已经足够启动解码流程了。机器内核打印的这块信息很标准Bank 4、Status 0xb800000000020000、CPU 签名 0x206a7、微码版本 0x1f。接下来我们来拆这串 Status。4.2 手动拆位演练先把 0xB800000000020000 转成二进制按位切开。高 32 位 0xB8000000 对应二进制 1011 1000 0000 0000 0000 0000 0000 0000低 32 位 0x00020000 对应 0000 0000 0000 0010 0000 0000 0000 0000。对照 MCi_STATUS 的位定义可以整理成这个表位域值字段解读bit 631VAL记录有效可以解码bit 620OVER没有发生覆盖只有本错误bit 611UC错误未被纠正bit 601EN错误上报使能bit 591MISCVMCi_MISC 中有补充信息bit 580ADDRV没有有效地址bit 570PCC处理器上下文未被破坏bit 560S非信号方式记录bits 31:160x0002MCA Error Code属于简单错误码bits 15:00x0000Model-Specific Error Code无进一步的模型信息到这里我们已经能读出第一层含义这是一个有效的未纠正错误发生在 Bank 4错误码 0x0002对应简单错误码表里的 Microcode ROM Parity Error。而且因为 PCC 位为 0理论上处理器上下文没有被破坏如果系统软件实现了恢复机制这类错误有希望做容错处理。4.3 再走一遍增量解码路径第一层解码只是给出了“微码 ROM 奇偶校验错误”这个方向但这颗 CPU 是 Family 06H Model 2A 的平台Bank 4 在这个平台上到底对应哪个微码单元还需要增量信息来补充。接着做这几件事确认微码 ROM 所在的错误源。在 Sandy Bridge 这个平台上Bank 4 通常对应核心内部某个错误源日志里同时出现了 MISCV 为 1所以我还应该去读 IA32_MC4_MISC 的值确认补充信息。查这个 Model 对应的 SDM 表格。里面会把 0x0002 这个简单错误码在 Model 2A 上的实际表现写清楚比如指向哪些微码段、是某个执行单元解码时触发的还是 MROM 取指时触发的。交叉验证 mcelog 的自动解码输出。mcelog 对 Intel CPU 会按 CPUID 选择正确的解码表输出结果比自己翻文档快得多。正常情况下走到第 3 步就能得到比较可靠的结论。如果 mcelog 报出来也是 Microcode ROM Parity Error并且频率不高通常是一个可以被软处理的错误如果同一错误反复出现就要考虑降级 CPU 或更换处理器了。4.4 工具链建议mcelog 和 rasdaemon生产环境我建议主用 mcelog它在 Intel 平台上的解码支持非常成熟支持按 CPUID 自动匹配错误码表。启动方式是让它常驻读取 /dev/mcelog再把解析结果写入系统日志。比如mcelog --daemon --logfile/var/log/mcelog.log对于新内核我更倾向用 rasdaemon它基于 tracepoint 直接订阅 MCE 事件不依赖 /dev/mcelog还能把记录存进 SQLite 数据库方便历史查询rasdaemon -r -d ras-mc-ctl --summary ras-mc-ctl --errors无论用哪个关键是把“自动解码”当成辅助而不是唯一依据。自动工具偶尔会因为内核版本或 CPU 型号太新而解码失败这时候手动拆位的功夫就派上用场了。5. 常见问题与排查经验5.1 错误码速查经验我把自己平时最容易遇到的几种情况整理一下场景状态特征惯用处理内存 ECC 错误ADDRV1Address 是物理内存地址MISCV 指向内存地址模式优先换内存微码 ROM 错误错误码 0x0002常出现在核心相关 Bank先升级 BIOS/微码再看频率总线互连错误复合错误码错误类型为 Bus and Interconnect检查 CPU 插槽、QPI/UPI 链路内部奇偶校验错误码 0x0005需要增量解码确认具体单元多颗故障则整机排查corrected MCE 持续增长UC0记录不断出现不要忽略硬件正在衰老提前安排维护这里要注意同一个错误码出现的频率比单次出现更重要。偶尔一次 corrected MCE 可能由电压波动等瞬时因素引起持续增长则说明硬件有实际退化。5.2 容易踩的坑先列几个我见过很多次的典型错误拿旧文档套新型号。这件事最坑。06H 家族的 Model 差异极大老文档里的 Bank 映射表放到新平台基本是废纸。只盯错误码忽略 Bank 编号。同样的 0x0001 Unclassified Error在核心 Bank 和内存控制器 Bank 里的处理思路完全不同。不看 PCC 就判断可恢复。PCC1 时必须按“上下文已损坏”处理不能随便继续跑。忽略 MCG_CAP。有些扩展功能比如 Local MCE、CMCI会影响错误上报方式不看能力位直接解码容易把“可纠正错误”误判成“不可纠正”。看到 0x0000 以为没错误。某些日志的 Status 会显示 0x0000这通常表示对应 Bank 没有有效记录别当成“正常错误码”去查。5.3 生产环境中的处理顺序最后把我处理生产 MCE 的固定动作写出来供参考先确认现场信息完整日志、CPU 型号CPUID Signature、BIOS 版本、微码版本、内存型号和插槽关系。判断错误的严重性看 UC、PCC、AR 三个标志位。UCPCC 是最坏组合需要尽快安排停机维护。手动拆 Status完成第一层解码。借助 mcelog 或 rasdaemon 做增量解码定位到 Bank 对应的硬件单元。如果定位到内存用 EDAC 信息和物理地址映射确认 DIMM 插槽准备更换。如果定位到 CPU 内部单元先检查运行环境供电、散热再考虑降级或更换 CPU。无论结论如何把原始日志、微码版本和 BIOS 版本一起存档后续排查同批次设备时非常有用。做了这么多年系统软件我最深的体会是机器检查错误码不是用来“背”的而是用来“查”的。关键是掌握解码路径和上下文收集方法知道每一步该向 CPU 要什么信息。现在我处理 MCE 的固定动作是先攒够现场四件套——Bank、Status、Addr、Misc再谈具体解码。还有一个私人心得遇到 corrected error 千万别因为“可纠正”就松口气持续增长的 corrected MCE 往往是大故障的前兆比单次 Uncorrected 更值得警惕。