You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Cilium eBPF获取Goroutine ID失败,求排查struct g定义问题

问题:Cilium eBPF获取Goroutine ID返回错误值

我使用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

排查与修复建议

  1. 校验结构体内存偏移
    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的计算偏移是否一致。

  2. 确认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地址对比,确认指针是否正确。如果地址不匹配,后续读取必然错误。

  3. 检查内存读取逻辑
    确保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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 03:15:50