内核驱动调试接口与使用方法入门

文章目录

  • [1. 引言](#1. 引言)
  • [2. 调试工具箱](#2. 调试工具箱)
  • [3. 基础日志](#3. 基础日志)
    • [3.1 规范化输出](#3.1 规范化输出)
    • [3.2 频控打印](#3.2 频控打印)
    • [3.3 动态开关](#3.3 动态开关)
      • [3.3.1 开启与关闭](#3.3.1 开启与关闭)
      • [3.3.2 加载时开启](#3.3.2 加载时开启)
    • [3.4 数据缓冲区打印](#3.4 数据缓冲区打印)
    • [3.5 断言](#3.5 断言)
  • [4. debugfs](#4. debugfs)
    • [4.1 代码示意](#4.1 代码示意)
    • [4.2 实时监控](#4.2 实时监控)
  • [5. trace_printk](#5. trace_printk)
    • [5.1 架构对比](#5.1 架构对比)
    • [5.2 使用方法](#5.2 使用方法)
  • [6. devmem](#6. devmem)
  • [7. Oops](#7. Oops)
    • [7.1 解读 Oops](#7.1 解读 Oops)
    • [7.2 快速定位](#7.2 快速定位)
      • [7.2.1 decode_stacktrace.sh](#7.2.1 decode_stacktrace.sh)
      • [7.2.2 faddr2line](#7.2.2 faddr2line)
    • [7.3 手动定位](#7.3 手动定位)
  • [8. KASAN](#8. KASAN)
  • [9. 总结](#9. 总结)

1. 引言

驱动开发的真正挑战往往不在于编写代码,而在于调试(Debugging)

在用户态程序开发中,拥有 GDB、IDE 断点和 printf 等简单方便的调试工具;但在内核态开发中,环境变得严苛且不透明。以下是内核驱动开发中常见的一些问题:

  • 模块加载失败insmod 报错,但是不知错在哪里。
  • Probe 未触发:确认代码写的没问题,但是驱动入口函数就是不执行。
  • 数据异常 :I2C/SPI 读回来的数据全是 0xFF 或乱码。
  • 中断怪象:IRQ 触发了,但执行结果与期望不符。
  • 内存踩踏:写越界导致随机崩溃,且崩溃点往往不在案发现场。
  • 系统崩溃:一个空指针解引用,整个系统直接崩溃。

内核态程序运行时,无法随意暂停系统。因此,我们需要构建一套从日志记录到实时追踪,再到现场分析的完整工具箱。本文将系统梳理 Linux 内核驱动开发中最高频、最实用的调试接口与方法。

2. 调试工具箱

在动手之前,根据问题现象选择合适的工具,能事半功倍。
逻辑/流程追踪
缓冲区数据打印
异常检测/断言
内部状态/变量查看
性能/时序/中断分析
硬件寄存器确认
系统崩溃/Oops
内存越界/踩踏检测
驱动调试需求
工具选择
printk / dynamic_debug
print_hex_dump
WARN_ON / BUG_ON
debugfs
trace_printk
devmem / devmem2
faddr2line / decode_stacktrace.sh
KASAN

3. 基础日志

内核打印是第一道防线,但滥用 printk 会导致日志洪水,养成良好的打印习惯,能让调试事半功倍。同时,学会使用断言能提前暴露问题。

3.1 规范化输出

在驱动代码中,建议放弃裸写 printk,改用内核推荐的等级宏,这有助于利用内核日志等级过滤无关信息。为了让日志统一带上驱动名称,可以在文件最开头定义 pr_fmt

c 复制代码
/* 放在 .c 文件开头,自动给所有日志加上前缀 */
#define pr_fmt(fmt) "MyDriver: " fmt

/* 自带日志级别,且易于搜索 */
pr_info("module loaded\n");
pr_err("i2c timeout (Addr: 0x%02x)\n", addr);

/* 使用 %pS 可以打印函数名,适合调试回调函数 */
pr_info("callback function is %pS\n", callback_func);

/* 调试专用,平时不打印,开启 DEBUG 宏后才生效 */
pr_debug("count = %d\n", count);

实用技巧 :查看日志时,可根据需要选用 dmesg -w(实时跟随模式)和 dmesg -C(清空缓冲区),提高阅读效率。

3.2 频控打印

在中断处理函数或高频循环中打印日志会导致系统卡顿甚至挂死。这时候推荐使用 _ratelimited 后缀的打印宏。

c 复制代码
/* 每秒最多打印默认次数,通常是 5 秒 10 次,具体阈值由内核实现决定(不同内核版本可能不同) */
if (status_error)
	pr_err_ratelimited("hardware error: 0x%x\n", status);

3.3 动态开关

如果需要临时开启 pr_debug 级别的日志,可以使用内核的动态调试功能(需开启 CONFIG_DYNAMIC_DEBUG 宏控)。代码中保留 pr_debug,默认不打印,运行时通过命令精准开启,无需重新编译内核。

3.3.1 开启与关闭

通过 debugfs 接口动态控制:

bash 复制代码
# 挂载 debugfs (通常系统会自动挂载)
sudo mount -t debugfs none /sys/kernel/debug

# 开启,指定文件名 (+p)
sudo sh -c 'echo "file my_driver.c +p" > /sys/kernel/debug/dynamic_debug/control'

# 开启,指定行号
sudo sh -c 'echo "file my_driver.c line 123 +p" > /sys/kernel/debug/dynamic_debug/control'

# 关闭,指定文件名 (-p)
sudo sh -c 'echo "file my_driver.c -p" > /sys/kernel/debug/dynamic_debug/control'

3.3.2 加载时开启

如果驱动在加载瞬间就报错,来不及通过 debugfs 接口写入参数时,可以使用模块参数:

bash 复制代码
# 整个模块
sudo modprobe my_driver dyndbg="module my_driver +p"
# 指定文件
sudo modprobe my_driver dyndbg="file my_driver.c +p"

3.4 数据缓冲区打印

驱动开发常涉及协议调试(如 SPI/I2C 数据包),不要自己写 for 循环打印,内核提供了标准接口 print_hex_dump

c 复制代码
/* 打印一段内存 buffer
 * KERN_DEBUG: 日志等级
 * "RX_DATA: ": 前缀字符串
 * DUMP_PREFIX_OFFSET: 显示偏移量
 * 16: 每行显示 16 字节
 * 1: 每个数据单元为 1 字节
 * buf: 数据指针
 * len: 数据长度
 * true: 是否显示 ASCII 字符
 * 打印效果:RX_DATA: 00000000: 1a 2b 3c 4d ... .+<M
 */
print_hex_dump(KERN_DEBUG, "RX_DATA: ", DUMP_PREFIX_OFFSET,
               16, 1, buf, len, true);

3.5 断言

在关键路径上检查条件是防御性编程的基础。

  • WARN_ON(condition):如果条件为真,打印堆栈信息,程序继续运行。适用于不应该发生,但发生了也能勉强运行的场景。
  • BUG_ON(condition):如果条件为真,打印堆栈信息,并引发 Kernel Panic(系统崩溃)。仅适用于如果继续运行会破坏核心数据结构或造成硬件损坏的极端场景。

注意 :驱动开发中应尽量使用 WARN_ON 并配合错误码返回(如 return -EINVAL),慎用 BUG_ON,因为它会导致整个系统不可用。

4. debugfs

虚拟文件系统中,sysfs 用于建立规范的标准设备模型,procfs 用于观测系统运行信息。如果你只是想查看驱动内部的变量(如寄存器值、统计计数、状态机状态等),更加自由的 debugfs 是最佳选择。

4.1 代码示意

c 复制代码
#include <linux/debugfs.h>

static struct dentry *dbg_dir;
static u32 irq_counter = 0;
static u64 last_timestamp = 0;

static int __init my_driver_init(void)
{
	// 1. 创建目录 /sys/kernel/debug/my_driver/
	dbg_dir = debugfs_create_dir("my_driver", NULL);
	if (!dbg_dir)
		return -ENOMEM;

	// 2. 创建文件,将变量暴露给用户空间(只读)
	// u32 打印的是十进制,x64 打印的是十六进制
	debugfs_create_u32("irq_count", 0444, dbg_dir, &irq_counter);
	debugfs_create_x64("last_ts", 0444, dbg_dir, &last_timestamp);

	return 0;
}

static void __exit my_driver_exit(void)
{
	// 递归删除目录及其下文件
	debugfs_remove_recursive(dbg_dir);
}

4.2 实时监控

在用户态配合 watch 命令,形成简易仪表盘:

bash 复制代码
# 每 0.5 秒刷新一次数据
sudo watch -n 0.5 cat /sys/kernel/debug/my_driver/irq_count

5. trace_printk

当你在调试中断延迟、调度时序或原子上下文问题时,普通的 printk 可能会因为 I/O 慢而改变时序,甚至掩盖 Bug(Heisenbug)。此时,我们需要 ftrace 子系统提供的 trace_printk,它的特点是只把日志写入内存缓冲区,不走虚拟终端、伪终端或串口等 TTY I/O 接口,速度极快,可在中断上下文放心使用。

5.1 架构对比

ftrace RingBuffer 控制台 printk RingBuffer 驱动代码 ftrace RingBuffer 控制台 printk RingBuffer 驱动代码 普通 printk (慢,可能阻塞) trace_printk (极快,无阻塞) 用户想看时才读取 写入日志 格式化输出 (耗时!) 仅写入内存缓冲区

5.2 使用方法

代码插桩

c 复制代码
/* 极其轻量,可用于中断上下文,几乎不影响时序 */
trace_printk("ISR enter: status=0x%x\n", reg_val);

查看记录

bash 复制代码
# 1. 开启追踪 (确认内核已开启 CONFIG_FTRACE)
sudo sh -c 'echo 1 > /sys/kernel/debug/tracing/tracing_on'

# ... 运行你的程序 ...

# 2. 读取追踪缓冲区
sudo cat /sys/kernel/debug/tracing/trace

# 3. 关闭追踪
sudo sh -c 'echo 0 > /sys/kernel/debug/tracing/tracing_on'

说明trace_printk 主要开销来自缓冲区写入,关闭后几乎没有开销。建议仅在调试时启用,避免在生产环境影响性能。

6. devmem

有时候检查代码没有问题,但是硬件寄存器没写进去,或者想直接读硬件状态。这时候我们不需要反复修改读写寄存器的代码,可以直接用内存地址读写工具 devmem2devmem 是 BusyBox 提供的简化版工具,devmem2 是独立实现的完整工具,两者功能类似,这里以 devmem2 为例说明。

直接通过物理地址读写寄存器可能导致硬件状态异常或系统崩溃,请注意在操作前确认地址正确性。

bash 复制代码
# 安装 devmem2,以 Debian 系发行版为例
sudo apt install devmem2

# 读取指定物理地址的值,4 字节长度
sudo devmem2 0x00200000 w

# 向指定物理地址写值,1 字节长度
sudo devmem2 0x00200000 b 0x1

读写操作日志如下所示:

bash 复制代码
$ sudo devmem2 0x9c0000000 b
/dev/mem opened.
Memory mapped at address 0xffff89399000.
Value at address 0xC0000000 (0xffff89399000): 0xFF

$ sudo devmem2 0x9c0000000 b 0x1
/dev/mem opened.
Memory mapped at address 0xffffa7625000.
Value at address 0xC0000000 (0xffffa7625000): 0xFF
Written 0x1; readback 0xFF

7. Oops

当发生空指针引用时,内核会打印 Oops 信息。调试内核或内核模块时分别需要加上 CONFIG_DEBUG_INFO=yEXTRA_CFLAGS += -g 打开调试信息。

检查文件中是否包含调试相关的段,如果没有 debug 相关输出,说明没有打开调试信息:

bash 复制代码
$ aarch64-linux-gnu-objdump -h hello_device.ko | grep debug
 20 .debug_info   00000750  0000000000000000  0000000000000000  00000640  2**0
 21 .debug_abbrev 0000017b  0000000000000000  0000000000000000  00000d90  2**0
 22 .debug_aranges 00000040  0000000000000000  0000000000000000  00000f0b  2**0
 23 .debug_rnglists 00000021  0000000000000000  0000000000000000  00000f4b  2**0
 24 .debug_line   00000108  0000000000000000  0000000000000000  00000f6c  2**0
 25 .debug_str    00000fd6  0000000000000000  0000000000000000  00001074  2**0
 26 .debug_line_str 000001f9  0000000000000000  0000000000000000  0000204a  2**0
 27 .debug_frame  00000060  0000000000000000  0000000000000000  00002248  2**3

7.1 解读 Oops

Oops 日志中最关键的是 PC 指针和 Call Trace:

bash 复制代码
[27580.005612] Unable to handle kernel NULL pointer dereference at virtual address 0000000000000000
[27580.006488] Mem abort info:
[27580.006872]   ESR = 0x0000000096000044
[27580.007251]   EC = 0x25: DABT (current EL), IL = 32 bits
[27580.007772]   SET = 0, FnV = 0
[27580.008085]   EA = 0, S1PTW = 0
[27580.008409]   FSC = 0x04: level 0 translation fault
[27580.008891] Data abort info:
[27580.009187]   ISV = 0, ISS = 0x00000044
[27580.009572]   CM = 0, WnR = 1
[27580.009875] user pgtable: 4k pages, 48-bit VAs, pgdp=00000004f1c40000
[27580.010552] [0000000000000000] pgd=0000000000000000, p4d=0000000000000000
[27580.011236] Internal error: Oops: 0000000096000044 [#1] SMP
[27580.011778] Modules linked in: hello_device(O+) led_class_multicolor btsdio brcmfmac brcmutil panfrost pwm_fan rk805_pwrkey drm_shmem_helper gpu_sched zram zsmalloc binfmt_misc sch_fq_codel fuse dm_mod nfnetlink ip_tables ipv6 nvmem_rockchip_otp yt6801 rockchip_cpuinfo uio_pdrv_genirq uio [last unloaded: hello_device(O)]
[27580.014673] CPU: 1 PID: 3512 Comm: insmod Tainted: G           O       6.1.75-vendor-rk35xx #1
[27580.015501] Hardware name: Orange Pi 5 Pro (DT)
[27580.015952] pstate: 60400009 (nZCv daif +PAN -UAO -TCO -DIT -SSBS BTYPE=--)
[27580.016631] pc : hello_init+0x24/0x1000 [hello_device]
[27580.017171] lr : hello_init+0x20/0x1000 [hello_device]
[27580.017700] sp : ffff800011233af0
[27580.018038] x29: ffff800011233af0 x28: ffff80000a29a990 x27: 0000000000000000
[27580.018753] x26: ffff800011233cb0 x25: 0000000000000000 x24: 0000000000000000
[27580.019465] x23: 0000000000000000 x22: 0000000000000000 x21: ffff800001364058
[27580.020174] x20: ffff80000a4ddfe0 x19: ffff800001252000 x18: 0000000000000000
[27580.020884] x17: 0000000000000000 x16: 0000000000000000 x15: 0000000000000000
[27580.021593] x14: 0000000000000000 x13: 0000000000000000 x12: 0000000000000000
[27580.022303] x11: 0000000000000000 x10: 0000000000000000 x9 : ffff8000081a06d4
[27580.023013] x8 : 00000db8c200000c x7 : 21646c726f57206f x6 : 6f57206f6c6c6548
[27580.023722] x5 : 0000000000000000 x4 : 0000000000000000 x3 : 0000000000000000
[27580.024431] x2 : 0000000000000000 x1 : ffff0004f08cadc0 x0 : 0000000000000000
[27580.025143] Call trace:
[27580.025399]  hello_init+0x24/0x1000 [hello_device]
[27580.025909]  do_one_initcall+0x94/0x1e4
[27580.026323]  do_init_module+0x58/0x1e0
[27580.026724]  load_module+0x1850/0x1918
[27580.027117]  __do_sys_finit_module+0xf8/0x118
[27580.027563]  __arm64_sys_finit_module+0x24/0x30
[27580.028032]  invoke_syscall+0x8c/0x128
[27580.028425]  el0_svc_common.constprop.0+0xd8/0x128
[27580.028911]  do_el0_svc+0xac/0xbc
[27580.029261]  el0_svc+0x2c/0x54
[27580.029590]  el0t_64_sync_handler+0xac/0x13c
[27580.030026]  el0t_64_sync+0x19c/0x1a0
[27580.030402]
               PC: 0xffff800001252024:
[27580.030892] 1e24  ******** ******** ******** ******** ******** ******** ******** ********
[27580.031771] 1e44  ******** ******** ******** ******** ******** ******** ******** ********

7.2 快速定位

7.2.1 decode_stacktrace.sh

Linux 内核源码树提供了一个强大的脚本 scripts/decode_stacktrace.sh,它可以自动加载 vmlinux 和 ko 文件,把 Oops 日志里的十六进制直接翻译成代码行号。如果 .ko 文件没有开启调试信息,执行脚本会报错 WARNING! Modules path isn't set, but is needed to parse this symbol

bash 复制代码
# 语法:将 dmesg 输出通过管道传给脚本
# 需要:带调试符号的 vmlinux,以及模块(.ko)所在目录
sudo dmesg | ./scripts/decode_stacktrace.sh vmlinux auto /home/dump_linux/learning_linux_kernel_driver_from_scratch/hello_device/

解析后偏移量信息被转换成了文件名:行号,参见如下日志:

bash 复制代码
[  114.248142] pc : hello_init (/home/dump_linux/learning_linux_kernel_driver_from_scratch/hello_device/hello_device.c:9) hello_device
[  114.248635] lr : hello_init (/home/dump_linux/learning_linux_kernel_driver_from_scratch/hello_device/hello_device.c:9) hello_device
[  114.249118] sp : ffff800011263af0
[  114.249431] x29: ffff800011263af0 x28: ffff80000a29a990 x27: 0000000000000000
[  114.256038] Call trace:
[  114.256270] hello_init (/home/dump_linux/learning_linux_kernel_driver_from_scratch/hello_device/hello_device.c:9) hello_device

7.2.2 faddr2line

内核源码提供了一个脚本工具 faddr2line,它支持 函数+偏移 语法,省去了手动查 nm 和计算的过程,这是目前内核开发者最常用的方式。

bash 复制代码
# 格式:faddr2line <ko文件> <函数名+偏移>
./scripts/faddr2line hello_device.ko hello_init+0x24

执行结果如下,偏移地址是 0x24,函数总长度是 0x34

bash 复制代码
hello_init+0x24/0x34:
hello_init at /home/dump_linux/learning_linux_kernel_driver_from_scratch/hello_device/hello_device.c:9

7.3 手动定位

如果你无法使用自动化脚本,可以手动计算偏移量。这里假设 Oops 显示 hello_init+0x24/0x1000,表示函数内偏移为 0x24,总长度为 0x1000(按页对齐)。

1. 获取函数的起始地址

使用 nm 工具从 .ko 文件中提取符号的基地址:

bash 复制代码
$ nm hello_device.ko | grep hello_init
0000000000000000 t hello_init

2. 计算绝对偏移量

nm 查到的基地址(0x0000000000000000)与 Oops 中的偏移量(0x24)相加,即绝对偏移量为 0x24

3. 使用 addr2line 定位源码行号

如果没有开启调试信息,.ko 文件中不会包含源码行号映射表,行号信息就会变成问号。

bash 复制代码
# -f: 显示函数名, -e: 指定文件
addr2line -f -e hello_device.ko 0x24

定位结果如下所示,可以看到函数名是 hello_init,代码行号是第 9 行:

bash 复制代码
hello_init
/home/dump_linux/learning_linux_kernel_driver_from_scratch/hello_device/hello_device.c:9

8. KASAN

如果你的驱动遇到莫名其妙的随机崩溃,或者写了一个变量却改变了另一个无关变量的值,大概率是内存越界或释放后使用。这种情况是最难调试的 Bug,因为崩溃点通常不是案发现场。

解决方案是开启 KASAN (Kernel Address Sanitizer),需要在内核配置时打开配置选项,这里仅供参考,相关配置项较多,具体请根据需要调整:

bash 复制代码
CONFIG_KASAN=y
CONFIG_KASAN_GENERIC=y
CONFIG_HAVE_ARCH_KASAN=y

重新编译内核后,KASAN 会自动监控所有内存访问,在每次内存访问时进行检查。一旦越界,它会立刻打印报错,并指出:

  1. 谁在非法访问(函数、行号)。
  2. 这块内存是谁分配的(分配时的堆栈)。
  3. 这块内存是谁释放的(释放后使用)。

典型 KASAN 报告如下所示:

bash 复制代码
BUG: KASAN: slab-out-of-bounds in kmalloc_oob_right+0xa8/0xbc [kasan_test]
Write of size 1 at addr ffff8801f44ec37b by task insmod/2760

CPU: 1 PID: 2760 Comm: insmod Not tainted 4.19.0-rc3+ #698
Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.10.2-1 04/01/2014
Call Trace:
 dump_stack+0x94/0xd8
 print_address_description+0x73/0x280
 kasan_report+0x144/0x187
 __asan_report_store1_noabort+0x17/0x20
 kmalloc_oob_right+0xa8/0xbc [kasan_test]
 kmalloc_tests_init+0x16/0x700 [kasan_test]
 do_one_initcall+0xa5/0x3ae
 do_init_module+0x1b6/0x547
 load_module+0x75df/0x8070
 __do_sys_init_module+0x1c6/0x200
 __x64_sys_init_module+0x6e/0xb0
 do_syscall_64+0x9f/0x2c0
 entry_SYSCALL_64_after_hwframe+0x44/0xa9
RIP: 0033:0x7f96443109da
RSP: 002b:00007ffcf0b51b08 EFLAGS: 00000202 ORIG_RAX: 00000000000000af
RAX: ffffffffffffffda RBX: 000055dc3ee521a0 RCX: 00007f96443109da
RDX: 00007f96445cff88 RSI: 0000000000057a50 RDI: 00007f9644992000
RBP: 000055dc3ee510b0 R08: 0000000000000003 R09: 0000000000000000
R10: 00007f964430cd0a R11: 0000000000000202 R12: 00007f96445cff88
R13: 000055dc3ee51090 R14: 0000000000000000 R15: 0000000000000000

Allocated by task 2760:
 save_stack+0x43/0xd0
 kasan_kmalloc+0xa7/0xd0
 kmem_cache_alloc_trace+0xe1/0x1b0
 kmalloc_oob_right+0x56/0xbc [kasan_test]
 kmalloc_tests_init+0x16/0x700 [kasan_test]
 do_one_initcall+0xa5/0x3ae
 do_init_module+0x1b6/0x547
 load_module+0x75df/0x8070
 __do_sys_init_module+0x1c6/0x200
 __x64_sys_init_module+0x6e/0xb0
 do_syscall_64+0x9f/0x2c0
 entry_SYSCALL_64_after_hwframe+0x44/0xa9

Freed by task 815:
 save_stack+0x43/0xd0
 __kasan_slab_free+0x135/0x190
 kasan_slab_free+0xe/0x10
 kfree+0x93/0x1a0
 umh_complete+0x6a/0xa0
 call_usermodehelper_exec_async+0x4c3/0x640
 ret_from_fork+0x35/0x40

The buggy address belongs to the object at ffff8801f44ec300
 which belongs to the cache kmalloc-128 of size 128
The buggy address is located 123 bytes inside of
 128-byte region [ffff8801f44ec300, ffff8801f44ec380)
The buggy address belongs to the page:
page:ffffea0007d13b00 count:1 mapcount:0 mapping:ffff8801f7001640 index:0x0
flags: 0x200000000000100(slab)
raw: 0200000000000100 ffffea0007d11dc0 0000001a0000001a ffff8801f7001640
raw: 0000000000000000 0000000080150015 00000001ffffffff 0000000000000000
page dumped because: kasan: bad access detected

Memory state around the buggy address:
 ffff8801f44ec200: fc fc fc fc fc fc fc fc fb fb fb fb fb fb fb fb
 ffff8801f44ec280: fb fb fb fb fb fb fb fb fc fc fc fc fc fc fc fc
>ffff8801f44ec300: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 03
                                                                ^
 ffff8801f44ec380: fc fc fc fc fc fc fc fc fb fb fb fb fb fb fb fb
 ffff8801f44ec400: fb fb fb fb fb fb fb fb fc fc fc fc fc fc fc fc

9. 总结

场景 推荐工具 核心优势
日常逻辑验证 pr_info / pr_debug / pr_fmt 简单直接,配合 dynamic_debug 灵活开关
数据包分析 print_hex_dump 格式化输出十六进制 Buffer,美观易读
防御性检查 WARN_ON 暴露逻辑错误但不至于让系统崩溃
查看内部状态 debugfs 干净,不污染 dmesg,支持按需读取
中断/时序分析 trace_printk 极低开销,不影响系统实时性
硬件物理验证 devmem / devmem2 绕过驱动,直接读写物理寄存器
内核崩溃 faddr2line / decode_stacktrace.sh 将晦涩的内存地址转换为具体的代码行号
内存越界/踩踏 KASAN 捕捉非法内存 Bug 的神器

调试驱动是驱动开发的必备技能,调试不能靠运气,得靠基于证据的推理。

相关推荐
Trouvaille ~2 小时前
【Linux】网络编程基础(三):Socket编程预备知识
linux·运维·服务器·网络·c++·socket·网络字节序
项目題供诗2 小时前
51单片机入门(六)
单片机·嵌入式硬件·51单片机
笑锝没心没肺2 小时前
Linux Audit 系统配置介绍
linux·运维·服务器
小义_2 小时前
【RH134知识点问答题】第6章 管理 SELinux 安全性
linux·网络·云原生·rhel
魏波.2 小时前
主流 Linux 发行版有哪些?
linux
REDcker3 小时前
RTSP 直播技术详解
linux·服务器·网络·音视频·实时音视频·直播·rtsp
丁劲犇3 小时前
CentOS 7.6 TCP连接奇慢故障排查:中文注释引发的sysctl配置异常
linux·tcp/ip·centos·速度慢
代码游侠3 小时前
学习笔记——Linux内核与嵌入式开发1
linux·运维·前端·arm开发·单片机·嵌入式硬件·学习
宇钶宇夕3 小时前
CoDeSys入门实战一起学习(二十八):(LD)三台电机顺起逆停程序详解—上升、下降沿使用上
单片机·嵌入式硬件·学习