Repository: cilium/ebpf
Pull request #129 (2020-08-26 16:37:55)

Rework ABI info Info structs, add some optional fields
This adds the Tag field to ProgramABI, which is useful to check whether two BPF programs are in fact the same bytecode.

lmb: cc @jsitnicki
lmb: `/proc` doesn't have the prog tag on 4.9, so I'll need to rework this a bit.
jsitnicki: LGTM. Will give it another look when you have the workaround for 4.9 ready.
lmb: This is ready for review now. I ended up tackling #33, which has been a long standing source of confusion. The downside is that this is an API breaking change, so I plan to tag a release before merging this so that users don't get broken immediately.

@nathanjsweet @tklauser both of you have done some thinking on this before, so I'd really appreciate your input.
lmb: This PR is now good to go, please take a look if you have some time.
jsitnicki: > One thing that worries me a bit are the pointer members in `type.MapInfo`/`type ProgramInfo` and that people might forget to check them for `nil` before accessing.

You could go with Null Object Pattern and return `MapInfo` with `Type` set to `MAP_PROG_TYPE_UNSPEC` instead.
lmb: > But maybe we could think about unexporting the pointer members and adding convenience getters/setters for them instead?

These are the signatures I could come up with:
* `func Tag(default string) string`: if there is no tag, return the default. Probably not very useful, since there isn't always a natural default, and it doesn't allow determining whether the value was present or not.
* `func Tag() (string, error)`: this is overkill, since there would only ever be a single error.
* `func Tag() (string, bool)`: this is actually quite nice, and allows ignoring the bool via `tag, _ := Tag()`. The user is forced to deal with the fact that tag might not be present.

I think I'll give the last option a try. It has the added benefit that we can avoid a bunch of allocations just for the sake of putting things behind a pointer.
lmb: Thank you both for your feedback, it was really valuable!

- For this PR, I've turned both Tag and Name into a plain `string`. It turns out that all kernels where we support getting `Info` (aka 4.10 and later) Tag is available. And for Name distinguishing between "has no name" and "kernel doesn't support names" doesn't seem useful.
- In the future, we should use the `func Thing() (type, bool)` for information that isn't always available.

Patch:
diff --git a/abi.go b/abi.go
deleted file mode 100644
index f86a17e..0000000
--- a/abi.go
+++ /dev/null
@@ -1,200 +0,0 @@
-package ebpf
-
-import (
-	"bufio"
-	"bytes"
-	"errors"
-	"fmt"
-	"io"
-	"os"
-	"syscall"
-
-	"github.com/cilium/ebpf/internal"
-)
-
-// MapABI are the attributes of a Map which are available across all supported kernels.
-type MapABI struct {
-	Type       MapType
-	KeySize    uint32
-	ValueSize  uint32
-	MaxEntries uint32
-	Flags      uint32
-}
-
-func newMapABIFromSpec(spec *MapSpec) *MapABI {
-	return &MapABI{
-		spec.Type,
-		spec.KeySize,
-		spec.ValueSize,
-		spec.MaxEntries,
-		spec.Flags,
-	}
-}
-
-func newMapABIFromFd(fd *internal.FD) (string, *MapABI, error) {
-	info, err := bpfGetMapInfoByFD(fd)
-	if err != nil {
-		if errors.Is(err, syscall.EINVAL) {
-			abi, err := newMapABIFromProc(fd)
-			return "", abi, err
-		}
-		return "", nil, err
-	}
-
-	return "", &MapABI{
-		MapType(info.mapType),
-		info.keySize,
-		info.valueSize,
-		info.maxEntries,
-		info.flags,
-	}, nil
-}
-
-func newMapABIFromProc(fd *internal.FD) (*MapABI, error) {
-	var abi MapABI
-	err := scanFdInfo(fd, map[string]interface{}{
-		"map_type":    &abi.Type,
-		"key_size":    &abi.KeySize,
-		"value_size":  &abi.ValueSize,
-		"max_entries": &abi.MaxEntries,
-		"map_flags":   &abi.Flags,
-	})
-	if err != nil {
-		return nil, err
-	}
-	return &abi, nil
-}
-
-// Equal returns true if two ABIs have the same values.
-func (abi *MapABI) Equal(other *MapABI) bool {
-	switch {
-	case abi.Type != other.Type:
-		return false
-	case abi.KeySize != other.KeySize:
-		return false
-	case abi.ValueSize != other.ValueSize:
-		return false
-	case abi.MaxEntries != other.MaxEntries:
-		return false
-	case abi.Flags != other.Flags:
-		return false
-	default:
-		return true
-	}
-}
-
-// ProgramABI are the attributes of a Program which are available across all supported kernels.
-type ProgramABI struct {
-	Type ProgramType
-}
-
-func newProgramABIFromFd(fd *internal.FD) (string, *ProgramABI, error) {
-	info, err := bpfGetProgInfoByFD(fd)
-	if err != nil {
-		if errors.Is(err, syscall.EINVAL) {
-			return newProgramABIFromProc(fd)
-		}
-
-		return "", nil, err
-	}
-
-	var name string
-	if bpfName := internal.CString(info.name[:]); bpfName != "" {
-		name = bpfName
-	} else {
-		name = internal.CString(info.tag[:])
-	}
-
-	return name, &ProgramABI{
-		Type: ProgramType(info.progType),
-	}, nil
-}
-
-func newProgramABIFromProc(fd *internal.FD) (string, *ProgramABI, error) {
-	var (
-		abi  ProgramABI
-		name string
-	)
-
-	err := scanFdInfo(fd, map[string]interface{}{
-		"prog_type": &abi.Type,
-		"prog_tag":  &name,
-	})
-	if errors.Is(err, errMissingFields) {
-		return "", nil, &internal.UnsupportedFeatureError{
-			Name:           "reading ABI from /proc/self/fdinfo",
-			MinimumVersion: internal.Version{4, 11, 0},
-		}
-	}
-	if err != nil {
-		return "", nil, err
-	}
-
-	return name, &abi, nil
-}
-
-func scanFdInfo(fd *internal.FD, fields map[string]interface{}) error {
-	raw, err := fd.Value()
-	if err != nil {
-		return err
-	}
-
-	fh, err := os.Open(fmt.Sprintf("/proc/self/fdinfo/%d", raw))
-	if err != nil {
-		return err
-	}
-	defer fh.Close()
-
-	if err := scanFdInfoReader(fh, fields); err != nil {
-		return fmt.Errorf("%s: %w", fh.Name(), err)
-	}
-	return nil
-}
-
-var errMissingFields = errors.New("missing fields")
-
-func scanFdInfoReader(r io.Reader, fields map[string]interface{}) error {
-	var (
-		scanner = bufio.NewScanner(r)
-		scanned int
-	)
-
-	for scanner.Scan() {
-		parts := bytes.SplitN(scanner.Bytes(), []byte("\t"), 2)
-		if len(parts) != 2 {
-			continue
-		}
-
-		name := bytes.TrimSuffix(parts[0], []byte(":"))
-		field, ok := fields[string(name)]
-		if !ok {
-			continue
-		}
-
-		if n, err := fmt.Fscanln(bytes.NewReader(parts[1]), field); err != nil || n != 1 {
-			return fmt.Errorf("can't parse field %s: %v", name, err)
-		}
-
-		scanned++
-	}
-
-	if err := scanner.Err(); err != nil {
-		return err
-	}
-
-	if scanned != len(fields) {
-		return errMissingFields
-	}
-
-	return nil
-}
-
-// Equal returns true if two ABIs have the same values.
-func (abi *ProgramABI) Equal(other *ProgramABI) bool {
-	switch {
-	case abi.Type != other.Type:
-		return false
-	default:
-		return true
-	}
-}
diff --git a/info.go b/info.go
new file mode 100644
index 0000000..588dfab
--- /dev/null
+++ b/info.go
@@ -0,0 +1,162 @@
+package ebpf
+
+import (
+	"bufio"
+	"encoding/hex"
+	"errors"
+	"fmt"
+	"io"
+	"os"
+	"strings"
+	"syscall"
+
+	"github.com/cilium/ebpf/internal"
+)
+
+// MapInfo describes a map.
+type MapInfo struct {
+	Type       MapType
+	KeySize    uint32
+	ValueSize  uint32
+	MaxEntries uint32
+	Flags      uint32
+	// Name as supplied by user space at load time.
+	Name string
+}
+
+func newMapInfoFromFd(fd *internal.FD) (*MapInfo, error) {
+	info, err := bpfGetMapInfoByFD(fd)
+	if errors.Is(err, syscall.EINVAL) {
+		return newMapInfoFromProc(fd)
+	}
+	if err != nil {
+		return nil, err
+	}
+
+	return &MapInfo{
+		MapType(info.map_type),
+		info.key_size,
+		info.value_size,
+		info.max_entries,
+		info.map_flags,
+		// name is available from 4.15.
+		internal.CString(info.name[:]),
+	}, nil
+}
+
+func newMapInfoFromProc(fd *internal.FD) (*MapInfo, error) {
+	var mi MapInfo
+	err := scanFdInfo(fd, map[string]interface{}{
+		"map_type":    &mi.Type,
+		"key_size":    &mi.KeySize,
+		"value_size":  &mi.ValueSize,
+		"max_entries": &mi.MaxEntries,
+		"map_flags":   &mi.Flags,
+	})
+	if err != nil {
+		return nil, err
+	}
+	return &mi, nil
+}
+
+// ProgramInfo describes a program.
+type ProgramInfo struct {
+	Type ProgramType
+	// Truncated hash of the BPF bytecode.
+	Tag string
+	// Name as supplied by user space at load time.
+	Name string
+}
+
+func newProgramInfoFromFd(fd *internal.FD) (*ProgramInfo, error) {
+	info, err := bpfGetProgInfoByFD(fd)
+	if errors.Is(err, syscall.EINVAL) {
+		return newProgramInfoFromProc(fd)
+	}
+	if err != nil {
+		return nil, err
+	}
+
+	return &ProgramInfo{
+		ProgramType(info.prog_type),
+		// tag is available if the kernel supports BPF_PROG_GET_INFO_BY_FD.
+		hex.EncodeToString(info.tag[:]),
+		// name is available from 4.15.
+		internal.CString(info.name[:]),
+	}, nil
+}
+
+func newProgramInfoFromProc(fd *internal.FD) (*ProgramInfo, error) {
+	var info ProgramInfo
+	err := scanFdInfo(fd, map[string]interface{}{
+		"prog_type": &info.Type,
+		"prog_tag":  &info.Tag,
+	})
+	if errors.Is(err, errMissingFields) {
+		return nil, &internal.UnsupportedFeatureError{
+			Name:           "reading program info from /proc/self/fdinfo",
+			MinimumVersion: internal.Version{4, 10, 0},
+		}
+	}
+	if err != nil {
+		return nil, err
+	}
+
+	return &info, nil
+}
+
+func scanFdInfo(fd *internal.FD, fields map[string]interface{}) error {
+	raw, err := fd.Value()
+	if err != nil {
+		return err
+	}
+
+	fh, err := os.Open(fmt.Sprintf("/proc/self/fdinfo/%d", raw))
+	if err != nil {
+		return err
+	}
+	defer fh.Close()
+
+	if err := scanFdInfoReader(fh, fields); err != nil {
+		return fmt.Errorf("%s: %w", fh.Name(), err)
+	}
+	return nil
+}
+
+var errMissingFields = errors.New("missing fields")
+
+func scanFdInfoReader(r io.Reader, fields map[string]interface{}) error {
+	var (
+		scanner = bufio.NewScanner(r)
+		scanned int
+	)
+
+	for scanner.Scan() {
+		parts := strings.SplitN(scanner.Text(), "\t", 2)
+		if len(parts) != 2 {
+			continue
+		}
+
+		name := strings.TrimSuffix(parts[0], ":")
+		field, ok := fields[string(name)]
+		if !ok {
+			continue
+		}
+
+		if n, err := fmt.Sscanln(parts[1], field); err != nil || n != 1 {
+			return fmt.Errorf("can't parse field %s: %v", name, err)
+		}
+
+		scanned++
+	}
+
+	if err := scanner.Err(); err != nil {
+		return err
+	}
+
+	if scanned != len(fields) {
+		return errMissingFields
+	}
+
+	return nil
+}
diff --git a/map.go b/map.go
index ddfd970..71bae94 100644
--- a/map.go
+++ b/map.go
@@ -90,20 +90,20 @@ type MapKV struct {
 
 func (ms *MapSpec) checkCompatibility(m *Map) error {
 	switch {
-	case m.abi.Type != ms.Type:
-		return fmt.Errorf("expected type %v, got %v", ms.Type, m.abi.Type)
+	case m.typ != ms.Type:
+		return fmt.Errorf("expected type %v, got %v", ms.Type, m.typ)
 
-	case m.abi.KeySize != ms.KeySize:
-		return fmt.Errorf("expected key size %v, got %v", ms.KeySize, m.abi.KeySize)
+	case m.keySize != ms.KeySize:
+		return fmt.Errorf("expected key size %v, got %v", ms.KeySize, m.keySize)
 
-	case m.abi.ValueSize != ms.ValueSize:
-		return fmt.Errorf("expected value size %v, got %v", ms.ValueSize, m.abi.ValueSize)
+	case m.valueSize != ms.ValueSize:
+		return fmt.Errorf("expected value size %v, got %v", ms.ValueSize, m.valueSize)
 
-	case m.abi.MaxEntries != ms.MaxEntries:
-		return fmt.Errorf("expected max entries %v, got %v", ms.MaxEntries, m.abi.MaxEntries)
+	case m.maxEntries != ms.MaxEntries:
+		return fmt.Errorf("expected max entries %v, got %v", ms.MaxEntries, m.maxEntries)
 
-	case m.abi.Flags != ms.Flags:
-		return fmt.Errorf("expected flags %v, got %v", ms.Flags, m.abi.Flags)
+	case m.flags != ms.Flags:
+		return fmt.Errorf("expected flags %v, got %v", ms.Flags, m.flags)
 	}
 	return nil
 }
@@ -118,9 +118,13 @@ func (ms *MapSpec) checkCompatibility(m *Map) error {
 // Implement encoding.BinaryMarshaler or encoding.BinaryUnmarshaler
 // if you require custom encoding.
 type Map struct {
-	name string
-	fd   *internal.FD
-	abi  MapABI
+	name       string
+	fd         *internal.FD
+	typ        MapType
+	keySize    uint32
+	valueSize  uint32
+	maxEntries uint32
+	flags      uint32
 	// Per CPU maps return values larger than the size in the spec
 	fullValueSize int
 }
@@ -132,14 +136,18 @@ func NewMapFromFD(fd int) (*Map, error) {
 	if fd < 0 {
 		return nil, errors.New("invalid fd")
 	}
-	bpfFd := internal.NewFD(uint32(fd))
 
-	name, abi, err := newMapABIFromFd(bpfFd)
+	return newMapFromFD(internal.NewFD(uint32(fd)))
+}
+
+func newMapFromFD(fd *internal.FD) (*Map, error) {
+	info, err := newMapInfoFromFd(fd)
 	if err != nil {
-		bpfFd.Forget()
-		return nil, err
+		fd.Close()
+		return nil, fmt.Errorf("get map info: %s", err)
 	}
-	return newMap(bpfFd, name, abi)
+
+	return newMap(fd, info.Name, info.Type, info.KeySize, info.ValueSize, info.MaxEntries, info.Flags)
 }
 
 // NewMap creates a new Map.
@@ -236,7 +244,7 @@ func createMap(spec *MapSpec, inner *internal.FD, handle *btf.Handle, opts MapOp
 		}
 	}
 
-	abi := newMapABIFromSpec(spec)
+	spec = spec.Copy()
 
 	switch spec.Type {
 	case ArrayOfMaps:
@@ -246,43 +254,43 @@ func createMap(spec *MapSpec, inner *internal.FD, handle *btf.Handle, opts MapOp
 			return nil, err
 		}
 
-		if abi.ValueSize != 0 && abi.ValueSize != 4 {
+		if spec.ValueSize != 0 && spec.ValueSize != 4 {
 			return nil, errors.New("ValueSize must be zero or four for map of map")
 		}
-		abi.ValueSize = 4
+		spec.ValueSize = 4
 
 	case PerfEventArray:
-		if abi.KeySize != 0 && abi.KeySize != 4 {
+		if spec.KeySize != 0 && spec.KeySize != 4 {
 			return nil, errors.New("KeySize must be zero or four for perf event array")
 		}
-		abi.KeySize = 4
+		spec.KeySize = 4
 
-		if abi.ValueSize != 0 && abi.ValueSize != 4 {
+		if spec.ValueSize != 0 && spec.ValueSize != 4 {
 			return nil, errors.New("ValueSize must be zero or four for perf event array")
 		}
-		abi.ValueSize = 4
+		spec.ValueSize = 4
 
-		if abi.MaxEntries == 0 {
+		if spec.MaxEntries == 0 {
 			n, err := internal.PossibleCPUs()
 			if err != nil {
 				return nil, fmt.Errorf("perf event array: %w", err)
 			}
-			abi.MaxEntries = uint32(n)
+			spec.MaxEntries = uint32(n)
 		}
 	}
 
-	if abi.Flags&(unix.BPF_F_RDONLY_PROG|unix.BPF_F_WRONLY_PROG) > 0 || spec.Freeze {
+	if spec.Flags&(unix.BPF_F_RDONLY_PROG|unix.BPF_F_WRONLY_PROG) > 0 || spec.Freeze {
 		if err := haveMapMutabilityModifiers(); err != nil {
 			return nil, fmt.Errorf("map create: %w", err)
 		}
 	}
 
 	attr := bpfMapCreateAttr{
-		mapType:    abi.Type,
-		keySize:    abi.KeySize,
-		valueSize:  abi.ValueSize,
-		maxEntries: abi.MaxEntries,
-		flags:      abi.Flags,
+		mapType:    spec.Type,
+		keySize:    spec.KeySize,
+		valueSize:  spec.ValueSize,
+		maxEntries: spec.MaxEntries,
+		flags:      spec.Flags,
 		numaNode:   spec.NumaNode,
 	}
 
@@ -313,7 +321,7 @@ func createMap(spec *MapSpec, inner *internal.FD, handle *btf.Handle, opts MapOp
 	}
 	defer closeOnError(fd)
 
-	m, err := newMap(fd, spec.Name, abi)
+	m, err := newMap(fd, spec.Name, spec.Type, spec.KeySize, spec.ValueSize, spec.MaxEntries, spec.Flags)
 	if err != nil {
 		return nil, err
 	}
@@ -331,15 +339,19 @@ func createMap(spec *MapSpec, inner *internal.FD, handle *btf.Handle, opts MapOp
 	return m, nil
 }
 
-func newMap(fd *internal.FD, name string, abi *MapABI) (*Map, error) {
+func newMap(fd *internal.FD, name string, typ MapType, keySize, valueSize, maxEntries, flags uint32) (*Map, error) {
 	m := &Map{
 		name,
 		fd,
-		*abi,
-		int(abi.ValueSize),
+		typ,
+		keySize,
+		valueSize,
+		maxEntries,
+		flags,
+		int(valueSize),
 	}
 
-	if !abi.Type.hasPerCPUValue() {
+	if !typ.hasPerCPUValue() {
 		return m, nil
 	}
 
@@ -348,47 +360,45 @@ func newMap(fd *internal.FD, name string, abi *MapABI) (*Map, error) {
 		return nil, err
 	}
 
-	m.fullValueSize = align(int(abi.ValueSize), 8) * possibleCPUs
+	m.fullValueSize = align(int(valueSize), 8) * possibleCPUs
 	return m, nil
 }
 
 func (m *Map) String() string {
 	if m.name != "" {
-		return fmt.Sprintf("%s(%s)#%v", m.abi.Type, m.name, m.fd)
+		return fmt.Sprintf("%s(%s)#%v", m.typ, m.name, m.fd)
 	}
-	return fmt.Sprintf("%s#%v", m.abi.Type, m.fd)
+	return fmt.Sprintf("%s#%v", m.typ, m.fd)
 }
 
 // Type returns the underlying type of the map.
 func (m *Map) Type() MapType {
-	return m.abi.Type
+	return m.typ
 }
 
 // KeySize returns the size of the map key in bytes.
 func (m *Map) KeySize() uint32 {
-	return m.abi.KeySize
+	return m.keySize
 }
 
 // ValueSize returns the size of the map value in bytes.
 func (m *Map) ValueSize() uint32 {
-	return m.abi.ValueSize
+	return m.valueSize
 }
 
 // MaxEntries returns the maximum number of elements the map can hold.
 func (m *Map) MaxEntries() uint32 {
-	return m.abi.MaxEntries
+	return m.maxEntries
 }
 
 // Flags returns the flags of the map.
 func (m *Map) Flags() uint32 {
-	return m.abi.Flags
+	return m.flags
 }
 
-// ABI gets the ABI of the Map.
-//
-// Deprecated: use Type, KeySize, ValueSize, MaxEntries and Flags instead.
-func (m *Map) ABI() MapABI {
-	return m.abi
+// Info returns metadata about the map.
+func (m *Map) Info() (*MapInfo, error) {
+	return newMapInfoFromFd(m.fd)
 }
 
 // Lookup retrieves a value from a Map.
@@ -408,8 +418,8 @@ func (m *Map) Lookup(key, valueOut interface{}) error {
 		return nil
 	}
 
-	if m.abi.Type.hasPerCPUValue() {
-		return unmarshalPerCPUValue(valueOut, int(m.abi.ValueSize), valueBytes)
+	if m.typ.hasPerCPUValue() {
+		return unmarshalPerCPUValue(valueOut, int(m.valueSize), valueBytes)
 	}
 
 	switch value := valueOut.(type) {
@@ -452,7 +462,7 @@ func (m *Map) Lookup(key, valueOut interface{}) error {
 func (m *Map) LookupAndDelete(key, valueOut interface{}) error {
 	valuePtr, valueBytes := makeBuffer(valueOut, m.fullValueSize)
 
-	keyPtr, err := marshalPtr(key, int(m.abi.KeySize))
+	keyPtr, err := marshalPtr(key, int(m.keySize))
 	if err != nil {
 		return fmt.Errorf("can't marshal key: %w", err)
 	}
@@ -480,7 +490,7 @@ func (m *Map) LookupBytes(key interface{}) ([]byte, error) {
 }
 
 func (m *Map) lookup(key interface{}, valueOut internal.Pointer) error {
-	keyPtr, err := marshalPtr(key, int(m.abi.KeySize))
+	keyPtr, err := marshalPtr(key, int(m.keySize))
 	if err != nil {
 		return fmt.Errorf("can't marshal key: %w", err)
 	}
@@ -514,16 +524,16 @@ func (m *Map) Put(key, value interface{}) error {
 
 // Update changes the value of a key.
 func (m *Map) Update(key, value interface{}, flags MapUpdateFlags) error {
-	keyPtr, err := marshalPtr(key, int(m.abi.KeySize))
+	keyPtr, err := marshalPtr(key, int(m.keySize))
 	if err != nil {
 		return fmt.Errorf("can't marshal key: %w", err)
 	}
 
 	var valuePtr internal.Pointer
-	if m.abi.Type.hasPerCPUValue() {
-		valuePtr, err = marshalPerCPUValue(value, int(m.abi.ValueSize))
+	if m.typ.hasPerCPUValue() {
+		valuePtr, err = marshalPerCPUValue(value, int(m.valueSize))
 	} else {
-		valuePtr, err = marshalPtr(value, int(m.abi.ValueSize))
+		valuePtr, err = marshalPtr(value, int(m.valueSize))
 	}
 	if err != nil {
 		return fmt.Errorf("can't marshal value: %w", err)
@@ -540,7 +550,7 @@ func (m *Map) Update(key, value interface{}, flags MapUpdateFlags) error {
 //
 // Returns ErrKeyNotExist if the key does not exist.
 func (m *Map) Delete(key interface{}) error {
-	keyPtr, err := marshalPtr(key, int(m.abi.KeySize))
+	keyPtr, err := marshalPtr(key, int(m.keySize))
 	if err != nil {
 		return fmt.Errorf("can't marshal key: %w", err)
 	}
@@ -557,7 +567,7 @@ func (m *Map) Delete(key interface{}) error {
 //
 // Returns ErrKeyNotExist if there is no next key.
 func (m *Map) NextKey(key, nextKeyOut interface{}) error {
-	nextKeyPtr, nextKeyBytes := makeBuffer(nextKeyOut, int(m.abi.KeySize))
+	nextKeyPtr, nextKeyBytes := makeBuffer(nextKeyOut, int(m.keySize))
 
 	if err := m.nextKey(key, nextKeyPtr); err != nil {
 		return err
@@ -581,7 +591,7 @@ func (m *Map) NextKey(key, nextKeyOut interface{}) error {
 //
 // Returns nil if there are no more keys.
 func (m *Map) NextKeyBytes(key interface{}) ([]byte, error) {
-	nextKey := make([]byte, m.abi.KeySize)
+	nextKey := make([]byte, m.keySize)
 	nextKeyPtr := internal.NewSlicePointer(nextKey)
 
 	err := m.nextKey(key, nextKeyPtr)
@@ -599,7 +609,7 @@ func (m *Map) nextKey(key interface{}, nextKeyOut internal.Pointer) error {
 	)
 
 	if key != nil {
-		keyPtr, err = marshalPtr(key, int(m.abi.KeySize))
+		keyPtr, err = marshalPtr(key, int(m.keySize))
 		if err != nil {
 			return fmt.Errorf("can't marshal key: %w", err)
 		}
@@ -662,7 +672,16 @@ func (m *Map) Clone() (*Map, error) {
 		return nil, fmt.Errorf("can't clone map: %w", err)
 	}
 
-	return newMap(dup, m.name, &m.abi)
+	return &Map{
+		m.name,
+		dup,
+		m.typ,
+		m.keySize,
+		m.valueSize,
+		m.maxEntries,
+		m.flags,
+		m.fullValueSize,
+	}, nil
 }
 
 // Pin persists the map past the lifetime of the process that created it.
@@ -696,29 +715,13 @@ func (m *Map) populate(contents []MapKV) error {
 }
 
 // LoadPinnedMap load a Map from a BPF file.
-//
-// The function is not compatible with nested maps.
-// Use LoadPinnedMapExplicit in these situations.
 func LoadPinnedMap(fileName string) (*Map, error) {
 	fd, err := internal.BPFObjGet(fileName)
 	if err != nil {
 		return nil, err
 	}
-	name, abi, err := newMapABIFromFd(fd)
-	if err != nil {
-		_ = fd.Close()
-		return nil, err
-	}
-	return newMap(fd, name, abi)
-}
 
-// LoadPinnedMapExplicit loads a map with explicit parameters.
-func LoadPinnedMapExplicit(fileName string, abi *MapABI) (*Map, error) {
-	fd, err := internal.BPFObjGet(fileName)
-	if err != nil {
-		return nil, err
-	}
-	return newMap(fd, "", abi)
+	return newMapFromFD(fd)
 }
 
 func unmarshalMap(buf []byte) (*Map, error) {
@@ -813,8 +816,8 @@ type MapIterator struct {
 func newMapIterator(target *Map) *MapIterator {
 	return &MapIterator{
 		target:     target,
-		maxEntries: target.abi.MaxEntries,
-		prevBytes:  make([]byte, int(target.abi.KeySize)),
+		maxEntries: target.maxEntries,
+		prevBytes:  make([]byte, target.keySize),
 	}
 }
 
@@ -902,13 +905,7 @@ func NewMapFromID(id MapID) (*Map, error) {
 		return nil, err
 	}
 
-	name, abi, err := newMapABIFromFd(fd)
-	if err != nil {
-		_ = fd.Close()
-		return nil, err
-	}
-
-	return newMap(fd, name, abi)
+	return newMapFromFD(fd)
 }
 
 // ID returns the systemwide unique ID of the map.
diff --git a/prog.go b/prog.go
index c3c14e6..54a5ba4 100644
--- a/prog.go
+++ b/prog.go
@@ -6,6 +6,7 @@ import (
 	"errors"
 	"fmt"
 	"math"
+	"path/filepath"
 	"strings"
 	"time"
 
@@ -95,10 +96,9 @@ type Program struct {
 	// otherwise it is empty.
 	VerifierLog string
 
-	fd         *internal.FD
-	name       string
-	abi        ProgramABI
-	attachType AttachType
+	fd   *internal.FD
+	name string
+	typ  ProgramType
 }
 
 // NewProgram creates a new Program.
@@ -147,9 +147,7 @@ func newProgramWithBTF(spec *ProgramSpec, btf *btf.Handle, opts ProgramOptions)
 
 	fd, err := bpfProgLoad(attr)
 	if err == nil {
-		prog := newProgram(fd, spec.Name, &ProgramABI{spec.Type})
-		prog.VerifierLog = internal.CString(logBuf)
-		return prog, nil
+		return &Program{internal.CString(logBuf), fd, spec.Name, spec.Type}, nil
 	}
 
 	logErr := err
@@ -177,28 +175,35 @@ func newProgramWithBTF(spec *ProgramSpec, btf *btf.Handle, opts ProgramOptions)
 //
 // You should not use fd after calling this function.
 //
-// Requires at least Linux 4.11.
+// Requires at least Linux 4.10.
 func NewProgramFromFD(fd int) (*Program, error) {
 	if fd < 0 {
 		return nil, errors.New("invalid fd")
 	}
-	bpfFd := internal.NewFD(uint32(fd))
 
-	name, abi, err := newProgramABIFromFd(bpfFd)
+	return newProgramFromFD(internal.NewFD(uint32(fd)))
+}
+
+// NewProgramFromID returns the program for a given id.
+//
+// Returns ErrNotExist, if there is no eBPF program with the given id.
+func NewProgramFromID(id ProgramID) (*Program, error) {
+	fd, err := bpfObjGetFDByID(internal.BPF_PROG_GET_FD_BY_ID, uint32(id))
 	if err != nil {
-		bpfFd.Forget()
-		return nil, err
+		return nil, fmt.Errorf("get program by id: %w", err)
 	}
 
-	return newProgram(bpfFd, name, abi), nil
+	return newProgramFromFD(fd)
 }
 
-func newProgram(fd *internal.FD, name string, abi *ProgramABI) *Program {
-	return &Program{
-		name: name,
-		fd:   fd,
-		abi:  *abi,
+func newProgramFromFD(fd *internal.FD) (*Program, error) {
+	info, err := newProgramInfoFromFd(fd)
+	if err != nil {
+		fd.Close()
+		return nil, fmt.Errorf("discover program type: %w", err)
 	}
+
+	return &Program{"", fd, "", info.Type}, nil
 }
 
 func convertProgramSpec(spec *ProgramSpec, handle *btf.Handle) (*bpfProgLoadAttr, error) {
@@ -277,21 +282,21 @@ func convertProgramSpec(spec *ProgramSpec, handle *btf.Handle) (*bpfProgLoadAttr
 
 func (p *Program) String() string {
 	if p.name != "" {
-		return fmt.Sprintf("%s(%s)#%v", p.abi.Type, p.name, p.fd)
+		return fmt.Sprintf("%s(%s)#%v", p.typ, p.name, p.fd)
 	}
-	return fmt.Sprintf("%s#%v", p.abi.Type, p.fd)
+	return fmt.Sprintf("%s(%v)", p.typ, p.fd)
 }
 
 // Type returns the underlying type of the program.
 func (p *Program) Type() ProgramType {
-	return p.abi.Type
+	return p.typ
 }
 
-// ABI gets the ABI of the Program.
+// Info returns metadata about the program.
 //
-// Deprecated: use Type instead.
-func (p *Program) ABI() ProgramABI {
-	return p.abi
+// Requires at least 4.10.
+func (p *Program) Info() (*ProgramInfo, error) {
+	return newProgramInfoFromFd(p.fd)
 }
 
 // FD gets the file descriptor of the Program.
@@ -323,7 +328,7 @@ func (p *Program) Clone() (*Program, error) {
 		return nil, fmt.Errorf("can't clone program: %w", err)
 	}
 
-	return newProgram(dup, p.name, &p.abi), nil
+	return &Program{p.VerifierLog, dup, p.name, p.typ}, nil
 }
 
 // Pin persists the Program past the lifetime of the process that created it
@@ -555,13 +560,13 @@ func LoadPinnedProgram(fileName string) (*Program, error) {
 		return nil, err
 	}
 
-	name, abi, err := newProgramABIFromFd(fd)
+	info, err := newProgramInfoFromFd(fd)
 	if err != nil {
 		_ = fd.Close()
-		return nil, fmt.Errorf("can't get ABI for %s: %w", fileName, err)
+		return nil, fmt.Errorf("info for %s: %w", fileName, err)
 	}
 
-	return newProgram(fd, name, abi), nil
+	return &Program{"", fd, filepath.Base(fileName), info.Type}, nil
 }
 
 // SanitizeName replaces all invalid characters in name.
@@ -588,24 +593,6 @@ func ProgramGetNextID(startID ProgramID) (ProgramID, error) {
 	return ProgramID(id), err
 }
 
-// NewProgramFromID returns the program for a given id.
-//
-// Returns ErrNotExist, if there is no eBPF program with the given id.
-func NewProgramFromID(id ProgramID) (*Program, error) {
-	fd, err := bpfObjGetFDByID(internal.BPF_PROG_GET_FD_BY_ID, uint32(id))
-	if err != nil {
-		return nil, err
-	}
-
-	name, abi, err := newProgramABIFromFd(fd)
-	if err != nil {
-		_ = fd.Close()
-		return nil, err
-	}
-
-	return newProgram(fd, name, abi), nil
-}
-
 // ID returns the systemwide unique ID of the program.
 func (p *Program) ID() (ProgramID, error) {
 	info, err := bpfGetProgInfoByFD(p.fd)
diff --git a/syscalls.go b/syscalls.go
index 494af99..277e83b 100644
--- a/syscalls.go
+++ b/syscalls.go
@@ -69,13 +69,20 @@ type bpfMapOpAttr struct {
 }
 
 type bpfMapInfo struct {
-	mapType    uint32
-	id         uint32
-	keySize    uint32
-	valueSize  uint32
-	maxEntries uint32
-	flags      uint32
-	mapName    bpfObjName // since 4.15 ad5b177bd73f
+	map_type                  uint32 // since 4.12 1e2709769086
+	id                        uint32
+	key_size                  uint32
+	value_size                uint32
+	max_entries               uint32
+	map_flags                 uint32
+	name                      bpfObjName // since 4.15 ad5b177bd73f
+	ifindex                   uint32     // since 4.16 52775b33bb50
+	btf_vmlinux_value_type_id uint32     // since 5.6  85d33df357b6
+	netns_dev                 uint64     // since 4.16 52775b33bb50
+	netns_ino                 uint64
+	btf_id                    uint32 // since 4.18 78958fca7ead
+	btf_key_type_id           uint32 // since 4.18 9b2cf328b2ec
+	btf_value_type_id         uint32
 }
 
 type bpfProgLoadAttr struct {
@@ -103,18 +110,40 @@ type bpfProgLoadAttr struct {
 }
 
 type bpfProgInfo struct {
-	progType     uint32
-	id           uint32
-	tag          [unix.BPF_TAG_SIZE]byte
-	jitedLen     uint32
-	xlatedLen    uint32
-	jited        internal.Pointer
-	xlated       internal.Pointer
-	loadTime     uint64 // since 4.15 cb4d2b3f03d8
-	createdByUID uint32
-	nrMapIDs     uint32
-	mapIds       internal.Pointer
-	name         bpfObjName
+	prog_type                uint32
+	id                       uint32
+	tag                      [unix.BPF_TAG_SIZE]byte
+	jited_prog_len           uint32
+	xlated_prog_len          uint32
+	jited_prog_insns         internal.Pointer
+	xlated_prog_insns        internal.Pointer
+	load_time                uint64 // since 4.15 cb4d2b3f03d8
+	created_by_uid           uint32
+	nr_map_ids               uint32
+	map_ids                  internal.Pointer
+	name                     bpfObjName // since 4.15 067cae47771c
+	ifindex                  uint32
+	gpl_compatible           uint32
+	netns_dev                uint64
+	netns_ino                uint64
+	nr_jited_ksyms           uint32
+	nr_jited_func_lens       uint32
+	jited_ksyms              internal.Pointer
+	jited_func_lens          internal.Pointer
+	btf_id                   uint32
+	func_info_rec_size       uint32
+	func_info                internal.Pointer
+	nr_func_info             uint32
+	nr_line_info             uint32
+	line_info                internal.Pointer
+	jited_line_info          internal.Pointer
+	nr_jited_line_info       uint32
+	line_info_rec_size       uint32
+	jited_line_info_rec_size uint32
+	nr_prog_tags             uint32
+	prog_tags                internal.Pointer
+	run_time_ns              uint64
+	run_cnt                  uint64
 }
 
 type bpfProgTestRunAttr struct {


Test patch:
diff --git a/abi_test.go b/abi_test.go
deleted file mode 100644
index 3421e31..0000000
--- a/abi_test.go
+++ /dev/null
@@ -1,98 +0,0 @@
-package ebpf
-
-import (
-	"testing"
-
-	"github.com/cilium/ebpf/internal/testutils"
-)
-
-func TestMapABIEqual(t *testing.T) {
-	abi := &MapABI{
-		Type:       Array,
-		KeySize:    4,
-		ValueSize:  2,
-		MaxEntries: 3,
-		Flags:      1,
-	}
-
-	if !abi.Equal(abi) {
-		t.Error("Equal returns true when comparing an ABI to itself")
-	}
-
-	if abi.Equal(&MapABI{}) {
-		t.Error("Equal returns true for different ABIs")
-	}
-}
-
-func TestMapABIFromProc(t *testing.T) {
-	hash, err := NewMap(&MapSpec{
-		Type:       Hash,
-		KeySize:    4,
-		ValueSize:  5,
-		MaxEntries: 2,
-		Flags:      0x1, // BPF_F_NO_PREALLOC
-	})
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer hash.Close()
-
-	abi, err := newMapABIFromProc(hash.fd)
-	if err != nil {
-		t.Fatal("Can't get map ABI:", err)
-	}
-
-	if abi.Type != Hash {
-		t.Error("Expected Hash, got", abi.Type)
-	}
-
-	if abi.KeySize != 4 {
-		t.Error("Expected KeySize of 4, got", abi.KeySize)
-	}
-
-	if abi.ValueSize != 5 {
-		t.Error("Expected ValueSize of 5, got", abi.ValueSize)
-	}
-
-	if abi.MaxEntries != 2 {
-		t.Error("Expected MaxEntries of 2, got", abi.MaxEntries)
-	}
-
-	if abi.Flags != 1 {
-		t.Error("Expected Flags to be 1, got", abi.Flags)
-	}
-
-	nested, err := NewMap(&MapSpec{
-		Type:       ArrayOfMaps,
-		KeySize:    4,
-		MaxEntries: 2,
-		InnerMap: &MapSpec{
-			Type:       Array,
-			KeySize:    4,
-			ValueSize:  4,
-			MaxEntries: 2,
-		},
-	})
-	testutils.SkipIfNotSupported(t, err)
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer nested.Close()
-
-	_, err = newMapABIFromProc(nested.fd)
-	if err != nil {
-		t.Fatal("Can't get nested map ABI from /proc:", err)
-	}
-}
-
-func TestProgramABI(t *testing.T) {
-	abi := &ProgramABI{Type: SocketFilter}
-
-	if !abi.Equal(abi) {
-		t.Error("Equal returns true when comparing an ABI to itself")
-	}
-
-	if abi.Equal(&ProgramABI{}) {
-		t.Error("Equal returns true for different ABIs")
-	}
-}
diff --git a/info_test.go b/info_test.go
new file mode 100644
index 0000000..8aad146
--- /dev/null
+++ b/info_test.go
@@ -0,0 +1,130 @@
+package ebpf
+
+import (
+	"strings"
+	"testing"
+
+	"github.com/cilium/ebpf/internal"
+	"github.com/cilium/ebpf/internal/testutils"
+)
+
+func TestMapInfoFromProc(t *testing.T) {
+	hash, err := NewMap(&MapSpec{
+		Name:       "testing",
+		Type:       Hash,
+		KeySize:    4,
+		ValueSize:  5,
+		MaxEntries: 2,
+		Flags:      0x1, // BPF_F_NO_PREALLOC
+	})
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer hash.Close()
+
+	info, err := newMapInfoFromProc(hash.fd)
+	if err != nil {
+		t.Fatal("Can't get map info:", err)
+	}
+
+	if info.Type != Hash {
+		t.Error("Expected Hash, got", info.Type)
+	}
+
+	if info.KeySize != 4 {
+		t.Error("Expected KeySize of 4, got", info.KeySize)
+	}
+
+	if info.ValueSize != 5 {
+		t.Error("Expected ValueSize of 5, got", info.ValueSize)
+	}
+
+	if info.MaxEntries != 2 {
+		t.Error("Expected MaxEntries of 2, got", info.MaxEntries)
+	}
+
+	if info.Flags != 1 {
+		t.Error("Expected Flags to be 1, got", info.Flags)
+	}
+
+	if info.Name != "" && info.Name != "testing" {
+		t.Error("Expected name to be testing, got", info.Name)
+	}
+
+	nested, err := NewMap(&MapSpec{
+		Type:       ArrayOfMaps,
+		KeySize:    4,
+		MaxEntries: 2,
+		InnerMap: &MapSpec{
+			Type:       Array,
+			KeySize:    4,
+			ValueSize:  4,
+			MaxEntries: 2,
+		},
+	})
+	testutils.SkipIfNotSupported(t, err)
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer nested.Close()
+
+	_, err = newMapInfoFromProc(nested.fd)
+	if err != nil {
+		t.Fatal("Can't get nested map info from /proc:", err)
+	}
+}
+
+func TestProgramInfo(t *testing.T) {
+	prog := createSocketFilter(t)
+	defer prog.Close()
+
+	for name, fn := range map[string]func(*internal.FD) (*ProgramInfo, error){
+		"generic": newProgramInfoFromFd,
+		"proc":    newProgramInfoFromProc,
+	} {
+		t.Run(name, func(t *testing.T) {
+			info, err := fn(prog.fd)
+			testutils.SkipIfNotSupported(t, err)
+			if err != nil {
+				t.Fatal("Can't get program info:", err)
+			}
+
+			if info.Type != SocketFilter {
+				t.Error("Expected Type to be SocketFilter, got", info.Type)
+			}
+
+			if info.Name != "" && info.Name != "test" {
+				t.Error("Expected Name to be test, got", info.Name)
+			}
+
+			if want := "d7edec644f05498d"; info.Tag != want {
+				t.Errorf("Expected Tag to be %s, got %s", want, info.Tag)
+			}
+		})
+	}
+}
+
+func TestScanFdInfoReader(t *testing.T) {
+	tests := []struct {
+		fields map[string]interface{}
+		valid  bool
+	}{
+		{nil, true},
+		{map[string]interface{}{"foo": new(string)}, true},
+		{map[string]interface{}{"zap": new(string)}, false},
+		{map[string]interface{}{"foo": new(int)}, false},
+	}
+
+	for _, test := range tests {
+		err := scanFdInfoReader(strings.NewReader("foo:\tbar\n"), test.fields)
+		if test.valid {
+			if err != nil {
+				t.Errorf("fields %v returns an error: %s", test.fields, err)
+			}
+		} else {
+			if err == nil {
+				t.Errorf("fields %v doesn't return an error", test.fields)
+			}
+		}
+	}
+}
diff --git a/map_test.go b/map_test.go
index 3909db5..e239624 100644
--- a/map_test.go
+++ b/map_test.go
@@ -3,6 +3,7 @@ package ebpf
 import (
 	"errors"
 	"fmt"
+	"io/ioutil"
 	"math"
 	"os"
 	"path/filepath"
@@ -128,6 +129,44 @@ func TestMapPin(t *testing.T) {
 	}
 }
 
+func TestNestedMapPin(t *testing.T) {
+	m, err := NewMap(&MapSpec{
+		Type:       ArrayOfMaps,
+		KeySize:    4,
+		ValueSize:  4,
+		MaxEntries: 2,
+		InnerMap: &MapSpec{
+			Type:       Array,
+			KeySize:    4,
+			ValueSize:  4,
+			MaxEntries: 1,
+		},
+	})
+	testutils.SkipIfNotSupported(t, err)
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer m.Close()
+
+	tmp, err := ioutil.TempDir("/sys/fs/bpf", "ebpf-test")
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer os.RemoveAll(tmp)
+
+	path := filepath.Join(tmp, "nested")
+	if err := m.Pin(path); err != nil {
+		t.Fatal(err)
+	}
+	m.Close()
+
+	m, err = LoadPinnedMap(path)
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer m.Close()
+}
+
 func createArray(t *testing.T) *Map {
 	t.Helper()
 
@@ -620,7 +659,7 @@ func TestMapName(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	if name := internal.CString(info.mapName[:]); name != "test" {
+	if name := internal.CString(info.name[:]); name != "test" {
 		t.Error("Expected name to be test, got", name)
 	}
 }
diff --git a/prog_test.go b/prog_test.go
index 51d1bdf..7d893db 100644
--- a/prog_test.go
+++ b/prog_test.go
@@ -197,8 +197,8 @@ func TestProgramPin(t *testing.T) {
 	}
 	defer prog.Close()
 
-	if prog.abi.Type != SocketFilter {
-		t.Error("Expected pinned program to have type SocketFilter, but got", prog.abi.Type)
+	if prog.Type() != SocketFilter {
+		t.Error("Expected pinned program to have type SocketFilter, but got", prog.Type())
 	}
 }