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

Go 1.18.1下eBPF获取并修改函数参数值失败问题排查

Go程序Uprobe捕获并修改函数参数失败分析与解决

问题背景

环境:Go 1.18.1
编写了两个程序tracee.go和tracer.go,需求是通过eBPF的uprobe捕获simpleFunction的参数值,并将其修改为520。

tracee.go代码

package main

import (
    "fmt"
)

//go:noinline
func simpleFunction(val int) {
    fmt.Println("item value:", val)
}

func main() {
    simpleFunction(100)
}

tracer.go原代码

package main

import (
    "bytes"
    "encoding/binary"
    "flag"
    "fmt"
    "github.com/iovisor/gobpf/bcc"
    "log"
    "os"
    "os/signal"
)

var eBPF_Text = `
#include <uapi/linux/ptrace.h>
#include <linux/string.h>

BPF_PERF_OUTPUT(events);

inline int function_was_called(struct pt_regs *ctx) {
   void* stackAddr = (void*)ctx->sp;
   long parameter_value;
   bpf_probe_read(&parameter_value, sizeof(parameter_value), stackAddr+8);

   long tmp = 520;
   bpf_probe_write_user(stackAddr+8, &tmp, sizeof(tmp));

   events.perf_submit(ctx, &parameter_value, sizeof(parameter_value));
   return 0;
}
`
var tracePrg string
var traceFun string

func init() {
    flag.StringVar(&tracePrg, "binary", "", "")
    flag.StringVar(&traceFun, "func", "", "The function to probe")
}

func main() {
    flag.Parse()

    bpfModule := bcc.NewModule(eBPF_Text, []string{})
    uprobeFd, err := bpfModule.LoadUprobe("function_was_called")
    if err != nil {
        panic(err)
    }
    if err = bpfModule.AttachUprobe(tracePrg,
        traceFun, uprobeFd, -1); err != nil {
        panic(err)
    }

    table := bcc.NewTable(bpfModule.TableId("events"), bpfModule)

    outputChannel := make(chan []byte)
    lostChan := make(chan uint64)

    perfMap, err := bcc.InitPerfMap(table, outputChannel, lostChan)
    if err != nil {
        log.Fatal(err)
    }

    sigCh := make(chan os.Signal, 1)
    signal.Notify(sigCh, os.Interrupt)

    go func() {
        for {
            value := <-outputChannel
            var item int64
            if err = binary.Read(bytes.NewBuffer(value),
                binary.LittleEndian, &item); err != nil {
                panic(err)
            }
            fmt.Println(item)
        }
    }()
    perfMap.Start()
    <-sigCh
    perfMap.Stop()
}

操作步骤:

  1. 编译两个程序:go build tracee.go、go build tracer.go
  2. 启动tracer:./tracer --binary ./tracee --func main.simpleFunction
  3. 运行tracee:./tracee

现象:tracee输出item value: 100,tracer捕获到的值为824633729440,参数修改未生效。


错误分析

  1. 参数位置判断错误:x86_64架构下,Go遵循System V调用约定,函数的第一个参数存储在rdi寄存器中,而非栈上的sp+8位置。直接读取栈地址得到的是未初始化的垃圾内存值,导致捕获结果无效。
  2. 修改时机与位置错误:uprobe附加在函数入口(偏移量-1),此时函数还未将寄存器中的参数写入栈空间。后续函数执行时会把rdi寄存器的值覆盖到栈上,你通过bpf_probe_write_user修改的栈内容会被覆盖,无法影响函数的参数使用。

修复方案

修改tracer.go中的eBPF代码,直接操作寄存器来获取和修改参数:

修改后的eBPF代码片段

#include <uapi/linux/ptrace.h>
#include <linux/string.h>

BPF_PERF_OUTPUT(events);

inline int function_was_called(struct pt_regs *ctx) {
    // x86_64架构下,第一个参数存放在rdi寄存器
    long parameter_value = ctx->rdi;
    
    // 修改rdi寄存器的值为520,函数后续执行会使用该值
    ctx->rdi = 520;
    
    events.perf_submit(ctx, &parameter_value, sizeof(parameter_value));
    return 0;
}

验证结果

  1. 重新编译tracer.go并启动,运行tracee后:
    • tracee会输出item value: 520,参数修改生效
    • tracer会捕获到原始参数值100

架构兼容说明

如果程序运行在ARM64架构下,函数第一个参数存储在x0寄存器中,需要将代码中的ctx->rdi替换为ctx->regs[0](或对应架构的寄存器字段)。


内容的提问来源于stack exchange,提问作者wuqinqiang

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 00:24:53