1.深入理解 kernel panic 的流程
2.Linux强制卸载内核模块(由于驱动异常导致rmmod不能卸载)
3.å¦ä½å¸è½½option of eds kernel debug
4.Debug Hacks中文版:深入调试的技术和工具目录
5.linux内核$(kallsyms.o)详解续篇 --- 内核符号表的生成和查找过程
6.解密Linux内核oops:从错误到调试
深入理解 kernel panic 的流程
深入理解 kernel panic 的流程
在项目开发中,遇到手机系统死机重启的情况,尤其是由于内核问题导致的 kernel panic,无疑会给调试带来巨大挑战。内核在死机前会输出关键信息,包括PC指针、蝴蝶起舞指标源码调用栈等,这些信息对于理解异常原因、定位问题至关重要。本文将从常见的主动触发 BUG() 开始,解析整个 kernel panic 的流程。
BUG() 是 Linux 内核中用于拦截内核程序超出预期行为的机制。它有两种主要用途:一是软件开发过程中,遇到致命的代码错误时,主动调用 BUG() 使系统崩溃,以方便定位问题;二是为了 debug 需要捕获内存快照时,引导系统进入 kernel panic 状态。
BUG() 的实现原理是插入一条未定义指令(0xe7ff2),触发 ARM 异常处理机制。Linux 内核源码分析可从 ke.qq.com 获取。
在理解了 BUG() 的作用后,我们将深入分析从调用 BUG() 到系统重启的整个流程。
BUG() 流程分析
调用 BUG() 会触发 CPU 执行未定义指令,导致 ARM 发生未定义指令异常,进而进入内核异常处理流程,输出关键调试线索,最终调用 panic() 终止自身并引导系统重启。这一过程包括 Oops、die()、__die() 等关键步骤。
die() 流程
die() 函数负责收集异常信息并准备输出。狙源码在执行中,它将打印出标志性的 log,如 “[ cut here ]” 表示发生了致命故障。通过搜索关键字 “Internal error: Oops” 可以快速识别出是 BUG() 导致的异常。
__die() 流程分析
__die() 函数输出异常的详细信息,如 PC 指针、调用栈等。通过打印关键信息,我们可以定位到异常发生的具体代码位置,如进程 ID、CPU、异常类型等。此外,它还会生成内存快照和调用栈,辅助调试。
panic 流程
panic() 函数表示内核遭遇了不可恢复的错误。它收集和输出所有关键信息,然后终止内核进程,引导系统进入重启流程。
通过理解这一流程,开发者能够更有效地定位并修复由 kernel panic 引起的异常,特别是通过主动调用 BUG() 导致的异常,其调试难度通常相对较低。
Linux强制卸载内核模块(由于驱动异常导致rmmod不能卸载)
在进行驱动编程时,若驱动出现异常,导致 insmod、rmmod 或使用过程中出现问题,使得驱动加载后无法被正常卸载,或者卸载时出错。例如,javashop源码在调试内核 OOPS 时使用的有异常的驱动 kerneloops 或者 createoops,它们在初始化函数 init中出现了 NULL 指针异常。
以 kerneloops 为例,我们首先编译并加载驱动,然后通过 dmesg 查看是否发现异常。接着尝试使用 rmmod 命令卸载 kerneloops,但会收到提示:“rmmod: ERROR: Module kerneloops is in use”。进一步分析发现,驱动的使用数目(Used)为 1。
分析原因发现,异常驱动由于在 insmod 时出现 NULL 指针异常,导致驱动虽然被加载,但运行过程中出现了内核段错误(OOPS),设备引用计数没有被正确释放,无法主动释放。此时内核误认为驱动正被使用,因此 rmmod 必然失败。
解决此类问题,关键在于通过内核中的其他方式主动将驱动的引用计数清零。这需要对内核卸载模块的流程有深入理解。
驱动卸载的流程涉及系统调用 sys_delet_module 和函数 delete_module 的调用。在内核中,尝试强制卸载模块的代码主要在 kernel/module.c 中。当模块的引用计数非零时,会返回 EWOULDBLOCK 错误。
驱动无法卸载的几种情形包括:驱动设计不合理或存在代码bug、模块执行 exit 函数时出现异常或未正确退出、模块在内核运行时出现段错误(OOPS)。针对这些情形,可以设计外部驱动模块(如 force_rmmod)用于卸载异常驱动,祈福源码或在驱动中注册 exit 函数,确保在异常情况下能够正确释放资源。
针对没有 exit 函数的驱动,可以通过外部注册 exit 函数的方式解决问题。例如,设计一个简单的 exit 函数替换异常驱动中的 exit 函数,确保在卸载驱动时能够正常释放资源。
在实际操作中,可以使用 force_rmmod 驱动实现卸载异常驱动,同时确保在卸载异常驱动时先卸载 force_rmmod,以避免循环卸载问题。具体实现时,需要关注外部 exit 函数与原驱动模块的实现兼容性。
为了帮助学习和实践内核模块相关知识,推荐加入 Linux 内核技术交流群:,获取学习资源和交流机会。群内提供书籍、视频资料、内核资料包等学习材料,包括源码学习、内存调优、文件系统、进程管理、设备驱动和网络协议栈等内容。
å¦ä½å¸è½½option of eds kernel debug
è°è¯æ¯è½¯ä»¶å¼åè¿ç¨ä¸ä¸ä¸ªå¿ ä¸å¯å°çç¯èï¼å¨ Linux å æ ¸å¼åçè¿ç¨ä¸ä¹ä¸å¯é¿å å°ä¼é¢å¯¹å¦ä½è°è¯å æ ¸çé®é¢ãä½æ¯ï¼Linux ç³»ç»çå¼åè åºäºä¿è¯å æ ¸ä»£ç æ£ç¡®æ§çèèï¼ä¸æ¿æå¨ Linux å æ ¸æºä»£ç æ ä¸å å ¥ä¸ä¸ªè°è¯å¨ãä»ä»¬è®¤ä¸ºå æ ¸ä¸çè°è¯å¨ä¼è¯¯å¯¼å¼åè ï¼ä»èå¼å ¥ä¸è¯çä¿®æ£[1].æ以对 Linux å æ ¸è¿è¡è°è¯ä¸ç´æ¯ä¸ªä»¤å æ ¸ç¨åºåæå°æ£æçé®é¢ï¼è°è¯å·¥ä½çè°è¦æ§æ¯å æ ¸çº§çå¼ååºå«äºç¨æ·çº§å¼åçä¸ä¸ªæ¾èç¹ç¹ã
尽管缺ä¹ä¸ç§å ç½®çè°è¯å æ ¸çæææ¹æ³ï¼ä½æ¯ Linux ç³»ç»å¨å æ ¸åå±çè¿ç¨ä¸ä¹éæ¸å½¢æäºä¸äºçè§å æ ¸ä»£ç åé误è·è¸ªçææ¯ãåæ¶ï¼è®¸å¤çè¡¥ä¸ç¨åºåºè¿èçï¼å®ä»¬ä¸ºæ åå æ ¸éå äºå æ ¸è°è¯çæ¯æã尽管è¿äºè¡¥ ä¸æäºå¹¶ä¸è¢« Linux å®æ¹ç»ç»è®¤å¯ï¼ä½ä»ä»¬ç¡®å®åè½å®åï¼åå强大ãè°è¯å æ ¸é®é¢æ¶ï¼å©ç¨è¿äºå·¥å ·ä¸æ¹æ³è·è¸ªå æ ¸æ§è¡æ åµï¼å¹¶æ¥çå ¶å ååæ°æ®ç»æå°æ¯é常æç¨çã
æ¬æå°é¦å ä»ç» Linux å æ ¸ä¸çä¸äºå æ ¸ä»£ç çè§åé误è·è¸ªææ¯ï¼è¿äºè°è¯åè·è¸ªæ¹æ³å æè¦æ±ç使ç¨ç¯å¢å使ç¨æ¹æ³èåæä¸åï¼ç¶åéç¹ä»ç»ä¸ç§ Linux å æ ¸çæºä»£ç 级çè°è¯æ¹æ³ã
1. Linux ç³»ç»å æ ¸çº§è½¯ä»¶çè°è¯ææ¯
printkï¼ï¼ æ¯è°è¯å æ ¸ä»£ç æ¶æ常ç¨çä¸ç§ææ¯ãå¨å æ ¸ä»£ç ä¸çç¹å®ä½ç½®å å ¥printkï¼ï¼ è°è¯è°ç¨ï¼å¯ä»¥ç´æ¥ææå ³å¿çä¿¡æ¯ææå°å°å±å¹ä¸ï¼ä»èå¯ä»¥è§å¯ç¨åºçæ§è¡è·¯å¾åæå ³å¿çåéãæéçä¿¡æ¯ã Linux å æ ¸è°è¯å¨ï¼Linux kernel debuggerï¼kdbï¼æ¯ Linux å æ ¸çè¡¥ä¸ï¼å®æä¾äºä¸ç§å¨ç³»ç»è½è¿è¡æ¶å¯¹å æ ¸å ååæ°æ®ç»æè¿è¡æ£æ¥çåæ³ãOopsãKDBå¨æç« ææ¡ Linux è°è¯ææ¯æ详ç»ä»ç»ï¼å¤§å®¶å¯ä»¥åèã Kprobes æä¾äºä¸ä¸ªå¼ºè¡è¿å ¥ä»»ä½å æ ¸ä¾ç¨ï¼å¹¶ä»ä¸æå¤çå¨æ å¹²æ°å°æ¶éä¿¡æ¯çæ¥å£ãä½¿ç¨ Kprobes å¯ä»¥è½»æ¾å°æ¶éå¤çå¨å¯åå¨åå ¨å±æ°æ®ç»æçè°è¯ä¿¡æ¯ï¼èæ é对Linuxå æ ¸é¢ç¹ç¼è¯åå¯å¨ï¼å ·ä½ä½¿ç¨æ¹æ³ï¼è¯·åèä½¿ç¨ Kprobes è°è¯å æ ¸ã
以ä¸ä»ç»äºè¿è¡Linuxå æ ¸è°è¯åè·è¸ªæ¶ç常ç¨ææ¯åæ¹æ³ãå½ç¶ï¼å æ ¸è°è¯ä¸è·è¸ªçæ¹æ³è¿ä¸æ¢ä»¥ä¸æå°çè¿äºãè¿äºè°è¯ææ¯çä¸ä¸ªå ±åçç¹ç¹å¨äºï¼ä»ä»¬ é½ä¸è½æä¾æºä»£ç 级çææçå æ ¸è°è¯æ段ï¼æäºåªè½ç§°ä¹ä¸ºé误è·è¸ªææ¯ï¼å æ¤è¿äºæ¹æ³é½åªè½æä¾æéçè°è¯è½åãä¸é¢å°ä»ç»ä¸ç§å®ç¨çæºä»£ç 级çå æ ¸è°è¯ æ¹æ³ã
2. 使ç¨KGDBæ建Linuxå æ ¸è°è¯ç¯å¢
kgdbæä¾äºä¸ç§ä½¿ç¨ gdbè°è¯ Linux å æ ¸çæºå¶ã使ç¨KGDBå¯ä»¥è±¡è°è¯æ®éçåºç¨ç¨åºé£æ ·ï¼å¨å æ ¸ä¸è¿è¡è®¾ç½®æç¹ãæ£æ¥åéå¼ãåæ¥è·è¸ªç¨åºè¿è¡çæä½ã使ç¨KGDBè°è¯æ¶éè¦ä¸¤å°æºå¨ï¼ ä¸å°ä½ä¸ºå¼åæºï¼Development Machineï¼ï¼å¦ä¸å°ä½ä¸ºç®æ æºï¼Target Machineï¼ï¼ä¸¤å°æºå¨ä¹é´éè¿ä¸²å£æè 以太ç½å£ç¸è¿ã串å£è¿æ¥çº¿æ¯ä¸æ ¹RS-æ¥å£ççµç¼ï¼å¨å ¶å é¨ä¸¤ç«¯ç第2èï¼TXDï¼ä¸ç¬¬3èï¼RXDï¼ äº¤åç¸è¿ï¼ç¬¬7èï¼æ¥å°èï¼ç´æ¥ç¸è¿ãè°è¯è¿ç¨ä¸ï¼è¢«è°è¯çå æ ¸è¿è¡å¨ç®æ æºä¸ï¼gdbè°è¯å¨è¿è¡å¨å¼åæºä¸ã
ç®åï¼kgdbåå¸æ¯æiãx_ã-bit PPCãSPARCçå ç§ä½ç³»ç»æçè°è¯å¨ãæå ³kgdbè¡¥ä¸çä¸è½½å°åè§åèèµæ[4].
2.1 kgdbçè°è¯åç
å®è£ kgdbè°è¯ç¯å¢éè¦ä¸ºLinuxå æ ¸åºç¨kgdbè¡¥ä¸ï¼è¡¥ä¸å®ç°çgdbè¿ç¨è°è¯æéè¦çåè½å æ¬å½ä»¤å¤çãé·é±å¤çå串å£é讯3个主è¦çé¨åã kgdbè¡¥ä¸ç主è¦ä½ç¨æ¯å¨Linuxå æ ¸ä¸æ·»å äºä¸ä¸ªè°è¯Stub.è°è¯Stubæ¯Linuxå æ ¸ä¸çä¸å°æ®µä»£ç ï¼æä¾äºè¿è¡gdbçå¼åæºåæè°è¯å æ ¸ä¹é´çä¸ä¸ªåªä»ãgdbåè°è¯stubä¹é´éè¿gdb串è¡åè®®è¿è¡é讯ãgdb串è¡åè®®æ¯ä¸ç§åºäºæ¶æ¯çASCIIç åè®®ï¼ å å«äºåç§è°è¯å½ä»¤ãå½è®¾ç½®æç¹æ¶ï¼kgdbè´è´£å¨è®¾ç½®æç¹çæ令åå¢å ä¸æ¡trapæ令ï¼å½æ§è¡å°æç¹æ¶æ§å¶æ就转移å°è°è¯stubä¸å»ãæ¤æ¶ï¼è°è¯ stubçä»»å¡å°±æ¯ä½¿ç¨è¿ç¨ä¸²è¡éä¿¡åè®®å°å½åç¯å¢ä¼ éç»gdbï¼ç¶åä»gdbå¤æ¥åå½ä»¤ãgdbå½ä»¤åè¯stubä¸ä¸æ¥è¯¥åä»ä¹ï¼å½stubæ¶å°ç»§ç»æ§ è¡çå½ä»¤æ¶ï¼å°æ¢å¤ç¨åºçè¿è¡ç¯å¢ï¼æ对CPUçæ§å¶æéæ°äº¤è¿ç»å æ ¸ã
Debug Hacks中文版:深入调试的技术和工具目录
本篇文章是Debug Hacks中文版,旨在深入探讨调试技术与工具。内容涵盖了从热身准备到高级调试技术的多个章节,旨在帮助读者掌握从基础到高级的调试技能。以下是文章中的一些关键部分,用于更直观地回答如何进行调试以及如何使用相关工具和技术进行深入分析。印章源码
第1章 热身准备:
本章介绍调试的基本概念,以及调试地图和调试心得,为后续章节的学习打下基础。
第2章 调试前的必知必会:
通过学习获取进程内核转储、GDB的基本使用方法、Intel架构知识、栈知识、参数传递方法、学习汇编语言、从汇编语言查找源代码,为深入调试做好准备。
第3章 内核调试的准备:
讲解了内核调试的基础知识,包括解读Oops信息、使用minicom进行串口连接、通过网络获取内核消息、使用SysRq键和diskdump、kdump获取内核崩溃转储、使用crash命令、在死机时利用IPMI watchdog timer或NMI watchdog获取崩溃转储、内核独有的汇编指令等。
第4章 应用程序调试实践:
从实际应用角度出发,探讨了应用程序调试中常见的问题,如SIGSEGV异常停止、backtrace问题、数组非法访问、malloc和free故障、应用程序响应停止(死锁或死循环)。
第5章 实践内核调试:
深入内核调试实践,包括处理kernel panic、内核停止响应的问题,如死循环、自旋锁和信号量问题,以及实时进程和运行缓慢的故障。
第6章 高手们的调试技术:
介绍高级调试技术,如使用strace、objdump、Valgrind、kprobes、jprobes、KAHO、systemtap、/proc/meminfo、分析内存使用、错误注入、Linux内核的init节、解决性能问题、获取VMware Vprobe信息、使用Xen获取内存转储、理解GOT/PLT调用函数、调试initramfs镜像、实时进程检测、x机器支持的位模式等。
附录 Debug hacks术语的基础知识:
提供Debug hacks术语的基础知识,以便读者在阅读过程中更好地理解相关概念。
索引:
为读者提供快速查找所需章节和术语的便利。
linux内核$(kallsyms.o)详解续篇 --- 内核符号表的生成和查找过程
在内核中,维护着一张符号表,记录着内核中的所有符号,包括函数与全局变量的地址与名称。这张表嵌入在内核镜像中,供内核运行时随时查找符号名。通过调用__print_symbol,内核代码能打印出符号名。 接下来,我们将详细解析内核符号表的生成与查找过程。系统映像文件与/proc/kallsyms的区别与联系
系统映像文件(System.map)在编译内核时生成,记录了内核中的所有符号及其在内存中的虚拟地址。文件由scripts/mksysmap脚本生成,依赖于nm命令。系统映像文件的每条记录包括地址、符号类型与符号名。符号类型包括函数、全局变量等。 而/proc/kallsyms文件是在内核启动后自动生成,位于/proc目录下,其代码实现于kernel/kallsyms.c。区别在于它包含了内核模块的符号列表,并且允许用户态访问,非内核常规操作。通常,我们只需关注_stext ~ _etext 和 _sinittext ~ _einittext之间的符号。内核符号表的生成与查找
内核在运行过程中可能需要查找地址对应的函数名,如Oops或调试信息输出。但内核并未依赖System.map或/proc/kallsyms文件,而是通过vmlinux中的符号表(.tmp_vmlinux2.o)实现快速查找。内核符号表结构
内嵌符号表通过scripts/kallsyms工具生成,源码位于kallsyms.c。该表包含6个全局变量:kallsyms_addresses、kallsyms_num_syms、kallsyms_names、kallsyms_token_table、kallsyms_token_index与kallsyms_markers。其中,kallsyms_addresses记录所有符号地址,kallsyms_num_syms统计符号数量,kallsyms_names存放符号名,kallsyms_token_table与kallsyms_token_index用于压缩存储高频率字符串。压缩算法与优化
内核使用压缩算法减少存储开销,将高频率字符串表示为token,并通过kallsyms_token_table与kallsyms_token_index实现压缩与解压。kallsyms_markers将符号每个分组,加速查找过程。查找过程实例与源码分析
举例说明查找地址0xc对应的符号名。首先在System.map中定位到该地址位于__create_page_tables与__enable_mmu之间。在.tmp_kallsyms2.S文件中,利用二分查找定位符号地址,然后通过kallsyms_names与kallsyms_markers加速查找过程。最后解析压缩的符号名,得到结果为__enable_mmu。内核模块符号查找
内核模块在启动时动态加载,其符号表存储在struct module结构中,所有已加载模块的struct module结构构成全局链表。查找内核模块中的符号时,调用kallsyms_lookup()函数,模块符号查找由get_ksymbol()函数完成。解密Linux内核oops:从错误到调试
前言:Linux内核的Oops信息是内核错误时打印的警告信息,常与非法内存访问或执行非法指令相关。内核通过这些信息追踪错误源,例如访问NULL指针或使用错误指针值。
概述:Oops信息由内核产生,当处理器无法将非法指针映射到物理地址时,触发页面失效信号,导致Oops信息出现。内核崩溃时打印的Oops信息包含了栈回溯信息,用于追踪错误的函数调用链。配置内核以显示更详细的栈回溯信息,可以加速定位错误。通过编译选项“-fno-omit-frame-pointer”来增强内核的调试功能。
内核异常处理:内核异常大致分为三个级别:BUG、oops和panic。BUG用于标记不符合内核预期设计的问题,oops表示内核出现异常,panic则表示内核遇到无法继续运行的情况。
BUG处理:在开发过程中,使用BUG()机制报告异常,以便快速定位和修正问题。对于ARM架构,BUG()和BUG_ON()会触发系统崩溃。而ARM架构中,BUG()会引发未定义指令异常。
oops处理:oops信息详细记录了错误时的上下文信息,如CPU状态、出错指令地址、函数调用栈等,帮助定位问题。如果遇到oops错误且有源码,可以使用arm的工具转储内核文件,或者利用GDB进行深入分析。对于无源码的oops错误,Linux源码目录下的脚本可将日志信息转换为更易于理解的汇编代码。
die()函数:die()是oops处理流程的核心,负责打印日志、执行回调函数、展示调用栈信息等。通过回调感兴趣的模块,提供错误信息。
panic处理:当oops信息达到严重程度时,系统可能进入panic状态,导致整个系统崩溃。panic_timeout参数允许设置等待重启的时间。panic_print配置了打印系统信息的位。
内核异常分析方法:PowerPC和MIPS架构的异常处理遵循各自特定的流程和规则。异常信息通常包括异常类型、信号、寄存器状态、调用栈等。通过分析这些信息,可以定位问题源头。PowerPC架构的调用栈信息直接显示函数和指令,而MIPS架构则需要借助反汇编工具来识别异常点。
总结:Linux内核的Oops信息是内核错误的警报,通过详细的上下文信息帮助开发者快速定位和解决问题。理解内核异常处理机制,以及如何分析异常信息,对提高系统稳定性至关重要。