Repository: cilium/ebpf
Pull request #1764 (2025-04-29 09:35:41)

info: add memlock field to ProgramInfo
This patch adds a ProgramInfo.Memlock() method, the memlock field is obtained from fdinfo.


Patch:
diff --git a/info.go b/info.go
index c5a6dd1..e6b854d 100644
--- a/info.go
+++ b/info.go
@@ -10,7 +10,6 @@ import (
 	"os"
 	"reflect"
 	"strings"
-	"syscall"
 	"time"
 
 	"github.com/cilium/ebpf/asm"
@@ -258,16 +257,17 @@ type ProgramInfo struct {
 	numLineInfos uint32
 	funcInfos    []byte
 	numFuncInfos uint32
+
+	memlock uint64
 }
 
 func newProgramInfoFromFd(fd *sys.FD) (*ProgramInfo, error) {
 	var info sys.ProgInfo
-	err := sys.ObjInfo(fd, &info)
-	if errors.Is(err, syscall.EINVAL) {
-		return newProgramInfoFromProc(fd)
-	}
-	if err != nil {
-		return nil, err
+	err1 := sys.ObjInfo(fd, &info)
+	// EINVAL means the kernel doesn't support BPF_OBJ_GET_INFO_BY_FD. Continue
+	// with fdinfo if that's the case.
+	if err1 != nil && !errors.Is(err1, unix.EINVAL) {
+		return nil, fmt.Errorf("getting object info: %w", err1)
 	}
 
 	typ, err := ProgramTypeForPlatform(platform.Native, info.Type)
@@ -291,6 +291,17 @@ func newProgramInfoFromFd(fd *sys.FD) (*ProgramInfo, error) {
 		verifiedInstructions: info.VerifiedInsns,
 	}
 
+	// Supplement OBJ_INFO with data from /proc/self/fdinfo. It contains fields
+	// like memlock that is not present in OBJ_INFO.
+	err2 := readProgramInfoFromProc(fd, &pi)
+	if err2 != nil && !errors.Is(err2, ErrNotSupported) {
+		return nil, fmt.Errorf("getting map info from fdinfo: %w", err2)
+	}
+
+	if err1 != nil && err2 != nil {
+		return nil, fmt.Errorf("ObjInfo and fdinfo both failed: objinfo: %w, fdinfo: %w", err1, err2)
+	}
+
 	if platform.IsWindows && info.Tag == [8]uint8{} {
 		// Windows doesn't support the tag field, clear it for now.
 		pi.Tag = ""
@@ -388,29 +399,29 @@ func newProgramInfoFromFd(fd *sys.FD) (*ProgramInfo, error) {
 	return &pi, nil
 }
 
-func newProgramInfoFromProc(fd *sys.FD) (*ProgramInfo, error) {
-	var info ProgramInfo
+func readProgramInfoFromProc(fd *sys.FD, pi *ProgramInfo) error {
 	var progType uint32
 	err := scanFdInfo(fd, map[string]interface{}{
 		"prog_type": &progType,
-		"prog_tag":  &info.Tag,
+		"prog_tag":  &pi.Tag,
+		"memlock":   &pi.memlock,
 	})
 	if errors.Is(err, ErrNotSupported) && !errors.Is(err, internal.ErrNotSupportedOnOS) {
-		return nil, &internal.UnsupportedFeatureError{
+		return &internal.UnsupportedFeatureError{
 			Name:           "reading program info from /proc/self/fdinfo",
 			MinimumVersion: internal.Version{4, 10, 0},
 		}
 	}
 	if err != nil {
-		return nil, err
+		return err
 	}
 
-	info.Type, err = ProgramTypeForPlatform(platform.Linux, progType)
+	pi.Type, err = ProgramTypeForPlatform(platform.Linux, progType)
 	if err != nil {
-		return nil, fmt.Errorf("program type: %w", err)
+		return fmt.Errorf("program type: %w", err)
 	}
 
-	return &info, nil
+	return nil
 }
 
 // ID returns the program ID.
@@ -743,6 +754,15 @@ func (pi *ProgramInfo) FuncInfos() (btf.FuncOffsets, error) {
 	)
 }
 
+// ProgramInfo returns an approximate number of bytes allocated to this program.
+//
+// Available from 4.10.
+//
+// The bool return value indicates whether this optional field is available.
+func (pi *ProgramInfo) Memlock() (uint64, bool) {
+	return pi.memlock, pi.memlock > 0
+}
+
 func scanFdInfo(fd *sys.FD, fields map[string]interface{}) error {
 	if platform.IsWindows {
 		return fmt.Errorf("read fdinfo: %w", internal.ErrNotSupportedOnOS)


Test patch:
diff --git a/info_test.go b/info_test.go
index 55d5feb..963b159 100644
--- a/info_test.go
+++ b/info_test.go
@@ -92,6 +92,10 @@ func validateProgInfo(t *testing.T, spec *ProgramSpec, info *ProgramInfo) {
 	if info.Tag != "" {
 		qt.Assert(t, qt.Equals(info.Tag, "d7edec644f05498d"))
 	}
+	memlock, ok := info.Memlock()
+	if ok {
+		qt.Assert(t, qt.Equals(memlock, 4096))
+	}
 }
 
 func TestProgramInfo(t *testing.T) {
@@ -161,11 +165,12 @@ func TestProgramInfoProc(t *testing.T) {
 	spec := fixupProgramSpec(basicProgramSpec)
 	prog := mustNewProgram(t, spec, nil)
 
-	info, err := newProgramInfoFromProc(prog.fd)
+	var info ProgramInfo
+	err := readProgramInfoFromProc(prog.fd, &info)
 	testutils.SkipIfNotSupported(t, err)
 	qt.Assert(t, qt.IsNil(err))
 
-	validateProgInfo(t, spec, info)
+	validateProgInfo(t, spec, &info)
 }
 
 func TestProgramInfoBTF(t *testing.T) {