mirror of
				https://github.com/go-delve/delve.git
				synced 2025-10-31 10:47:27 +08:00 
			
		
		
		
	 1b2f7f0051
			
		
	
	1b2f7f0051
	
	
	
		
			
			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
 | |
| }
 |