Repository: cilium/ebpf
Pull request #136 (2020-09-04 21:54:30)

Extend kernel BTF search to other likely locations
Supports #114 


Patch:
diff --git a/internal/btf/btf.go b/internal/btf/btf.go
index 3dd000a..57a8154 100644
--- a/internal/btf/btf.go
+++ b/internal/btf/btf.go
@@ -59,29 +59,9 @@ func LoadSpecFromReader(rd io.ReaderAt) (*Spec, error) {
 	}
 	defer file.Close()
 
-	var (
-		btfSection    *elf.Section
-		btfExtSection *elf.Section
-		sectionSizes  = make(map[string]uint32)
-	)
-
-	for _, sec := range file.Sections {
-		switch sec.Name {
-		case ".BTF":
-			btfSection = sec
-		case ".BTF.ext":
-			btfExtSection = sec
-		default:
-			if sec.Type != elf.SHT_PROGBITS && sec.Type != elf.SHT_NOBITS {
-				break
-			}
-
-			if sec.Size > math.MaxUint32 {
-				return nil, fmt.Errorf("section %s exceeds maximum size", sec.Name)
-			}
-
-			sectionSizes[sec.Name] = uint32(sec.Size)
-		}
+	btfSection, btfExtSection, sectionSizes, err := findBtfSections(file)
+	if err != nil {
+		return nil, err
 	}
 
 	if btfSection == nil {
@@ -129,6 +109,48 @@ func LoadSpecFromReader(rd io.ReaderAt) (*Spec, error) {
 	return spec, nil
 }
 
+func findBtfSections(file *elf.File) (*elf.Section, *elf.Section, map[string]uint32, error) {
+	var (
+		btfSection    *elf.Section
+		btfExtSection *elf.Section
+		sectionSizes  = make(map[string]uint32)
+	)
+
+	for _, sec := range file.Sections {
+		switch sec.Name {
+		case ".BTF":
+			btfSection = sec
+		case ".BTF.ext":
+			btfExtSection = sec
+		default:
+			if sec.Type != elf.SHT_PROGBITS && sec.Type != elf.SHT_NOBITS {
+				break
+			}
+
+			if sec.Size > math.MaxUint32 {
+				return nil, nil, nil, fmt.Errorf("section %s exceeds maximum size", sec.Name)
+			}
+
+			sectionSizes[sec.Name] = uint32(sec.Size)
+		}
+	}
+	return btfSection, btfExtSection, sectionSizes, nil
+}
+
+func loadNakedSpecFromReader(rd io.ReaderAt, sectionSizes map[string]uint32, variableOffsets map[variable]uint32) (*Spec, error) {
+	file, err := elf.NewFile(rd)
+	if err != nil {
+		return nil, err
+	}
+	defer file.Close()
+
+	btfSection, _, _, err := findBtfSections(file)
+	if btfSection == nil {
+		return nil, fmt.Errorf("unable to find .BTF ELF section")
+	}
+	return loadNakedSpec(btfSection.Open(), file.ByteOrder, sectionSizes, variableOffsets)
+}
+
 func loadNakedSpec(btf io.ReadSeeker, bo binary.ByteOrder, sectionSizes map[string]uint32, variableOffsets map[variable]uint32) (*Spec, error) {
 	rawTypes, rawStrings, err := parseBTF(btf, bo)
 	if err != nil {
@@ -176,16 +198,50 @@ func LoadKernelSpec() (*Spec, error) {
 }
 
 func loadKernelSpec() (*Spec, error) {
-	fh, err := os.Open("/sys/kernel/btf/vmlinux")
-	if os.IsNotExist(err) {
-		return nil, fmt.Errorf("can't open kernel BTF at /sys/kernel/btf/vmlinux: %w", ErrNotFound)
-	}
+	release, err := unix.KernelRelease()
 	if err != nil {
-		return nil, fmt.Errorf("can't read kernel BTF: %s", err)
+		return nil, fmt.Errorf("can't read kernel release number: %w", err)
+	}
+
+	// use same list of locations as libbpf
+	// https://github.com/libbpf/libbpf/blob/9a3a42608dbe3731256a5682a125ac1e23bced8f/src/btf.c#L3114-L3122
+	locations := []struct {
+		pathFmt string
+		raw     bool
+	}{
+		/* try canonical vmlinux BTF through sysfs first */
+		{"/sys/kernel/btf/vmlinux", true /* raw BTF */},
+		/* fall back to trying to find vmlinux ELF on disk otherwise */
+		{"/boot/vmlinux-%s", false},
+		{"/lib/modules/%s/vmlinux-%s", false},
+		{"/lib/modules/%s/build/vmlinux", false},
+		{"/usr/lib/modules/%s/kernel/vmlinux", false},
+		{"/usr/lib/debug/boot/vmlinux-%s", false},
+		{"/usr/lib/debug/boot/vmlinux-%s.debug", false},
+		{"/usr/lib/debug/lib/modules/%s/vmlinux", false},
+	}
+
+	var spec *Spec
+	for _, loc := range locations {
+		path := fmt.Sprintf(loc.pathFmt, release)
+		fh, err := os.Open(path)
+		if err != nil {
+			continue
+		}
+		defer fh.Close()
+		if loc.raw {
+			spec, err = loadNakedSpec(fh, internal.NativeEndian, nil, nil)
+		} else {
+			spec, err = loadNakedSpecFromReader(fh, nil, nil)
+		}
+
+		if err != nil {
+			continue
+		}
+		return spec, nil
 	}
-	defer fh.Close()
 
-	return loadNakedSpec(fh, internal.NativeEndian, nil, nil)
+	return nil, fmt.Errorf("BTF for kernel version %s: %w", release, ErrNotFound)
 }
 
 func parseBTF(btf io.ReadSeeker, bo binary.ByteOrder) ([]rawType, stringTable, error) {
@@ -402,9 +458,19 @@ func (s *Spec) Map(name string) (*Map, []Member, error) {
 		switch member.Name {
 		case "key":
 			key = member.Type
+			if pk, isPtr := key.(*Pointer); !isPtr {
+				return nil, nil, fmt.Errorf("key type is not a pointer: %T", key)
+			} else {
+				key = pk.Target
+			}
 
 		case "value":
 			value = member.Type
+			if vk, isPtr := value.(*Pointer); !isPtr {
+				return nil, nil, fmt.Errorf("value type is not a pointer: %T", value)
+			} else {
+				value = vk.Target
+			}
 		}
 	}
 
@@ -621,9 +687,7 @@ type bpfLoadBTFAttr struct {
 }
 
 func bpfLoadBTF(attr *bpfLoadBTFAttr) (*internal.FD, error) {
-	const _BTFLoad = 18
-
-	fd, err := internal.BPF(_BTFLoad, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
+	fd, err := internal.BPF(internal.BPF_BTF_LOAD, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
 	if err != nil {
 		return nil, err
 	}
diff --git a/internal/btf/types.go b/internal/btf/types.go
index 59121fc..8db5b66 100644
--- a/internal/btf/types.go
+++ b/internal/btf/types.go
@@ -23,6 +23,8 @@ type Type interface {
 	// Make a copy of the type, without copying Type members.
 	copy() Type
 
+	// Enumerate all nested Types. Repeated calls must visit nested
+	// types in the same order.
 	walk(*copyStack)
 }
 
@@ -223,36 +225,39 @@ func (td *Typedef) copy() Type {
 	return &cpy
 }
 
-// Volatile is a modifier.
+// Volatile is a qualifier.
 type Volatile struct {
 	TypeID
 	Type Type
 }
 
+func (v *Volatile) qualify() Type      { return v.Type }
 func (v *Volatile) walk(cs *copyStack) { cs.push(&v.Type) }
 func (v *Volatile) copy() Type {
 	cpy := *v
 	return &cpy
 }
 
-// Const is a modifier.
+// Const is a qualifier.
 type Const struct {
 	TypeID
 	Type Type
 }
 
+func (c *Const) qualify() Type      { return c.Type }
 func (c *Const) walk(cs *copyStack) { cs.push(&c.Type) }
 func (c *Const) copy() Type {
 	cpy := *c
 	return &cpy
 }
 
-// Restrict is a modifier.
+// Restrict is a qualifier.
 type Restrict struct {
 	TypeID
 	Type Type
 }
 
+func (r *Restrict) qualify() Type      { return r.Type }
 func (r *Restrict) walk(cs *copyStack) { cs.push(&r.Type) }
 func (r *Restrict) copy() Type {
 	cpy := *r
@@ -354,6 +359,16 @@ var (
 	_ sizer = (*Datasec)(nil)
 )
 
+type qualifier interface {
+	qualify() Type
+}
+
+var (
+	_ qualifier = (*Const)(nil)
+	_ qualifier = (*Restrict)(nil)
+	_ qualifier = (*Volatile)(nil)
+)
+
 // Sizeof returns the size of a type in bytes.
 //
 // Returns an error if the size can't be computed.
@@ -382,14 +397,9 @@ func Sizeof(typ Type) (int, error) {
 		case *Typedef:
 			typ = v.Type
 			continue
-		case *Volatile:
-			typ = v.Type
-			continue
-		case *Const:
-			typ = v.Type
-			continue
-		case *Restrict:
-			typ = v.Type
+
+		case qualifier:
+			typ = v.qualify()
 			continue
 
 		default:
@@ -439,7 +449,7 @@ func copyType(typ Type) Type {
 }
 
 // copyStack keeps track of pointers to types which still
-// need to be copied.
+// need to be visited.
 type copyStack []*Type
 
 // push adds a type to the stack.
diff --git a/internal/unix/types_linux.go b/internal/unix/types_linux.go
index a4b820b..d8135c1 100644
--- a/internal/unix/types_linux.go
+++ b/internal/unix/types_linux.go
@@ -3,6 +3,7 @@
 package unix
 
 import (
+	"bytes"
 	"syscall"
 
 	linux "golang.org/x/sys/unix"
@@ -149,3 +150,15 @@ func Gettid() int {
 func Tgkill(tgid int, tid int, sig syscall.Signal) (err error) {
 	return linux.Tgkill(tgid, tid, sig)
 }
+
+func KernelRelease() (string, error) {
+	var uname Utsname
+	err := Uname(&uname)
+	if err != nil {
+		return "", err
+	}
+
+	end := bytes.IndexByte(uname.Release[:], 0)
+	release := string(uname.Release[:end])
+	return release, nil
+}
diff --git a/internal/unix/types_other.go b/internal/unix/types_other.go
index ecd1138..416b8d2 100644
--- a/internal/unix/types_other.go
+++ b/internal/unix/types_other.go
@@ -216,3 +216,7 @@ func Gettid() int {
 func Tgkill(tgid int, tid int, sig syscall.Signal) (err error) {
 	return errNonLinux
 }
+
+func KernelRelease() (string, error) {
+	return "", errNonLinux
+}


Test patch:
diff --git a/internal/btf/types_test.go b/internal/btf/types_test.go
index 0f67282..45d119f 100644
--- a/internal/btf/types_test.go
+++ b/internal/btf/types_test.go
@@ -43,15 +43,7 @@ func TestCopyType(t *testing.T) {
 	}
 
 	t.Run("cyclical", func(t *testing.T) {
-		ptr := &Pointer{}
-		foo := &Struct{
-			Members: []Member{
-				{Type: ptr},
-			},
-		}
-		ptr.Target = foo
-
-		_ = copyType(foo)
+		_ = copyType(newCyclicalType(2))
 	})
 }
 
@@ -79,3 +71,75 @@ func ExampleType_validTypes() {
 	t = &Datasec{}
 	_ = t
 }
+
+func TestType(t *testing.T) {
+	types := []func() Type{
+		func() Type { return &Void{} },
+		func() Type { return &Int{} },
+		func() Type { return &Pointer{} },
+		func() Type { return &Array{} },
+		func() Type { return &Struct{} },
+		func() Type { return &Union{} },
+		func() Type { return &Enum{} },
+		func() Type { return &Fwd{} },
+		func() Type { return &Typedef{} },
+		func() Type { return &Volatile{} },
+		func() Type { return &Const{} },
+		func() Type { return &Restrict{} },
+		func() Type { return &Func{} },
+		func() Type { return &FuncProto{} },
+		func() Type { return &Var{} },
+		func() Type { return &Datasec{} },
+	}
+
+	for _, fn := range types {
+		typ := fn()
+		t.Run(fmt.Sprintf("%T", typ), func(t *testing.T) {
+			if typ == typ.copy() {
+				t.Error("Copy doesn't copy")
+			}
+
+			var first, second copyStack
+			typ.walk(&first)
+			typ.walk(&second)
+
+			a, b := first.pop(), second.pop()
+			for a != nil && b != nil {
+				if a != b {
+					t.Fatal("Order of walk is not the same")
+				}
+				a, b = first.pop(), second.pop()
+			}
+
+			if a != nil || b != nil {
+				t.Fatal("Number of types walked is not the same")
+			}
+		})
+	}
+}
+
+func newCyclicalType(n int) Type {
+	ptr := &Pointer{}
+	prev := Type(ptr)
+	for i := 0; i < n; i++ {
+		switch i % 5 {
+		case 0:
+			prev = &Struct{
+				Members: []Member{
+					{Type: prev},
+				},
+			}
+
+		case 1:
+			prev = &Const{Type: prev}
+		case 2:
+			prev = &Volatile{Type: prev}
+		case 3:
+			prev = &Typedef{Type: prev}
+		case 4:
+			prev = &Array{Type: prev}
+		}
+	}
+	ptr.Target = prev
+	return ptr
+}