环境部署:
编译源码bcc, 解决这个问题就解决了70%了


一个入门的案例:

from bcc import BPF

program = r"""
BPF_HASH(counter_table, u64, u64);

int hello(void *ctx) {
    u64 uid_gid = bpf_get_current_uid_gid();
    u32 uid = uid_gid & 0xFFFFFFFF;
    u32 gid = uid_gid >> 32;

    u64 pid_tgid = bpf_get_current_pid_tgid();
    u32 pid = pid_tgid & 0xFFFFFFFF;
    u32 tgid = pid_tgid >> 32;

    char comm[16];
    bpf_get_current_comm(&comm, sizeof(comm));

    u64 counter = 0;
    u64 uid_key = (u64)uid;  // 将 uid 转换为 u64 类型
    u64 *p = counter_table.lookup(&uid_key);
    if (p != 0) {
        counter = *p;
    }
    counter+`+;
    counter_`table.update(&uid_key, &counter);

    // 使用多个 bpf_trace_printk 来避免超过 3 个转换说明符的限制
    bpf_trace_printk("UID: %d, GID: %d\\n", uid, gid);
    bpf_trace_printk("PID: %d, TGID: %d\\n", pid, tgid);
    bpf_trace_printk("COMM: %s\\n", comm);
    return 0;
}
"""

b = BPF(text=program)
syscall = b.get_syscall_fnname("execve")
b.attach_kprobe(event=syscall, fn_name="hello")
b.trace_print()

测试输出:

b'           <...>-76524   [002] d..21 33269.288460: bpf_trace_printk: UID: 0, GID: 0\\n'
b'           <...>-76524   [002] d..21 33269.288471: bpf_trace_printk: PID: 76524, TGID: 76524\\n'
b'           <...>-76524   [002] d..21 33269.288472: bpf_trace_printk: COMM: zsh\\n'

获取进程信息
bpf_get_current_uid_gid:
获取当前进程的用户 ID 和组 ID。
返回值:高 32 位是 GID,低 32 位是 UID。

bpf_get_current_pid_tgid:
获取当前进程的进程 ID (PID) 和线程组 ID (TGID)。
返回值:高 32 位是 TGID,低 32 位是 PID。
bpf_get_current_comm:

获取当前进程的命令名。
参数:一个字符数组,用于存储命令名。
返回值:0 表示成功,负值表示失败。

时间相关
bpf_ktime_get_ns:
获取从系统启动以来的时间(以纳秒为单位)。
返回值:时间戳,单位为纳秒。

bpf_ktime_get_boot_ns:
获取从系统启动以来的时间(以纳秒为单位),不包括系统睡眠时间。
返回值:时间戳,单位为纳秒。

网络相关
bpf_skb_load_bytes:
从数据包中加载指定偏移量的字节。
参数:数据包结构、偏移量、目标缓冲区、字节数。
返回值:0 表示成功,负值表示失败。

bpf_skb_store_bytes:
向数据包的指定偏移量存储字节。
参数:数据包结构、偏移量、源缓冲区、字节数、标志。
返回值:0 表示成功,负值表示失败。

bpf_l3_csum_replace:
替换 IPv4 头部的校验和。
参数:数据包结构、偏移量、旧值、新值、标志。
返回值:0 表示成功,负值表示失败。

bpf_l4_csum_replace:
替换 TCP/UDP 头部的校验和。
参数:数据包结构、偏移量、旧值、新值、标志。
返回值:0 表示成功,负值表示失败。

追踪和调试
bpf_trace_printk:
向内核日志中打印调试信息。
参数:格式字符串和相应的参数。
返回值:实际打印的字符数。

bpf_get_stackid:
获取当前调用栈的 ID。
参数:上下文、栈追踪 map、标志。
返回值:栈 ID,负值表示失败。

内存操作
bpf_probe_read:
从用户空间或内核空间读取数据。
参数:目标缓冲区、字节数、源地址。
返回值:0 表示成功,负值表示失败。

bpf_probe_write_user:
向用户空间写入数据。
参数:目标地址、源缓冲区、字节数。
返回值:0 表示成功,负值表示失败。


BPF 映射(map)是 BPF 程序和用户空间之间共享数据的主要机制

  1. BPF_HASH
    用途:用于存储键值对,类似于哈希表。

    BPF_HASH(my_hash_map, u32, u64);

    my_hash_map 是映射的名称。
    u32 是键的类型。
    u64 是值的类型。

  2. BPF_ARRAY
    用途:用于存储固定大小的数组。

    BPF_ARRAY(my_array, u64, 128);

    my_array 是映射的名称。
    u64 是数组元素的类型。

  3. 是数组的大小。
  4. BPF_PERF_EVENT_ARRAY
    用途:用于将 BPF 程序的输出发送到用户空间。

    BPF_PERF_EVENT_ARRAY(my_perf_array);

    my_perf_array 是映射的名称。

  5. BPF_PROG_ARRAY
    用途:用于存储 BPF 程序的句柄,支持动态调用其他 BPF 程序。

    BPF_PROG_ARRAY(my_prog_array, 128);

    my_prog_array 是映射的名称。

  6. 是数组的大小。
  7. BPF_STACK_TRACE
    用途:用于存储内核栈跟踪信息。

    BPF_STACK_TRACE(my_stack_trace, 1024);

    my_stack_trace 是映射的名称。

  8. 是最大存储的栈跟踪数量。

BPF_PERF_OUTPUT(events); // 定义一个性能缓冲区

一个案例:

from bcc import BPF

program = r"""
BPF_PERF_OUTPUT(events);

struct data_t {
    u32 pid;
    u64 ts;
};

int trace_sys_clone(struct pt_regs *ctx) {
    struct data_t data = {};
    data.pid = bpf_get_current_pid_tgid() >> 32;
    data.ts = bpf_ktime_get_ns();
    events.perf_submit(ctx, &data, sizeof(data));
    return 0;
}
"""

b = BPF(text=program)
b.attach_kprobe(event="sys_clone", fn_name="trace_sys_clone")

def print_event(cpu, data, size):
    event = b["events"].event(data)
    print(f"PID: {event.pid}, Timestamp: {event.ts}")

b["events"].open_perf_buffer(print_event)

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

BPF 映射:用于存储和管理数据,支持复杂的数据结构和算法,适合持久化数据和状态信息。
性能缓冲区:用于实时传递事件数据,适合高频率数据传输和实时监控。


putao
8 声望1 粉丝

推动世界向前发展,改善民生。