文章目录
- 内核函数调用堆栈打印
- [1. dump_stack()](#1. dump_stack())
- 2.WARN_ON()
- [3. panic()](#3. panic())
- [4. BUG_ON()](#4. BUG_ON())
内核函数调用堆栈打印
1. dump_stack()
dump_stack()是Linux内核中用于打印栈回溯信息的函数,主要用于内核调试。以下是对dump_stack()内核打印的详细介绍:
一、作用
当内核出现严重的错误,如Oops错误或内核认为系统运行状态异常时,内核会打印出当前进程的栈回溯信息。这些信息包括当前执行代码的位置、相邻的指令、产生错误的原因、关键寄存器的值以及函数调用关系等,对于调试内核错误非常有用。dump_stack()函数就是用于打印这些函数调用关系的,它可以帮助开发者了解内核代码的执行流程,定位问题所在。
二、工作原理
dump_stack()函数的工作原理是通过遍历堆栈,找到所有可能是内核函数的内容,并打印对应的函数。函数调用时会把下一条指令的地址放到堆栈中,因此只要找到这些返回地址,就可以找到它们所在的函数,进而打印出函数的调用关系。
具体来说,dump_stack()函数会执行以下步骤:
- 从当前进程的栈中找到当前函数的返回地址。
- 根据函数返回地址,从代码段中定位该地址位于哪个函数中,找到的函数即为调用者(caller)函数。
- 打印caller函数的函数名。
- 重复以上步骤,直到返回值为0或不在内核记录的符号表范围内。
三、实现方式
dump_stack()函数的实现与系统结构紧密相关,不同的硬件架构对应的实现也不同。但原理大致相同,都是通过读取关键寄存器的值(如栈指针、返回地址、程序计数器等),然后遍历堆栈,打印出函数调用关系。
以ARM体系为例,dump_stack()函数会读取FP(帧指针)寄存器的值,通过FP的值找到当前函数的函数栈的地址。然后,它会获取当前函数的代码段地址(通过PC寄存器获得),并在函数栈中找到保存的PC寄存器的备份,以定位到函数的第一条指令(即函数的入口地址)。接着,内核中保存了所有函数地址和函数名的对应关系,因此可以打印出函数名。在当前函数的函数栈中,还保存了caller函数的帧指针(FP寄存器的值),因此可以找到caller函数的函数栈的位置,并继续执行上述步骤,直到某个函数的函数栈中保存的帧指针为0或非法。
内核代码
四、示例
以下是一个使用dump_stack()函数的示例代码:
文件名 dump_stack.c
c
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
static int __init hello_init(void) {
printk(KERN_ALERT "dump_stack() start\n");
dump_stack();
printk(KERN_ALERT "dump_stack() over\n");
return 0;
}
static void __exit hello_exit(void) {
printk(KERN_ALERT "test module\n");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR(" Name");
MODULE_DESCRIPTION("A simple test module using dump_stack().");
对应的makefile
c
obj-m += dump_stack.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
在内核模块加载时,会调用hello_init()函数,该函数会打印出"dump_stack start",然后调用dump_stack()函数打印栈回溯信息,最后打印出"dump_stack over"。通过dmesg命令可以查看打印的栈回溯信息。
实际演示
执行make命令生成对应的.ko文件
bas
sudo insmod dump_stack.ko // 加载模块进内核
dmesg 查看dump_stack()打印如下
最后卸载模块
bash
sudo rmmod dump_stack
2.WARN_ON()
WARN_ON
是 Linux 内核中用于检测不应该发生的条件的一个宏。当 WARN_ON
中的条件评估为真(非零)时,它会在内核日志中打印一条警告消息,并且可能会触发内核的调试器(如果内核是在调试模式下编译的,并且调试器是附着的)。这个宏的目的是帮助开发者在开发过程中发现潜在的错误或异常行为。
使用 WARN_ON
时,你需要提供一个布尔表达式作为参数。如果这个表达式的结果为真,那么 WARN_ON
会执行以下操作:
-
打印警告消息:它会将一条警告消息输出到内核日志中,通常包含触发警告的文件名、行号和条件表达式。
-
性能影响 :在大多数情况下,
WARN_ON
的性能开销是可以忽略不计的,因为它只会在不应该发生的条件下执行。然而,在性能敏感的代码路径中,频繁触发WARN_ON
可能会导致性能下降。 -
调试和验证 :
WARN_ON
通常用于开发阶段,以捕获和调试不应该发生的条件。一旦代码被充分测试并验证为正确,这些WARN_ON
检查可能会被移除或替换为更轻量级的断言。 -
避免潜在问题 :通过及时发出警告,
WARN_ON
可以帮助开发者避免可能导致系统不稳定或安全漏洞的问题。
Linux代码定义如下
WARON_ON()
实际上也是调用dump_stack()
加了参数condition判断。
使用代码如下
c
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
static int __init hello_init(void) {
printk(KERN_ALERT "WARON_ON()\n");
WARN_ON(1);
printk(KERN_ALERT "WARON_ON() over\n");
return 0;
}
static void __exit hello_exit(void) {
printk(KERN_ALERT "test module\n");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR(" Name");
MODULE_DESCRIPTION("A simple test module using WARON_ON().");
3. panic()
panic() 函数是 Linux 内核中一个重要的函数,用于处理系统遇到的严重错误。当系统检测到无法处理的异常情况,如内核内部错误、硬件故障等,会调用 panic() 函数,将系统置于一个不可恢复的状态,并停止运行。以下是对 panic() 函数的详细解释:
一、函数作用
panic() 函数的主要作用是停止当前系统的运行,并在系统监测到异常时调用。它会记录错误信息,并可能触发系统重启。在调用 panic() 函数后,系统会进入一种紧急状态,通常会显示蓝屏信息,并给出一些参数帮助用户了解错误原因。
二、函数行为
在 panic() 函数被调用时,内核会执行一系列操作,包括但不限于:
- 关闭本地中断:防止任务抢占,确保只有一个 CPU 执行紧急代码。
- 修改 console 级别:以便 printk 能把消息打印出来。
- 通知运行 panic 处理函数:通过通知链调用注册的 panic 处理函数。
- dump CPU 寄存器的状态:输出一些堆栈的信息,帮助开发人员定位问题。
- 重启系统:如果配置了 panic_timeout 参数,并且其值大于 0,则在 panic_timeout 后重启系统。
三、panic() 函数的参数
panic() 函数通常会接受一些参数,这些参数有助于开发人员快速定位问题和进行故障排查。常见的参数包括:
- message:描述出错原因的字符串,通常会包含一些错误信息和调试信息。
- pid:出错的进程号,有助于定位到具体哪个进程导致了系统崩溃。
- time:系统发生错误的时间戳,有助于追踪问题发生的时间点。
- stack:系统当前的调用栈信息,有助于了解系统在出错时的调用流程。
四、使用场景
panic() 函数通常用于以下场景:
- 内核内部错误:如非法内存访问、内核态引用未初始化的野指针等。
- 硬件故障:如 CPU 过热、内存故障等。
- 系统崩溃:当系统遇到无法处理的异常情况时,会调用 panic() 函数停止系统运行。
内核代码如下
案例代码
c
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/panic.h>
static int __init hello_init(void) {
printk(KERN_ALERT "panic()\n");
panic("BUG!");
printk(KERN_ALERT "panic() over\n");
return 0;
}
static void __exit hello_exit(void) {
printk(KERN_ALERT "test module\n");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Name");
MODULE_DESCRIPTION("A simple test module using panic().");
4. BUG_ON()
BUG_ON()
是 Linux 内核中用于断言的一个宏,它用于检测代码中不应该发生的条件。如果 BUG_ON()
中的条件为真(即表达式的结果非零),则系统会立即触发 panic()
,导致内核崩溃,并打印出错误信息,包括调用栈的回溯。这种机制主要用于开发阶段的调试,帮助开发者及早发现并修复代码中的严重错误。
使用场景
BUG_ON()
通常用于以下场景:
- 检测逻辑错误 :当代码中的某个条件不应该为真时,可以使用
BUG_ON()
来检测这个条件。如果条件为真,说明代码中存在逻辑错误,需要修复。 - 防止资源泄漏 :在资源管理中,如果某个资源(如内存、文件描述符等)没有被正确释放或回收,可能会导致资源泄漏。
BUG_ON()
可以用于检测这种情况,并在发现时触发内核崩溃,以便开发者能够及时发现并修复问题。 - 验证系统状态 :在某些情况下,系统状态应该满足特定的条件。如果条件不满足,说明系统可能处于不稳定或错误的状态。
BUG_ON()
可以用于验证这些条件,并在不满足时触发内核崩溃。
BUG_ON()调用BUG()调用panic();
内核代码如下
案例代码
c
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/bug.h>
static int __init hello_init(void) {
printk(KERN_ALERT "BUG_ON()\n");
BUG_ON(1);
printk(KERN_ALERT "BUG_ON()over\n");
return 0;
}
static void __exit hello_exit(void) {
printk(KERN_ALERT "test module\n");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Name");
MODULE_DESCRIPTION("A simple test module using BUG_ON().");