环境部署:
编译源码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 程序和用户空间之间共享数据的主要机制
BPF_HASH
用途:用于存储键值对,类似于哈希表。BPF_HASH(my_hash_map, u32, u64);
my_hash_map 是映射的名称。
u32 是键的类型。
u64 是值的类型。BPF_ARRAY
用途:用于存储固定大小的数组。BPF_ARRAY(my_array, u64, 128);
my_array 是映射的名称。
u64 是数组元素的类型。- 是数组的大小。
BPF_PERF_EVENT_ARRAY
用途:用于将 BPF 程序的输出发送到用户空间。BPF_PERF_EVENT_ARRAY(my_perf_array);
my_perf_array 是映射的名称。
BPF_PROG_ARRAY
用途:用于存储 BPF 程序的句柄,支持动态调用其他 BPF 程序。BPF_PROG_ARRAY(my_prog_array, 128);
my_prog_array 是映射的名称。
- 是数组的大小。
BPF_STACK_TRACE
用途:用于存储内核栈跟踪信息。BPF_STACK_TRACE(my_stack_trace, 1024);
my_stack_trace 是映射的名称。
- 是最大存储的栈跟踪数量。
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 映射:用于存储和管理数据,支持复杂的数据结构和算法,适合持久化数据和状态信息。
性能缓冲区:用于实时传递事件数据,适合高频率数据传输和实时监控。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。