一、顶级架构一句话总结
printk(日志) → 动态调试 → ftrace(跟踪) → kprobes(探针) → KGDB(调试器)
内核调试是驱动开发中定位问题、分析性能的核心技能。
二、printk调试
日志级别
#include<linux/printk.h>// 日志级别定义#defineKERN_EMERG"<0>"// 系统不可用#defineKERN_ALERT"<1>"// 必须立即处理#defineKERN_CRIT"<2>"// 严重错误#defineKERN_ERR"<3>"// 错误#defineKERN_WARNING"<4>"// 警告#defineKERN_NOTICE"<5>"// 正常但重要#defineKERN_INFO"<6>"// 信息#defineKERN_DEBUG"<7>"// 调试信息// 使用方式printk(KERN_INFO"Hello, kernel\n");printk(KERN_ERR"Error occurred: %d\n",error);// 便捷宏pr_info("Information message\n");pr_err("Error message\n");pr_warn("Warning message\n");pr_debug("Debug message\n");// 需要定义DEBUGdev_info(dev,"Device info\n");dev_err(dev,"Device error\n");查看日志
# 查看内核日志dmesgdmesg|tail-50dmesg-w# 实时查看# 查看日志级别cat/proc/sys/kernel/printk# 设置日志级别echo"8 4 1 7">/proc/sys/kernel/printk# 清空日志dmesg-c三、动态调试(Dynamic Debug)
启用动态调试
# 内核配置CONFIG_DYNAMIC_DEBUG=y# 启用所有调试信息echo"8">/proc/sys/kernel/printk动态调试命令
# 查看所有动态调试点cat/sys/kernel/debug/dynamic_debug/control# 启用模块的所有调试echo"module mydriver +p">/sys/kernel/debug/dynamic_debug/control# 启用文件的所有调试echo"file mydriver.c +p">/sys/kernel/debug/dynamic_debug/control# 启用函数的调试echo"func my_probe +p">/sys/kernel/debug/dynamic_debug/control# 启用行号范围echo"file mydriver.c:100-200 +p">/sys/kernel/debug/dynamic_debug/control# 禁用调试echo"module mydriver -p">/sys/kernel/debug/dynamic_debug/control# 启用所有调试echo"+p">/sys/kernel/debug/dynamic_debug/control调试标志
| 标志 | 说明 |
|---|---|
| p | 打印到日志 |
| f | 打印函数名 |
| l | 打印行号 |
| m | 打印模块名 |
| t | 打印线程ID |
代码中使用
// 动态调试宏pr_debug("Debug message: %d\n",value);dev_dbg(dev,"Device debug: %d\n",value);// 条件调试pr_debug("Value is %d\n",value);四、ftrace跟踪
启用ftrace
# 挂载debugfsmount-tdebugfs none /sys/kernel/debug# 进入ftrace目录cd/sys/kernel/debug/tracing函数跟踪
# 查看可用跟踪器catavailable_tracers# 启用函数跟踪echofunction>current_tracer# 跟踪特定函数echomy_probe>set_ftrace_filter# 跟踪特定模块echo":mod:mydriver">set_ftrace_filter# 查看结果cattrace# 清空缓冲区echo>trace函数图跟踪
# 启用函数图echofunction_graph>current_tracer# 设置跟踪函数echomy_probe>set_graph_function# 查看结果cattrace事件跟踪
# 查看可用事件lsevents/# 启用事件echo1>events/sched/sched_switch/enable# 查看结果cattrace跟踪特定进程
# 设置跟踪进程PIDecho1234>set_ftrace_pid五、kprobes探针
kprobes类型
┌─────────────────────────────────────────────────────────┐ │ kprobes类型 │ ├─────────────────────────────────────────────────────────┤ │ kprobe - 在任意指令处插入探针 │ │ jprobe - 函数入口探针(已废弃) │ │ kretprobe - 函数返回探针 │ └─────────────────────────────────────────────────────────┘使用kprobes
# 添加kprobeecho'p:myprobe my_probe'>/sys/kernel/debug/kprobes/add_probe# 添加kretprobeecho'r:myretprobe my_probe'>/sys/kernel/debug/kprobes/add_probe# 启用探针echo1>/sys/kernel/debug/kprobes/enable# 查看结果cat/sys/kernel/debug/kprobes/listcat/sys/kernel/debug/tracing/trace# 禁用探针echo0>/sys/kernel/debug/kprobes/enable# 删除探针echo'-:myprobe'>/sys/kernel/debug/kprobes/del_probe内核模块中使用
#include<linux/kprobes.h>staticstructkprobekp={.symbol_name="my_probe",};staticinthandler_pre(structkprobe*p,structpt_regs*regs){pr_info("kprobe: %s called\n",p->symbol_name);return0;}staticvoidhandler_post(structkprobe*p,structpt_regs*regs,unsignedlongflags){pr_info("kprobe: %s returned\n",p->symbol_name);}staticint__initmy_init(void){kp.pre_handler=handler_pre;kp.post_handler=handler_post;if(register_kprobe(&kp)<0){pr_err("Failed to register kprobe\n");return-1;}return0;}staticvoid__exitmy_exit(void){unregister_kprobe(&kp);}六、KGDB远程调试
配置KGDB
# 内核配置CONFIG_KGDB=yCONFIG_KGDB_SERIAL_CONSOLE=yCONFIG_KGDB_KDB=y# 内核启动参数kgdboc=ttyS0,115200 kgdbwait进入调试模式
# 在目标机上触发echog>/proc/sysrq-trigger# 或使用魔术键SysRq + gGDB调试
# 在开发机上启动GDBgdb vmlinux# 连接目标机target remote /dev/ttyS0# GDB命令breakmy_probecontinuestep next print variable backtraceKDB命令
# 在KGDB提示符下kdb>bp my_probe# 设置断点kdb>go# 继续kdb>bt# 调用栈kdb>ps# 进程列表kdb>rd# 寄存器kdb>md<addr># 内存dump七、内核oops分析
oops信息解读
[12345.678901] Unable to handle kernel NULL pointer dereference at virtual address 00000000 [12345.678902] pgd = c0004000 [12345.678903] [00000000] *pgd=00000000 [12345.678904] [12345.678905] PC is at my_probe+0x20/0x100 [mydriver] [12345.678906] LR is at my_probe+0x18/0x100 [mydriver] [12345.678907] pc : [<bf012345>] lr : [<bf01233d>] psr: 60000013 [12345.678908] sp : c0123456 ip : c0123458 fp : c012345a [12345.678909] r10: 00000000 r9 : c012345c r8 : c012345e [12345.678910] r7 : 00000001 r6 : 00000002 r5 : 00000003 r4 : 00000004 [12345.678911] r3 : 00000000 r2 : 00000005 r1 : 00000006 r0 : 00000007 [12345.678912] Flags: nZCv IRQs on FIQs on Mode SVC_32 ISA ARM [12345.678913] Control: 10c5387d Table: 4000404a DAC: 00000015 [12345.678914] Process myprocess (pid: 1234, stack limit = 0xc0123456) [12345.678915] Stack: (0xc0123456 to 0xc0123456) ... [12345.678920] Backtrace: [12345.678921] [<bf012345>] (my_probe [mydriver]) from [<bf023456>] (driver_probe_device+0x56/0x100)分析步骤
# 1. 定位出错的函数和偏移# PC is at my_probe+0x20/0x100# 2. 使用addr2line定位源码行addr2line-emydriver.ko 0x20# 3. 使用gdb反汇编gdb mydriver.ko(gdb)disassemble my_probe# 4. 分析调用栈# Backtrace中的函数调用链八、内存调试
SLAB调试
# 内核配置CONFIG_DEBUG_SLAB=yCONFIG_DEBUG_KMEMLEAK=y# 查看内存泄漏cat/sys/kernel/debug/kmemleakKASAN(地址消毒器)
# 内核配置CONFIG_KASAN=y# 运行时检测内存错误# 自动报告越界访问、use-after-free等lockdep(锁依赖检测)
# 内核配置CONFIG_LOCKDEP=y# 查看锁依赖cat/proc/lockdepcat/proc/lockdep_stats九、调试工具对比
| 工具 | 用途 | 开销 | 适用场景 |
|---|---|---|---|
| printk | 简单日志 | 低 | 快速定位 |
| 动态调试 | 可控日志 | 低 | 生产环境 |
| ftrace | 函数跟踪 | 中 | 性能分析 |
| kprobes | 动态探针 | 中 | 深度调试 |
| KGDB | 源码调试 | 高 | 复杂问题 |
| KASAN | 内存检测 | 高 | 内存问题 |
十、终极总结
内核调试 = 定位问题的关键能力
- printk:最简单、最常用的调试手段
- 动态调试:生产环境可控的调试输出
- ftrace:函数调用跟踪、性能分析
- kprobes:动态插入探针、深度调试
- KGDB:源码级调试、复杂问题定位
掌握多种调试技术,才能高效解决内核问题!