驱动程序开发的调试建议

本文档作为调试设备驱动程序的一般起点和查找。虽然本指南侧重于需要重新编译模块/内核的调试,但 用户空间调试指南 将指导您使用诸如动态调试、ftrace 和其他用于调试问题和行为的工具。有关一般调试建议,请参阅 一般建议文档

以下部分将向您展示可用的工具。

printk() & 相关函数

这些是 printf() 的变体,具有不同的目标,并支持动态开启或关闭,或缺乏此功能。

简单的 printk()

经典方法,可以非常有效地用于快速和粗略地开发新模块或提取用于故障排除的任意必要数据。

先决条件:CONFIG_PRINTK(通常默认启用)

优点:

  • 无需学习任何东西,易于使用

  • 易于根据您的需求进行修改(数据的格式(参见:如何正确使用 printk 格式说明符),在日志中的可见性)

  • 可能会导致代码执行延迟(有利于确认时序是否是一个因素)

缺点:

  • 需要重新构建内核/模块

  • 可能会导致代码执行延迟(这可能会导致问题无法重现)

有关完整文档,请参阅 使用 printk 记录消息

Trace_printk

先决条件:CONFIG_DYNAMIC_FTRACE & #include <linux/ftrace.h>

它比 printk() 使用起来稍微不方便一点,因为您必须从跟踪文件中读取消息(参见:读取 ftrace 日志 而不是从内核日志中读取,但是当 printk() 向代码执行中添加不必要的延迟,导致问题不稳定或隐藏时,它非常有用。)

如果对此进行处理仍然会导致时序问题,那么您可以尝试 trace_puts()

有关完整文档,请参阅 trace_printk()

dev_dbg

打印语句,可以通过 动态调试 定位,其中包含有关上下文中使用的设备的附加信息。

何时适合在代码中保留调试打印?

永久调试语句必须对开发人员解决驱动程序错误行为有所帮助。判断这一点比科学更像是一门艺术,但 编码风格指南 中有一些指导原则。在几乎所有情况下,都不应将调试语句上游化,因为正常工作的驱动程序应该是静默的。

自定义 printk

示例

#define core_dbg(fmt, arg...) do { \
        if (core_debug) \
                printk(KERN_DEBUG pr_fmt("core: " fmt), ## arg); \
        } while (0)

您应该在什么时候这样做?

最好只使用 pr_debug(),稍后可以使用动态调试打开/关闭它。此外,许多驱动程序通过模块参数设置的变量(如 core_debug)来激活这些打印。但是,不再推荐使用模块参数

Ftrace

创建自定义 Ftrace 跟踪点

跟踪点会在您的代码中添加一个钩子,当启用跟踪点时,该钩子将被调用并记录。例如,这可以用于跟踪命中条件分支或在调试会话期间转储代码流特定点的内部状态。

这是 如何实现新跟踪点 的基本描述。

有关完整的事件跟踪文档,请参阅 事件跟踪

有关完整的 Ftrace 文档,请参阅 ftrace - 函数跟踪器

DebugFS

先决条件:CONFIG_DEBUG_FS` & `#include <linux/debugfs.h>

DebugFS 与其他调试方法不同,因为它不会将消息写入内核日志,也不会向代码添加跟踪。相反,它允许开发人员处理一组文件。通过这些文件,您可以存储变量的值或进行寄存器/内存转储,或者您可以使这些文件可写并修改驱动程序中的值/设置。

其他可能的用例

  • 存储寄存器值

  • 跟踪变量

  • 存储错误

  • 存储设置

  • 切换像调试开/关这样的设置

  • 错误注入

当数据转储的大小难以作为一般内核日志的一部分消化时(例如,当转储原始比特流数据时),或者当您不是一直对所有值感兴趣,而是可以检查它们时,这尤其有用。

总体思路是

  • 在探测期间创建目录(struct dentry *parent = debugfs_create_dir("my_driver", NULL);

  • 创建文件(debugfs_create_u32("my_value", 444, parent, &my_variable);

    • 在此示例中,该文件位于 /sys/kernel/debug/my_driver/my_value 中(具有用户/组/所有人的读取权限)

    • 任何读取该文件的操作都将返回变量 my_variable 的当前内容

  • 删除设备时清理目录(debugfs_remove_recursive(parent);

有关完整文档,请参阅 DebugFS

KASAN、UBSAN、lockdep 和其他错误检查器

KASAN(内核地址清理器)

先决条件:CONFIG_KASAN

KASAN 是一种动态内存错误检测器,可帮助查找释放后使用和越界错误。它使用编译时检测来检查每次内存访问。

有关完整文档,请参阅 内核地址清理器 (KASAN)

UBSAN(未定义行为清理器)

先决条件:CONFIG_UBSAN

UBSAN 依赖于编译器检测和运行时检查来检测未定义的行为。它旨在查找各种问题,包括带符号整数溢出、数组索引越界等。

有关完整文档,请参阅 未定义行为清理器 - UBSAN

lockdep(锁依赖验证器)

先决条件:CONFIG_DEBUG_LOCKDEP

lockdep 是一种运行时锁依赖验证器,可检测内核中潜在的死锁和其他与锁定相关的问题。它跟踪锁的获取和释放,构建一个依赖关系图,该图用于分析潜在的死锁。lockdep 对于验证内核中锁排序的正确性尤其有用。

PSI(压力暂缓信息跟踪)

先决条件:CONFIG_PSI

PSI 是一种测量工具,用于识别硬件资源上过度的过度提交,这可能会导致性能中断甚至 OOM 终止。

设备核心转储

先决条件:#include <linux/devcoredump.h>

为驱动程序提供向用户空间提供任意数据的基础设施。它通常与udev或类似的用户空间应用程序结合使用,以监听内核的uevents,这些事件指示转储已准备就绪。udev有规则将该文件复制到某个地方进行长期存储和分析,因为默认情况下,转储的数据会在5分钟后自动清理。该数据使用特定于驱动程序的工具或GDB进行分析。

您可以在以下位置找到示例实现:drivers/media/platform/qcom/venus/core.c

版权所有 ©2024 : Collabora