ebpf_bcc_tools之stacksnoop(跟踪进程指定内核函数堆栈调用信息)

一、BPF映射

eBPF 中实现内核态代码与用户态代码是可以实时通信的,这主要靠 BPF映射 来实现。

BPF映射是内核空间的一段内存,以键值对的方式存储。内核态程序可以直接访问 BPF 映射,用户态需要通过系统调用才能访问这段地址。

BPF 映射有很多种类型,如下表所示:

类型说明
BPF_HASH哈希表
BPF_ARRAY数组
BPF_HISTOGRAM直方图
BPF_STACK_TRACE跟踪栈
BPF_PERF_ARRAY硬件性能数组
BPF_PERCPU_HASH单CPU哈希表
BPF_PERCPU_ARRAY单CPU数组
BPF_LPM_TRIE最长前缀匹配映射
BPF_PROG_ARRAY尾调用程序数组
...

...

二、BPF_STACK_TRACE跟踪栈

     跟踪栈的用法:

  • BPF_STACK_TRACE(stack_traces, 128):定义一个跟踪栈,深度为 128。

  • stack_traces.get_stackid(ctx, 0):遍历通过 ctx 找到的堆栈,返回它的唯一 ID。

  • stack_traces = b.get_table("stack_traces"):用户态获取跟踪栈。

  • for addr in stack_traces.walk(event.stack_id):根据跟踪栈的唯一 id 遍历栈内元素,函数调用地址信息。拿到地址信息后,通过 b.ksym() 函数将其翻译为内核函数名。注意,b.ksym() 函数 接收一个 show_offset 参数,用于控制是否显示偏移地址。

  • matched = b.num_open_kprobes():另外,这段程序最终接收一个参数,作为跟踪的内核函数名。因此需要判断其是否合法。num_open_kprobes() 将返回能够匹配上的内核探针数量,这里被应用于检测输入的内核函数是否合法。

三、stacksnoop源码解析

 # stacksnoop.py

from __future__ import print_function
from bcc import BPF
import argparse
import time

#初始化命令项选项与参数解析
parser = argparse.ArgumentParser(
        description="Trace and print kernel stack traces for a kernel function",
        formatter_class=argparse.RawDescriptionHelpFormatter)
#跟踪函数参数
parser.add_argument("function", help="kernel function name")
#指定pid跟踪
parser.add_argument("-p", "--pid",help="trace this PID only")
args = parser.parse_args()
function = parser.parse_args().function
offset = False

# define BPF program
bpf_text = """
#include <uapi/linux/ptrace.h>
#include <linux/sched.h>

//定义了内核Probe程序与用户空间程序通信的结构体data_t
struct data_t {
        u64 stack_id;
        u32 pid;
        char comm[TASK_COMM_LEN];
};
// 定义跟踪栈 BPF_STACK_TRACE(name, max_entries),创建名称为name的 stack trace map,并设置最大的entry条目。map用于存储stack栈的调用记录信息。
BPF_STACK_TRACE(stack_traces, 256);

//通过BBC宏定义内核中events变量
BPF_PERF_OUTPUT(events);

void trace_stack(struct pt_regs *ctx) {
        u32 pid = bpf_get_current_pid_tgid() >> 32;
        FILTER
        struct data_t data = {};
        //int map.get_stackid(void *ctx, u64 flags)     //在内核端,通过stack_traces.get_stackid(ctx,0)通过当前进程ctx上下文保存栈,获取当前进程的内核栈的栈编号,其中ctx是上下文信息,0的意义暂时未知;该工具将每个进程的stackid都传送到了用户端。
        data.stack_id = stack_traces.get_stackid(ctx, 0);
        data.pid = pid;
        //bpf_get_current_comm(char *buf, int size_of_buf) 用当前进程名字填充buf参数地址。
        bpf_get_current_comm(&data.comm, sizeof(data.comm));
        //通过perf_submit方法将event数据发送至用户空间
        events.perf_submit(ctx, &data, sizeof(data));
}
"""
#增加bpf pid过滤条件
if args.pid:
    bpf_text = bpf_text.replace('FILTER',
        'if (pid != %s) { return; }' % args.pid)
else:
    bpf_text = bpf_text.replace('FILTER', '')

# initialize BPF
b = BPF(text=bpf_text)
#在eBPF用户态程序中,可以通过attach_kprobe函数将内核态eBPF程序通过kprobes机制附加到某个内核函数中。attach_kprobe 函数会创建一个 perf event,再将 eBPF 内核态程序附加到 perf event。每个 perf event 的 kprobe probe handler 都是 kprobe_dispatch 函数,他会去 perf event 中获取注册在当前 perf event 的回调函数列表并依次执行,同时将指向 perf ringbuffer 的指针的传递给 eBPF 程序,eBPF 程序可以通过 libbpf 封装好的 PT_REGS_PARAMx 宏定义来获取缓冲区中的数据。 (tracepoint 类型的 eBPF 程序需要定义好 tracepoint 关联的函数的参数的数据结构 /sys/kernel/tracing/events)
#该kprobe会执行自定义的trace_stack()函数。可以通过多次执行attach_kprobe() ,将自定义的函数附加到多个内核函数上。
b.attach_kprobe(event=function, fn_name="trace_stack")

# linux/sched.h
TASK_COMM_LEN = 16

# 判断输入的 function 是否合法
matched = b.num_open_kprobes()
if matched == 0:
        print("Function \"%s\" not found. Exiting." % function)
        exit()

# 获取跟踪栈,返回表对象。此函数已经淘汰,现在BFP可以将表作为items来读取,例如BFP[name].
stack_traces = b.get_table("stack_traces")

start_ts = time.time()

# header
print("%-18s %-12s %-6s %-3s %s" % ("TIME(s)", "COMM", "PID", "CPU", "FUNCTION"))

#用户空间Python程序则需要定义 print_event事件处理函数,并使用 perf_buffer_poll 函数轮训消费。
def print_event(cpu, data, size):
                #读取出对应的数据并生成结构数据
        event = b["events"].event(data)
        ts = time.time() - start_ts
        print("%-18.9f %-12.12s %-6d %-3d %s" % (ts, event.comm.decode('utf-8', 'replace'), event.pid, cpu, function))
        #在用户端,通过stack_traces.walk(event.stack_id)获取了stackid对应的栈列表。
        #根据 stack.id 遍历堆栈
        for addr in stack_traces.walk(event.stack_id):
            # BPF.ksym(addr) 将一个内核内存地址转成一个内核函数名字
            sym = b.ksym(addr, show_offset=offset).decode('utf-8', 'replace')
            print("\t%s" % sym)
        print()

#从perf ring buffers等待数据,有数据会调用open_perf_buffer指定的回调函数。
b["events"].open_perf_buffer(print_event)

while 1:
        try:
                b.perf_buffer_poll()
        except KeyboardInterrupt:
                exit()

 # python  stacksnoop.py __kmalloc -p 1138

  kprobe内核函数查找:

# bpftrace -l | grep kprobe | grep malloc
kprobe:__kmalloc
kprobe:__kmalloc_node
kprobe:__kmalloc_node_track_caller
kprobe:__kmalloc_reserve.isra.54
kprobe:__kmalloc_track_caller
kprobe:__kmem_vmalloc
kprobe:__vmalloc
kprobe:__vmalloc_node
kprobe:__vmalloc_node_flags_caller
kprobe:__vmalloc_node_range
kprobe:bpf_map_kmalloc_node
kprobe:debug_kmalloc
kprobe:dev_memalloc_noio
kprobe:devm_kmalloc
kprobe:devm_kmalloc_match
kprobe:devm_kmalloc_release
kprobe:drm_dp_mst_get_port_malloc
kprobe:drm_dp_mst_put_mstb_malloc
kprobe:drm_dp_mst_put_port_malloc
kprobe:drmm_kmalloc
kprobe:gfp_pfmemalloc_allowed
kprobe:is_vmalloc_or_module_addr
kprobe:kmalloc_fix_flags
kprobe:kmalloc_large_node
kprobe:kmalloc_order
kprobe:kmalloc_order_trace
kprobe:kmalloc_slab
kprobe:kvmalloc_node
kprobe:mempool_kmalloc
kprobe:obj_malloc.isra.34
kprobe:pm_runtime_set_memalloc_noio
kprobe:remap_vmalloc_range
kprobe:remap_vmalloc_range_partial
kprobe:sk_clear_memalloc
kprobe:sk_set_memalloc
kprobe:sock_kmalloc
kprobe:sock_omalloc
kprobe:sock_wmalloc
kprobe:vmalloc
kprobe:vmalloc_32
kprobe:vmalloc_32_user
kprobe:vmalloc_exec
kprobe:vmalloc_fault
kprobe:vmalloc_node
kprobe:vmalloc_sync_mappings
kprobe:vmalloc_sync_unmappings
kprobe:vmalloc_to_page
kprobe:vmalloc_to_pfn
kprobe:vmalloc_user
kprobe:vmalloc_user_node_flags
kprobe:zbud_zpool_malloc
kprobe:zpool_malloc
kprobe:zs_malloc
kprobe:zs_zpool_malloc

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值