21-Linux 设备驱动的调试

调试是驱动开发最花时间 的环节。7.2.5 的调试手段比 4.0 丰富得多: 从最原始的 printk,到 dynamic debug、ftrace、perf、eBPF、KASAN、 lockdep、pstore,再到 QEMU + gdbstub 的单步源码级调试。

本章的主线:按"从粗到细"的顺序,逐层掌握调试武器。

复制代码
   粗  printk / dev_dbg / dynamic debug
    │   /proc / /sys / debugfs
    │   Oops 分析 / decode_stacktrace.sh
    │   BUG_ON / WARN_ON / lockdep
    │   ftrace / perf / eBPF / KASAN
   细  QEMU gdbstub + vmlinux 源码级单步调试

21.1 GDB 调试器的用法

21.1.1 GDB 的基本用法

GDB 是用户空间程序的调试器,也可以(配合 gdbstub)调试内核。

编译带调试信息的程序:

bash 复制代码
gcc -g -O0 -o myprog myprog.c

常用命令:

命令 简写 作用
break b 设断点(函数名/行号/file:line)
run r 运行
continue c 继续
next n 单步(跳过函数)
step s 单步(进入函数)
finish 运行到当前函数返回
print p 打印变量/表达式
display 每次停下自动打印
backtrace bt 调用栈
info locals / info args 局部变量/参数
list l 看源码
watch 观察点(值变化时中断)
x/16xb addr 查看内存
info registers 寄存器
set var x = 5 改变量
thread apply all bt 所有线程栈

调试带参数/核心转储的程序:

bash 复制代码
gdb --args ./myprog arg1 arg2
gdb ./myprog core
gdb -p <pid>          # attach 到运行中的进程

调试内核模块时的技巧 :把 .ko 的调试信息加到 GDB:

bash 复制代码
(gdb) add-symbol-file mydrv.ko 0xffffffffc0000000

(地址从 /sys/module/mydrv/sections/.text 读,需 CONFIG_KALLSYMS。)

21.1.2 DDD 图形界面调试工具

DDD(Data Display Debugger) 是 GDB 的图形前端,用鼠标点断点、看数据结构:

bash 复制代码
sudo apt install ddd
ddd ./myprog
DDD 特色 说明
源码窗口 + 断点标记 点击行号设断点
数据显示窗口 把变量拖进去,图形化看链表/树
调用栈窗口 点击栈帧跳转
汇编窗口 源码/汇编对照
寄存器窗口 实时寄存器值

7.2.5 时代的替代品:

工具 说明
VS Code + C/C++ 扩展 最流行的图形调试器
gdb TUI (gdb -tui) 终端里的伪图形界面
gdb-dashboard gdb 的 Python 增强
gef / pwndbg 面向安全的 gdb 增强

21.2 Linux 内核调试

内核调试的三大支柱:

支柱 手段
日志 printk、dynamic debug、earlycon、pstore
追踪 ftrace、perf、eBPF、tracepoints
检查器 KASAN、KCSAN、KMSAN、UBSAN、lockdep、kmemleak

关键内核配置(调试用,生产环境慎用):

bash 复制代码
CONFIG_DEBUG_KERNEL=y
CONFIG_DEBUG_INFO=y
CONFIG_DEBUG_INFO_DWARF5=y
CONFIG_GDB_SCRIPTS=y
CONFIG_FRAME_POINTER=y
CONFIG_KALLSYMS=y
CONFIG_KALLSYMS_ALL=y
CONFIG_DEBUG_FS=y
CONFIG_DYNAMIC_DEBUG=y
CONFIG_FTRACE=y
CONFIG_FUNCTION_TRACER=y
CONFIG_STACKTRACE=y
CONFIG_KASAN=y                # 内存错误
CONFIG_KCSAN=y                # 数据竞争
CONFIG_UBSAN=y                # 未定义行为
CONFIG_PROVE_LOCKING=y        # lockdep
CONFIG_LOCKDEP=y
CONFIG_DEBUG_ATOMIC_SLEEP=y   # 原子上下文睡眠
CONFIG_DEBUG_MUTEXES=y
CONFIG_DEBUG_SPINLOCK=y
CONFIG_KMEMLEAK=y             # 内存泄漏
CONFIG_PSTORE=y               # 崩溃日志持久化

内核源码自带 kernel/configs/debug.config 和 kernel/configs/x86_debug.config, 可以合并进你的配置:

bash 复制代码
make -C "$KSRC" O="$KBUILD" x86_64_defconfig
scripts/kconfig/merge_config.sh -O "$KBUILD" \
        "$KBUILD/.config" "$KSRC/kernel/configs/debug.config"
make -C "$KSRC" O="$KBUILD" olddefconfig

21.3 内核打印信息------printk()

printk 是驱动调试的第一武器。7.2.5 的 printk 比 4.0 强大很多。

21.3.1 日志级别

bash 复制代码
printk(KERN_EMERG   "system is unusable\n");    /* 0 */
printk(KERN_ALERT   "action must be taken\n");  /* 1 */
printk(KERN_CRIT    "critical condition\n");    /* 2 */
printk(KERN_ERR     "error condition\n");       /* 3 */
printk(KERN_WARNING "warning\n");               /* 4 */
printk(KERN_NOTICE  "normal but significant\n");/* 5 */
printk(KERN_INFO    "informational\n");         /* 6 */
printk(KERN_DEBUG   "debug-level\n");           /* 7 */

现代写法(推荐):

cpp 复制代码
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/printk.h>

pr_err("failed to open: %d\n", ret);
pr_warn("retrying\n");
pr_info("device ready\n");
pr_debug("register = 0x%08x\n", val);    /* 默认不打印,见 dynamic debug */

/* 设备相关(自动带设备名前缀) */
dev_err(dev, "probe failed: %d\n", ret);
dev_warn(dev, "unexpected value\n");
dev_info(dev, "registered as %s\n", name);
dev_dbg(dev, "state = %d\n", state);

7.2.5 的重要变化:

4.0 7.2.5
多行 printk 可能被其它 CPU 插入 printk 有 per-cpu 缓冲,输出原子性更好
printk_ratelimit() printk_ratelimited() 系列宏
无时间戳格式选项 printk.time=、printk.devkmsg=
无结构化日志 printk 支持 %p 扩展、print_hex_dump()
无序号 CONFIG_PRINTK_CALLER 打印调用者

实用打印函数:

函数 作用
pr_info() / dev_info() 信息
pr_err() / dev_err() 错误
dev_err_probe(dev, err, fmt, ...) 7.2.5 推荐 :只在非 -EPROBE_DEFER 时打印
print_hex_dump(level, prefix, prefix_type, rowsize, groupsize, buf, len, ascii) 十六进制转储
print_hex_dump_debug() 只在 dynamic debug 打开时输出
WARN_ON() / WARN_ON_ONCE() 警告(见 21.7)
dump_stack() 打印调用栈

21.3.2 动态调试(dynamic debug)

这是 7.2.5 相比 4.0 最重要的调试增强 :不用重编译就能打开 pr_debug()/dev_dbg()。

bash 复制代码
CONFIG_DYNAMIC_DEBUG=y
CONFIG_DEBUG_FS=y
bash 复制代码
# 打开某个模块的所有 pr_debug
echo 'module mydrv +p' > /sys/kernel/debug/dynamic_debug/control

# 打开某个文件的所有调试输出
echo 'file mydrv.c +p' > /sys/kernel/debug/dynamic_debug/control

# 打开匹配某行的
echo 'file mydrv.c line 42 +p' > /sys/kernel/debug/dynamic_debug/control

# 查看所有被修改的
cat /sys/kernel/debug/dynamic_debug/control | grep '=p'

# 也可以用命令行
# 内核参数:dyndbg="module mydrv +p"

好处 :pr_debug() 在未打开时零开销 (编译进一个 _DPRINTK_FLAGS_NONE 分支)。

21.3.3 控制台与日志

bash 复制代码
# 控制台日志级别(只打印 < 该级别的消息)
echo 8 > /proc/sys/kernel/printk
cat /proc/sys/kernel/printk

# 用户空间读内核日志
dmesg -w            # 实时跟踪
dmesg -T            # 人类可读时间
journalctl -k       # systemd 系统

# 串口控制台参数
console=ttyAMA0,115200 loglevel=7

pstore:崩溃后日志持久化(重启不丢):

bash 复制代码
CONFIG_PSTORE=y
CONFIG_PSTORE_CONSOLE=y
CONFIG_PSTORE_RAM=y
bash 复制代码
ls /sys/fs/pstore/
cat /sys/fs/pstore/dmesg-ramoops-0

21.4 DEBUG_LL 和 EARLY_PRINTK

(详细见第 20.5 节,这里讲调试用法。)

问题 :内核在串口驱动加载之前崩溃了,怎么办?

机制 用途 命令/配置
earlycon 早期控制台 earlycon=pl011,mmio32,0x9000000
keep_bootcon 保留早期控制台 内核参数
DEBUG_LL(ARM32) 汇编级输出 CONFIG_DEBUG_LL + DEBUG_UART_PHYS
EARLY_PRINTK(x86) 早期打印 CONFIG_EARLY_PRINTK + earlyprintk
boot_delay= 启动延迟 方便看清每行

QEMU 实例:

bash 复制代码
qemu-system-aarch64 -machine virt -nographic \
  -kernel Image \
  -append "console=ttyAMA0 earlycon=pl011,mmio32,0x9000000 keep_bootcon"

观察 :如果串口驱动有问题,earlycon 仍然能输出, 从而定位"卡在哪一步"。

21.5 使用 "/proc"

/proc 是内核暴露给用户空间的运行时信息接口。

文件 内容
/proc/interrupts 每个 CPU 的中断计数
/proc/iomem 物理内存/IO 映射
/proc/ioports IO 端口
/proc/meminfo 内存统计
/proc/slabinfo slab 分配器
/proc/vmallocinfo vmalloc 区域
/proc/modules 已加载模块
/proc/kallsyms 所有内核符号(需 CONFIG_KALLSYMS)
/proc/devices 已注册的字符/块设备主号
/proc/cmdline 内核命令行
/proc/version 内核版本
/proc/config.gz 内核配置(需 CONFIG_IKCONFIG_PROC)
/proc/device-tree/ 设备树
/proc/self/maps 进程内存映射
/proc/<pid>/stack 内核栈(需权限)
/proc/timer_list 定时器列表

驱动可以创建自己的 /proc 条目(新代码更推荐 debugfs):

cpp 复制代码
#include <linux/proc_fs.h>
#include <linux/seq_file.h>

static int my_show(struct seq_file *m, void *v)
{
	struct my_dev *d = m->private;

	seq_printf(m, "state=%d count=%u\n", d->state, d->count);
	return 0;
}

static int my_open(struct inode *inode, struct file *file)
{
	return single_open(file, my_show, PDE_DATA(inode));
}

/* 7.2.5 用 struct proc_ops(不再是 file_operations) */
static const struct proc_ops my_proc_ops = {
	.proc_open    = my_open,
	.proc_read    = seq_read,
	.proc_lseek   = seq_lseek,
	.proc_release = single_release,
};

proc_create("mydrv", 0444, NULL, &my_proc_ops);

7.2.5 变化 :struct file_operations → struct proc_ops (成员带 proc_ 前缀)。

sysfs 与 debugfs(更现代的选择):

bash 复制代码
ls /sys/kernel/debug/
cat /sys/kernel/debug/clk/clk_summary
cat /sys/kernel/debug/regmap/
cat /sys/kernel/debug/gpio
cat /sys/kernel/debug/dmaengine/

21.6 Oops

Oops 是内核遇到非法操作时的"崩溃报告" ,不是 panic(除非 panic_on_oops=1)。

典型 Oops 内容:

bash 复制代码
BUG: unable to handle page fault for address: 0000000000000010
#PF: supervisor read access in kernel mode
#PF: error_code(0x0000) - not-present page
PGD 0 P4D 0
Oops: 0000 [#1] SMP
CPU: 0 PID: 1234 Comm: insmod Tainted: G           OE  7.2.5
RIP: 0010:my_probe+0x2a/0x100 [mydrv]
Code: 48 8b 47 10 ...
RSP: 0018:ffffc900003cfd10 EFLAGS: 00010246
RAX: 0000000000000000 RBX: ffff888100a4c000 RCX: ...
Call Trace:
 <TASK>
 my_probe+0x2a/0x100 [mydrv]
 platform_probe+0x4a/0x90
 really_probe+0x1c0/0x430
 __driver_probe_device+0x11a/0x1f0
 driver_probe_device+0x1e/0x90
 __device_attach_driver+0x9e/0x120
 ...
 </TASK>

分析步骤:

  1. 看 RIP (出错的指令地址)和 Call Trace(调用链)。
  2. 用 scripts/decode_stacktrace.sh 把地址翻译成函数+行号:
bash 复制代码
./scripts/decode_stacktrace.sh vmlinux < oops.txt
  1. 用 scripts/faddr2line 把 my_probe+0x2a/0x100 翻译成源码行:
bash 复制代码
./scripts/faddr2line vmlinux my_probe+0x2a/0x100
  1. 看 Code: 行的字节,配合 objdump -d 定位指令:
bash 复制代码
objdump -dS --start-address=0x... --stop-address=0x... vmlinux
  1. 常见原因:空指针 (0000000000000010 = NULL + 0x10)、 野指针 、use-after-free 、栈溢出。

打开更多信息:

bash 复制代码
CONFIG_KALLSYMS=y
CONFIG_DEBUG_INFO=y
CONFIG_FRAME_POINTER=y
复制代码
echo 1 > /proc/sys/kernel/panic_on_oops    # 崩溃即重启(配合 pstore)

21.7 BUG_ON() 和 WARN_ON()

宏 行为 使用场景
BUG() 立即崩溃(oops/panic) 不可能发生且继续会破坏数据
BUG_ON(cond) 条件为真时崩溃 同上
WARN_ON(cond) 打印警告 + 栈,继续运行 可疑但可继续
WARN_ON_ONCE(cond) 只警告一次 高频路径
WARN(cond, fmt, ...) 带格式化信息
panic_on_warn WARN 变成 panic 测试用
cpp 复制代码
if (WARN_ON(!priv->regmap))
	return -EINVAL;

/* 7.2.5 推荐:用 WARN 而非 BUG,除非真的无法继续 */
WARN_ON_ONCE(d->state != EXPECTED);

7.2.5 的观点 :尽量少用 BUG_ON() 。现代内核倾向于用 WARN_ON() + 错误返回,因为 BUG_ON() 会让整台机器不可用(对生产系统是灾难)。 CONFIG_BUG=n 时 BUG_ON() 甚至会被优化掉。

dump_stack():只想打印调用栈、不崩溃:

bash 复制代码
dump_stack();

lockdep(锁依赖检查):检测死锁和锁顺序错误:

bash 复制代码
CONFIG_PROVE_LOCKING=y
CONFIG_LOCKDEP=y
CONFIG_DEBUG_LOCK_ALLOC=y
bash 复制代码
echo 1 > /proc/sys/kernel/lock_stat
cat /proc/lockdep_stats
cat /proc/lockdep

DEBUG_ATOMIC_SLEEP:检测"在原子上下文睡眠":

复制代码
CONFIG_DEBUG_ATOMIC_SLEEP=y

KASAN:检测越界/use-after-free:

bash 复制代码
CONFIG_KASAN=y
CONFIG_KASAN_INLINE=y
bash 复制代码
BUG: KASAN: slab-out-of-bounds in my_read+0x45/0x100 [mydrv]
Read of size 4 at addr ffff888100a4c010 by task cat/1234

KCSAN :检测数据竞争。KMSAN:检测未初始化内存。

21.8 strace

strace 跟踪用户空间程序的系统调用,是"驱动接口是否正常"的照妖镜。

bash 复制代码
strace ./myprog
strace -f ./myprog                 # 跟踪子进程
strace -e openat,read,write,ioctl ./myprog
strace -o log.txt ./myprog
strace -T ./myprog                 # 每个调用耗时
strace -p <pid>                    # attach

调试驱动的典型用法:

bash 复制代码
strace -e ioctl ./my_i2c_test
# ioctl(3, I2C_RDWR, 0x7ffd...) = 0
# 如果返回 -1 EPERM/EINVAL,看是哪个 ioctl 失败

相关工具:

工具 作用
strace 系统调用
ltrace 库函数调用
perf trace 系统调用(带时间戳,开销更低)
bpftrace 用 eBPF 追踪任意函数

7.2.5 的现代替代 :perf trace 和 bpftrace 开销更低、能力更强:

bash 复制代码
perf trace -e 'syscalls:sys_enter_ioctl' ./myprog
bpftrace -e 'tracepoint:syscalls:sys_enter_ioctl { printf("%s %x\n", comm, args->cmd); }'

21.9 KGDB

KGDB 让 GDB 能像调试用户程序一样调试内核(断点、单步、看变量)。

21.9.1 配置

bash 复制代码
CONFIG_KGDB=y
CONFIG_KGDB_SERIAL_CONSOLE=y
CONFIG_KGDB_KDB=y
CONFIG_DEBUG_INFO=y
CONFIG_FRAME_POINTER=y
CONFIG_GDB_SCRIPTS=y
CONFIG_MAGIC_SYSRQ=y

21.9.2 用 QEMU 的 gdbstub(推荐,比串口 KGDB 简单)

这是无开发板调试内核的最佳方式:

bash 复制代码
# 宿主机:启动 QEMU,开放 gdbstub 端口 1234,并在启动时暂停
qemu-system-x86_64 \
  -kernel "$KBUILD/arch/x86/boot/bzImage" \
  -append "console=ttyS0 nokaslr" \
  -s -S \
  -nographic

# 另一个终端:用 GDB 连接
gdb "$KBUILD/vmlinux"
(gdb) target remote :1234
(gdb) hbreak start_kernel
(gdb) continue
(gdb) break my_probe            # 驱动断点
(gdb) continue
(gdb) bt
(gdb) p *dev
(gdb) list

-s = -gdb tcp::1234,-S = 启动时暂停。

关键点:

  • nokaslr 让内核地址固定,断点才稳定。
  • 硬件断点用 hbreak(内核里软件断点常常无效)。
  • 加载内核自带的 GDB 脚本(scripts/gdb/)增强功能:
bash 复制代码
(gdb) source "$KSRC/vmlinux-gdb.py"
(gdb) lx-version
(gdb) lx-dmesg
(gdb) lx-lsmod
(gdb) lx-ps
(gdb) lx-symbols        # 加载所有模块符号
(gdb) lx-device-list

scripts/gdb/linux/ 下有一整套命令(lx-dmesg、lx-lsmod、lx-ps、 lx-device-list、lx-tasks 等),驱动调试非常有用。

21.9.3 串口 KGDB(真实硬件)

bash 复制代码
# 内核参数
kgdboc=ttyS0,115200 kgdbwait
复制代码
# 宿主
gdb vmlinux
(gdb) target remote /dev/ttyUSB0

21.9.4 KDB(无 GDB 的内核调试器)

CONFIG_KGDB_KDB 提供 KDB,在控制台按 SysRq-G 进入:

bash 复制代码
kdb> help
kdb> bt
kdb> md 0xffffffff81000000
kdb> go

Magic SysRq 常用键 (echo h > /proc/sysrq-trigger 看帮助):

键 作用
b 立即重启
c 触发 crash(测试 kdump)
d 显示所有锁
e 给所有进程发 SIGTERM
g 进入 KDB
h 帮助
l 所有 CPU 的栈
m 内存信息
p 寄存器
s 同步磁盘
t 所有任务栈
w 阻塞任务

21.10 使用仿真器调试内核

(QEMU 是"仿真器"的代表,见 21.9.2。)

仿真器 说明
QEMU 全系统仿真,-s -S + gdbstub,首选
QEMU user mode 只仿真用户程序(qemu-arm ./prog)
Renode 面向嵌入式的外设级仿真
Spike RISC-V 官方 ISS
Simics(商业) 全系统仿真

QEMU 调试进阶:

bash 复制代码
# 记录/回放(逆向调试)
qemu-system-x86_64 -icount shift=auto,rr=record,rrfile=/tmp/replay.bin ...
qemu-system-x86_64 -icount shift=auto,rr=replay,rrfile=/tmp/replay.bin ...

# 单步执行到某地址
(gdb) hbreak *0xffffffff81234567
(gdb) stepi
(gdb) info registers

观察外设寄存器 :QEMU 的 -d 选项可以打印各种事件:

复制代码
qemu-system-x86_64 -d int,cpu_reset -D /tmp/qemu.log ...

21.11 应用程序调试

驱动工程师也常要调试用户空间的测试程序:

工具 作用
gdb / gdb -tui 源码级调试
valgrind 内存错误/泄漏
AddressSanitizer(-fsanitize=address) 编译期插桩的内存检查
UndefinedBehaviorSanitizer(-fsanitize=undefined) 未定义行为
ThreadSanitizer(-fsanitize=thread) 数据竞争
perf 性能分析
gprof / callgrind 函数级剖析
strace / ltrace 调用跟踪
bash 复制代码
# ASan 示例
gcc -fsanitize=address -g -o myprog myprog.c
./myprog        # 越界/UAF 会打印详细报告

# valgrind
valgrind --leak-check=full ./myprog

# perf 剖析
perf record -g ./myprog && perf report

驱动测试程序常见问题 :open() 权限、ioctl 命令号编码错误、 未处理 EINTR、缓冲区大小与内核不一致。

21.12 Linux 性能监控与调优工具

工具 层面 作用
top / htop 进程 CPU/内存占用
perf 全栈 采样剖析、tracepoint、PMU
ftrace 内核 函数/调度/中断追踪
bpftrace / bcc 全栈 eBPF 动态追踪
ftrace + trace-cmd 内核 事件记录与可视化
LTTng 全栈 低开销追踪
vmstat / iostat 系统 IO/内存
powertop 电源 唤醒源/耗电
perf lock 内核 锁竞争
perf sched 内核 调度延迟
slabtop 内核 slab 使用
pmap / smem 进程 内存映射

21.12.1 ftrace

bash 复制代码
cd /sys/kernel/debug/tracing

# 函数追踪
echo function > current_tracer
echo my_probe > set_ftrace_filter
echo 1 > tracing_on
cat trace
echo 0 > tracing_on

# 函数图(看调用链)
echo function_graph > current_tracer
cat trace

# 追踪事件(中断、调度、GPIO、regmap 等)
echo 1 > events/irq/enable
echo 1 > events/sched/sched_switch/enable

# tracepoint 列表
ls events/

7.2.5 的 tracepoint 覆盖极广 :gpio、regmap、i2c、spi、dma、 irq、sched、power、usb......驱动调试必备。

21.12.2 perf

bash 复制代码
# 采样剖析
perf top
perf record -g -a sleep 10 && perf report

# 追踪系统调用
perf trace ./myprog

# 硬件计数器
perf stat -e cycles,instructions,cache-misses ./myprog

# 锁竞争
perf lock record && perf lock report

# 调度延迟
perf sched record && perf sched latency

21.12.3 eBPF / bpftrace

bash 复制代码
# 追踪 ioctl
bpftrace -e 'tracepoint:syscalls:sys_enter_ioctl { printf("%s cmd=%x\n", comm, args->cmd); }'

# 追踪内核函数耗时
bpftrace -e 'kprobe:my_hw_xfer { @start[tid] = nsecs; }
             kretprobe:my_hw_xfer /@start[tid]/ {
                 @ns = hist(nsecs - @start[tid]); delete(@start[tid]); }'

7.2.5 的 eBPF 能力 :kprobe、kretprobe、tracepoint、perf event、fentry/fexit、 BTF、CO-RE,几乎可以追踪任何内核行为,是 4.0 时代完全不具备的。

21.12.4 内核自带脚本

bash 复制代码
./scripts/checkpatch.pl --no-tree -f mydrv.c    # 代码风格检查
./scripts/decode_stacktrace.sh vmlinux < oops.txt
./scripts/faddr2line vmlinux my_probe+0x2a/0x100
./scripts/checkstack.pl                         # 栈使用量
./scripts/get_maintainer.pl mydrv.c             # 找维护者
./scripts/dtc/dt-extract-compatibles            # 提取 compatible

21.13 常见错误

错误 现象 修正
用 printk 不带级别 编译告警/默认级别 用 pr_*/dev_*
dev_dbg 没输出 以为代码没执行 用 dynamic debug 打开
Oops 地址看不懂 无符号 开 CONFIG_KALLSYMS + DEBUG_INFO
用 BUG_ON() 做参数检查 整机崩溃 用 WARN_ON() + 返回错误
gdb 断点打不上 软件断点无效 用 hbreak,加 nokaslr
KGDB 连不上 串口/端口配置错 检查 kgdboc= 和 QEMU -s
strace 看不到 ioctl 参数 命令号难读 对照 _IO* 宏解码
ftrace 无输出 未开 tracing_on 或 filter 检查 set_ftrace_filter
perf 权限不足 perf_event_paranoid echo 1 > /proc/sys/kernel/perf_event_paranoid
proc_create 用 file_operations 编译错误 用 struct proc_ops
KASAN 报告被淹没 日志太乱 只保留第一条报告
lockdep 误报 假死锁 检查是否真的持锁顺序错误

21.14 本章实验清单

实验 目的 关键观察
GDB 用户程序 学习断点/单步 b/n/p/bt
DDD 图形调试 学习图形界面 数据窗口看链表
printk 级别 学习日志 dmesg 输出
dynamic debug 学习零开销调试 dyndbg/control
earlycon 学习早期日志 启动最开头输出
/proc/interrupts 学习运行时信息 中断计数
制造 Oops 学习崩溃分析 decode_stacktrace.sh
WARN_ON 学习非致命检查 栈+继续运行
KASAN 学习内存错误 越界报告
lockdep 学习死锁检测 锁顺序报告
strace 学习系统调用 ioctl 跟踪
QEMU + GDB 内核源码级调试 hbreak+lx-dmesg
ftrace 学习函数追踪 function_graph
perf 学习性能剖析 perf report
bpftrace 学习动态追踪 自定义探针

21.15 总结

  1. 调试按"从粗到细":printk → /proc/debugfs → Oops 分析 → 检查器 → 追踪 → GDB。
  2. printk 仍是第一武器 ;7.2.5 用 pr_*/dev_* 宏, dev_err_probe() 是 probe 的推荐错误打印。
  3. dynamic debug 让 pr_debug()/dev_dbg() 零开销且可运行时打开。
  4. earlycon 是无重编译的早期调试;ARM32 还有汇编级 DEBUG_LL。
  5. Oops 分析 用 scripts/decode_stacktrace.sh 和 scripts/faddr2line。
  6. WARN_ON() 优先于 BUG_ON();用 KASAN/KCSAN/lockdep 主动发现 bug。
  7. strace 看用户态系统调用;ftrace/perf/bpftrace 看内核行为。
  8. QEMU + gdbstub(-s -S)是无开发板调试内核的最佳方式 , 配合 scripts/gdb/ 的 lx-* 命令可以像调试用户程序一样调试内核。
  9. KGDB/KDB 在真实硬件上用串口调试内核;SysRq 提供运行时"急救"手段。
  10. 原书的 GDB/DDD、printk、DEBUG_LL/EARLY_PRINTK、/proc、Oops、BUG_ON/WARN_ON、 strace、KGDB、仿真器调试、应用调试、性能工具在本章全部覆盖, 并补充了 7.2.5 的 dynamic debug、ftrace、perf、eBPF、KASAN、lockdep。
相关推荐
Snasph4 小时前
22-QEMU 实验总览
linux 驱动学习
Snasph4 小时前
13-Linux 块设备驱动
linux 驱动学习
Snasph6 小时前
15- Linux I2C 核心、总线与设备驱动
linux 驱动学习
Snasph6 小时前
附录-API 速查与术语表
linux 驱动学习
Snasph7 小时前
11-内存与 I/O 访问
linux 驱动学习
Snasph8 小时前
14-Linux 网络设备驱动
linux 驱动学习
Snasph9 小时前
18-ARM Linux 设备树
linux 驱动学习
Snasph10 小时前
20-Linux 芯片级移植及底层驱动
linux 驱动学习
Snasph10 小时前
12- Linux 设备驱动的软件架构思想
linux 驱动学习