linxu 内核调试(3) - printk debug

  • 了解printk

1.Description

  Specify the initial console log level. Any log messages with levels less than this (that is, of higher priority) will be printed to the console, whereas any messages with levels equal to or greater than this will not be displayed.

  The console log level can also be changed by the klogd program, or by writing the specified level to the /proc/sys/kernel/printk file.

  The kernel log levels are:

  • 0 (KERN_EMERG)
    The system is unusable.

  • 1 (KERN_ALERT)
    Actions that must be taken care of immediately.

  • 2 (KERN_CRIT)
    Critical conditions.

  • 3 (KERN_ERR)
    Non-critical error conditions.

  • 4 (KERN_WARNING)
    Warning conditions that should be taken care of.

  • 5 (KERN_NOTICE)
    Normal, but significant events.

  • 6 (KERN_INFO)
    Informational messages that require no action.

  • 7 (KERN_DEBUG)
    Kernel debugging messages, output by the kernel if the developer enabled debugging at compile time.

  预定义的内核log等级:

// include/linux/kern_levels.h
#define KERN_SOH    "\001"      /* ASCII Start Of Header */
#define KERN_SOH_ASCII  '\001'
#define KERN_EMERG  KERN_SOH "0"    /* system is unusable */
#define KERN_ALERT  KERN_SOH "1"    /* action must be taken immediately */
#define KERN_CRIT   KERN_SOH "2"    /* critical conditions */
#define KERN_ERR    KERN_SOH "3"    /* error conditions */
#define KERN_WARNING    KERN_SOH "4"    /* warning conditions */
#define KERN_NOTICE KERN_SOH "5"    /* normal but significant condition */
#define KERN_INFO   KERN_SOH "6"    /* informational */
#define KERN_DEBUG  KERN_SOH "7"    /* debug-level messages */
#define KERN_DEFAULT    KERN_SOH "d"    /* the default kernel loglevel */

在这里插入图片描述
  The pr_* macros (with exception of pr_debug) are simple shorthand definitions in include/linux/printk.h for their respective printk call and should probably be used in newer drivers.

  pr_devel and pr_debug are replaced with printk(KERN_DEBUG … if the kernel was compiled with DEBUG, otherwise replaced with an empty statement.

  For drivers the pr_debug should not be used anymore (use dev_dbg instead).

  If you don’t specify a log level in your message it defaults to MESSAGE_LOGLEVEL_DEFAULT (usually “4”=KERN_WARNING) which can be set via the CONFIG_MESSAGE_LOGLEVEL_DEFAULT kernel config option (make menuconfig-> Kernel Hacking -> Default message log level)

  函数宏定义如下:

 include/linux/printk.h:
  297 #define pr_emerg(fmt, ...) \
  298     printk(KERN_EMERG pr_fmt(fmt), ##__VA_ARGS__)
  299 #define pr_alert(fmt, ...) \
  300     printk(KERN_ALERT pr_fmt(fmt), ##__VA_ARGS__)
  301 #define pr_crit(fmt, ...) \
  302     printk(KERN_CRIT pr_fmt(fmt), ##__VA_ARGS__)
  303 #define pr_err(fmt, ...) \                                                                             
  304     printk(KERN_ERR pr_fmt(fmt), ##__VA_ARGS__)
  305 #define pr_warning(fmt, ...) \
  306     printk(KERN_WARNING pr_fmt(fmt), ##__VA_ARGS__)
  307 #define pr_warn pr_warning
  308 #define pr_notice(fmt, ...) \
  309     printk(KERN_NOTICE pr_fmt(fmt), ##__VA_ARGS__)
  310 #define pr_info(fmt, ...) \
  311     printk(KERN_INFO pr_fmt(fmt), ##__VA_ARGS__)

2.log_buf_len Description

  Set the size of the kernel’s internal log buffer. n must be a power of 2, if not, it will be rounded up to be a power of two. This value can also be changed by the CONFIG_LOG_BUF_SHIFT kernel configuration value.

  Kernel log 默认大小是128KB,即17,修改该值,然后重新编译内核即可.

 kernel/init/Kconfig
 741 config LOG_BUF_SHIFT
 742     int "Kernel log buffer size (16 => 64KB, 17 => 128KB)"                                                            
 743     range 12 21
 744     default 17
 745     help
 746       Select kernel log buffer size as a power of 2.
 747       Examples:
 748              17 => 128 KB
 749              16 => 64 KB
 750                  15 => 32 KB
 751                  14 => 16 KB
 752              13 =>  8 KB
 753              12 =>  4 KB
  • 编译后,查看android/out/target/product/msm8952_64/obj/KERNEL_OBJ/.config:

CONFIG_LOG_BUF_SHIFT=19

For example:

add log buf size from 128K to 2M

-CONFIG_LOG_CPU_MAX_BUF_SHIFT=17
+CONFIG_LOG_BUF_SHIFT=21
+CONFIG_LOG_CPU_MAX_BUF_SHIFT=21

3.printk

3.1.printk的log等级控制

// linux/include/printk.h
/* printk's without a loglevel use this.. */
#define MESSAGE_LOGLEVEL_DEFAULT CONFIG_MESSAGE_LOGLEVEL_DEFAULT
/* We show everything that is MORE important than this.. */
#define CONSOLE_LOGLEVEL_DEFAULT 7 
#define CONSOLE_LOGLEVEL_MIN     1 
#define CONSOLE_LOGLEVEL_DEFAULT 7 

-------------------------------------------------------------------------
// kernel/printk.c
int console_printk[4] = {
    CONSOLE_LOGLEVEL_DEFAULT,   /* console_loglevel */
    MESSAGE_LOGLEVEL_DEFAULT,   /* default_message_loglevel */
    CONSOLE_LOGLEVEL_MIN,       /* minimum_console_loglevel */
    CONSOLE_LOGLEVEL_DEFAULT,   /* default_console_loglevel */
};
-------------------------------------------------------------------------
// linux/include/printk.h
extern int console_printk[];
#define console_loglevel (console_printk[0])
#define default_message_loglevel (console_printk[1])
#define minimum_console_loglevel (console_printk[2])
#define default_console_loglevel (console_printk[3])
  • console_loglevel
    只有当printk打印消息的log优先级高于console_loglevel时,才能输出到终端上

  • default_message_loglevel
    printk打印消息时默认的log等级

  • minimum_console_loglevel
    console_loglevel可以被设置的最小值

  • default_console_loglevel
    console_loglevel的缺省值

  只有当printk打印信息时的loglevel小于console loglevel的值(即:优先级高于console loglevel),这些信息才会被打印到console上。

  未指定日志级别的printk() 采用的默认级别是 default_message_loglevel。

3.2.改变console loglevel方法

  • 启动时Kernel boot option:loglevel=level
  • 运行时Runtime: dmesg -n level
    (注意:demsg -n level 改变的是console上的loglevel,dmesg命令仍然会打印出所有级别的系统信息。)
  • 运行时Runtime: echo $level > /proc/sys/kernel/printk
  • 运行时Runtime:写程序使用syslog系统调用(可以man syslog)

3.3.通过procfs改变log等级 [案例参考]

/proc/sys/kernel/printk:
              The  four values in this file are console_loglevel, default_mes-
              sage_loglevel,    minimum_console_level     and     default_con-
              sole_loglevel.   These  values  influence printk() behavior when
              printing or logging error messages.   Messages with a higher priority
              than console_loglevel will be printed to the console.   Messages
              without  an  explicit  priority  will  be  printed with priority
              default_message_level.  minimum_console_loglevel is the  minimum
              (highest)   value   to   which   console_loglevel  can  be  set.
              default_console_loglevel  is  the   default   value   for   con-
              sole_loglevel.

1.查看当前printk的log等级

# cat /proc/sys/kernel/printk
# 7 4 1 7

  “7 4 1 7” 分别对应console_loglevel、default_message_loglevel、minimum_c onsole_loglevel、default_console_loglevel,意味着只有优先级高于KERN_DEBUG(7)的打印消息才能输出到终端。

2.改变console_loglevel

# echo 8 4 1 7 > /proc/sys/kernel/printk

  输入“8 4 1 7”改变console_loglevel值,使得所有的打印消息都能输出到终端

3.4.修改内核配置

lib/Kconfig.debug:
CONFIG_PRINTK_TIME=y
CONFIG_CONSOLE_LOGLEVEL_DEFAULT=7
CONFIG_MESSAGE_LOGLEVEL_DEFAULT=4
  38 config CONSOLE_LOGLEVEL_DEFAULT
  39     int "Default console loglevel (1-15)"
  40     range 1 15
  41     default "7"
  42     help
  43       Default loglevel to determine what will be printed on the console.
  44 
  45       Setting a default here is equivalent to passing in loglevel=<x> in
  46       the kernel bootargs. loglevel=<x> continues to override whatever
  47       value is specified here as well.
  48 
  49       Note: This does not affect the log level of un-prefixed printk()
  50       usage in the kernel. That is controlled by the MESSAGE_LOGLEVEL_DEFAULT
  51       option.    
  -----------------------------------------------------------------------------------
  64 config MESSAGE_LOGLEVEL_DEFAULT
  65     int "Default message log level (1-7)"
  66     range 1 7
  67     default "4"
  68     help
  69       Default log level for printk statements with no specified priority.
  70 
  71       This was hard-coded to KERN_WARNING since at least 2.6.10 but folks
  72       that are auditing their logs closely may want to set it to a lower
  73       priority.
  74 
  75       Note: This does not affect what message level gets printed on the console                         
  76       by default. To change that, use loglevel=<x> in the kernel bootargs,
  77       or pick a different CONSOLE_LOGLEVEL_DEFAULT configuration value.

3.5.ignore_loglevel [Documentation/kernel-parameters.txt]

  如果dtsi 加上该属性bootargs=“ignore_loglevel”,则修改如下属性,不再起任何作用,去掉之后,才起作用。

CONFIG_CONSOLE_LOGLEVEL_DEFAULT=7
CONFIG_MESSAGE_LOGLEVEL_DEFAULT=4

动态调试:/sys/module/printk/parameters/ignore_loglevel.

3.6. loglevel

  dtsi 加上该属性bootargs=“loglevel=6”,设置loglevel.

4.dmesg

  dmesg是从kernel的ring buffer(环缓冲区)中读取信息的.

dmesg is used to examine or control the kernel ring buffer.The program helps users to print out their bootup messages. Instead of copying the messages by hand, the user need only: dmesg > dmesg.log

  ring buffer:

  在LINUX中,所有的系统信息(包括内核信息)都会传送到ring buffer中。而内核产生的信息由printk()打印出来。系统启动时所看到的信息都是由该函数打印到屏幕中。printk()打出的信息往往以 <0>…<2>… 这的数字表明消息的重要级别。高于一定的优先级别(当前的console loglevel)就会打印到console上,否则只会保留在系统的缓冲区中(ring buffer)。

4.1.Not all kernel messages are displayed by dmesg

  The printk-times feature adds a number of bytes at the beginning of each printk message. The default kernel message buffer size may not be sufficient to hold all the messages with this additional overhead. You can increase the kernel message buffer size when compiling the kernel, by adjusting the “Kernel Log buffer size” (found on the “General Setup” menu). Note that you must also specify a larger buffer read size with “dmesg”, with the ‘-s’ option.

ex: dmesg -s 128000 >/tmp/bootup_printks

5.Dynamic debug

  dynamic debug (dyndbg)是内核提供的一个调试功能,允许动态的开关内核打印输出,包含的API:pr_debug()、dev_dbg() 、print_hex_dump_debug()、rint_hex_dump_bytes()和netdev_dbg等。 dynamic debug通过设置/dynamic_debug/control文件来控制内核输出,有多种匹配的条件:文件名,函数名,行号,模块名和输出字符的格式; control文件的语法格式(Documentation/admin-guide/dynamic-debug-howto.rst):

command ::= match-spec* flags-spec \ 如:echo -n ‘file svcsock.c line 1603 +p’ > /dynamic_debug/control

其中match-spec的关键词是:

match-spec ::= 'func' string |
               'file' string |
               'module' string |
               'format' string |
               'line' line-range

  可以通过设置不同的flag输出函数名,行号,模块名和线程ID,这些信息可以帮助我们调试。

p enables the pr_debug() callsite.
f Include the function name in the printed message
l Include line number in the printed message
m Include module name in the printed message
t Include thread ID in messages not generated from interrupt context
_ No flags are set. (Or'd with others on input)

5.1.Enable debugfs in kernel dynamically

  Kernel developer has provided a functionality known as “debugfs” to debug the kernel, enabling the log at the run time. It is very useful for the modules which have a lots of logs, and enabling the log statically will make the system very slow. So Kernel developers came up with this idea to enabling the logs only for the time when user wants to check a particular scenario.

So here is the process to enable it :

  • Enable the debugfs, which can be done by following command :
    Enabled the CONFIG_DYNAMIC_DEBUG=y flag in kernel config file,than mount -o rw,remount -t debugfs none /sys/kernel/debug
//check your debugfs mount sistuation
adb root
adb shell
mount
  • Now if you want to see what are the logs defined you can do the same using following command : cat /sys/kernel/debug/dynamic_debug/control
    [Here /sys/kernel/debug/ is the directory we mounted in previous command, you can change it if you want]

  • Now enable the debug log for the files or paricular function you want to see in the logs:
    echo ‘file mdp.c +p’ > /sys/kernel/debug/dynamic_debug/control [It will enable all debugfs logs in mdp.c]

  • Now you can check the logs in demsg or cat /proc/kmsg.

5.2.代码分析

函数定义:

  • pr_debug(include/linux/printk.h)
    pr_debug定义在文件include/linux/printk.h,从pr_debug的源码注释建议:如果写驱动,请用dev_dbg。
/* If you are writing a driver, please use dev_dbg instead */
#if defined(CONFIG_DYNAMIC_DEBUG)
#include <linux/dynamic_debug.h>

/* dynamic_pr_debug() uses pr_fmt() internally so we don't need it here */
#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

#define dynamic_pr_debug(fmt, ...)				\
do {								\
	DEFINE_DYNAMIC_DEBUG_METADATA(descriptor, fmt);		\
	if (DYNAMIC_DEBUG_BRANCH(descriptor))			\
		__dynamic_pr_debug(&descriptor, pr_fmt(fmt),	\
				   ##__VA_ARGS__);		\
} while (0)
  • dev_dbg(include/linux/device.h)
    dev_debug的定义在文件include/linux/device.h
#if defined(CONFIG_DYNAMIC_DEBUG)
#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

#define dynamic_dev_dbg(dev, fmt, ...)				\
do {								\
	DEFINE_DYNAMIC_DEBUG_METADATA(descriptor, fmt);		\
	if (DYNAMIC_DEBUG_BRANCH(descriptor))			\
		__dynamic_dev_dbg(&descriptor, dev, fmt,	\
				  ##__VA_ARGS__);		\
} while (0)

  根据CONFIG_DYNAMIC_DEBUG和DEBUG的配置,pr_debug/dev_dbg的输出分为以下情况:
在这里插入图片描述

实现机制:

  • dynamic_pr_debug宏展开情况(CONFIG_DYNAMIC_DEBUG=y):
#define dynamic_pr_debug(fmt, ...)				\
do {								\
	DEFINE_DYNAMIC_DEBUG_METADATA(descriptor, fmt);		\
	if (DYNAMIC_DEBUG_BRANCH(descriptor))			\
		__dynamic_pr_debug(&descriptor, pr_fmt(fmt),	\
				   ##__VA_ARGS__);		\
} while (0)

展开:
#define DEFINE_DYNAMIC_DEBUG_METADATA_KEY(name, fmt, key, init)	\
	static struct _ddebug  __aligned(8)			\
	__attribute__((section("__verbose"))) name = {		\
		.modname = KBUILD_MODNAME,			\
		.function = __func__,				\
		.filename = __FILE__,				\
		.format = (fmt),				\
		.lineno = __LINE__,				\
		.flags = _DPRINTK_FLAGS_DEFAULT,		\
		dd_key_init(key, init)				\
	}

static struct _ddebug __aligned(8)	\
 __attribute__((section("__verbose"))) descriptor = {	\
	.modname = KBUILD_MODNAME,	\
	.function = __func__,	\
	.filename = __FILE__,	\
	.format = ("hello"),	\
	.lineno = __LINE__,	\
	.flags = _DPRINTK_FLAGS_DEFAULT,	\
	dd_key_init(key, init)	\
 }
 
 if (unlikely(descriptor.flags & _DPRINTK_FLAGS_PRINT))	\
__dynamic_pr_debug(&descriptor, pr_fmt(fmt),	\
     ##__VA_ARGS__);	\

  在调用pr_debug的源文件定义一个存放在kerenl的__verbose段的名为descriptor,类型为struct _ddebug变量;然后判断descriptor.flags & _DPRINTK_FLAGS_PRINT为true的话,就输出pr_debug内容。在定义descriptor时,将其flag成员赋值为_DPRINTK_FLAGS_DEFAULT,看这个宏的定义:

#if defined DEBUG
#define _DPRINTK_FLAGS_DEFAULT _DPRINTK_FLAGS_PRINT
#else
#define _DPRINTK_FLAGS_DEFAULT 0
#endif

  如果源文件定义了DEBUG宏,_DPRINTK_FLAGS_DEFAULT 等于_DPRINTK_FLAGS_PRINT,所以默认就是可以打印的;如果没有定义DEBUG宏,那么_DPRINTK_FLAGS_DEFAULT 就是0,上面的条件就不成立,就不会打印出来。通过写/sys/kernel/debug/dynamic_debug/control节点来动态修改descriptor.flags为_DPRINTK_FLAGS_PRINT来使条件成立。control属性节点在源码<lib/dynamic_debug.c>中实现。 接着看__dynamic_pr_debug函数:


void __dynamic_pr_debug(struct _ddebug *descriptor, const char *fmt, ...)
{
	va_list args;
	struct va_format vaf;
	char buf[PREFIX_SIZE];

	BUG_ON(!descriptor);
	BUG_ON(!fmt);

	va_start(args, fmt);

	vaf.fmt = fmt;
	vaf.va = &args;

	printk(KERN_DEBUG "%s%pV", dynamic_emit_prefix(descriptor, buf), &vaf);

	va_end(args);
}
EXPORT_SYMBOL(__dynamic_pr_debug);

refer to

  • kernel/printk/printk.c
  • https://www.cnblogs.com/mylinux/p/4028787.html
  • https://www.cnblogs.com/pengdonglin137/p/5808373.html
  • https://www.cnblogs.com/pengdonglin137/p/4621576.html
  • https://wiki.st.com/stm32mpu/wiki/How_to_use_the_kernel_dynamic_debug
发布了161 篇原创文章 · 获赞 15 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/weixin_41028621/article/details/104691262