proc/internal/ebpf: drop dependency on cgo (#3072)

The ebpf implementations uses cgo, but only to access some C struct
definitions. Instead of using cgo simply duplicate the defintion of
those two structs in Go and add a test to check that the duplicate
definitions remain synchronized.

Fixes #2827
This commit is contained in:
Alessandro Arzilli 2022-07-22 19:39:18 +02:00 committed by GitHub
parent 6ef5284505
commit 5452c30fac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 110 additions and 32 deletions

@ -41,7 +41,7 @@ GOPATH=$(pwd)/go
export GOPATH
export PATH=$PATH:$GOROOT/bin:$GOPATH/bin
go version
go install honnef.co/go/tools/cmd/staticcheck@2021.1.1 || true
go install honnef.co/go/tools/cmd/staticcheck@2022.1.2 || true
uname -a
echo "$PATH"

@ -1182,10 +1182,6 @@ func TestVersion(t *testing.T) {
}
func TestStaticcheck(t *testing.T) {
if goversion.VersionAfterOrEqual(runtime.Version(), 1, 18) {
//TODO(aarzilli): remove this before version 1.8.0 is released
t.Skip("staticcheck does not currently support Go 1.18")
}
_, err := exec.LookPath("staticcheck")
if err != nil {
t.Skip("staticcheck not installed")

@ -150,9 +150,7 @@ func disassemble(memrw MemoryReadWriter, regs Registers, breakpoints *Breakpoint
for len(mem) > 0 {
bp, atbp := breakpoints.M[pc]
if atbp {
for i := range bp.OriginalData {
mem[i] = bp.OriginalData[i]
}
copy(mem, bp.OriginalData)
}
file, line, fn := bi.PCToLine(pc)

@ -1,10 +1,8 @@
//go:build linux && amd64 && cgo && go1.16
// +build linux,amd64,cgo,go1.16
//go:build linux && amd64 && go1.16
// +build linux,amd64,go1.16
package ebpf
// #include "./bpf/include/function_vals.bpf.h"
import "C"
import (
"debug/elf"
"encoding/binary"
@ -22,6 +20,36 @@ import (
"github.com/cilium/ebpf/ringbuf"
)
//lint:file-ignore U1000 some fields are used by the C program
// function_parameter_t tracks function_parameter_t from function_vals.bpf.h
type function_parameter_t struct {
kind uint32
size uint32
offset int32
in_reg bool
n_pieces int32
reg_nums [6]int32
daddr uint64
val [0x30]byte
deref_val [0x30]byte
}
// function_parameter_list_t tracks function_parameter_list_t from function_vals.bpf.h
type function_parameter_list_t struct {
goid_offset uint32
g_addr_offset uint64
goroutine_id uint32
fn_addr uint32
is_ret bool
n_parameters uint32
params [6]function_parameter_t
n_ret_parameters uint32
ret_params [6]function_parameter_t
}
//go:generate go run github.com/cilium/ebpf/cmd/bpf2go -tags "go1.16" -target amd64 trace bpf/trace.bpf.c -- -I./bpf/include
const FakeAddressBase = 0xbeed000000000000
@ -56,7 +84,7 @@ func (ctx *EBPFContext) UpdateArgMap(key uint64, goidOffset int64, args []UProbe
return errors.New("eBPF map not loaded")
}
params := createFunctionParameterList(key, goidOffset, args, isret)
params.g_addr_offset = C.longlong(gAddrOffset)
params.g_addr_offset = gAddrOffset
return ctx.bpfArgMap.Update(unsafe.Pointer(&key), unsafe.Pointer(&params), ebpf.UpdateAny)
}
@ -118,7 +146,7 @@ func LoadEBPFTracingProgram(path string) (*EBPFContext, error) {
}
func parseFunctionParameterList(rawParamBytes []byte) RawUProbeParams {
params := (*C.function_parameter_list_t)(unsafe.Pointer(&rawParamBytes[0]))
params := (*function_parameter_list_t)(unsafe.Pointer(&rawParamBytes[0]))
defer runtime.KeepAlive(params) // Ensure the param is not garbage collected.
@ -126,14 +154,14 @@ func parseFunctionParameterList(rawParamBytes []byte) RawUProbeParams {
rawParams.FnAddr = int(params.fn_addr)
rawParams.GoroutineID = int(params.goroutine_id)
parseParam := func(param C.function_parameter_t) *RawUProbeParam {
parseParam := func(param function_parameter_t) *RawUProbeParam {
iparam := &RawUProbeParam{}
data := make([]byte, 0x60)
ret := param
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)
val := ret.val[:ret.size]
rawDerefValue := ret.deref_val[:0x30]
copy(data, val)
copy(data[0x30:], rawDerefValue)
iparam.Data = data
@ -166,26 +194,26 @@ func parseFunctionParameterList(rawParamBytes []byte) RawUProbeParams {
return rawParams
}
func createFunctionParameterList(entry uint64, goidOffset int64, args []UProbeArgMap, isret bool) C.function_parameter_list_t {
var params C.function_parameter_list_t
params.goid_offset = C.uint(goidOffset)
params.fn_addr = C.uint(entry)
params.is_ret = C.bool(isret)
params.n_parameters = C.uint(0)
params.n_ret_parameters = C.uint(0)
func createFunctionParameterList(entry uint64, goidOffset int64, args []UProbeArgMap, isret bool) function_parameter_list_t {
var params function_parameter_list_t
params.goid_offset = uint32(goidOffset)
params.fn_addr = uint32(entry)
params.is_ret = isret
params.n_parameters = 0
params.n_ret_parameters = 0
for _, arg := range args {
var param C.function_parameter_t
param.size = C.uint(arg.Size)
param.offset = C.int(arg.Offset)
param.kind = C.uint(arg.Kind)
var param function_parameter_t
param.size = uint32(arg.Size)
param.offset = int32(arg.Offset)
param.kind = uint32(arg.Kind)
if arg.InReg {
param.in_reg = true
param.n_pieces = C.int(len(arg.Pieces))
param.n_pieces = int32(len(arg.Pieces))
for i := range arg.Pieces {
if i > 5 {
break
}
param.reg_nums[i] = C.int(arg.Pieces[i])
param.reg_nums[i] = int32(arg.Pieces[i])
}
}
if !arg.Ret {

@ -1,5 +1,5 @@
//go:build !linux || !amd64 || !go1.16 || !cgo
// +build !linux !amd64 !go1.16 !cgo
//go:build !linux || !amd64 || !go1.16
// +build !linux !amd64 !go1.16
package ebpf

@ -0,0 +1,43 @@
//go:build linux && amd64 && cgo && go1.16
// +build linux,amd64,cgo,go1.16
package ebpf
import (
"reflect"
"testing"
"github.com/go-delve/delve/pkg/proc/internal/ebpf/testhelper"
)
func compareStructTypes(t *testing.T, gostructVal, cstructVal interface{}) {
gostruct := reflect.ValueOf(gostructVal).Type()
cstruct := reflect.ValueOf(cstructVal).Type()
if gostruct.NumField() != cstruct.NumField() {
t.Errorf("mismatched field number %d %d", gostruct.NumField(), cstruct.NumField())
return
}
for i := 0; i < cstruct.NumField(); i++ {
gofield := gostruct.Field(i)
cfield := cstruct.Field(i)
t.Logf("%d %s %s\n", i, gofield.Name, cfield.Name)
if gofield.Name != cfield.Name {
t.Errorf("mismatched name for field %s %s", gofield.Name, cfield.Name)
}
if gofield.Offset != cfield.Offset {
t.Errorf("mismatched offset for field %s %s (%d %d)", gofield.Name, cfield.Name, gofield.Offset, cfield.Offset)
}
if gofield.Type.Size() != cfield.Type.Size() {
t.Errorf("mismatched size for field %s %s (%d %d)", gofield.Name, cfield.Name, gofield.Type.Size(), cfield.Type.Size())
}
}
}
func TestStructConsistency(t *testing.T) {
t.Run("function_parameter_t", func(t *testing.T) {
compareStructTypes(t, function_parameter_t{}, testhelper.Function_parameter_t{})
})
t.Run("function_parameter_list_t", func(t *testing.T) {
compareStructTypes(t, function_parameter_list_t{}, testhelper.Function_parameter_list_t{})
})
}

@ -0,0 +1,13 @@
//go:build linux && amd64 && cgo && go1.16
// +build linux,amd64,cgo,go1.16
package testhelper
// #include "../bpf/include/function_vals.bpf.h"
import "C"
// Function_parameter_t exports function_parameter_t from function_vals.bpf.h
type Function_parameter_t C.function_parameter_t
// Function_parameter_list_t exports function_parameter_list_t from function_vals.bpf.h
type Function_parameter_list_t C.function_parameter_list_t