Kernel之debug调试信息

背景

平时我们在调试内核或驱动的时候,需要打开或增加调试信息,最常见的比如dev_dbg()pr_debug()
以内核v5.15为例,我们先来看一下上面2个日志打印函数在内核里面的大致定义:

  • dev_dbg() [include/linux/dev_printk.h]
    #if defined(CONFIG_DYNAMIC_DEBUG) || \
        (defined(CONFIG_DYNAMIC_DEBUG_CORE) && defined(DYNAMIC_DEBUG_MODULE))
    #define dev_dbg(dev, fmt, ...)						\
        dynamic_dev_dbg(dev, dev_fmt(fmt), ##__VA_ARGS__)
    #elif defined(DEBUG)
    #define dev_dbg(dev, fmt, ...)						\
        dev_printk(KERN_DEBUG, dev, dev_fmt(fmt), ##__VA_ARGS__)
    #else
    #define dev_dbg(dev, fmt, ...)						\
    ({									\
        if (0)								\
            dev_printk(KERN_DEBUG, dev, dev_fmt(fmt), ##__VA_ARGS__); \
    })
    #endif
  • pr_debug() [include/linux/printk.h]
    /* If you are writing a driver, please use dev_dbg instead */
    #if defined(CONFIG_DYNAMIC_DEBUG) || \
        (defined(CONFIG_DYNAMIC_DEBUG_CORE) && defined(DYNAMIC_DEBUG_MODULE))
    #include <linux/dynamic_debug.h>
    
    /**
    * pr_debug - Print a debug-level message conditionally
    * @fmt: format string
    * @...: arguments for the format string
    *
    * This macro expands to dynamic_pr_debug() if CONFIG_DYNAMIC_DEBUG is
    * set. Otherwise, if DEBUG is defined, it's equivalent to a printk with
    * KERN_DEBUG loglevel. If DEBUG is not defined it does nothing.
    *
    * It uses pr_fmt() to generate the format string (dynamic_pr_debug() uses
    * pr_fmt() internally).
    */
    #define pr_debug(fmt, ...)			\
        dynamic_pr_debug(fmt, ##__VA_ARGS__)
    #elif defined(DEBUG)
    #define pr_debug(fmt, ...) \
        printk(KERN_DEBUG pr_fmt(fmt), ##__VA_ARGS__)
    #else
    #define pr_debug(fmt, ...) \
        no_printk(KERN_DEBUG pr_fmt(fmt), ##__VA_ARGS__)
    #endif    
    从上面的代码可以看出,默认情况下, 这些调试信息是不会打印的,如果需要输出,则要我们手动打开,主要有2种方式:
  • 动态调试(DYNAMIC DEBUG)
  • 静态调试(DEBUG)

动态调试(DYNAMIC DEBUG)

  1. 确认内核是否已打开动态调试:
    /proc/dynamic_debug/control, 若存在,说明已打开

  2. 打开内核动态调试,配置打开以下选项即可:

    CONFIG_DYNAMIC_DEBUG=y        # build catalog, enables CORE
    CONFIG_DYNAMIC_DEBUG_CORE=y   # enable mechanics only, skip catalog

    说明:
    如果你不想全局使用动态调试(比如一些嵌入式系统),你可以只打开CONFIG_DYNAMIC_DEBUG_CORE这个配置来支持基本的动态调试,
    然后当任何模块需要动态调试时,只要在其Makefile中添加 ccflags := -DDYNAMIC_DEBUG_MODULE 就行

  3. 主要支持的函数:

    pr_debug()
    dev_dbg()
    print_hex_dump_debug()
    print_hex_dump_bytes()
  4. 如何使用:
    可以控制文件、函数的打印日志

    方式一: 添加启动参数
    cmdline中添加启动参数 dyndbg="file drivers/usb/* +p"能输出drviers/usb下所有文件的debug日志;

    方式二: 动态开关
    适用于调试非启动时的情况

    #切换su
    #mount -t debugfs none /sys/kernel/debug 这一步可省略
    
    #打开 hub_event 函数的调试信息
    echo -n 'func hub_event +p' > /sys/kernel/debug/dynamic_debug/control
    #关闭
    echo -n 'func hub_event -p' > /sys/kernel/debug/dynamic_debug/control

静态调试(DEBUG)

在需要调试的驱动文件最开头定义DEBUG宏,如下面的例子:

#define DEBUG
#include <xxx.h>
...

但这个有个弊端,只能一个个源码文件添加DEBUG宏,如果同时有多个文件,可以通过C编译器选项来传递,即通过ccflags传递DEBUG,在makefile中定义:
ccflags += -DDEBUG
即可

提高日志等级

以上2种调试日志打印都需要提高日志等级,大于等于7即可

有以下几种方式:

  1. 动态修改,适用于调试非启动时的情况
    echo 7 > /proc/sys/kernel/printk
  2. 修改内核配置,比较暴力
    CONFIG_CONSOLE_LOGLEVEL_DEFAULT=7
  3. 在cmdline添加启动参数:
    loglevel=7

    参考

    内核官方文档