BUG: unable to handle kernel paging request 完整排查:dmesg 四要素与三路定罪
BUG: unable to handle kernel paging request at 地址 完整排查dmesg 四要素、坏内存与第三方模块定罪TL;DR你的服务器 dmesg 里冒出BUG: unable to handle kernel paging request at ffff...机器要么卡死重启要么某个进程被内核杀掉。这行日志本身不是病它是报告内核访问了一块它不该碰的内存。真正要做的有三件事先取四要素地址、RIP、Call Trace、Modules linked in再分三路定罪第三方模块、内存硬件、内核自身 bug最后决定是换内存条、卸模块还是升级内核。下面给可直接照抄的命令以及一句今晚就能做的自检。这句话在说什么打个比方内核运行在特权态可以访问几乎整个地址空间。当它访问一个既没映射、也不属于任何进程的地址时CPU 会触发缺页异常#PF内核发现自己在上层根本没资格碰这里就会打印这一行然后把当前进程杀掉如果当时正在中断上下文或持锁路径就升级成Kernel panic整机重启。所以你看到的不是某个程序崩了而是内核自己走错路了。这类问题要么是硬件内存坏了要么是别人喂了它坏数据第三方模块要么是它自己的 bug。第一步取出四要素崩溃之后机器往往已经重启所以要看上一次开机的日志dmesg -T | grep -iE -A40 unable to handle|NULL pointer dereference|general protection|Oops # 或 journalctl -k -b -1 | grep -i -A60 unable to handle从这一大段里你只需要盯四个东西要素长什么样用途出错地址at ffff880562ea7f00或CR2:那一行判断是内核地址高位全 f还是近空指针很小RIP / IP 行IP: [ffffffff811612e0] kmem_cache_alloc0x50/0xd0出事的函数名若末尾带[模块名]就是第三方模块Call Trace一串调用栈谁调用到它根因常常在更下面几层Modules linked in一长串模块名带PEO标记判断有没有加载树外/未签名模块这四行抓齐问题基本就定性了一半。下面所有排查都是围绕它们展开。措辞会随内核版本变别搜错关键词这行日志是个家族不同年代长得不一样。你搜不到答案很可能只是搜错了措辞。平台 / 版本实际措辞v3.10 ~ v4.19RHEL 6/7 时代BUG: unable to handle kernel paging request at addr如果是空指针则是BUG: unable to handle kernel NULL pointer dereference at addrv5.4 至今拆成两句BUG: kernel NULL pointer dereference, address: addr和BUG: unable to handle page fault for address: addr后面跟#PF: supervisor write access in kernel mode、#PF: error_code(0x0002) - not-present pageARM64Unable to handle kernel paging request at virtual address ffff000018664000加Mem abort info:、ESR 0x...、EC 0x25: DABT (current EL)ARM32Unable to handle kernel paging request at virtual address ebfeb0a0加Internal error: Oops: 80000005 [#1] PREEMPT SMP ARM打印这句的代码在arch/x86/mm/fault.c的show_fault_oops()里。x86 的缺页入口是同一个文件里的exc_page_fault()往下分do_kern_addr_fault()内核地址空间和do_user_addr_fault()用户地址空间。内核态修复失败后走kernelmode_fixup_or_oops()进入 oops 流程最终由kernel/exit.c的make_task_dead()收尾。顺带一个有用的细节make_task_dead()里有 oops 计数超过kernel.oops_limit默认 10000会直接panic(Oopsed too often)。反复 oops 本身会继续破坏内存现场所以取证要趁早。六个根因按排查性价比排序#根因怎么认怎么处置1第三方 / 树外内核模块RIP 或 Call Trace 行末尾带[模块名]Tainted:里带P/E/O升级或卸载该模块黑名单验证找模块厂商2内存硬件故障Tainted无异常但随机地址、随机函数崩EDAC/MCE 有记录memtester / memtest86 确认后换内存条3内核自身 bug崩在核心函数vma_stop、nf_conntrack 清理等无第三方模块升级到含修复的内核版本4内核栈溢出自写驱动崩在ffffffffffffffff这类地址RIP 是0xfffffffffffffffe把栈上的大结构体改kmalloc()5DMA / IOMMU 问题崩在arch_sync_dma_for_device、__clean_dcache_area_poc这类 DMA 路径查驱动的 DMA 映射用iommuoff做对照6写错地址 / 执行用户态代码自写模块按硬编码地址写sys_call_table日志有unable to execute userspace code (SMEP?)别硬编码内核符号地址KASLR 会变第 1 类在生产环境里占比最高。Red Hat 知识库对这个报错的结论十次里有七八次是你加载了第三方模块。几个真实案例一台 HP ProLiant DL380 Gen9 生产机崩溃在down_read()上Tainted: P E专有 未签名模块调用栈全是falcon_lsm_serviceable一个第三方安全模块。某 RHEL 7 环境崩溃在memcpy栈里全是_ZdlPv [falcon_lsm_serviceable]判定为第三方模块破坏了内存。某虚拟化环境里veeamdeferio进程崩溃RIP 落在模块地址Code:行显示Unable to access opcode bytes模块代码段已失效环境变量直接点名veeamsnap驱动。怎么快速确认是第三方模块干的lsmod | grep 模块名 # 它在不在 modinfo 模块名 # 看 license / vermagic cat /proc/sys/kernel/tainted # 内核污染位 sh tools/debugging/kernel-chktaint # 官方解码脚本内核污染位值得记一下P(1) 专有模块、B(32) 坏页标志、U(64) 用户态请求污染、D(128) 刚 oops 过、W(512) 内核告警、C(1024) staging 驱动、O(4096) 树外模块、E(8192) 未签名模块。怀疑内存三条命令从软到硬如果崩溃地址随机、崩的函数每次都不同、Tainted又很干净先怀疑内存条。# 1) 查硬件错误记录不用重启就能发现坏内存 dmesg | grep -iE edac|mce|machine check edac-util -v # edac-utils 包 ras-mc-ctl --summary --errors # rasdaemon 包 mcelog --client # mcelog 包 # 2) 在线压测能抓到正在使用的坏页不用停机 memtester 2G 5 # 3) 看内存条位置准备更换 dmidecode -t memory | grep -iE Size|Locator|Manufacturer|Serial|Part|Speed第 2 条最有说服力。Proxmox 论坛有个案例Intel NUC8 上 KVM 虚机被卡死内核崩在kvm_intel的vmx_handle_exitTainted: P D O。用户跑memtester 1G 5第 2 轮 Bit Flip 测试直接报Bit Flip : testing 49FAILURE: 0x00000040 ! 0x02000040 at offset 0x04fc8910内存故障坐实。宁可跑一次 memtester 花十几分钟也别在是内核 bug 还是硬件之间反复猜。如果机器可以停机维护用 U 盘启动 memtest86至少完整跑满一轮 pass最好过夜记下出错的物理地址交给硬件厂商。想更硬dmidecode拿到内存条序列号直接申请更换。逐步把 RIP 变成哪一行源码有了 RIP 的符号加偏移就能还原到具体源码行这一步能省掉大量猜测# 内核树自带需带符号的 vmlinux scripts/faddr2line vmlinux symbol0xoff/0xsize # 或 addr2line -e vmlinux -f -i addr # 有 vmcore 时用 crash 工具 crash bt # 调用栈 crash sym 地址 # 地址 → 符号 crash dis -lr 函数 # 反汇编并显示源码行 crash mod -t # 列出第三方模块及其污染标记没有 vmcore 的时候确认崩溃函数归属用这招grep -n 函数名 /boot/System.map-$(uname -r)出于这个目的生产机建议开 kdumpsystemctl status kdump崩溃后的 vmcore 落在/var/crash/。如果机器一崩就断电、什么日志都没留下那第一件事是接串口 console 或稳定 kdump而不是继续排查。还有一种情况要单独处理让 oops 直接变成 panic kdump避免反复 oops 把现场冲掉echo 1 | sudo tee /proc/sys/kernel/panic_on_oops这会让机器在第一次 oops 时就转储代价是少了一次自然恢复的机会取证场景值得。几个常见误区一看是NULL pointer dereference就以为不用管它和 paging request 是同一个家族只是地址小于一个页近空指针。该走的四要素流程一步都不能省。Not tainted就断定是内核 bugServerFault 上有个典型案例全新 Ubuntu 服务器每天随机崩一次崩在kmem_cache_alloc内核完全没被污染用户跑 memtester 也没查出错误帖子至今无解。排除第三方模块只是排除了一条路不等于答案就是内核 bug内存控制器层面的问题 memtester 不一定抓得到。在 ARM 平台上只看崩溃函数ARM64 的日志前面往往有Mem abort info:段先看EC 0x25: DABT还是IABT数据访问还是指令访问再看这条日志前面一条是哪个驱动打印的能大幅缩小范围。自写驱动把大结构体放内核栈内核栈通常只有 8K 或 16K远小于用户态。有案例是驱动里写了abc_T abc {}读写超过 32 字节就越界RIP 直接变成0xfffffffffffffffe函数指针被踩。今晚就能做的一件事把你机器的内核污染状态和最近的硬件错误记录查一遍两条命令echo taint$(cat /proc/sys/kernel/tainted); \ sh tools/debugging/kernel-chktaint 2/dev/null || cat /proc/sys/kernel/tainted; \ dmesg -T | grep -iE edac|mce|machine check|unable to handle|Oops | tail -30taint非 0尤其是带P/E/O先去查你加载了哪些第三方模块这是最常见的元凶这一步通常就能结案。taint为 0 但日志里有edac/mce记录走内存硬件路径跑 memtester准备换条。两样都干净但反复崩开 kdump 抓 vmcore把 RIP 用faddr2line还原成源码行再去对比内核版本和上游修复。一个完整排查 walkthrough三回合定罪场景一台云主机在夜里重启了早上看journalctl -k -b -1有一大段内核日志。第 1 回合取四要素。先从日志里摘出地址、RIP、Call Trace、Modules linked in。假设摘出来是这样BUG: unable to handle kernel paging request at ffffffffc079ad20 RIP: 0010:0xffffffffc079ad20 Code: Unable to access opcode bytes at RIP 0xffffffffc079acf6. Tainted: G O E地址落在ffffffffc0...这一段是模块地址区间RIP 不带符号名且Code:说读不到 opcode说明这段代码已经失效模块被卸载或代码段被回收。Tainted里的O树外模块和E未签名模块已经给出了方向。第 2 回合查是哪个模块。lsmod | grep -i 关键字 modinfo 模块名 grep -n 符号名 /boot/System.map-$(uname -r)如果反复发生用黑名单把它摘掉再观察echo blacklist 模块名 | sudo tee /etc/modprobe.d/blacklist-模块名.conf写完这个配置文件重启机器看日志里还犯不犯。第 3 回合如果摘掉模块还犯转向内存。查 EDAC/MCE再跑memtester 2G 5。出现FAILURE或Bit Flip就基本坐实硬件内存故障。到这里三条路各有了结论是第三方模块就升级或卸载是内存就换条两者都干净就升级内核并抓 vmcore。分诊表五分钟决定往哪条路走你观察到的最可能的方向下一步动作RIP / Call Trace 末尾带[模块名]第三方模块modinfo该模块黑名单验证Tainted含P/E/O专有 / 未签名 / 树外模块同上且别在生产环境保留树外模块崩溃地址随机、崩的函数每次都不同内存硬件EDAC/MCE → memtester → 换条崩在核心函数调度器、内存管理、网络清理内核自身 bug查System.map升级内核到含修复版本RIP 是0xfffffffffffffffe或地址全f栈溢出 / 野函数指针检查自写驱动的大局部变量崩在 DMA 相关函数DMA / IOMMU查驱动映射iommuoff做对照实验日志里有unable to execute userspace code (SMEP?)内核态试图执行用户态代码检查是不是在写sys_call_table这类硬编码地址kdump 落地清单让下次崩溃有据可查一崩就断电、事后什么都查不到是最难受的情况。按这四条配好下次崩溃会留下 vmcore[ ]systemctl enable --now kdump systemctl status kdump确认服务在跑[ ] 预留崩溃转储内存GRUB 里加crashkernel512M大内存机可加大[ ] 崩溃文件落在/var/crash/确认分区有足够空间[ ] 需要强制取证时临时开panic_on_oops1让第一次 oops 就转储拿到 vmcore 之后用crash工具把 RIP 还原成源码行crash bt、crash dis -lr 函数、crash mod -t。这一步做完报障给模块厂商或内核社区时对方几乎不用反问。内核源码里这句话是怎么出来的简读想彻底弄明白看arch/x86/mm/fault.c就够。CPU 触发缺页后进入exc_page_fault()按地址落在哪片空间分到do_kern_addr_fault()或do_user_addr_fault()。内核态的处理失败走kernelmode_fixup_or_oops()如果是坏地址进__bad_area_nosemaphore()/bad_area_nosemaphore()决定是给进程发 SIGSEGV 还是直接 oops。真正的打印发生在show_fault_oops()里它把地址、#PF解码、Tainted状态一次打全再由arch/x86/kernel/dumpstack.c的oops_begin()/oops_end()负责寄存器转储最后kernel/exit.c的make_task_dead()结束这个进程。明白了这条链路你就能反过来读日志show_fault_oops()打印的那些行本来就是内核在替你做完四要素采集。别把 Not tainted 当免罪符ServerFault 上那个案例值得记住一台全新 Ubuntu 服务器每天随机崩一次崩在kmem_cache_alloc内核完全没有被污染Not tainted用户跑memtester也没查出任何错误帖子至今没有答案。所以排查顺序是先排除能排除的不是排除完就一定有答案。第三方模块、内存硬件、内核 bug 这三条路各走一遍是为了把可能性收窄到某一条而不是保证一定落在某一条上。内存控制器层面的偶发问题在线 memtester 不一定抓得到得靠 EDAC/MCE 的长期记录或者用 memtest86 离线跑满一轮才能暴露。如果三条路都走干净了还在犯把下面这些整理好去内核社区或发行版厂商报障完整的那段 oops含地址、RIP、Call Trace、Modules linked in、Tainteduname -r的内核版本以及lsmod全量输出硬件型号、BIOS 版本、内存条信息复现条件多少并发、多久一次、有没有特定操作带上这些对方几乎不用反问直接就能对着你的栈去查 commit。出处内核源码arch/x86/mm/fault.c的show_fault_oops()/exc_page_fault()/do_kern_addr_fault()/do_user_addr_fault()/kernelmode_fixup_or_oops()arch/x86/kernel/dumpstack.c的oops_begin()/oops_end()kernel/exit.c的make_task_dead()kernel.org 文档Documentation/admin-guide/tainted-kernels.rst与tools/debugging/kernel-chktaintRed Hat 知识库7024709falcon 模块 down_read、6193041falcon 模块 memcpy、522043liscal 模块 update_curr、7024709 / 7087720veeamsnapProxmox 论坛 thread 52792kvm_intelvmx_handle_exit崩溃memtester 定位内存故障Arch Linux 论坛 thread 250210休眠唤醒后 soundcore / i915 崩溃BIOS 关闭 Deep Sleep 后消失LKML 2011-03fs/proc/task_mmu.c的vma_stop()未检查ERR_PTRLinus 给出的修复补丁Stack Overflow 55004444内核栈溢出导致ffffffffffffffff地址崩溃Stack Overflow 38195587、ServerFault 582750内存控制器故障与至今无解的悬案GitHub espressif/esp-hosted#544esp_hosted_ng树外模块的 DMA 路径崩溃本文首发于 CSDN 专栏《运维漏洞指南》。