新闻详情

新闻详情

首页 / 资讯中心 / 详情

从零搭建QEMU+ARM64内核调试环境:让panic可控可分析

发布时间:2026/9/27 13:58:40来源:尧图网络
从零搭建QEMU+ARM64内核调试环境:让panic可控可分析
1. 为什么我要从零搭一套能随时 panic 的内核调试环境搞内核开发的人大概都有过这种体验改了两行代码编译半小时烧到板子上串口一片死寂连个 panic 信息都没打出来。你盯着那块开发板怀疑是驱动写错了、时钟配错了、还是压根没启动起来。这种黑盒调试的痛苦是逼着我搭一套本地可复现调试环境的直接原因。这套环境的核心诉求就一个让内核想崩就崩而且崩了我能立刻看到现场。听起来有点反直觉——别人都在追求稳定为什么我要追求随时能 panic因为内核调试的本质不是让系统跑起来而是让系统在出问题时把话说清楚。一个能稳定复现 panic、能挂上 gdb 单步、能随时查看寄存器和调用栈的环境比一块看起来能跑但一出问题就失联的板子有价值得多。具体来说这套环境要满足几个硬指标。第一可复现同样的代码、同样的操作每次都能触发同样的崩溃点不能是偶发的玄学问题。第二可观测panic 发生时完整的调用栈、寄存器状态、内存映射都要能拿到最好还能在崩溃前打断点。第三可回滚改坏了内核几分钟就能换回上一个能启动的版本不用重新烧写整个系统。第四低成本不需要额外的硬件一台普通的开发机就能跑起来。基于这几个诉求我最终选定的方案是QEMU 模拟 ARM64 自编译 Linux 内核 gdb 远程调试。这套组合的好处在于QEMU 把硬件抽象掉了你不需要关心具体的开发板型号、外设驱动、启动流程差异自编译内核让你能随意加 printk、加断点、改 panic 行为gdb 则提供了源码级的调试能力比串口打印高效太多。这篇文章是16 集内核课的第一集我会把从零搭建这套环境的完整过程讲清楚包括工具选型背后的逻辑、每一步操作的意图、以及我在实际搭建中踩过的坑。不管你是刚接触内核的新手还是想换一套更顺手的调试环境的老手这套流程都能直接抄作业。提示本文所有操作基于 x86_64 主机 ARM64 目标架构主机系统以 Ubuntu 22.04/24.04 为例。如果你用其他发行版包管理命令需要相应调整但核心逻辑完全一致。2. 工具链选型为什么是 QEMU 自编译内核 gdb 这套组合2.1 QEMU 在调试场景里到底扮演什么角色很多人对 QEMU 的印象停留在虚拟机觉得它就是个跑系统的模拟器。但在内核调试场景里QEMU 的价值远不止于此。它本质上是一个全系统模拟器能模拟 CPU 指令集、内存控制器、中断控制器、串口、定时器等一整套硬件。这意味着你可以在完全没有物理开发板的情况下跑起一个完整的内核。更关键的是QEMU 提供了两个对调试极其友好的能力。第一个是-s -S参数-s会在 1234 端口开启 gdb server-S会让 CPU 在启动时暂停等待 gdb 连接。这两个参数组合起来你就能在内核执行第一条指令之前就挂上调试器实现真正的从零开始调试。第二个是-d系列参数可以 dump 出 CPU 状态、内存访问、中断触发等信息配合-D输出到文件用于事后分析。我选 QEMU 而不是其他模拟器比如 Renode、Bochs主要考虑三点。一是架构支持全ARM64、RISC-V、x86_64 都能模拟换架构不用换工具。二是社区活跃内核社区大量使用 QEMU 做 CI 测试遇到问题容易找到答案。三是与 gdb 集成成熟QEMU 的 gdb stub 实现稳定断点、单步、内存读写都很可靠。2.2 为什么不用现成的发行版内核非要自己编译这是新手最容易偷懒的地方直接拿 Ubuntu 的 ARM64 内核镜像塞进 QEMU 不就行了能跑但调试体验很差。原因有几个。第一发行版内核默认关闭了大量调试选项。比如CONFIG_DEBUG_INFO生成调试符号、CONFIG_GDB_SCRIPTS提供 gdb 辅助脚本、CONFIG_KGDB内核内置调试器这些发行版为了性能和体积通常都不开。没有调试符号gdb 里看到的全是地址没法对应到源码行。第二发行版内核的 panic 行为被定制过。很多发行版配置了panic_timeoutpanic 后自动重启你还没来得及看现场系统就重启了。自编译内核可以设成 panic 后死等方便你慢慢分析。第三你需要能改代码。调试过程中经常要加 printk、改断言、甚至故意制造 panic 来验证调试链路。发行版内核你改不了自编译内核想怎么改就怎么改。第四编译过程本身就是学习。从make defconfig到make menuconfig再到make -j你会直观感受到内核的配置体系和构建流程这对理解内核结构帮助很大。2.3 gdb 版本选择和远程调试的连接逻辑gdb 这边有个坑版本要够新。老版本 gdb 对 ARM64 的支持不完善加载内核符号时可能报错或者解析错误。我实测下来gdb 13.2 及以上版本对 ARM64 内核调试支持比较稳。Ubuntu 24.04 自带的 gdb 版本通常够用如果太老就自己编译一个。远程调试的连接逻辑是这样的QEMU 启动时通过-s在本地 1234 端口监听gdb 通过target remote :1234连上去。连上之后gdb 就能读写目标机的内存、寄存器设置断点单步执行。这里要注意gdb 加载的符号文件必须是和 QEMU 里跑的内核完全一致的那份 vmlinux否则地址对不上断点会设到错误的位置。还有一个细节内核开启了 KASLR地址空间随机化的话每次启动内核加载地址都不一样gdb 里的符号地址就对不上了。调试环境里建议在启动参数里加nokaslr关掉它保证地址固定。工具版本要求作用选型理由QEMU6.0模拟 ARM64 硬件gdb stub 稳定架构支持全Linux 内核5.15被调试对象长期支持版文档全gdb13.2源码级调试ARM64 支持完善交叉编译工具链gcc-aarch64-linux-gnu编译 ARM64 内核Ubuntu 官方源直接装3. 环境搭建实操从装包到第一次成功启动3.1 主机依赖安装与交叉编译工具链配置第一步是把主机上的依赖装齐。Ubuntu 下一条命令搞定sudo apt update sudo apt install -y build-essential git flex bison libssl-dev libelf-dev \ qemu-system-arm gcc-aarch64-linux-gnu gdb-multiarch \ bc rsync cpio这里解释几个容易忽略的包。flex和bison是内核构建时的词法/语法分析工具缺了会在编译早期报错。libssl-dev和libelf-dev是内核签名和 ELF 解析需要的。bc是个计算器工具内核构建脚本用它做算术运算看着不起眼但缺了会中断编译。cpio用于生成 initramfs。gdb-multiarch这个包很关键。普通 gdb 可能只支持主机架构调试 ARM64 目标需要多架构版本。装好之后用gdb-multiarch命令启动而不是gdb。交叉编译工具链装好后验证一下aarch64-linux-gnu-gcc --version能打印出版本信息就说明工具链就绪。这里有个经验工具链版本和内核版本要匹配。太新的工具链编译老内核可能报 warning 甚至 error太老的编译新内核可能不支持某些特性。我一般用 Ubuntu 官方源的版本兼容性最稳。3.2 内核源码获取与最小化配置源码我建议用内核官方的长期支持版LTS比如 6.1 或 6.6。下载和解压wget https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.6.tar.xz tar -xf linux-6.6.tar.xz cd linux-6.6接下来是配置。直接用defconfig生成 ARM64 默认配置make ARCHarm64 CROSS_COMPILEaarch64-linux-gnu- defconfig但默认配置不够用要手动开几个关键选项。用menuconfig进去改make ARCHarm64 CROSS_COMPILEaarch64-linux-gnu- menuconfig需要确认开启的选项Kernel hacking→Compile-time checks and compiler options→Compile the kernel with debug info对应CONFIG_DEBUG_INFOKernel hacking→Generic Kernel Debugging Instruments→KGDB: kernel debugger对应CONFIG_KGDBKernel hacking→Tracers下的相关选项按需开Device Drivers→Character devices→Serial drivers→ARM AMBA PL010 serial port supportQEMU 的串口还有一个隐藏坑CONFIG_DEBUG_INFO开了之后默认可能用DEBUG_INFO_REDUCED模式符号信息不全。要确保CONFIG_DEBUG_INFO_REDUCED是关闭的CONFIG_DEBUG_INFO_DWARF4或DWARF5开启这样 gdb 才能拿到完整的类型和行号信息。配置改完保存退出配置文件在.config。建议把它备份一份下次重编译直接复用。3.3 编译内核并生成可调试的 vmlinux编译命令make ARCHarm64 CROSS_COMPILEaarch64-linux-gnu- -j$(nproc)-j$(nproc)用满所有 CPU 核心能显著加快编译。第一次编译大概 10 到 30 分钟取决于机器性能。编译产物里最关键的是vmlinux。这是带调试符号的未压缩内核 ELF 文件gdb 加载的就是它。另外arch/arm64/boot/Image是压缩后的内核镜像QEMU 启动时加载的是这个。两者必须来自同一次编译否则符号对不上。编译完检查一下 vmlinux 有没有调试符号file vmlinux输出里应该包含with debug_info, not stripped。如果显示stripped说明调试符号没生成回去检查CONFIG_DEBUG_INFO配置。注意编译过程中如果报No rule to make target debian/canonical-certs.pem之类的错误是因为内核配置里引用了发行版的证书路径。解决办法是在 menuconfig 里把CONFIG_SYSTEM_TRUSTED_KEYS和CONFIG_SYSTEM_REVOCATION_KEYS清空。3.4 用 QEMU 启动内核并挂上 gdb先准备一个最简单的根文件系统。没有根文件系统内核会 panic这其实正好符合我们随时能 panic的主题但为了能进 shell 做更多测试还是准备一个。最省事的办法是用 busybox 做一个 initramfs或者直接下载一个现成的 rootfs 镜像。启动 QEMU 的命令qemu-system-aarch64 \ -M virt \ -cpu cortex-a57 \ -smp 2 \ -m 1024 \ -kernel arch/arm64/boot/Image \ -append consolettyAMA0 nokaslr \ -nographic \ -s -S逐个参数解释。-M virt指定 QEMU 的虚拟开发板这是 ARM64 最通用的虚拟平台。-cpu cortex-a57指定 CPU 型号cortex-a57 是 ARMv8 的经典核心兼容性好。-smp 2给两个 CPU 核心方便调试多核场景。-m 1024给 1GB 内存。-kernel指定内核镜像。-append传内核启动参数consolettyAMA0指定串口控制台nokaslr关闭地址随机化。-nographic表示不用图形界面串口输出直接打到终端。-s -S就是前面说的 gdb server 加启动暂停。启动后 QEMU 会停住等待 gdb 连接。另开一个终端gdb-multiarch vmlinux进入 gdb 后(gdb) target remote :1234 (gdb) b start_kernel (gdb) ctarget remote :1234连上 QEMU。b start_kernel在内核入口函数下断点。c继续执行。如果一切正常gdb 会停在start_kernel这时候你就可以单步、看变量、看调用栈了。4. 让内核想崩就崩panic 触发与现场捕获的几种手法4.1 主动触发 panic 的三种方式及适用场景调试环境搭好后第一件事是验证panic 可控。我常用三种方式主动触发 panic各有适用场景。第一种是内核命令行参数。启动时加panic1或者oopspanic前者让内核在 panic 后 1 秒重启后者把 oops 也升级成 panic。这种方式适合测试 panic 后的重启流程。第二种是通过 sysrq 触发。系统跑起来后往/proc/sysrq-trigger写cecho c /proc/sysrq-trigger这会立即触发一个 crash内核打印调用栈后 panic。这种方式的好处是不用改代码随时能触发适合验证调试链路是否通畅。第三种是代码里主动调用panic()或BUG()。在你想调试的位置插入panic(debug: intentional panic at %s:%d\n, __FILE__, __LINE__);或者用BUG()触发 oops。这种方式最灵活能精确控制崩溃点适合调试特定代码路径。我一般先用 sysrq 验证链路确认 gdb 能捕获到 panic 现场再在具体代码里插 panic 做精细调试。4.2 panic 现场里到底该看什么panic 发生时内核会打印一大堆信息。新手容易被淹没其实重点看这几块。调用栈Call trace是第一位的。它告诉你崩溃时函数是怎么一层层调下来的直接指向问题代码路径。栈里的地址配合 gdb 的list命令能定位到源码行。寄存器状态在 ARM64 上会打印 x0 到 x30 以及 sp、pc 等。pc 是崩溃时的指令地址x0 到 x7 通常是函数参数。看这些能判断是空指针解引用、非法地址访问还是断言失败。PC 值对应的符号最关键。panic 信息里通常会有一行pc : some_function0x1c/0x40这个some_function就是崩溃函数0x1c是偏移。在 gdb 里用info line *地址能精确定位到行。内存映射和页表信息在地址访问异常时很有用能看出访问的地址是否在合法映射范围内。我习惯在 panic 后先看调用栈定位大致范围再用 gdb 连上去看寄存器和内存最后回到源码分析根因。这个顺序比一上来就啃寄存器高效。4.3 用 gdb 在 panic 前打断点把问题扼杀在崩溃之前比崩溃后分析更高阶的是崩溃前拦截。既然 gdb 能下断点那就在可能出问题的地方提前断住看状态对不对。比如怀疑某个指针是空的可以在解引用之前下断(gdb) b suspicious_function (gdb) c断住之后(gdb) p pointer_variable (gdb) p *pointer_variable如果发现是 NULL那就找到根因了不用等它崩溃。对于 panic 函数本身也可以下断(gdb) b panic这样任何 panic 调用都会先断在 gdb 里你能看到完整的调用栈和参数比看串口打印的信息全得多。这是我最推荐的调试手法——把 gdb 断点下在 panic 入口让每次崩溃都变成一次可控的暂停。4.4 硬件断点与软件断点的选择gdb 在 QEMU 上支持硬件断点和软件断点。软件断点通过替换指令实现数量不限但会修改内存硬件断点用 CPU 的调试寄存器数量有限通常 4 到 8 个但不改内存。在内核调试里只读代码段建议用硬件断点因为软件断点改不了只读内存。用hbreak命令下硬件断点(gdb) hbreak start_kernel普通break是软件断点。如果下断点时报Cannot insert breakpoint多半是内存只读换成hbreak就行。5. 搭建过程中最容易翻车的几个点5.1 内核和 gdb 符号对不上地址错位的排查这是最常见的坑。现象是 gdb 里下断点跑起来断在了莫名其妙的地方或者list显示的源码和实际执行的对不上。根因通常是三个。一是vmlinux 和 QEMU 加载的 Image 不是同一次编译的。解决办法是每次重编译后确保 QEMU 用的 Image 和 gdb 用的 vmlinux 来自同一个源码目录。二是KASLR 没关。启动参数里必须加nokaslr。三是gdb 加载了错误的符号文件比如加载了 stripped 版本或者别的架构的 vmlinux。排查方法在 gdb 里执行info files看加载的符号文件路径对不对。再执行p start_kernel看打印的地址和 panic 信息里的地址是否一致。不一致就是符号问题。5.2 QEMU 启动卡住不动串口和控制台配置的坑QEMU 启动后终端一片空白什么都不打印这种情况我遇到过好几次。第一个原因是串口设备没配对。ARM64 的 virt 平台默认串口是ttyAMA0如果内核配置里没开对应的串口驱动或者启动参数里 console 写错了就看不到输出。检查CONFIG_SERIAL_AMBA_PL011是否开启启动参数是否是consolettyAMA0。第二个原因是**-nographic和图形输出冲突**。如果同时用了-nographic又没正确配置串口输出可能被吞掉。确保只用-nographic不要加-serial之类的额外参数。第三个原因是内核压根没启动起来。这时候用-S让 QEMU 暂停gdb 连上去单步看卡在哪条指令。常见的是 CPU 型号不匹配或者内存布局问题。5.3 编译报错合集证书、工具链、依赖缺失编译内核时的报错五花八门我整理几个高频的。No rule to make target debian/canonical-certs.pem前面提过清空CONFIG_SYSTEM_TRUSTED_KEYS。flex: command not found或bison: command not found装flex和bison。fatal error: openssl/opensslv.h: No such file装libssl-dev。error: unrecognized command line option -mgeneral-regs-only工具链太老升级gcc-aarch64-linux-gnu。BTF: .tmp_vmlinux.btf: pahole (pahole) is not available装dwarves包或者关掉CONFIG_DEBUG_INFO_BTF。这些报错看着吓人其实都是依赖问题装包或者改配置就能解决。关键是看报错信息里的关键词对症下药。5.4 gdb 连接超时和端口占用target remote :1234报连接超时通常是 QEMU 没启动或者-s参数没加。确认 QEMU 进程在跑且启动命令里有-s。端口被占用的话换个端口。QEMU 用-gdb tcp::1235指定端口gdb 里target remote :1235对应连上。还有一种情况是 QEMU 启动了但没加-S内核已经跑飞了gdb 连上去时系统已经 panic 或者卡死。调试场景建议始终加-S让 CPU 先暂停。6. 把这套环境用起来几个真实调试场景的复盘6.1 定位一个空指针解引用有次我写了个字符设备驱动加载模块后一读设备就 panic。串口打印的调用栈指向mydev_read函数。gdb 连上去在mydev_read下断点重新触发读操作断住后看参数(gdb) p dev $1 (struct mydev *) 0x0果然是空指针。回头看代码发现open函数里忘了给file-private_data赋值read里直接拿它当设备结构体用了。这种问题用 printk 也能查但 gdb 一眼就能看到指针值效率高得多。6.2 追踪一个偶发的内存越界偶发问题最难查因为复现不稳定。我的做法是在可疑的内存操作前后下断点配合 watchpoint 监控特定内存地址。(gdb) watch *(int *)0xffff800012345678watchpoint 在内存被写时触发能抓到是谁改的。QEMU 对 watchpoint 支持不错虽然会拖慢执行速度但定位偶发问题很有效。6.3 用 gdb 脚本自动化重复调试每次调试都手动敲一堆 gdb 命令很烦。可以把常用命令写成脚本gdb 启动时用-x加载# debug.gdb target remote :1234 b start_kernel b panic c启动gdb-multiarch vmlinux -x debug.gdb这样每次调试自动连上、自动下断点、自动继续省去重复劳动。对于需要反复复现的场景这个技巧能省不少时间。6.4 保存和恢复调试现场调试到一半要关机下次想接着调怎么办gdb 可以 dump 内存和寄存器状态(gdb) dump binary memory memdump.bin 0xffff000000000000 0xffff000010000000不过更实用的做法是把复现步骤和断点配置记录下来下次重新启动环境后按记录操作。内核调试环境本身是可重建的与其保存现场不如保存如何到达现场的脚本。7. 关于这套环境的一些个人体会搭这套环境前后花了我大概两天时间其中大部分时间耗在编译和排查配置问题上。但搭好之后后面每次调试内核都省了大量时间这笔投入非常值。有几个体会想分享给准备动手的朋友。第一第一次编译内核一定要有耐心报错是正常的逐个解决就行解决完一次以后就顺了。第二配置文件要备份.config和编译好的vmlinux都留着换机器或者重装系统时能直接复用。第三gdb 脚本要积累把常用的断点、打印命令存成脚本调试效率会越来越高。第四不要怕 panic这套环境的意义就是让 panic 变成可控的、可分析的而不是可怕的。这套环境搭好之后下一集我会讲怎么在这个环境里调试一个真实的驱动模块包括模块加载、符号解析、以及模块和内核交互时的调试技巧。如果你在搭建过程中遇到本文没覆盖的问题大概率是环境差异导致的按报错信息逐个排查基本都能解决。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

字符集问题的初步探讨(五)-导出文件字符集:Oracle NLS_LANG 与 csscan 排查配置 2026/9/27 14:48:29

字符集问题的初步探讨(五)-导出文件字符集:Oracle NLS_LANG 与 csscan 排查配置

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
上海个人做网站防挂马3招源码下载避坑指南 2026/9/27 14:48:29

上海个人做网站防挂马3招源码下载避坑指南

上海个人做网站防挂马3招源码下载避坑指南 网站半夜被塞满博彩广告,浏览器弹出满屏黄图,域名直接进谷歌黑名单。这种 网站被黑挂马不知道怎么办…

阅读更多 →
利用wix建手机网站别只看颜值,源码下载后的3个安全雷区 2026/9/27 14:48:23

利用wix建手机网站别只看颜值,源码下载后的3个安全雷区

利用wix建手机网站别只看颜值,源码下载后的3个安全雷区 网站做好了没人访问,这不仅是流量焦虑,更是因为你的网站在移动端存在致命的安全漏洞,导致搜索引擎降权甚至被用户直接划走。很多运营人员以为用 Wix 这种 SaaS…

阅读更多 →
OpenClaw 接入 GPT-Image-2 + Claude 4.7:openclaw.json 配置实战(2026) 2026/9/27 14:48:23

OpenClaw 接入 GPT-Image-2 + Claude 4.7:openclaw.json 配置实战(2026)

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
拒绝模板烂大街,手机靓号网站制作最佳实践全解析 2026/9/27 14:47:57

拒绝模板烂大街,手机靓号网站制作最佳实践全解析

拒绝模板烂大街,手机靓号网站制作最佳实践全解析 别再被那些千篇一律的模板网站糊弄了。做手机靓号生意,客户看的是数字的“面子”,你的网站就是店铺的“门面”。用那些免费模板搭出来的站,配色土气、排版混乱,加载还慢,客户点进来三秒就走了。想靠这个…

阅读更多 →
智能体 skill 基本原理拆解:从 config.toml 骨架到 TaoToken 统一 Key 接入 2026/9/27 14:47:50

智能体 skill 基本原理拆解:从 config.toml 骨架到 TaoToken 统一 Key 接入

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉