gitlab.com/Raven-IO/raven-delve@v1.22.4/pkg/proc/internal/ebpf/helpers.go (about)

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