Linux函数stack_trace_save的用法及注意事项

stack_trace_save 是 Linux 内核中用于捕获当前调用堆栈信息的函数,属于内核调试工具的一部分。以下是其用法及注意事项的详细说明:


函数原型

1
unsigned int stack_trace_save(unsigned long *store, unsigned int size, int skip);

参数说明

  • store: 指向 unsigned long 类型数组的指针,用于存储堆栈地址(返回地址)。
  • size: 缓冲区 store 的最大容量(最多可保存的地址数量)。
  • skip: 忽略堆栈顶部的若干层调用(例如跳过函数自身或公共跟踪代码)。

返回值

  • 实际保存到 store 中的堆栈条目数量。

典型用法

1
2
3
4
5
6
7
8
9
10
11
12
#include <linux/stacktrace.h>

void capture_stack(void) {
unsigned long buffer[32]; // 分配足够大的缓冲区
unsigned int nr_entries;

// 捕获当前堆栈,跳过 0 层(不忽略任何帧)
nr_entries = stack_trace_save(buffer, ARRAY_SIZE(buffer), 0);

// 后续处理:打印或分析堆栈
stack_trace_print(buffer, nr_entries, 0);
}

注意事项

1. 内核配置依赖

  • 确保内核启用了 CONFIG_STACKTRACE,否则该函数不可用。
  • 调试符号解析需要 CONFIG_KALLSYMS 支持。

2. 上下文限制

  • 中断上下文:可在中断上下文中使用,但需确保缓冲区分配安全(例如使用全局或静态内存,避免 GFP_KERNEL 分配)。
  • 抢占与并发:堆栈捕获可能因抢占或中断导致不完整。必要时使用 preempt_disable()raw_local_irq_save() 保护。

3. 缓冲区管理

  • 大小不足:若缓冲区太小,堆栈可能被截断。检查返回值 nr_entries 是否等于 size,以判断是否需要扩大缓冲区。
  • 内存安全:确保 store 指向有效内存,避免野指针或溢出。

4. 符号解析

  • 保存的地址需通过 %pS 格式符或 stack_trace_print() 转换为函数名。例如:
    1
    printk("Backtrace: %pS\n", (void *)buffer[i]);

5. 性能影响

  • 堆栈遍历是耗时操作,避免在性能敏感路径(如网络/存储热路径)中频繁调用。

6. 跳过帧数(skip 参数)

  • 根据调用场景调整 skip。例如:
    • skip=1 跳过 stack_trace_save 自身的调用帧。
    • skip=0 保留完整堆栈(包括当前函数)。

7. 多核与竞态

  • 若需捕获特定 CPU 的堆栈,需结合 get_cpu()/put_cpu() 绑定 CPU。

8. KASLR 影响

  • 若内核启用 KASLR(地址随机化),直接保存的地址需结合 /proc/kallsyms 解析符号。

相关函数

  • stack_trace_print(): 将堆栈地址转换为可读字符串。
  • stack_trace_snprint(): 安全格式化堆栈到字符串缓冲区。
  • dump_stack(): 直接打印当前堆栈(简化版,无需手动处理缓冲区)。

示例:保存并打印堆栈

1
2
3
4
5
6
7
8
9
10
11
12
13
void debug_stack(void) {
unsigned long buffer[16];
unsigned int entries;
char strbuf[256];

entries = stack_trace_save(buffer, ARRAY_SIZE(buffer), 0);
if (entries >= ARRAY_SIZE(buffer))
printk("Warning: Stack truncated!\n");

// 将堆栈格式化为字符串
stack_trace_snprint(strbuf, sizeof(strbuf), buffer, entries, 0);
printk("Call trace:\n%s\n", strbuf);
}

通过合理使用 stack_trace_save,可以高效捕获内核上下文中的调用关系,但需谨慎处理内存、并发及性能问题。