delve/pkg/proc/internal/ebpf/helpers.go
Derek Parker 1b2f7f0051
pkg/proc: Parse Goroutine ID in eBPF tracer (#2654)
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.
2021-08-24 14:53:27 +02:00

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(&params))
}
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
}