github.com/cnboonhan/delve@v0.0.0-20230908061759-363f2388c2fb/pkg/proc/internal/ebpf/helpers.go (about)

     1  //go:build linux && amd64 && go1.16
     2  // +build linux,amd64,go1.16
     3  
     4  package ebpf
     5  
     6  import (
     7  	"debug/elf"
     8  	"encoding/binary"
     9  	"errors"
    10  	"reflect"
    11  	"runtime"
    12  	"sync"
    13  	"unsafe"
    14  
    15  	"github.com/go-delve/delve/pkg/dwarf/godwarf"
    16  	"github.com/go-delve/delve/pkg/dwarf/op"
    17  
    18  	"github.com/cilium/ebpf"
    19  	"github.com/cilium/ebpf/link"
    20  	"github.com/cilium/ebpf/ringbuf"
    21  	"github.com/cilium/ebpf/rlimit"
    22  )
    23  
    24  //lint:file-ignore U1000 some fields are used by the C program
    25  
    26  // function_parameter_t tracks function_parameter_t from function_vals.bpf.h
    27  type function_parameter_t struct {
    28  	kind      uint32
    29  	size      uint32
    30  	offset    int32
    31  	in_reg    bool
    32  	n_pieces  int32
    33  	reg_nums  [6]int32
    34  	daddr     uint64
    35  	val       [0x30]byte
    36  	deref_val [0x30]byte
    37  }
    38  
    39  // function_parameter_list_t tracks function_parameter_list_t from function_vals.bpf.h
    40  type function_parameter_list_t struct {
    41  	goid_offset   uint32
    42  	g_addr_offset uint64
    43  	goroutine_id  uint32
    44  	fn_addr       uint64
    45  	is_ret        bool
    46  
    47  	n_parameters uint32
    48  	params       [6]function_parameter_t
    49  
    50  	n_ret_parameters uint32
    51  	ret_params       [6]function_parameter_t
    52  }
    53  
    54  //go:generate go run github.com/cilium/ebpf/cmd/bpf2go -tags "go1.16" -target amd64 trace bpf/trace.bpf.c -- -I./bpf/include
    55  
    56  const FakeAddressBase = 0xbeed000000000000
    57  
    58  type EBPFContext struct {
    59  	objs       *traceObjects
    60  	bpfEvents  chan []byte
    61  	bpfRingBuf *ringbuf.Reader
    62  	executable *link.Executable
    63  	bpfArgMap  *ebpf.Map
    64  	links      []link.Link
    65  
    66  	parsedBpfEvents []RawUProbeParams
    67  	m               sync.Mutex
    68  }
    69  
    70  func (ctx *EBPFContext) Close() {
    71  	if ctx.objs != nil {
    72  		ctx.objs.Close()
    73  	}
    74  	if ctx.links != nil {
    75  		for _, l := range ctx.links {
    76  			l.Close()
    77  		}
    78  	}
    79  }
    80  
    81  func (ctx *EBPFContext) AttachUprobe(pid int, name string, offset uint64) error {
    82  	if ctx.executable == nil {
    83  		return errors.New("no eBPF program loaded")
    84  	}
    85  	l, err := ctx.executable.Uprobe(name, ctx.objs.tracePrograms.UprobeDlvTrace, &link.UprobeOptions{PID: pid, Offset: offset})
    86  	ctx.links = append(ctx.links, l)
    87  	return err
    88  }
    89  
    90  func (ctx *EBPFContext) UpdateArgMap(key uint64, goidOffset int64, args []UProbeArgMap, gAddrOffset uint64, isret bool) error {
    91  	if ctx.bpfArgMap == nil {
    92  		return errors.New("eBPF map not loaded")
    93  	}
    94  	params := createFunctionParameterList(key, goidOffset, args, isret)
    95  	params.g_addr_offset = gAddrOffset
    96  	return ctx.bpfArgMap.Update(unsafe.Pointer(&key), unsafe.Pointer(&params), ebpf.UpdateAny)
    97  }
    98  
    99  func (ctx *EBPFContext) GetBufferedTracepoints() []RawUProbeParams {
   100  	ctx.m.Lock()
   101  	defer ctx.m.Unlock()
   102  
   103  	if len(ctx.parsedBpfEvents) == 0 {
   104  		return make([]RawUProbeParams, 0)
   105  	}
   106  
   107  	events := make([]RawUProbeParams, len(ctx.parsedBpfEvents))
   108  	copy(events, ctx.parsedBpfEvents)
   109  	ctx.parsedBpfEvents = ctx.parsedBpfEvents[:0]
   110  	return events
   111  }
   112  
   113  func LoadEBPFTracingProgram(path string) (*EBPFContext, error) {
   114  	var (
   115  		ctx  EBPFContext
   116  		err  error
   117  		objs traceObjects
   118  	)
   119  
   120  	if err = rlimit.RemoveMemlock(); err != nil {
   121  		return nil, err
   122  	}
   123  	ctx.executable, err = link.OpenExecutable(path)
   124  	if err != nil {
   125  		return nil, err
   126  	}
   127  
   128  	if err := loadTraceObjects(&objs, nil); err != nil {
   129  		return nil, err
   130  	}
   131  	ctx.objs = &objs
   132  
   133  	ctx.bpfRingBuf, err = ringbuf.NewReader(objs.Events)
   134  	if err != nil {
   135  		return nil, err
   136  	}
   137  
   138  	ctx.bpfArgMap = objs.ArgMap
   139  
   140  	// TODO(derekparker): This should eventually be moved to a more generalized place.
   141  	go func() {
   142  		for {
   143  			e, err := ctx.bpfRingBuf.Read()
   144  			if err != nil {
   145  				return
   146  			}
   147  
   148  			parsed := parseFunctionParameterList(e.RawSample)
   149  
   150  			ctx.m.Lock()
   151  			ctx.parsedBpfEvents = append(ctx.parsedBpfEvents, parsed)
   152  			ctx.m.Unlock()
   153  		}
   154  	}()
   155  
   156  	return &ctx, nil
   157  }
   158  
   159  func parseFunctionParameterList(rawParamBytes []byte) RawUProbeParams {
   160  	params := (*function_parameter_list_t)(unsafe.Pointer(&rawParamBytes[0]))
   161  
   162  	defer runtime.KeepAlive(params) // Ensure the param is not garbage collected.
   163  
   164  	var rawParams RawUProbeParams
   165  	rawParams.FnAddr = int(params.fn_addr)
   166  	rawParams.GoroutineID = int(params.goroutine_id)
   167  	rawParams.IsRet = params.is_ret
   168  
   169  	parseParam := func(param function_parameter_t) *RawUProbeParam {
   170  		iparam := &RawUProbeParam{}
   171  		data := make([]byte, 0x60)
   172  		ret := param
   173  		iparam.Kind = reflect.Kind(ret.kind)
   174  
   175  		val := ret.val[:ret.size]
   176  		rawDerefValue := ret.deref_val[:0x30]
   177  		copy(data, val)
   178  		copy(data[0x30:], rawDerefValue)
   179  		iparam.Data = data
   180  
   181  		pieces := make([]op.Piece, 0, 2)
   182  		pieces = append(pieces, op.Piece{Size: 0x30, Kind: op.AddrPiece, Val: FakeAddressBase})
   183  		pieces = append(pieces, op.Piece{Size: 0x30, Kind: op.AddrPiece, Val: FakeAddressBase + 0x30})
   184  		iparam.Pieces = pieces
   185  
   186  		iparam.Addr = FakeAddressBase
   187  
   188  		switch iparam.Kind {
   189  		case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
   190  			iparam.RealType = &godwarf.UintType{BasicType: godwarf.BasicType{CommonType: godwarf.CommonType{ByteSize: 8}}}
   191  		case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Bool:
   192  			iparam.RealType = &godwarf.IntType{BasicType: godwarf.BasicType{CommonType: godwarf.CommonType{ByteSize: 8}}}
   193  		case reflect.String:
   194  			strLen := binary.LittleEndian.Uint64(val[8:])
   195  			iparam.Base = FakeAddressBase + 0x30
   196  			iparam.Len = int64(strLen)
   197  		}
   198  		return iparam
   199  	}
   200  
   201  	for i := 0; i < int(params.n_parameters); i++ {
   202  		rawParams.InputParams = append(rawParams.InputParams, parseParam(params.params[i]))
   203  	}
   204  	for i := 0; i < int(params.n_ret_parameters); i++ {
   205  		rawParams.ReturnParams = append(rawParams.ReturnParams, parseParam(params.ret_params[i]))
   206  	}
   207  
   208  	return rawParams
   209  }
   210  
   211  func createFunctionParameterList(entry uint64, goidOffset int64, args []UProbeArgMap, isret bool) function_parameter_list_t {
   212  	var params function_parameter_list_t
   213  	params.goid_offset = uint32(goidOffset)
   214  	params.fn_addr = entry
   215  	params.is_ret = isret
   216  	params.n_parameters = 0
   217  	params.n_ret_parameters = 0
   218  	for _, arg := range args {
   219  		var param function_parameter_t
   220  		param.size = uint32(arg.Size)
   221  		param.offset = int32(arg.Offset)
   222  		param.kind = uint32(arg.Kind)
   223  		if arg.InReg {
   224  			param.in_reg = true
   225  			param.n_pieces = int32(len(arg.Pieces))
   226  			for i := range arg.Pieces {
   227  				if i > 5 {
   228  					break
   229  				}
   230  				param.reg_nums[i] = int32(arg.Pieces[i])
   231  			}
   232  		}
   233  		if !arg.Ret {
   234  			params.params[params.n_parameters] = param
   235  			params.n_parameters++
   236  		} else {
   237  			params.ret_params[params.n_ret_parameters] = param
   238  			params.n_ret_parameters++
   239  		}
   240  	}
   241  	return params
   242  }
   243  
   244  func AddressToOffset(f *elf.File, addr uint64) (uint64, error) {
   245  	sectionsToSearchForSymbol := []*elf.Section{}
   246  
   247  	for i := range f.Sections {
   248  		if f.Sections[i].Flags == elf.SHF_ALLOC+elf.SHF_EXECINSTR {
   249  			sectionsToSearchForSymbol = append(sectionsToSearchForSymbol, f.Sections[i])
   250  		}
   251  	}
   252  
   253  	var executableSection *elf.Section
   254  
   255  	// Find what section the symbol is in by checking the executable section's
   256  	// addr space.
   257  	for m := range sectionsToSearchForSymbol {
   258  		if addr > sectionsToSearchForSymbol[m].Addr &&
   259  			addr < sectionsToSearchForSymbol[m].Addr+sectionsToSearchForSymbol[m].Size {
   260  			executableSection = sectionsToSearchForSymbol[m]
   261  		}
   262  	}
   263  
   264  	if executableSection == nil {
   265  		return 0, errors.New("could not find symbol in executable sections of binary")
   266  	}
   267  
   268  	return uint64(addr - executableSection.Addr + executableSection.Offset), nil
   269  }