使用Cilium eBPF获取Goroutine ID失败,求排查struct g定义问题
我使用Cilium eBPF编写程序获取Goroutine ID,但运行后得到的ID值明显错误。核心怀疑是Go的g结构体与自定义头文件goroutine.h中的struct g定义不匹配,以下是相关代码及运行输出,请求排查:
uprobe.c
SEC("uprobe/runtime.newproc1") int uprobe_runtime_newproc1(struct pt_regs *ctx) { u32 key = 2; u64 initval = 1, *valp; valp = bpf_map_lookup_elem(&uprobe_map, &key); if (!valp) { bpf_map_update_elem(&uprobe_map, &key, &initval, BPF_ANY); return 0; } __sync_fetch_and_add(valp, 1); struct g* goroutine_struct = (void *)PT_REGS_PARM4(ctx); // retrieve output parameter s64 goid = 0; bpf_probe_read(&goid, sizeof(goid), &goroutine_struct->goid); bpf_printk("bpf_printk bpf_probe_read goroutine_struct->goid: %lld", goid); struct g gs; bpf_probe_read(&gs, sizeof(gs), (void *)PT_REGS_PARM4(ctx)); bpf_printk("bpf_printk bpf_probe_read goroutine_struct.goid: %lld", gs.goid); // test void* ptr = (void *)PT_REGS_PARM4(ctx); s64 goid2 = 0; bpf_probe_read(&goid2, sizeof(goid2), (void *)(ptr+152)); bpf_printk("bpf_printk bpf_probe_read goid2: %lld", goid2); return 0; }
goroutine.h
#include "common.h" struct stack { u64 lo; u64 hi; }; struct gobuf { u64 sp; u64 pc; u64 g; u64 ctxt; u64 ret; u64 lr; u64 bp; }; /* go version go1.17.2 linux/amd64 type stack struct { lo uintptr hi uintptr } type gobuf struct { sp uintptr pc uintptr g uintptr ctxt uintptr ret uintptr lr uintptr bp uintptr } type g struct { stack stack // offset known to runtime/cgo stackguard0 uintptr // offset known to liblink stackguard1 uintptr // offset known to liblink _panic *_panic // innermost panic - offset known to liblink _defer *_defer // innermost defer m *m // current m; offset known to arm liblink sched gobuf syscallsp uintptr // if status==Gsyscall, syscallsp = sched.sp to use during gc syscallpc uintptr // if status==Gsyscall, syscallpc = sched.pc to use during gc stktopsp uintptr // expected sp at top of stack, to check in traceback param unsafe.Pointer atomicstatus uint32 stackLock uint32 // sigprof/scang lock; TODO: fold in to atomicstatus goid int64 } */ struct g { struct stack stack; u64 stackguard0; u64 stackguard1; u64 _panic; u64 _defer; u64 m; struct gobuf sched ; u64 syscallsp; u64 syscallpc; u64 stktopsp; u64 param; u32 atomicstatus; u32 stackLock; s64 goid; // Here it is! };
运行输出
执行cat /sys/kernel/debug/tracing/trace_pipe得到:
<...>-1336127 [000] d... 20113210.986990: bpf_trace_printk: bpf_printk bpf_probe_read goroutine_struct->goid: 4938558469562467144 <...>-1336127 [000] d... 20113210.986998: bpf_trace_printk: bpf_printk bpf_probe_read goroutine_struct.goid: 4938558469562467144 <...>-1336127 [000] d... 20113210.986998: bpf_trace_printk: bpf_printk bpf_probe_read goid2: 4938558469562467144
排查与修复建议
校验结构体内存偏移
Go编译时会对结构体字段做自动内存对齐,即使字段定义顺序一致,实际偏移也可能因为对齐规则变化。直接用Go代码获取goid的真实偏移:package main import ( "fmt" "unsafe" ) type stack struct { lo uintptr hi uintptr } type gobuf struct { sp uintptr pc uintptr g uintptr ctxt uintptr ret uintptr lr uintptr bp uintptr } type g struct { stack stack stackguard0 uintptr stackguard1 uintptr _panic uintptr _defer uintptr m uintptr sched gobuf syscallsp uintptr syscallpc uintptr stktopsp uintptr param unsafe.Pointer atomicstatus uint32 stackLock uint32 goid int64 } func main() { var gInst g fmt.Printf("goid实际偏移量: %d\n", unsafe.Offsetof(gInst.goid)) }编译运行后得到的偏移量,替换你代码中硬编码的152,同时对比
goroutine.h中goid的计算偏移是否一致。确认uprobe参数有效性
你用PT_REGS_PARM4(ctx)获取g指针,需确认x86_64架构下该宏是否对应runtime.newproc1的第四个参数寄存器。x86_64 System V调用约定中,前六个参数依次存在rdi、rsi、rdx、rcx、r8、r9,Cilium eBPF的PT_REGS_PARM4对应rcx,而runtime.newproc1的第四个参数确实是callergp *g,但建议打印goroutine_struct的地址,和Go程序中通过unsafe获取的goroutine地址对比,确认指针是否正确。如果地址不匹配,后续读取必然错误。检查内存读取逻辑
确保bpf_probe_read读取的是用户空间有效地址,若指针本身错误,读取的内容就是无效值。可以先打印goroutine_struct的地址,再在目标Go程序中通过以下代码获取当前goroutine地址对比:package main import ( "fmt" "runtime" "unsafe" ) func getG() uintptr { var g uintptr runtime.Stack([]byte{}, false) // 触发栈扫描,让g寄存器指向当前goroutine // 通过内联汇编获取g寄存器值(x86_64) asm := ` MOVQ GS:0, AX MOVQ AX, 0(DI) ` unsafe.Asms(asm, &g) return g } func main() { fmt.Printf("当前goroutine地址: %x\n", getG()) }
内容的提问来源于stack exchange,提问作者weizhao

