You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在eBPF内核侧读取栈追踪?能否用bpf_get_stack获取栈地址?

嘿,我来帮你理清这个困惑~

首先得明确两个文档说的不是一回事:

  • 内核文档里提到的「无法获取栈地址」,指的是eBPF程序不能直接读取栈指针寄存器(SP)的值,也不能随意访问栈上的任意内存——这是出于安全验证的限制,防止eBPF程序越界访问敏感内存。
  • 而邮件里说的「查看所有栈追踪」,是指通过内核提供的官方辅助函数(比如bpf_get_stack)来合法获取栈追踪信息,这些函数会帮你完成安全的栈遍历,返回栈帧的返回地址或者栈ID,完全符合eBPF的安全规则。

所以结论是:你想要的需求完全可行,下面是具体的实现思路和代码示例:

如何用bpf_get_stack在内核侧获取栈追踪地址

bpf_get_stack是内核提供的eBPF辅助函数,专门用来获取当前上下文的栈追踪信息(内核态或用户态)。

1. 函数原型与参数说明

它的核心原型大致如下:

int bpf_get_stack(struct pt_regs *regs, void *buf, u32 size, u64 flags);

各参数作用:

  • regs:指向当前寄存器上下文的指针,通常从kprobe/tracepoint的回调参数中获取(比如kprobe里的ctx)。
  • buf:用来存储栈追踪地址的缓冲区,一般定义为u64数组(每个元素对应一个栈帧的返回地址)。
  • size:缓冲区的总字节大小。
  • flags:控制行为的标志,常用的有:
    • BPF_F_KERNEL_STACK:获取内核态栈追踪
    • BPF_F_USER_STACK:获取用户态栈追踪
    • BPF_F_REUSE_STACKID:让内核给重复的栈分配唯一ID(减少内存占用,适合不需要原始地址的场景)

2. 示例代码:过滤包含_do_fork的栈追踪并写入map

下面是一个kprobe程序的示例,当栈追踪中包含_do_fork的地址时,将栈信息写入map:

#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>

// 定义存储栈信息的哈希map
struct {
    __uint(type, BPF_MAP_TYPE_HASH);
    __type(key, u64);          // 用进程ID作为key
    __type(value, u64[8]);     // 存储最多8个栈帧地址
    __uint(max_entries, 1024);
} stack_map SEC(".maps");

// 存储_do_fork的内核地址(由用户空间程序写入)
struct {
    __uint(type, BPF_MAP_TYPE_ARRAY);
    __type(key, u32);
    __type(value, u64);
    __uint(max_entries, 1);
} target_addr_map SEC(".maps");

SEC("kprobe/sys_clone")
int BPF_KPROBE(sys_clone) {
    u64 stack_addrs[8];
    u32 stack_buf_size = sizeof(stack_addrs);
    u32 key = 0;
    u64 *target_addr;
    u64 pid = bpf_get_current_pid_tgid() >> 32; // 获取进程ID
    int ret;

    // 从map中获取_do_fork的实时内核地址(解决KASLR随机化问题)
    target_addr = bpf_map_lookup_elem(&target_addr_map, &key);
    if (!target_addr) {
        return 0;
    }

    // 获取内核态栈追踪地址
    ret = bpf_get_stack(ctx, stack_addrs, stack_buf_size, BPF_F_KERNEL_STACK);
    if (ret <= 0) {
        return 0;
    }

    // 遍历栈地址,检查是否包含_do_fork
    for (int i = 0; i < ret / sizeof(u64); i++) {
        if (stack_addrs[i] == *target_addr) {
            // 将符合条件的栈信息写入map
            bpf_map_update_elem(&stack_map, &pid, stack_addrs, BPF_ANY);
            break;
        }
    }

    return 0;
}

char _license[] SEC("license") = "GPL";

3. 关键注意事项

  • KASLR适配:内核开启地址随机化后,_do_fork的地址会动态变化,不能硬编码。需要用户空间程序读取内核符号表(比如/proc/kallsyms),获取_do_fork的实时地址,再写入target_addr_map供eBPF程序使用。
  • 栈深度限制:示例中定义了8个栈帧的缓冲区,你可以根据实际需求调整大小。如果栈深度超过缓冲区,bpf_get_stack会返回实际写入的字节数,你可以根据这个值处理部分栈信息。
  • 内核版本支持:bpf_get_stack从Linux 4.15版本开始支持,不同的flags(比如BPF_F_REUSE_STACKID)可能需要更高的内核版本(比如4.18+)。

内容的提问来源于stack exchange,提问作者LeChatP

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 06:41:02