Repository: cilium/ebpf
Pull request #132 (2020-08-28 21:57:30)

Add BTF Int encoding, offset, and bits fields
In support of #114 


Patch:
diff --git a/internal/btf/btf_types.go b/internal/btf/btf_types.go
index e34a87e..a4cde3f 100644
--- a/internal/btf/btf_types.go
+++ b/internal/btf/btf_types.go
@@ -40,10 +40,12 @@ const (
 )
 
 const (
-	btfTypeKindShift = 24
-	btfTypeKindLen   = 4
-	btfTypeVlenShift = 0
-	btfTypeVlenMask  = 16
+	btfTypeKindShift     = 24
+	btfTypeKindLen       = 4
+	btfTypeVlenShift     = 0
+	btfTypeVlenMask      = 16
+	btfTypeKindFlagShift = 31
+	btfTypeKindFlagMask  = 1
 )
 
 // btfType is equivalent to struct btf_type in Documentation/bpf/btf.rst.
@@ -136,6 +138,10 @@ func (bt *btfType) SetVlen(vlen int) {
 	bt.setInfo(uint32(vlen), btfTypeVlenMask, btfTypeVlenShift)
 }
 
+func (bt *btfType) KindFlag() bool {
+	return bt.info(btfTypeKindFlagMask, btfTypeKindFlagShift) == 1
+}
+
 func (bt *btfType) Linkage() btfFuncLinkage {
 	return btfFuncLinkage(bt.info(btfTypeVlenMask, btfTypeVlenShift))
 }
@@ -257,3 +263,7 @@ func readTypes(r io.Reader, bo binary.ByteOrder) ([]rawType, error) {
 		types = append(types, rawType{header, data})
 	}
 }
+
+func intEncoding(raw uint32) (IntEncoding, uint32, byte) {
+	return IntEncoding((raw & 0x0f000000) >> 24), (raw & 0x00ff0000) >> 16, byte(raw & 0x000000ff)
+}
diff --git a/internal/btf/types.go b/internal/btf/types.go
index 264142a..59121fc 100644
--- a/internal/btf/types.go
+++ b/internal/btf/types.go
@@ -43,13 +43,26 @@ func (v *Void) size() uint32    { return 0 }
 func (v *Void) copy() Type      { return (*Void)(nil) }
 func (v *Void) walk(*copyStack) {}
 
+type IntEncoding byte
+
+const (
+	Signed IntEncoding = 1 << iota
+	Char
+	Bool
+)
+
 // Int is an integer of a given length.
 type Int struct {
 	TypeID
 	Name
 
 	// The size of the integer in bytes.
-	Size uint32
+	Size     uint32
+	Encoding IntEncoding
+	// Offset is the starting bit offset. Currently always 0.
+	// See https://www.kernel.org/doc/html/latest/bpf/btf.html#btf-kind-int
+	Offset uint32
+	Bits   byte
 }
 
 func (i *Int) size() uint32    { return i.Size }
@@ -109,6 +122,10 @@ func (s *Struct) copy() Type {
 	return &cpy
 }
 
+func (s *Struct) members() []Member {
+	return s.Members
+}
+
 // Union is a compound type where members occupy the same memory.
 type Union struct {
 	TypeID
@@ -133,25 +150,51 @@ func (u *Union) copy() Type {
 	return &cpy
 }
 
+func (u *Union) members() []Member {
+	return u.Members
+}
+
+type composite interface {
+	members() []Member
+}
+
+var (
+	_ composite = (*Struct)(nil)
+	_ composite = (*Union)(nil)
+)
+
 // Member is part of a Struct or Union.
 //
 // It is not a valid Type.
 type Member struct {
 	Name
-	Type   Type
-	Offset uint32
+	Type Type
+	// Offset is the bit offset of this member
+	Offset       uint32
+	BitfieldSize uint32
 }
 
 // Enum lists possible values.
 type Enum struct {
 	TypeID
 	Name
+	Values []EnumValue
+}
+
+// EnumValue is part of an Enum
+//
+// Is is not a valid Type
+type EnumValue struct {
+	Name
+	Value int32
 }
 
 func (e *Enum) size() uint32    { return 4 }
 func (e *Enum) walk(*copyStack) {}
 func (e *Enum) copy() Type {
 	cpy := *e
+	cpy.Values = make([]EnumValue, len(e.Values))
+	copy(cpy.Values, e.Values)
 	return &cpy
 }
 
@@ -233,15 +276,28 @@ func (f *Func) copy() Type {
 type FuncProto struct {
 	TypeID
 	Return Type
-	// Parameters not supported yet
+	Params []FuncParam
+}
+
+func (fp *FuncProto) walk(cs *copyStack) {
+	cs.push(&fp.Return)
+	for _, m := range fp.Params {
+		cs.push(&m.Type)
+	}
 }
 
-func (fp *FuncProto) walk(cs *copyStack) { cs.push(&fp.Return) }
 func (fp *FuncProto) copy() Type {
 	cpy := *fp
+	cpy.Params = make([]FuncParam, len(fp.Params))
+	copy(cpy.Params, fp.Params)
 	return &cpy
 }
 
+type FuncParam struct {
+	Name
+	Type Type
+}
+
 // Var is a global variable.
 type Var struct {
 	TypeID
@@ -427,7 +483,7 @@ func inflateRawTypes(rawTypes []rawType, rawStrings stringTable) (namedTypes map
 		fixups = append(fixups, fixupDef{id, expectedKind, typ})
 	}
 
-	convertMembers := func(raw []btfMember) ([]Member, error) {
+	convertMembers := func(raw []btfMember, kindFlag bool) ([]Member, error) {
 		// NB: The fixup below relies on pre-allocating this array to
 		// work, since otherwise append might re-allocate members.
 		members := make([]Member, 0, len(raw))
@@ -436,10 +492,15 @@ func inflateRawTypes(rawTypes []rawType, rawStrings stringTable) (namedTypes map
 			if err != nil {
 				return nil, fmt.Errorf("can't get name for member %d: %w", i, err)
 			}
-			members = append(members, Member{
+			m := Member{
 				Name:   name,
 				Offset: btfMember.Offset,
-			})
+			}
+			if kindFlag {
+				m.BitfieldSize = btfMember.Offset >> 24
+				m.Offset &= 0xffffff
+			}
+			members = append(members, m)
 		}
 		for i := range members {
 			fixup(raw[i].Type, kindUnknown, &members[i].Type)
@@ -466,7 +527,8 @@ func inflateRawTypes(rawTypes []rawType, rawStrings stringTable) (namedTypes map
 
 		switch raw.Kind() {
 		case kindInt:
-			typ = &Int{id, name, raw.Size()}
+			encoding, offset, bits := intEncoding(*raw.data.(*uint32))
+			typ = &Int{id, name, raw.Size(), encoding, offset, bits}
 
 		case kindPointer:
 			ptr := &Pointer{id, nil}
@@ -483,21 +545,33 @@ func inflateRawTypes(rawTypes []rawType, rawStrings stringTable) (namedTypes map
 			typ = arr
 
 		case kindStruct:
-			members, err := convertMembers(raw.data.([]btfMember))
+			members, err := convertMembers(raw.data.([]btfMember), raw.KindFlag())
 			if err != nil {
 				return nil, fmt.Errorf("struct %s (id %d): %w", name, id, err)
 			}
 			typ = &Struct{id, name, raw.Size(), members}
 
 		case kindUnion:
-			members, err := convertMembers(raw.data.([]btfMember))
+			members, err := convertMembers(raw.data.([]btfMember), raw.KindFlag())
 			if err != nil {
 				return nil, fmt.Errorf("union %s (id %d): %w", name, id, err)
 			}
 			typ = &Union{id, name, raw.Size(), members}
 
 		case kindEnum:
-			typ = &Enum{id, name}
+			rawvals := raw.data.([]btfEnum)
+			vals := make([]EnumValue, 0, len(rawvals))
+			for i, btfVal := range rawvals {
+				name, err := rawStrings.LookupName(btfVal.NameOff)
+				if err != nil {
+					return nil, fmt.Errorf("can't get name for enum value %d: %s", i, err)
+				}
+				vals = append(vals, EnumValue{
+					Name:  name,
+					Value: btfVal.Val,
+				})
+			}
+			typ = &Enum{id, name, vals}
 
 		case kindForward:
 			typ = &Fwd{id, name}
@@ -528,7 +602,22 @@ func inflateRawTypes(rawTypes []rawType, rawStrings stringTable) (namedTypes map
 			typ = fn
 
 		case kindFuncProto:
-			fp := &FuncProto{id, nil}
+			rawparams := raw.data.([]btfParam)
+			params := make([]FuncParam, 0, len(rawparams))
+			for i, param := range rawparams {
+				name, err := rawStrings.LookupName(param.NameOff)
+				if err != nil {
+					return nil, fmt.Errorf("can't get name for func proto parameter %d: %s", i, err)
+				}
+				params = append(params, FuncParam{
+					Name: name,
+				})
+			}
+			for i := range params {
+				fixup(rawparams[i].Type, kindUnknown, &params[i].Type)
+			}
+
+			fp := &FuncProto{id, nil, params}
 			fixup(raw.Type(), kindUnknown, &fp.Return)
 			typ = fp
 
diff --git a/internal/unix/types_linux.go b/internal/unix/types_linux.go
index 9363d0b..a4b820b 100644
--- a/internal/unix/types_linux.go
+++ b/internal/unix/types_linux.go
@@ -19,6 +19,7 @@ const (
 	EPERM                    = linux.EPERM
 	ESRCH                    = linux.ESRCH
 	ENODEV                   = linux.ENODEV
+	BPF_F_NUMA_NODE          = linux.BPF_F_NUMA_NODE
 	BPF_F_RDONLY_PROG        = linux.BPF_F_RDONLY_PROG
 	BPF_F_WRONLY_PROG        = linux.BPF_F_WRONLY_PROG
 	BPF_OBJ_NAME_LEN         = linux.BPF_OBJ_NAME_LEN
diff --git a/internal/unix/types_other.go b/internal/unix/types_other.go
index 2dea950..ecd1138 100644
--- a/internal/unix/types_other.go
+++ b/internal/unix/types_other.go
@@ -20,6 +20,7 @@ const (
 	EPERM                    = syscall.EPERM
 	ESRCH                    = syscall.ESRCH
 	ENODEV                   = syscall.ENODEV
+	BPF_F_NUMA_NODE          = 0
 	BPF_F_RDONLY_PROG        = 0
 	BPF_F_WRONLY_PROG        = 0
 	BPF_OBJ_NAME_LEN         = 0x10
diff --git a/map.go b/map.go
index 461b995..64decb1 100644
--- a/map.go
+++ b/map.go
@@ -31,6 +31,11 @@ type MapSpec struct {
 	MaxEntries uint32
 	Flags      uint32
 
+	// Specify numa node during map creation
+	// (effective only if unix.BPF_F_NUMA_NODE flag is set,
+	// which can be imported from golang.org/x/sys/unix)
+	NumaNode uint32
+
 	// The initial contents of the map. May be nil.
 	Contents []MapKV
 
@@ -190,6 +195,7 @@ func createMap(spec *MapSpec, inner *internal.FD, handle *btf.Handle) (*Map, err
 		valueSize:  abi.ValueSize,
 		maxEntries: abi.MaxEntries,
 		flags:      abi.Flags,
+		numaNode:   spec.NumaNode,
 	}
 
 	if inner != nil {


Test patch:
diff --git a/internal/btf/btf_test.go b/internal/btf/btf_test.go
index 1dc2eda..39a547b 100644
--- a/internal/btf/btf_test.go
+++ b/internal/btf/btf_test.go
@@ -31,10 +31,39 @@ func TestParseVmlinux(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	_, err = loadNakedSpec(bytes.NewReader(buf), binary.LittleEndian, nil, nil)
+	spec, err := loadNakedSpec(bytes.NewReader(buf), binary.LittleEndian, nil, nil)
 	if err != nil {
 		t.Fatal("Can't load BTF:", err)
 	}
+
+	var iphdr Struct
+	err = spec.FindType("iphdr", &iphdr)
+	if err != nil {
+		t.Fatalf("unable to find `iphdr` struct: %s", err)
+	}
+	for _, m := range iphdr.Members {
+		if m.Name == "version" {
+			// __u8 is a typedef
+			td, ok := m.Type.(*Typedef)
+			if !ok {
+				t.Fatalf("version member of iphdr should be a __u8 typedef: actual: %T", m.Type)
+			}
+			u8int, ok := td.Type.(*Int)
+			if !ok {
+				t.Fatalf("__u8 typedef should point to an Int type: actual: %T", td.Type)
+			}
+			if u8int.Bits != 8 {
+				t.Fatalf("incorrect bit size of an __u8 int: expected: 8 actual: %d", u8int.Bits)
+			}
+			if u8int.Encoding != 0 {
+				t.Fatalf("incorrect encoding of an __u8 int: expected: 0 actual: %x", u8int.Encoding)
+			}
+			if u8int.Offset != 0 {
+				t.Fatalf("incorrect int offset of an __u8 int: expected: 0 actual: %d", u8int.Offset)
+			}
+			return
+		}
+	}
 }
 
 func TestParseCurrentKernelBTF(t *testing.T) {
diff --git a/run-tests.sh b/run-tests.sh
index 779b407..d48b848 100755
--- a/run-tests.sh
+++ b/run-tests.sh
@@ -22,7 +22,7 @@ if [[ "${1:-}" = "--in-vm" ]]; then
 
   echo Running tests...
   # TestLibBPFCompat runs separately to pass the "-elfs" flag only for it: https://github.com/cilium/ebpf/pull/119
-  /usr/local/bin/go test -v -elfs "$elfs" -run TestLibBPFCompat
+  /usr/local/bin/go test -v -run TestLibBPFCompat -elfs "$elfs"
   /usr/local/bin/go test -v ./...
   touch "$1/success"
   exit 0