eBPF无法直接捕获Go panic,因其为纯用户态行为;可行方案是Uprobe hook runtime.fatalpanic1等函数并借助BTF解析_panic结构体,或用SetPanicHook注入,但均需适配版本差异与调试信息。

Go panic 不经过系统调用,eBPF 无法直接 hook
eBPF 不能捕获 Go 程序的未捕获 panic,因为 panic 是 Go 运行时纯用户态行为:不触发任何系统调用、不进入内核、不写入 signal、不调用 abort() 或 exit_group()。你在 sys_enter_panic 或类似 tracepoint 上永远等不到事件——内核压根没有这个 tracepoint。
常见误判是看到进程退出了,就以为能从 tracepoint/syscalls/sys_exit_exit_group 捕获到 panic 上下文,但实际只能拿到 exit code(比如 2),完全丢失 panic message、stack trace、goroutine ID 等关键信息。
可行路径只有两条:运行时注入 or 符号解析
真正能拿到 panic 文本和栈的,只有 Go runtime 自己。所以必须在 runtime 层面介入:
- 用
runtime/debug.SetPanicHook(Go 1.21+)注册钩子,把 panic 写进 perf event ringbuf 或 eBPF map —— 但这需要修改业务代码,违背“无侵入”前提 - 用 eBPF + Uprobe hook
runtime.fatalpanic或runtime.throw(取决于 panic 类型),再通过 BTF 或符号偏移读取runtime._panic结构体中的arg和pc字段
注意:runtime.fatalpanic 在 Go 1.20+ 中被重命名为 runtime.fatalpanic1,且不同 Go 版本中 _panic 结构体字段顺序、大小、对齐方式不一致;必须用 BTF(如果 Go 二进制带 DWARF/BTF)或 go tool objdump -s "runtime\." ./myapp 手动确认符号位置。
立即学习“go语言免费学习笔记(深入)”;
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
libbpf-go 是唯一现实选择,cilium/ebpf 基本不可用
要稳定解析 runtime._panic,必须依赖 BTF 动态推导结构体布局。cilium/ebpf 不读 BTF,硬编码字段偏移,在 Go 1.21+ 编译的二进制上会直接读错地址,返回 -EFAULT 并静默丢弃事件。
libbpf-go 可加载用户态二进制的 BTF(需编译时加 -buildmode=exe -ldflags="-buildid=" 并保留调试信息),再用 btfgen 生成匹配的 Go struct。实操要点:
- 确保 Go 二进制启用 DWARF:
go build -gcflags="all=-N -l" -ldflags="-compressdwarf=false" - 检查是否含 BTF:
readelf -S ./myapp | grep btf,若无则 fallback 到符号 + objdump 手动定位 - Uprobe 目标函数名必须精确:
runtime.fatalpanic1而非fatalpanic1,否则 attach 失败报no such device - 读取
_panic.arg时,要用bpf_probe_read_kernel_str(不是_user_str),因 panic message 存在 runtime heap 上,属内核地址空间映射
即使成功 hook,也只覆盖部分 panic 场景
不是所有 panic 都走 runtime.fatalpanic1。例如:
- 栈溢出(stack growth failure)会直接触发
sigsegv,需额外 hooksignal_deliver并过滤SIGSEGV+ 栈指针异常 - CGO 调用中发生的 panic(如 C 函数里调
panic("xxx"))可能绕过 runtime 主路径 - 被
recover()捕获的 panic 完全不会触发任何 hook
最终拿到的 panic message 是原始字节流,不含 goroutine name、HTTP path、traceID 等上下文 —— 这些必须靠 sidecar 或提前注入的 context.Value 与之关联,eBPF 本身做不到跨栈追踪。

















