因为bpf_get_current_pid_tgid()仅返回pid/tgid整数,不提供进程名;ebpf运行在内核态,无法直接访问用户态/proc/[pid]/comm,需借助tracepoint/syscalls/sys_enter_execve捕获argv[0]或通过btf+bpf_probe_read_kernel_str()读取task_struct->comm。

为什么用 bpf_get_current_pid_tgid() 拿不到用户态进程名
直接调用 bpf_get_current_pid_tgid() 只能拿到 pid 和 tgid(线程组 ID),但 eBPF 程序运行在内核态,无法直接读取用户态的 /proc/[pid]/comm 或 argv。这是初学者最常卡住的地方——以为拿到 PID 就能查进程名,结果发现没有字符串读取能力。
真正可行的路径是:用内核态辅助结构(如 struct task_struct)配合 BTF 信息,通过 bpf_probe_read_kernel_str() 逐级读取 comm 字段;或借助 tracepoint/syscalls/sys_enter_execve 捕获新进程启动时传入的 argv[0]。
- 优先用
tracepoint/syscalls/sys_enter_execve获取真实启动命令(含路径),它天然携带struct pt_regs *,可安全读取用户栈上的argv[0] - 若需监控任意时刻的活跃进程(比如定时采样),必须启用 BTF 并依赖
task_struct->comm,且要处理读取失败(如进程已退出、内存不可访问) - 不要尝试用
bpf_probe_read_user_str()直接读argv[0]—— 用户地址可能已失效,eBPF verifier 会拒绝加载
如何用 libbpf-go 绑定 execve tracepoint 并提取 argv[0]
libbpf-go 是目前最稳定的 Go eBPF 绑定库,但它的事件回调默认不暴露 struct pt_regs *,需要手动调整 map 类型和数据解析逻辑。
关键点在于:execve tracepoint 的数据结构由内核定义(/sys/kernel/debug/tracing/events/syscalls/sys_enter_execve/format),其中 args 是一个 6 字节的 packed struct,第 0 个字段是 filename 的用户地址(const char __user *)。Go 端不能直接解引用,必须用 eBPF C 代码完成读取,并把结果写入 perf event ring buffer。
- eBPF C 侧用
bpf_probe_read_user_str(buf, sizeof(buf), (void *)args->filename)把字符串拷贝进局部缓冲区 - 确保
buf在栈上分配(不能是全局或 map 值),长度 ≤ 256 字节(避免 verifier 拒绝) - perf event map 的 value 类型需与 Go 结构体字段对齐,例如:
type ExecveEvent struct { Pid uint32 Comm [16]byte Path [256]byte } - Go 中用
perfReader.Read()接收事件后,用bytes.TrimRight(path[:], "\x00")清理 C 字符串尾部零
bpf_override_return() 不能用于篡改进程信息获取逻辑
有人想用 bpf_override_return() hook getpid() 或 prctl(PR_GET_NAME) 来注入伪造信息,这在技术上不可行:该 helper 仅适用于 kprobe/kretprobe 上下文,且只能修改函数返回值,无法改变用户态内存内容或绕过权限检查。
更严重的是,它完全不适用于 tracepoint(execve 是 tracepoint,不是 kprobe),尝试绑定会直接导致程序加载失败,报错 invalid argument 或 operation not supported。
- tracepoint 不支持
bpf_override_return()—— 它没有 “返回” 概念,只是事件快照 - 即使在 kretprobe 中使用,也仅能控制返回值(如让
getpid()返回固定数),但无法让用户态看到“新进程名”,因为进程名存在内核task_struct里,不随系统调用返回值变化 - 真要改进程名,得用
prctl(PR_SET_NAME, ...)用户态调用,eBPF 无权代劳
BTF 缺失时读 task_struct->comm 会静默失败
如果目标内核没开启 CONFIG_DEBUG_INFO_BTF=y(如多数 CentOS 7/8、Debian 10 默认配置),bpf_core_read_str(&comm, sizeof(comm), &task->comm) 会编译通过但运行时返回 -1,且不会报错提示,只会收不到数据。
这不是 bug,而是 CO-RE(Compile Once – Run Everywhere)机制降级为传统偏移量读取失败后的静默 fallback。你得主动检查返回值,并准备备选方案(比如只记录 PID/TGID,不强求进程名)。
- 用
bpftool btf dump file /sys/kernel/btf/vmlinux format c验证 BTF 是否可用 - 在 eBPF C 中对
bpf_core_read_str()返回值做判断:if (len ,避免把空字符串当有效数据发出去 - Go 侧接收事件前,先用
map.LookupAndDelete()检查是否存在兜底 map(如记录 PID→comm 映射),减少重复读取开销
/sys/kernel/btf/vmlinux 为空。这时哪怕代码全对,也拿不到 comm 字段——得去装对应 kernel-debuginfo 包,而不是调代码。golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











