Linux驱动的调试-打印函数调用堆栈

本文详细介绍了Linux内核中四种常用的调试方法:dump_stack(), WARN_ON(), BUG()和panic()的使用场景及效果。通过具体实例,展示了这些函数如何帮助开发者定位和解决内核级问题,包括函数调用堆栈的打印、条件警告、致命错误处理和系统崩溃前的紧急响应。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

(1)dump_stack()打印函数调用堆栈

#include<linux/init.h>
#include<linux/module.h>

static int hello_init()
{
    printk("hello_init\n");
    dump_stack();
    return 0;
}
static int hello_exit()
{
    printk("hello_exit\n");
    return 0;
}


module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");

调用后现象:打印下面内容,内核可以正常使用

# insmod hello.ko 
hello_init
CPU: 0 PID: 509 Comm: insmod Tainted: G           O 3.10.107 #12
Backtrace: 
[<c00121b0>] (dump_backtrace+0x0/0x108) from [<c00123c0>] (show_stack+0x18/0x1c)
 r6:bf000084 r5:bf000000 r4:c0502f80 r3:00000000
[<c00123a8>] (show_stack+0x0/0x1c) from [<c0390e68>] (dump_stack+0x20/0x28)
[<c0390e48>] (dump_stack+0x0/0x28) from [<bf000018>] (hello_init+0x18/0x24 [hello])
[<bf000000>] (hello_init+0x0/0x24 [hello]) from [<c0008870>] (do_one_initcall+0xf4/0x170)
[<c000877c>] (do_one_initcall+0x0/0x170) from [<c005c5cc>] (load_module+0x1928/0x1e10)
[<c005aca4>] (load_module+0x0/0x1e10) from [<c005cc18>] (SyS_finit_module+0x6c/0x7c)
[<c005cbac>] (SyS_finit_module+0x0/0x7c) from [<c000ee80>] (ret_fast_syscall+0x0/0x34)
 r6:bec50de2 r5:00000006 r4:0089b008

(2)WARN_ON();有参数,根据真假来判断是否打印调用堆栈。

#include<linux/init.h>
#include<linux/module.h>

static int hello_init()
{
    printk("hello_init\n");
    WARN_ON(1);
    return 0;
}
static int hello_exit()
{
    printk("hello_exit\n");
    return 0;
}


module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");

现象,还会告诉在源代码第几行调用的WARN_ON();系统正常运行。

# insmod hello.ko 
hello_init
------------[ cut here ]------------
WARNING: at /home/myhome/module/oop/hello.c:8 hello_init+0x20/0x30 [hello]()
Modules linked in: hello(O+) [last unloaded: hello]
CPU: 0 PID: 517 Comm: insmod Tainted: G           O 3.10.107 #12
Backtrace: 
[<c00121b0>] (dump_backtrace+0x0/0x108) from [<c00123c0>] (show_stack+0x18/0x1c)
 r6:bf004020 r5:00000009 r4:00000000 r3:00000000
[<c00123a8>] (show_stack+0x0/0x1c) from [<c0390e68>] (dump_stack+0x20/0x28)
[<c0390e48>] (dump_stack+0x0/0x28) from [<c001cf34>] (warn_slowpath_common+0x54/0x70)
[<c001cee0>] (warn_slowpath_common+0x0/0x70) from [<c001cff4>] (warn_slowpath_null+0x24/0x2c)
 r8:00000000 r7:00000001 r6:bf0040b0 r5:bf004000 r4:c0502f80
r3:00000009
[<c001cfd0>] (warn_slowpath_null+0x0/0x2c) from [<bf004020>] (hello_init+0x20/0x30 [hello])
[<bf004000>] (hello_init+0x0/0x30 [hello]) from [<c0008870>] (do_one_initcall+0xf4/0x170)
[<c000877c>] (do_one_initcall+0x0/0x170) from [<c005c5cc>] (load_module+0x1928/0x1e10)
[<c005aca4>] (load_module+0x0/0x1e10) from [<c005cc18>] (SyS_finit_module+0x6c/0x7c)
[<c005cbac>] (SyS_finit_module+0x0/0x7c) from [<c000ee80>] (ret_fast_syscall+0x0/0x34)
 r6:be922de2 r5:00000006 r4:01b73008
---[ end trace c8d0911921e7ffd0 ]---

(3)BUG():会引发oops

BUG_ON()有条件判断

在驱动错误的地方用,模块崩溃,系统还能正常

 

# insmod hello.ko 
hello_init
------------[ cut here ]------------
kernel BUG at /home/myhome/module/oop/hello.c:8!
Internal error: Oops - BUG: 0 [#1] PREEMPT ARM
Modules linked in: hello(O+)
CPU: 0 PID: 505 Comm: insmod Tainted: G           O 3.10.107 #12
task: c38be000 ti: c39dc000 task.ti: c39dc000
PC is at hello_init+0x14/0x1c [hello]
LR is at 0xc04edc4c
pc : [<bf000014>]    lr : [<c04edc4c>]    psr: 60000013
sp : c39dde40  ip : c04edc4c  fp : c39dde4c
r10: 3cffb81c  r9 : c39dc010  r8 : 00000000
r7 : 00000001  r6 : bf0000a8  r5 : bf000000  r4 : c0502f80
r3 : 00000001  r2 : 00000000  r1 : 60000093  r0 : 0000000a
Flags: nZCv  IRQs on  FIQs on  Mode SVC_32  ISA ARM  Segment user
Control: 0005317f  Table: 03014000  DAC: 00000015
Process insmod (pid: 505, stack limit = 0xc39dc1b8)
Stack: (0xc39dde40 to 0xc39de000)
de40: c39dde8c c39dde50 c0008870 bf000010 c39dde7c c39dde60 c39ddf44 bf0000b4
de60: bf0000a8 c39ddf44 bf0000b4 bf0000a8 00000001 c3004800 c3004808 3cffb81c
de80: c39ddf3c c39dde90 c005c5cc c000878c bf0000b4 00007fff c0059194 c00bc844
dea0: bf0001c0 bf0000b4 bf0001c0 00000000 00000001 bf0000f0 c39dc010 00000000
dec0: c4a07000 00000988 00ca3c45 00000000 0000000b 00000000 00000000 00000000
dee0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
df00: 00000000 00000000 00000000 00000000 00000008 00000000 00000003 01ce4008
df20: 0000017b c000f028 c39dc000 00000000 c39ddfa4 c39ddf40 c005cc18 c005acb4
df40: c00c7284 c4a07000 00000988 c4a07690 c4a075d0 c4a074f0 000001d4 00000274
df60: 00000000 00000000 00000000 000002d0 00000010 00000011 0000000a 00000000
df80: 00000007 00000000 c39ddfa4 01ce4008 00000006 be89ade2 00000000 c39ddfa8
dfa0: c000ee80 c005cbbc 01ce4008 00000006 00000003 01ce4008 00000000 be89ade2
dfc0: 01ce4008 00000006 be89ade2 0000017b 00000000 00000000 b6f59f74 00000000
dfe0: be89ab28 be89ab18 00025af0 b6ea3d60 a0000010 00000003 00000000 00000000
Backtrace: 
[<bf000000>] (hello_init+0x0/0x1c [hello]) from [<c0008870>] (do_one_initcall+0xf4/0x170)
[<c000877c>] (do_one_initcall+0x0/0x170) from [<c005c5cc>] (load_module+0x1928/0x1e10)
[<c005aca4>] (load_module+0x0/0x1e10) from [<c005cc18>] (SyS_finit_module+0x6c/0x7c)
[<c005cbac>] (SyS_finit_module+0x0/0x7c) from [<c000ee80>] (ret_fast_syscall+0x0/0x34)
 r6:be89ade2 r5:00000006 r4:01ce4008
Code: e92dd800 e24cb004 e59f0004 eb4e3eeb (e7f001f2) 
---[ end trace ae02eee25f704b15 ]---
Segmentation fault

 

打印oops之后,不能卸载模块,显示:

# rmmod hello
rmmod: can't unload module 'hello': Device or resource busy
# lsmod 
Module                  Size  Used by    Tainted: G  
hello                   1425  1 

(4)panic("hello");参数是字符串,会被打印出来

打印调用栈

控制台彻底没反应。

 

# insmod hello.ko 
hello_init
Kernel panic - not syncing: hello
CPU: 0 PID: 502 Comm: insmod Tainted: G           O 3.10.107 #12
Backtrace: 
[<c00121b0>] (dump_backtrace+0x0/0x108) from [<c00123c0>] (show_stack+0x18/0x1c)
 r6:bf00008c r5:bf000000 r4:c0502f80 r3:00000000
[<c00123a8>] (show_stack+0x0/0x1c) from [<c0390e68>] (dump_stack+0x20/0x28)
[<c0390e48>] (dump_stack+0x0/0x28) from [<c038fa40>] (panic+0xa0/0x1f4)
[<c038f9a0>] (panic+0x0/0x1f4) from [<bf00001c>] (hello_init+0x1c/0x24 [hello])
 r3:00000001 r2:00000000 r1:60000093 r0:bf000074
 r7:00000001
[<bf000000>] (hello_init+0x0/0x24 [hello]) from [<c0008870>] (do_one_initcall+0xf4/0x170)
[<c000877c>] (do_one_initcall+0x0/0x170) from [<c005c5cc>] (load_module+0x1928/0x1e10)
[<c005aca4>] (load_module+0x0/0x1e10) from [<c005cc18>] (SyS_finit_module+0x6c/0x7c)
[<c005cbac>] (SyS_finit_module+0x0/0x7c) from [<c000ee80>] (ret_fast_syscall+0x0/0x34)
 r6:bee62de2 r5:00000006 r4:003fc008

 

<think>我们正在讨论Linux驱动开发中如何打印调用栈(call stack)。在调试驱动程序时,打印调用栈可以帮助开发者理解函数调用的路径,从而定位问题。 在Linux内核中,提供了一些函数来打印当前的调用栈。常用的函数有`dump_stack()`,它会在内核日志中打印当前的调用栈信息。 另外,还有`WARN_ON()`和`BUG_ON()`等宏,它们不仅会打印调用栈,还会根据情况产生警告或使内核崩溃(用于严重错误)。 根据引用[2]中的信息,我们知道在中断上下文中调用可能休眠的函数会导致错误,比如打印“BUG: scheduling while atomic”。在调试这类问题时,打印调用栈可以帮助我们了解函数调用的路径。 下面我们介绍几种在驱动中打印调用栈的方法: 1. 使用`dump_stack()`函数: 这个函数会直接打印当前进程的调用栈。它可以在驱动代码的任何位置调用(但要注意上下文,比如在中断上下文中使用可能会增加延迟)。 示例: ```c #include <linux/kernel.h> void my_function(void) { // ... 你的代码 dump_stack(); // 打印调用栈 } ``` 2. 使用`WARN_ON(condition)`: 如果条件为真,则打印警告信息(包括调用栈)。它不会导致内核崩溃,但会记录一条警告消息。 示例: ```c if (some_error_condition) { WARN_ON(1); // 条件为真,打印警告和调用栈 } ``` 3. 使用`BUG()`或`BUG_ON(condition)`: 这些宏会引发一个内核错误,导致当前的进程崩溃(oops),并打印调用栈。通常用于处理不应该发生的严重错误。 示例: ```c if (critical_error) { BUG(); // 直接崩溃并打印调用栈 } // 或者 BUG_ON(critical_error); ``` 需要注意的是,在驱动开发中,打印调用栈可能会对系统性能产生影响,因此只建议在调试阶段使用。 另外,在中断上下文中(如中断处理函数)使用这些函数要特别小心,因为中断上下文要求快速执行,不能睡眠,而`dump_stack()`等函数会占用较多时间,可能导致中断处理超时或其他问题。 根据引用[3]中的代码片段,我们看到驱动代码中可能会包含针对不同内核版本的预处理指令。因此,在编写代码时,要注意兼容性。不过`dump_stack()`函数在内核中已经存在很长时间,通常不需要特别处理版本问题。 下面是一个简单的示例模块,展示如何在驱动中使用`dump_stack()`: ```c #include <linux/module.h> #include <linux/kernel.h> static int __init my_module_init(void) { printk(KERN_INFO "My module loaded\n"); dump_stack(); // 打印模块加载时的调用栈 return 0; } static void __exit my_module_exit(void) { printk(KERN_INFO "My module unloaded\n"); } module_init(my_module_init); module_exit(my_module_exit); MODULE_LICENSE("GPL"); ``` 加载这个模块时,你可以在`dmesg`中看到调用栈信息。 另外,除了打印调用栈,还可以使用`oops`消息或`kdump`等机制来获取更详细的信息。 总结:在Linux驱动开发中,打印调用栈主要使用`dump_stack()`、`WARN_ON()`、`BUG_ON()`等函数或宏。这些工具对于调试非常有用,但要注意在正式发布的驱动中移除或谨慎使用。 相关问题: 1. 如何在Linux内核中获取更详细的堆栈跟踪信息? 2. 在驱动开发中,除了打印调用栈,还有哪些调试技巧? 3. 如何处理在不同内核版本中打印调用栈的兼容性问题?</think>在 Linux 驱动开发中,打印函数调用栈(call stack)是重要的调试手段,尤其用于定位内核崩溃、死锁或原子上下文违规等问题。以下是常用的方法和技术: ### 1. **基础方法:`dump_stack()`** 在内核代码中直接调用 `dump_stack()` 函数,会在内核日志(`dmesg`)中打印当前 CPU 的调用栈: ```c #include <linux/kernel.h> void debug_function(void) { printk("Call stack:\n"); dump_stack(); // 打印调用栈 } ``` - **输出示例**: ``` Call Trace: [<ffffffffc0123456>] debug_function+0x12/0x30 [my_driver] [<ffffffff98765432>] some_kernel_function+0x40/0x80 ... ``` ### 2. **警告宏:`WARN()` 和 `WARN_ON()`** 在条件触发时打印调用栈(不会导致内核崩溃): ```c WARN(condition, "Warning message"); // 条件为真时打印栈 WARN_ON(condition); // 简化版 ``` - 适用于非致命错误(如引用[2]提到的原子上下文调度问题)。 ### 3. **致命错误宏:`BUG()` 和 `BUG_ON()`** 触发内核 oops 并强制打印调用栈: ```c BUG_ON(irqs_disabled()); // 若中断被禁用则崩溃并打印栈 ``` - 用于无法恢复的严重错误。 ### 4. **动态调试:`ftrace`** 通过内核的 `ftrace` 机制动态跟踪函数调用: ```bash # 启用函数跟踪 echo function > /sys/kernel/debug/tracing/current_tracer # 仅跟踪特定函数(如驱动函数) echo 'my_driver_*' > /sys/kernel/debug/tracing/set_ftrace_filter # 捕获调用栈 echo stacktrace > /sys/kernel/debug/tracing/trace_options ``` ### 5. **符号解析** 调用栈中的地址需转换为函数名: - 使用 `addr2line` 工具: ```bash addr2line -e vmlinux ffffffffc0123456 ``` - 或通过 `gdb` 加载内核镜像 (`vmlinux`) 解析。 --- ### **注意事项** 1. **上下文限制**: - 在中断上下文(如引用[2]场景)中避免调用可能阻塞的函数。 - `dump_stack()` 本身安全,但可能因日志刷新增大延迟。 2. **内核版本兼容性**: - 如引用[3]所示,驱动代码需处理不同内核版本的宏和头文件差异。 3. **日志级别**: - 使用 `printk` 时指定日志级别(如 `KERN_DEBUG`)避免刷屏。 --- ### **示例场景** 调试原子上下文调度错误(引用[2]): ```c if (in_atomic()) { // 检查是否在原子上下文 pr_err("BUG:调度发生在原子上下文!\n"); dump_stack(); // 打印调用栈定位源头 } ``` --- ### 相关问题 1. 如何在 Linux 内核中捕获并分析 oops 崩溃信息? 2. 驱动开发中如何安全地在中断处理函数(ISR)中打印调试信息? 3. `ftrace` 和 `perf` 工具在性能分析中有哪些核心差异? > 引用参考: > [^1]: 嵌入式 Linux 系统关键组件(C 库、Bootloader 等) > [^2]: 原子上下文调度错误的原因分析 > [^3]: Linux 驱动兼容性代码示例
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值