前言
在内核中,函数调用堆栈非常重要,因为它可以帮助开发人员理解代码是如何执行的,从而进行调试、性能优化或问题排查。堆栈可以显示当前执行的函数以及导致该函数调用的先前函数,从而形成一个函数调用链。本篇博客就介绍堆栈打印内核函数的调用。
嵌入式驱动学习专栏将详细记录博主学习驱动的详细过程,未来预计四个月将高强度更新本专栏,喜欢的可以关注本博主并订阅本专栏,一起讨论一起学习。现在关注就是老粉啦!
目录
- 前言
- dump_stack函数
- WARN_ON函数
- BUG_ON函数
- panic函数
- 参考资料
dump_stack函数
该函数作用是打印内核调用堆栈,并打印函数的调用关系。
下面给出一段实验代码,在该内核模块中,我们定义四个函数aaa
、bbb
、ccc
、ddd
,然后bbb
中调用aaa
,ccc
中调用bbb
,ddd
函数谁都不调用。在入口函数中,我们调用ccc
与ddd
函数。
在aaa函数中使用dump_stack
函数,查看aaa
函数的调用栈
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/delay.h>
void aaa(void) {
printk(KERN_EMERG "aaa\n");
dump_stack();
msleep(100);
}
void bbb(void) {
printk(KERN_EMERG "bbb\n");
aaa();
msleep(100);
}
void ccc(void) {
printk(KERN_EMERG "ccc\n");
bbb();
msleep(100);
}
void ddd(void) {
printk(KERN_EMERG "ddd\n");
msleep(100);
}
static int __init chrdevTest_init(void) {
printk(KERN_EMERG "INIT func\r\n");
ccc();
ddd();
return 0;
}
static void __exit chrdevTest_exit(void) {
printk(KERN_EMERG "EXIT func\r\n");
}
module_init(chrdevTest_init);
module_exit(chrdevTest_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("wp");
加载驱动后,显示信息如下所示,可以看到先打印INIT func,然后按照调用关系分别打印ccc,bbb,aaa。
之后就是aaa的调用栈,框中就是调用关系,因为是chrdevTest_init
调用ccc
,ccc
调用bbb
,bbb
调用aaa
,由于Cortex-A处理器的堆栈是向下生长的,因此先压入chrdevTest_init
函数(地址较大),在压入ccc
函数(地址递减),以此类推到aaa
函数。
最后打印了一下谁都不调用的ddd
函数。
dump_stack()函数有助于我们调试,查看目标函数的调用关系。
如果信息消失了,可以使用dmesg
指令重新再控制台打印出来
WARN_ON函数
WARN_ON(condition)
函数的作用是,在括号中的条件成立时,内核会抛出栈回溯,打印函数的调用关系,通常用于内核抛出一个警告,暗示某种不太合理的事情发生了。
WARN_ON
实际上也是调用了dump_stack
,只是多了一个条件判断是否成立。
在刚刚dump_stack
函数的实验代码基础上,我们将aaa
函数中dump_stack
函数调用的位置改为WARN_ON(1)
。
void aaa(void) {
printk(KERN_EMERG "aaa\n");
WARN_ON(1); // 条件为真,打印调用信息
msleep(100);
}
加载模块后,基本和上一个dump_stack的结果一样,但是可以看到,dump_stack函数也被压进调用栈了,因此可以确定WARN_ON
是调用的dump_stack
函数。
现在我们将WARN_ON中的条件改为false,再看看结果:
void aaa(void) {
printk(KERN_EMERG "aaa\n");
WARN_ON(0); // 条件为假,不打印调用信息
msleep(100);
}
可以看到控制台并没有输出调用栈。
BUG_ON函数
内核中也有许多地方用到了BUG_ON
函数,这个函数就像一个内核运行时的断言,意味着本来不该执行到BUG_ON
这句,一旦执行就会抛出oops
,导致栈的回溯和错误信息的打印,大部分体系结构把BUG()
和BUG_ON()
定义成某种非法操作,这样自然会产生需要的oops
。
在上面代码的基础上,将aaa函数中改为BUG_ON函数
void aaa(void) {
printk(KERN_EMERG "aaa\n");
BUG_ON(1);
msleep(100);
}
加载模块后,打印出来的信息如下所示,可以看到其中抛出了oops
,并且最后并没有打印ddd
函数的信息。然后函数调用关系也打印出来了,寄存器值也都打印出来了。
我们将BUG_ON
中的条件改为false
void aaa(void) {
printk(KERN_EMERG "aaa\n");
BUG_ON(0);
msleep(100);
}
加载驱动后如下所示,可以看到不打印任何堆栈信息,并且ddd
函数可以顺利执行。
panic函数
可以用panic()
引发更严重的错误。调用panic()不但会打印错误消息(Oops)而且还会挂起整个系统。显然,你只应该在极端恶劣的情况下使用它。
将同样的位置换为panic()
函数
void aaa(void) {
printk(KERN_EMERG "aaa\n");
panic("###########################################wpwpwpwp");
msleep(100);
}
可以看到,打印完aaa函数后,控制台打印出panic中的字符串,然后整个进程进入到了阻塞状态。
参考资料
[1] Linux打印内核函数调用栈(dump_stack)
[2] Linux内核之BUG_ON()和WARN_ON()
[3] linux 内核态调试函数BUG_ON()