一、内核日志基本框架
1.内核日志通过printk函数实现的,它与用户空间对应的函数printf具有同样的作用,
内核会创建一个__log_buf环形缓冲区保存日志信息,定义在[kernel/printk/printk.c]
static char __log_buf[__LOG_BUF_LEN] __aligned(LOG_ALIGN);
用户态可以通过Syslog相关的系统调用或者/proc文件以及/dev/kmsg设备节点来查看_log_buf的信息,这些操作都是通过do_syslog的系统调用接口来实现的。
2.printk()
2.1.Kernel API printk() 是整个Kernel Log机制的基础API,几乎所有的Log方式都是基于printk来实现的。
Printk的格式如下:int printk( const char * fmt, ... );
其中,fmt是一个定义文本和格式的字符串,其后面的参数列表是输出格式中需要输入的可变个数参数。
通过 printk 实现的日志是通过内核配置选项CONFIG_PRINTK激活的。虽然 CONFIG_PRINTK一般都是激活的,但是不包含这个选项的系统对内核的调用会返回一个ENOSYS 错误返回值。
2.2.printk()实现
流程大致如下图所示:
printk的流程大致可以分为两步:
a)将所有Log输出到内核的Log buffer,该Log Buffer是一个循环缓冲区,其地址可以在内核中用log_buf变量访问。
b)根据设定的Log级别决定是否将Log输出到Console。[接口:call_console_drivers]
所以我们打印的log会走向两个位置:
a)Log Buffer,该Buffer的内容可以在user space通过/proc/kmsg来访问。
b)Console,Console的实现可以有很多,目前我们用到的有UART Console和RAM Console。通向UART Console的log会在对应的UART端口打印出来。而RAM Console它可以记录重启前最后时刻的一段log,7KB~9KB的空间,可以通过/proc/last_kmsg来访问。【last_kmsg和ram console - bobfly1984 - 博客园】
对于console log,不可避免的对系统的性能有损失,尤其是像UART Log这种收到硬件传输效率影响的。所以对于console log设置了两道关卡。第一个是对Log级别进行过滤,只能输出高优先级的log;第二个是为UART Console设置单独的开关,在不必要的时候可以将其关闭以提高系统性能。
————————————————
2.3.日志级别
内核允许每一个消息根据日志级别(定义不同消息重要必的八种级别之一)来分类。这些级别可以用来判断系统是否不可用(紧急消息)、是否发现严重状况(严重消息)或者是否为简单报告消息。这个内核代码直接将日志级别定义消息的第一个参数。
例如下面的代码:
printk( KERN_CRIT "Error code %08x.\n", val );
如果调用者未将日志级别提供给printk,那么系统就会使用默认值KERN_WARNING(表示只有KERN_WARNING 级别以上的日志消息会被记录。)
系统默认的优先级可以通过读取/proc文件来得到,如下所示:
D:\>adb shell cat /proc/sys/kernel/printk
6 4 1 6
读取的这4个数字的含义分别是:
a)第一个参数表示console log level,即只有优先级大于这个级别的Log才可以打印到Console。
b)第二个参数表示默认Log级别,即打印Log未明确指定Log level的时候默认采用的Log级别。
c)第三个参数表示最小可用的Log Level,用于do_syslog()系统调用。
d)第四个参数表示默认的Console Log Level,未找到使用的地方。
Console Log 输出控制
我们还可以通过/proc/sys/kernel/printk设置console_loglevel来达到控制 console log 输出的目的,方法如下(修改这个参数需要有root权限):
D:\>adb shell "echo 8 > /proc/sys/kernel/printk"
D:\>adb shell cat /proc/sys/kernel/printk
8 4 1 6
通过echo命令将console_loglevel设置8,即所有级别的log(因为定义的最低优先级的Log Level是7)都可以输出到console。通过这个命令也可以禁止一些低优先级的log输出到console,只要将console_loglevel的值设置小一些即可。
UART Console 的控制
UART Console单独设置了开关,主要是因为其对系统性能的影响比较大,因为UART Console是同步的方式通过硬件以固定的传输速率印Log,如果Log量比较大的时候,会引起较多的Performance问题,如UI卡顿等。另外由于往UART Console印Log会关闭中断,严重的时候可能会引起系统无法响应或者重启。
也正因为此,我们在user版本上面默认是关闭UART Console Log的。
可以使用如下命令在runtime控制UART Console 的打开和关闭(执行该命令需要有root权限):
D:\>adb shell "echo 1 > /sys/module/printk/parameters/disable_uart"
D:\>adb shell "echo 0 > /sys/module/printk/parameters/disable_uart"
通过将disable_uart参数设置为1来关闭UART Console,反之通过将disable_uart参数设置为0来打开UART Console。
二、kernel调试方法
Linux内核调试方法总结 - FGQ的开源人生 - OSCHINA - 中文开源技术交流社区
Linux Kernel - Debug Guide (Linux内核调试指南 ) - zengkefu - 博客园
1.打印调试printk使用
1.1 在内核常用一些变形实现,例如:pr_xxx ,dev_xxx
include\linux\printk.h定义pr_err,pr_warn ,pr_info
drivers\base\core.c 通过宏实现define_dev_printk_level
define_dev_printk_level(_dev_err, KERN_ERR);
define_dev_printk_level(_dev_warn, KERN_WARNING);
define_dev_printk_level(_dev_info, KERN_INFO);
1.2 常用打印标签
--printk("%s%d\n", __func__, __LINE__); 打印函数名,行号
%p可打印裸指针(raw pointer)
%pF可打印函数指针的函数名和偏移地址
%pf只打印函数指针的函数名,不打印偏移地址。
如:printk("%pf %pF\n", ptr, ptr) will print:
module_start module_start+0x0/0x62 [hello]
说明文档:
Documentation\core-api\printk-formats.rst
Documentation\translations\zh_CN\core-api\printk-formats.rst 中文
2.proc/kmsg ,dev/kmsg 读取实时log
3.dmesg
dmesg 命令也可用于打印和控制内核环缓冲区。这个命令使用 klogctl 系统调用来读取内核环缓冲区,并将它转发到标准输出(stdout)
获取开机log
Dmesg -w 实时监控并输出
搭配grep命令:
-i:在搜索的时候忽略大小写
-n:显示结果所在行号
-E:使用扩展正则表达式,而不是基本正则表达式,在使用”-E”选项时,相当于使用egrep
grep -iE "a|b"
4.使用/proc文件系统
通过在proc目录创建文件节点的形式获取我们需要输出的内核执行信息
接口:
example_dir=proc_mkdir("procfs_example",NULL);
Create_proc_entry("example_file",0666,example_dir);
5.OOPS & Panic
5.1 大部分错误都是因为对NULL指针取值或因为使用了其他不正确的指针值,这些错误通常会导致一个oops消息,这个消息会完整告诉了内核出错信息,出错的原因。,
panic的程度显然是高于oops的 内核panic后,就死机了,俗称内核崩溃。但是内核报oops,这个时候不见得会panic,它可能只是报个oops,杀死进程而已。oops不一定panic,中断上下文的oops会panic,如果panic_on_oops设置为1,一律panic。
下面例子是访问了一个NULL指针,出错的PC指针位置是globalfifo_read + 0x50/0x200(flobalfifo_read 最开始一条指令偏移0x50的位置,flobalfifo_read 整个代码段加起来是0x200) ,之后会列出完整的backtrace。
5.2 dump_stack()这个函数来追踪函数调用关系。可用来定位Kernel Panic和Oop的问题,配合objdump和addr2line可以定位到哪一行的哪句代码出现异常
BUG_ON()执行后抛出Oops
WARN_ON()执行后抛出栈回溯,不会panic
6.调试工具 prebuilts/gcc/linux-x86/arm/arm-linux-gnueabihf/bin
6.1 arm-linux-androideabi-addr2line
把程序地址转换为文件名和行号。在命令行中给它一个地址和一个可执行文件名,它就会使用这个可执行文件的调试信息指出在给出的地址上是哪个文件以及行号。
应用场景:调试内核的时候,内核崩溃了,此时可以根据各种日志和打印中的偏移内存地址,然后根据这个地址定位到具体是哪个文件的哪一行
//通过arm-linux-gnu-addr2line --help可以查看该命令的用法
XXX$ arm-linux-gnu-addr2line -e output/build/vmlinux -C -f 8050e7bc
output/build/linux-20160205B/drivers/modules/dev_linux.c:900 可以知道是dev_linux.c的900行
6.2 arm-linux-androideabi-objdump 反汇编器,将可执行程序反汇编为汇编代码
arm-linux-gnueabihf-objdump -d -l -f -g -S drivers/usb/gadget/udc-core.ko > udc-core.log
arm-linux-gnueabihf-objdump -d -l -f -g -S vmlinux > vmlinux.log
或者通过start-address 和stop-address反汇编地址范围的内容
6.3 arm-linux-androideabi-gdb 调试工具,当应用程序发生核心错误(段错误)时,使用该工具分析生成的core文件可得到报错信息
arm-linux-androideabi-gdb vmlinux 加载符号表
输入list *函数名+函数偏移地址即可查看特定位置代码
可以正常使用gdb各种操作指令
6.4 arm-linux-androideabi-nm 列出目标文件中的符号
arm-linux-androideabi-nm vmlinux |grep 函数名
返回:函数首地址 T 函数名
通过首地址加偏移量得到异常地址,然后通过addr2line定位
6.5 Trace32集成化调试工具
可以加载符号表和coredump文件根据异常地址或者函数来分析汇编指令,同时可以添加对应源码
7.内核模块源码级调试
kgdb提供了一种使用 gdb调试 Linux 内核的机制。使用KGDB可以象调试普通的应用程序那样,在内核中进行设置断点、检查变量值、单步跟踪程序运行等操作。使用KGDB调试时需要两台机器,一台作为开发机(Development Machine),另一台作为目标机(Target Machine),两台机器之间通过串口或者以太网口相连
8.其他内核debug选项和功能
8.1 no_console_suspend
在suspend的时候console 不进行suspend,否则console suspend之后其他driver在suspend 过程中印的log都显示不出来,因此加这个参数一般用于调试suspend 和 resume。在bootargs中添加"no_console_suspend"参数。
8.2 SLUB_DEBUG kmemleak --->内存检测
Linux常见的内存访问错误有:越界访问(out of bounds)、访问已经释放的内存(use after free)、重复释放、内存泄露(memory leak)、栈溢出(stack overflow)等。
(a)、SLUB_DEBUG 内核中小块内存大量使用slab/slub分配器,slub_debug提供了:访问已经释放的内存、越界访问、重复释放内存等功能检测。
内核配置
重新配置kernel选项,打开如下选项即可。
CONFIG_SLUB=y
CONFIG_SLUB_DEBUG=y
CONFIG_SLUB_DEBUG_ON=y
8.3 kmemleak是内核提供的一种检测内存泄露工具,启动一个内核线程扫描内存,并打印发现新的未引用对象数量。
使用kmemlieak,需要打开如下内核选项。
CONFIG_HAVE_DEBUG_KMEMLEAK=y
CONFIG_DEBUG_KMEMLEAK=y
CONFIG_DEBUG_KMEMLEAK_EARLY_LOG_SIZE=400
# CONFIG_DEBUG_KMEMLEAK_TEST is not set
CONFIG_DEBUG_KMEMLEAK_DEFAULT_OFF=y---------或者关闭此选项,则不需要在命令行添加kmemleak=on。
8.4 DEBUG_ATOMIC_SLEEP -->检测在spinlock,中断,软中断中的sleep