
This patch enables the eBPF tracer backend to parse the ID of the Goroutine which hit the uprobe. This implementation is specific to AMD64 and will have to be generalized further in order to be used on other architectures.
195 lines
4.9 KiB
Go
195 lines
4.9 KiB
Go
//go:build ebpf
|
|
// +build ebpf
|
|
|
|
package ebpf
|
|
|
|
// #include "./trace_probe/function_vals.bpf.h"
|
|
import "C"
|
|
import (
|
|
_ "embed"
|
|
"encoding/binary"
|
|
"errors"
|
|
"reflect"
|
|
"runtime"
|
|
"sync"
|
|
"unsafe"
|
|
|
|
"github.com/go-delve/delve/pkg/dwarf/godwarf"
|
|
"github.com/go-delve/delve/pkg/dwarf/op"
|
|
|
|
bpf "github.com/aquasecurity/libbpfgo"
|
|
"github.com/aquasecurity/libbpfgo/helpers"
|
|
)
|
|
|
|
//go:embed trace_probe/trace.o
|
|
var TraceProbeBytes []byte
|
|
|
|
const FakeAddressBase = 0xbeed000000000000
|
|
|
|
type EBPFContext struct {
|
|
bpfModule *bpf.Module
|
|
bpfProg *bpf.BPFProg
|
|
bpfEvents chan []byte
|
|
bpfRingBuf *bpf.RingBuffer
|
|
bpfArgMap *bpf.BPFMap
|
|
|
|
parsedBpfEvents []RawUProbeParams
|
|
m sync.Mutex
|
|
}
|
|
|
|
func (ctx *EBPFContext) Close() {
|
|
if ctx.bpfModule != nil {
|
|
ctx.bpfModule.Close()
|
|
}
|
|
}
|
|
|
|
func (ctx *EBPFContext) AttachUprobe(pid int, name string, offset uint32) error {
|
|
if ctx.bpfProg == nil {
|
|
return errors.New("no eBPF program loaded")
|
|
}
|
|
_, err := ctx.bpfProg.AttachUprobe(pid, name, offset)
|
|
return err
|
|
}
|
|
|
|
func (ctx *EBPFContext) UpdateArgMap(key uint64, goidOffset int64, args []UProbeArgMap, gAddrOffset uint64) error {
|
|
if ctx.bpfArgMap == nil {
|
|
return errors.New("eBPF map not loaded")
|
|
}
|
|
params := createFunctionParameterList(key, goidOffset, args)
|
|
params.g_addr_offset = C.longlong(gAddrOffset)
|
|
return ctx.bpfArgMap.Update(unsafe.Pointer(&key), unsafe.Pointer(¶ms))
|
|
}
|
|
|
|
func (ctx *EBPFContext) GetBufferedTracepoints() []RawUProbeParams {
|
|
ctx.m.Lock()
|
|
defer ctx.m.Unlock()
|
|
|
|
if len(ctx.parsedBpfEvents) == 0 {
|
|
return make([]RawUProbeParams, 0)
|
|
}
|
|
|
|
events := make([]RawUProbeParams, len(ctx.parsedBpfEvents))
|
|
copy(events, ctx.parsedBpfEvents)
|
|
ctx.parsedBpfEvents = ctx.parsedBpfEvents[:0]
|
|
return events
|
|
}
|
|
|
|
func SymbolToOffset(file, symbol string) (uint32, error) {
|
|
return helpers.SymbolToOffset(file, symbol)
|
|
}
|
|
|
|
func LoadEBPFTracingProgram() (*EBPFContext, error) {
|
|
var ctx EBPFContext
|
|
var err error
|
|
|
|
ctx.bpfModule, err = bpf.NewModuleFromBuffer(TraceProbeBytes, "trace.o")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
ctx.bpfModule.BPFLoadObject()
|
|
prog, err := ctx.bpfModule.GetProgram("uprobe__dlv_trace")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
ctx.bpfProg = prog
|
|
|
|
ctx.bpfEvents = make(chan []byte)
|
|
ctx.bpfRingBuf, err = ctx.bpfModule.InitRingBuf("events", ctx.bpfEvents)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
ctx.bpfRingBuf.Start()
|
|
|
|
ctx.bpfArgMap, err = ctx.bpfModule.GetMap("arg_map")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// TODO(derekparker): This should eventually be moved to a more generalized place.
|
|
go func() {
|
|
for {
|
|
b, ok := <-ctx.bpfEvents
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
parsed := ParseFunctionParameterList(b)
|
|
|
|
ctx.m.Lock()
|
|
ctx.parsedBpfEvents = append(ctx.parsedBpfEvents, parsed)
|
|
ctx.m.Unlock()
|
|
}
|
|
}()
|
|
|
|
return &ctx, nil
|
|
}
|
|
|
|
func ParseFunctionParameterList(rawParamBytes []byte) RawUProbeParams {
|
|
params := (*C.function_parameter_list_t)(unsafe.Pointer(&rawParamBytes[0]))
|
|
|
|
defer runtime.KeepAlive(params) // Ensure the param is not garbage collected.
|
|
|
|
var rawParams RawUProbeParams
|
|
rawParams.FnAddr = int(params.fn_addr)
|
|
rawParams.GoroutineID = int(params.goroutine_id)
|
|
|
|
for i := 0; i < int(params.n_parameters); i++ {
|
|
iparam := &RawUProbeParam{}
|
|
data := make([]byte, 0x60)
|
|
ret := params.params[i]
|
|
iparam.Kind = reflect.Kind(ret.kind)
|
|
|
|
val := C.GoBytes(unsafe.Pointer(&ret.val), C.int(ret.size))
|
|
rawDerefValue := C.GoBytes(unsafe.Pointer(&ret.deref_val[0]), 0x30)
|
|
copy(data, val)
|
|
copy(data[0x30:], rawDerefValue)
|
|
iparam.Data = data
|
|
|
|
pieces := make([]op.Piece, 0, 2)
|
|
pieces = append(pieces, op.Piece{Size: 0x30, Kind: op.AddrPiece, Val: FakeAddressBase})
|
|
pieces = append(pieces, op.Piece{Size: 0x30, Kind: op.AddrPiece, Val: FakeAddressBase + 0x30})
|
|
iparam.Pieces = pieces
|
|
|
|
iparam.Addr = FakeAddressBase
|
|
|
|
switch iparam.Kind {
|
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
|
iparam.RealType = &godwarf.IntType{BasicType: godwarf.BasicType{CommonType: godwarf.CommonType{ByteSize: 8}}}
|
|
case reflect.String:
|
|
strLen := binary.LittleEndian.Uint64(val[8:])
|
|
iparam.Base = FakeAddressBase + 0x30
|
|
iparam.Len = int64(strLen)
|
|
}
|
|
|
|
rawParams.InputParams = append(rawParams.InputParams, iparam)
|
|
}
|
|
|
|
return rawParams
|
|
}
|
|
|
|
func createFunctionParameterList(entry uint64, goidOffset int64, args []UProbeArgMap) C.function_parameter_list_t {
|
|
var params C.function_parameter_list_t
|
|
params.goid_offset = C.uint(goidOffset)
|
|
params.n_parameters = C.uint(len(args))
|
|
params.fn_addr = C.uint(entry)
|
|
for i, arg := range args {
|
|
var param C.function_parameter_t
|
|
param.size = C.uint(arg.Size)
|
|
param.offset = C.uint(arg.Offset)
|
|
param.kind = C.uint(arg.Kind)
|
|
if arg.InReg {
|
|
param.in_reg = true
|
|
param.n_pieces = C.int(len(arg.Pieces))
|
|
for i := range arg.Pieces {
|
|
if i > 5 {
|
|
break
|
|
}
|
|
param.reg_nums[i] = C.int(arg.Pieces[i])
|
|
}
|
|
}
|
|
params.params[i] = param
|
|
}
|
|
return params
|
|
}
|