Repository: cilium/ebpf
Pull request #342 (2021-07-13 21:21:30)

link/uprobe: support filtering by pid


Patch:
diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md
index aee9c0a..6cbb31b 100644
--- a/ARCHITECTURE.md
+++ b/ARCHITECTURE.md
@@ -57,7 +57,7 @@ Objects
 loading a spec will fail because the kernel is too old, or a feature is not
 enabled. There are multiple ways the library deals with that:
 
-* Fallback: older kernels don't allowing naming programs and maps. The library
+* Fallback: older kernels don't allow naming programs and maps. The library
   automatically detects support for names, and omits them during load if
   necessary. This works since name is primarily a debug aid.
 
@@ -68,7 +68,7 @@ enabled. There are multiple ways the library deals with that:
 Once program and map objects are loaded they expose the kernel's low-level API,
 e.g. `NextKey`. Often this API is awkward to use in Go, so there are safer
 wrappers on top of the low-level API, like `MapIterator`. The low-level API is
-useful as an out when our higher-level API doesn't support a particular use case.
+useful when our higher-level API doesn't support a particular use case.
 
 Links
 ---
diff --git a/elf_reader.go b/elf_reader.go
index c2afbc3..1bc2562 100644
--- a/elf_reader.go
+++ b/elf_reader.go
@@ -907,6 +907,11 @@ func getProgType(sectionName string) (ProgramType, AttachType, uint32, string) {
 		"cgroup/setsockopt":  {CGroupSockopt, AttachCGroupSetsockopt, 0},
 		"classifier":         {SchedCLS, AttachNone, 0},
 		"action":             {SchedACT, AttachNone, 0},
+
+		"cgroup/getsockname4": {CGroupSockAddr, AttachCgroupInet4GetSockname, 0},
+		"cgroup/getsockname6": {CGroupSockAddr, AttachCgroupInet6GetSockname, 0},
+		"cgroup/getpeername4": {CGroupSockAddr, AttachCgroupInet4GetPeername, 0},
+		"cgroup/getpeername6": {CGroupSockAddr, AttachCgroupInet6GetPeername, 0},
 	}
 
 	for prefix, t := range types {
diff --git a/features/doc.go b/features/doc.go
new file mode 100644
index 0000000..f445101
--- /dev/null
+++ b/features/doc.go
@@ -0,0 +1,2 @@
+// Package features allows probing for BPF features available to the calling process.
+package features
diff --git a/features/map.go b/features/map.go
new file mode 100644
index 0000000..51b52d3
--- /dev/null
+++ b/features/map.go
@@ -0,0 +1,186 @@
+package features
+
+import (
+	"errors"
+	"fmt"
+	"os"
+	"sync"
+	"unsafe"
+
+	"github.com/cilium/ebpf"
+	"github.com/cilium/ebpf/internal"
+	"github.com/cilium/ebpf/internal/unix"
+)
+
+func init() {
+	mc.mapTypes = make(map[ebpf.MapType]error)
+}
+
+var (
+	mc mapCache
+)
+
+type mapCache struct {
+	sync.Mutex
+	mapTypes map[ebpf.MapType]error
+}
+
+func createMapTypeAttr(mt ebpf.MapType) *internal.BPFMapCreateAttr {
+	var (
+		keySize        uint32 = 4
+		valueSize      uint32 = 4
+		maxEntries     uint32 = 1
+		innerMapFd     uint32
+		flags          uint32
+		btfKeyTypeID   uint32
+		btfValueTypeID uint32
+		btfFd          uint32
+	)
+
+	// switch on map types to generate correct bpfMapCreateAttr
+	switch mt {
+	case ebpf.StackTrace:
+		// valueSize needs to be sizeof(uint64)
+		valueSize = 8
+	case ebpf.LPMTrie:
+		// keySize and valueSize need to be sizeof(struct{u32 + u8}) + 1 + padding = 8
+		// BPF_F_NO_PREALLOC needs to be set
+		// checked at allocation time for lpm_trie maps
+		keySize = 8
+		valueSize = 8
+		flags = unix.BPF_F_NO_PREALLOC
+	case ebpf.ArrayOfMaps, ebpf.HashOfMaps:
+		// assign invalid innerMapFd to pass validation check
+		// will return EBADF
+		innerMapFd = ^uint32(0)
+	case ebpf.CGroupStorage, ebpf.PerCPUCGroupStorage:
+		// keySize needs to be sizeof(struct{u32 + u64}) = 12 (+ padding = 16)
+		// by using unsafe.Sizeof(int) we are making sure that this works on 32bit and 64bit archs
+		// checked at allocation time
+		var align int
+		keySize = uint32(8 + unsafe.Sizeof(align))
+		maxEntries = 0
+	case ebpf.Queue, ebpf.Stack:
+		// keySize needs to be 0, see alloc_check for queue and stack maps
+		keySize = 0
+	case ebpf.RingBuf:
+		// keySize and valueSize need to be 0
+		// maxEntries needs to be power of 2 and PAGE_ALIGNED
+		// checked at allocation time
+		keySize = 0
+		valueSize = 0
+		maxEntries = uint32(os.Getpagesize())
+	case ebpf.SkStorage, ebpf.InodeStorage, ebpf.TaskStorage:
+		// maxEntries needs to be 0
+		// BPF_F_NO_PREALLOC needs to be set
+		// btf* fields need to be set
+		// see alloc_check for local_storage map types
+		maxEntries = 0
+		flags = unix.BPF_F_NO_PREALLOC
+		btfKeyTypeID = 1   // BTF_KIND_INT
+		btfValueTypeID = 3 // BTF_KIND_ARRAY
+		btfFd = ^uint32(0)
+	}
+
+	return &internal.BPFMapCreateAttr{
+		MapType:        uint32(mt),
+		KeySize:        keySize,
+		ValueSize:      valueSize,
+		MaxEntries:     maxEntries,
+		InnerMapFd:     innerMapFd,
+		Flags:          flags,
+		BTFKeyTypeID:   btfKeyTypeID,
+		BTFValueTypeID: btfValueTypeID,
+		BTFFd:          btfFd,
+	}
+
+}
+
+// HaveMapType probes the running kernel for the availability of the specified map type.
+// Return values have the following semantics:
+//
+//   err == nil: The feature is available.
+//   errors.Is(err, ebpf.ErrNotSupported): The feature is not available.
+//   err != nil: Any errors encountered during probe execution, wrapped.
+//
+// Note that the latter case may include false negatives, and that map creation may succeed
+// despite an error being returned. Some map types cannot reliably be probed and will also
+// return error. Only `nil` and `ebpf.ErrNotSupported` are conclusive.
+//
+// Probe results are cached and persist throughout any process capability changes.
+func HaveMapType(mt ebpf.MapType) error {
+	if err := validateMaptype(mt); err != nil {
+		return err
+	}
+
+	return haveMapType(mt)
+}
+
+func validateMaptype(mt ebpf.MapType) error {
+	if mt > mt.Max() {
+		return os.ErrInvalid
+	}
+
+	if mt == ebpf.StructOpts {
+		// A probe for a StructOpts map has vmlinux BTF requirements we currently cannot meet
+		// Once we figure out how to add a working probe in this package, we can remove this check
+		return errors.New("a probe for MapType StructOpts isn't implemented")
+	}
+
+	return nil
+}
+
+func haveMapType(mt ebpf.MapType) error {
+	mc.Lock()
+	defer mc.Unlock()
+	err, ok := mc.mapTypes[mt]
+	if ok {
+		return err
+	}
+
+	_, err = internal.BPFMapCreate(createMapTypeAttr(mt))
+
+	switch {
+	// For nested and storage map types we accept EBADF as indicator that these maps are supported
+	case errors.Is(err, unix.EBADF):
+		if isMapOfMaps(mt) || isStorageMap(mt) {
+			err = nil
+		}
+
+	// EINVAL occurs when attempting to create a map with an unknown type.
+	// E2BIG occurs when BPFMapCreateAttr contains non-zero bytes past the end
+	// of the struct known by the running kernel, meaning the kernel is too old
+	// to support the given map type.
+	case errors.Is(err, unix.EINVAL), errors.Is(err, unix.E2BIG):
+		err = ebpf.ErrNotSupported
+
+	// EPERM is kept as-is and is not converted or wrapped.
+	case errors.Is(err, unix.EPERM):
+		break
+
+	// Wrap unexpected errors.
+	case err != nil:
+		err = fmt.Errorf("unexpected error during feature probe: %w", err)
+	}
+
+	mc.mapTypes[mt] = err
+
+	return err
+}
+
+func isMapOfMaps(mt ebpf.MapType) bool {
+	switch mt {
+	case ebpf.ArrayOfMaps, ebpf.HashOfMaps:
+		return true
+	}
+	return false
+}
+
+func isStorageMap(mt ebpf.MapType) bool {
+	switch mt {
+	case ebpf.SkStorage, ebpf.InodeStorage, ebpf.TaskStorage:
+		return true
+	}
+
+	return false
+}
diff --git a/internal/unix/types_linux.go b/internal/unix/types_linux.go
index 0a18eaf..e502b03 100644
--- a/internal/unix/types_linux.go
+++ b/internal/unix/types_linux.go
@@ -20,10 +20,11 @@ const (
 	EPERM   = linux.EPERM
 	ESRCH   = linux.ESRCH
 	ENODEV  = linux.ENODEV
+	EBADF   = linux.EBADF
+	E2BIG   = linux.E2BIG
 	// ENOTSUPP is not the same as ENOTSUP or EOPNOTSUP
 	ENOTSUPP = syscall.Errno(0x20c)
 
-	EBADF                    = linux.EBADF
 	BPF_F_NO_PREALLOC        = linux.BPF_F_NO_PREALLOC
 	BPF_F_NUMA_NODE          = linux.BPF_F_NUMA_NODE
 	BPF_F_RDONLY             = linux.BPF_F_RDONLY
diff --git a/internal/unix/types_other.go b/internal/unix/types_other.go
index 1b06def..d99b225 100644
--- a/internal/unix/types_other.go
+++ b/internal/unix/types_other.go
@@ -21,6 +21,7 @@ const (
 	ESRCH  = syscall.ESRCH
 	ENODEV = syscall.ENODEV
 	EBADF  = syscall.Errno(0)
+	E2BIG  = syscall.Errno(0)
 	// ENOTSUPP is not the same as ENOTSUP or EOPNOTSUP
 	ENOTSUPP = syscall.Errno(0x20c)
 
diff --git a/link/kprobe.go b/link/kprobe.go
index ea71d6d..626a818 100644
--- a/link/kprobe.go
+++ b/link/kprobe.go
@@ -72,10 +72,11 @@ func (pt probeType) RetprobeBit() (uint64, error) {
 // given kernel symbol starts executing. See /proc/kallsyms for available
 // symbols. For example, printk():
 //
-//	Kprobe("printk", prog)
+//	kp, err := Kprobe("printk", prog)
 //
-// The resulting Link must be Closed during program shutdown to avoid leaking
-// system resources.
+// Losing the reference to the resulting Link (kp) will close the Kprobe
+// and prevent further execution of prog. The Link must be Closed during
+// program shutdown to avoid leaking system resources.
 func Kprobe(symbol string, prog *ebpf.Program) (Link, error) {
 	k, err := kprobe(symbol, prog, false)
 	if err != nil {
@@ -95,10 +96,11 @@ func Kprobe(symbol string, prog *ebpf.Program) (Link, error) {
 // before the given kernel symbol exits, with the function stack left intact.
 // See /proc/kallsyms for available symbols. For example, printk():
 //
-//	Kretprobe("printk", prog)
+//	kp, err := Kretprobe("printk", prog)
 //
-// The resulting Link must be Closed during program shutdown to avoid leaking
-// system resources.
+// Losing the reference to the resulting Link (kp) will close the Kretprobe
+// and prevent further execution of prog. The Link must be Closed during
+// program shutdown to avoid leaking system resources.
 func Kretprobe(symbol string, prog *ebpf.Program) (Link, error) {
 	k, err := kprobe(symbol, prog, true)
 	if err != nil {
@@ -157,7 +159,7 @@ func kprobe(symbol string, prog *ebpf.Program, ret bool) (*perfEvent, error) {
 // pmuKprobe opens a perf event based on the kprobe PMU.
 // Returns os.ErrNotExist if the given symbol does not exist in the kernel.
 func pmuKprobe(symbol string, ret bool) (*perfEvent, error) {
-	return pmuProbe(kprobeType, symbol, "", 0, ret)
+	return pmuProbe(kprobeType, symbol, "", 0, perfAllThreads, ret)
 }
 
 // pmuProbe opens a perf event based on a Performance Monitoring Unit.
@@ -167,7 +169,7 @@ func pmuKprobe(symbol string, ret bool) (*perfEvent, error) {
 // 33ea4b24277b "perf/core: Implement the 'perf_uprobe' PMU"
 //
 // Returns ErrNotSupported if the kernel doesn't support perf_[k,u]probe PMU
-func pmuProbe(typ probeType, symbol, path string, offset uint64, ret bool) (*perfEvent, error) {
+func pmuProbe(typ probeType, symbol, path string, offset uint64, pid int, ret bool) (*perfEvent, error) {
 	// Getting the PMU type will fail if the kernel doesn't support
 	// the perf_[k,u]probe PMU.
 	et, err := getPMUEventType(typ)
@@ -220,7 +222,7 @@ func pmuProbe(typ probeType, symbol, path string, offset uint64, ret bool) (*per
 		}
 	}
 
-	fd, err := unix.PerfEventOpen(&attr, perfAllThreads, 0, -1, unix.PERF_FLAG_FD_CLOEXEC)
+	fd, err := unix.PerfEventOpen(&attr, pid, 0, -1, unix.PERF_FLAG_FD_CLOEXEC)
 
 	// Since commit 97c753e62e6c, ENOENT is correctly returned instead of EINVAL
 	// when trying to create a kretprobe for a missing symbol. Make sure ENOENT
@@ -228,6 +230,11 @@ func pmuProbe(typ probeType, symbol, path string, offset uint64, ret bool) (*per
 	if errors.Is(err, os.ErrNotExist) || errors.Is(err, unix.EINVAL) {
 		return nil, fmt.Errorf("symbol '%s' not found: %w", symbol, os.ErrNotExist)
 	}
+	// Since at least commit cb9a19fe4aa51, ENOTSUPP is returned
+	// when attempting to set a uprobe on a trap instruction.
+	if errors.Is(err, unix.ENOTSUPP) {
+		return nil, fmt.Errorf("failed setting uprobe on offset %#x (possible trap insn): %w", offset, err)
+	}
 	if err != nil {
 		return nil, fmt.Errorf("opening perf event: %w", err)
 	}
@@ -246,7 +253,7 @@ func pmuProbe(typ probeType, symbol, path string, offset uint64, ret bool) (*per
 
 // tracefsKprobe creates a Kprobe tracefs entry.
 func tracefsKprobe(symbol string, ret bool) (*perfEvent, error) {
-	return tracefsProbe(kprobeType, symbol, "", 0, ret)
+	return tracefsProbe(kprobeType, symbol, "", 0, perfAllThreads, ret)
 }
 
 // tracefsProbe creates a trace event by writing an entry to <tracefs>/[k,u]probe_events.
@@ -255,7 +262,7 @@ func tracefsKprobe(symbol string, ret bool) (*perfEvent, error) {
 // Path and offset are only set in the case of uprobe(s) and are used to set
 // the executable/library path on the filesystem and the offset where the probe is inserted.
 // A perf event is then opened on the newly-created trace event and returned to the caller.
-func tracefsProbe(typ probeType, symbol, path string, offset uint64, ret bool) (*perfEvent, error) {
+func tracefsProbe(typ probeType, symbol, path string, offset uint64, pid int, ret bool) (*perfEvent, error) {
 	// Generate a random string for each trace event we attempt to create.
 	// This value is used as the 'group' token in tracefs to allow creating
 	// multiple kprobe trace events with the same name.
@@ -288,7 +295,7 @@ func tracefsProbe(typ probeType, symbol, path string, offset uint64, ret bool) (
 	}
 
 	// Kprobes are ephemeral tracepoints and share the same perf event type.
-	fd, err := openTracepointPerfEvent(tid)
+	fd, err := openTracepointPerfEvent(tid, pid)
 	if err != nil {
 		return nil, err
 	}
diff --git a/link/link.go b/link/link.go
index 16cfff4..d41ec93 100644
--- a/link/link.go
+++ b/link/link.go
@@ -29,8 +29,8 @@ type Link interface {
 
 	// Close frees resources.
 	//
-	// The link will be broken unless it has been pinned. A link
-	// may continue past the lifetime of the process if Close is
+	// The link will be broken unless it has been successfully pinned.
+	// A link may continue past the lifetime of the process if Close is
 	// not called.
 	Close() error
 
diff --git a/link/perf_event.go b/link/perf_event.go
index 5267a47..110da98 100644
--- a/link/perf_event.go
+++ b/link/perf_event.go
@@ -236,7 +236,7 @@ func getPMUEventType(typ probeType) (uint64, error) {
 // openTracepointPerfEvent opens a tracepoint-type perf event. System-wide
 // [k,u]probes created by writing to <tracefs>/[k,u]probe_events are tracepoints
 // behind the scenes, and can be attached to using these perf events.
-func openTracepointPerfEvent(tid uint64) (*internal.FD, error) {
+func openTracepointPerfEvent(tid uint64, pid int) (*internal.FD, error) {
 	attr := unix.PerfEventAttr{
 		Type:        unix.PERF_TYPE_TRACEPOINT,
 		Config:      tid,
@@ -245,7 +245,7 @@ func openTracepointPerfEvent(tid uint64) (*internal.FD, error) {
 		Wakeup:      1,
 	}
 
-	fd, err := unix.PerfEventOpen(&attr, perfAllThreads, 0, -1, unix.PERF_FLAG_FD_CLOEXEC)
+	fd, err := unix.PerfEventOpen(&attr, pid, 0, -1, unix.PERF_FLAG_FD_CLOEXEC)
 	if err != nil {
 		return nil, fmt.Errorf("opening tracepoint perf event: %w", err)
 	}
diff --git a/link/tracepoint.go b/link/tracepoint.go
index b8ae04b..7423df8 100644
--- a/link/tracepoint.go
+++ b/link/tracepoint.go
@@ -11,7 +11,11 @@ import (
 // tracepoints. The top-level directory is the group, the event's subdirectory
 // is the name. Example:
 //
-//	Tracepoint("syscalls", "sys_enter_fork", prog)
+//	tp, err := Tracepoint("syscalls", "sys_enter_fork", prog)
+//
+// Losing the reference to the resulting Link (tp) will close the Tracepoint
+// and prevent further execution of prog. The Link must be Closed during
+// program shutdown to avoid leaking system resources.
 //
 // Note that attaching eBPF programs to syscalls (sys_enter_*/sys_exit_*) is
 // only possible as of kernel 4.14 (commit cf5f5ce).
@@ -34,7 +38,7 @@ func Tracepoint(group, name string, prog *ebpf.Program) (Link, error) {
 		return nil, err
 	}
 
-	fd, err := openTracepointPerfEvent(tid)
+	fd, err := openTracepointPerfEvent(tid, perfAllThreads)
 	if err != nil {
 		return nil, err
 	}
diff --git a/link/uprobe.go b/link/uprobe.go
index 2bc395e..e7fc5ac 100644
--- a/link/uprobe.go
+++ b/link/uprobe.go
@@ -41,6 +41,9 @@ type UprobeOptions struct {
 	// Symbol offset. Must be provided in case of external symbols (shared libs).
 	// If set, overrides the offset eventually parsed from the executable.
 	Offset uint64
+	// Only set the uprobe on the given process ID. Useful when tracing
+	// shared library calls or programs that have many running instances.
+	PID int
 }
 
 // To open a new Executable, use:
@@ -112,11 +115,14 @@ func (ex *Executable) symbol(symbol string) (*elf.Symbol, error) {
 // When using symbols which belongs to shared libraries,
 // an offset must be provided via options:
 //
-//  ex.Uprobe("main", prog, &UprobeOptions{Offset: 0x123})
+//	up, err := ex.Uprobe("main", prog, &UprobeOptions{Offset: 0x123})
 //
-// The resulting Link must be Closed during program shutdown to avoid leaking
-// system resources. Functions provided by shared libraries can currently not
-// be traced and will result in an ErrNotSupported.
+// Losing the reference to the resulting Link (up) will close the Uprobe
+// and prevent further execution of prog. The Link must be Closed during
+// program shutdown to avoid leaking system resources.
+//
+// Functions provided by shared libraries can currently not be traced and
+// will result in an ErrNotSupported.
 func (ex *Executable) Uprobe(symbol string, prog *ebpf.Program, opts *UprobeOptions) (Link, error) {
 	u, err := ex.uprobe(symbol, prog, opts, false)
 	if err != nil {
@@ -141,11 +147,14 @@ func (ex *Executable) Uprobe(symbol string, prog *ebpf.Program, opts *UprobeOpti
 // When using symbols which belongs to shared libraries,
 // an offset must be provided via options:
 //
-//  ex.Uretprobe("main", prog, &UprobeOptions{Offset: 0x123})
+//	up, err := ex.Uretprobe("main", prog, &UprobeOptions{Offset: 0x123})
+//
+// Losing the reference to the resulting Link (up) will close the Uprobe
+// and prevent further execution of prog. The Link must be Closed during
+// program shutdown to avoid leaking system resources.
 //
-// The resulting Link must be Closed during program shutdown to avoid leaking
-// system resources. Functions provided by shared libraries can currently not
-// be traced and will result in an ErrNotSupported.
+// Functions provided by shared libraries can currently not be traced and
+// will result in an ErrNotSupported.
 func (ex *Executable) Uretprobe(symbol string, prog *ebpf.Program, opts *UprobeOptions) (Link, error) {
 	u, err := ex.uprobe(symbol, prog, opts, true)
 	if err != nil {
@@ -191,8 +200,13 @@ func (ex *Executable) uprobe(symbol string, prog *ebpf.Program, opts *UprobeOpti
 		offset = sym.Value
 	}
 
+	pid := perfAllThreads
+	if opts != nil && opts.PID != 0 {
+		pid = opts.PID
+	}
+
 	// Use uprobe PMU if the kernel has it available.
-	tp, err := pmuUprobe(symbol, ex.path, offset, ret)
+	tp, err := pmuUprobe(symbol, ex.path, offset, pid, ret)
 	if err == nil {
 		return tp, nil
 	}
@@ -201,7 +215,7 @@ func (ex *Executable) uprobe(symbol string, prog *ebpf.Program, opts *UprobeOpti
 	}
 
 	// Use tracefs if uprobe PMU is missing.
-	tp, err = tracefsUprobe(uprobeSanitizedSymbol(symbol), ex.path, offset, ret)
+	tp, err = tracefsUprobe(uprobeSanitizedSymbol(symbol), ex.path, offset, pid, ret)
 	if err != nil {
 		return nil, fmt.Errorf("creating trace event '%s:%s' in tracefs: %w", ex.path, symbol, err)
 	}
@@ -210,13 +224,13 @@ func (ex *Executable) uprobe(symbol string, prog *ebpf.Program, opts *UprobeOpti
 }
 
 // pmuUprobe opens a perf event based on the uprobe PMU.
-func pmuUprobe(symbol, path string, offset uint64, ret bool) (*perfEvent, error) {
-	return pmuProbe(uprobeType, symbol, path, offset, ret)
+func pmuUprobe(symbol, path string, offset uint64, pid int, ret bool) (*perfEvent, error) {
+	return pmuProbe(uprobeType, symbol, path, offset, pid, ret)
 }
 
 // tracefsUprobe creates a Uprobe tracefs entry.
-func tracefsUprobe(symbol, path string, offset uint64, ret bool) (*perfEvent, error) {
-	return tracefsProbe(uprobeType, symbol, path, offset, ret)
+func tracefsUprobe(symbol, path string, offset uint64, pid int, ret bool) (*perfEvent, error) {
+	return tracefsProbe(uprobeType, symbol, path, offset, pid, ret)
 }
 
 // uprobeSanitizedSymbol replaces every invalid characted for the tracefs api with an underscore.
diff --git a/types.go b/types.go
index 441a82f..c45dd64 100644
--- a/types.go
+++ b/types.go
@@ -10,6 +10,11 @@ import (
 // that will be initialized in the kernel.
 type MapType uint32
 
+// Max returns the latest supported MapType.
+func (_ MapType) Max() MapType {
+	return maxMapType - 1
+}
+
 // All the various map types that can be created
 const (
 	UnspecifiedMap MapType = iota
@@ -85,10 +90,17 @@ const (
 	SkStorage
 	// DevMapHash - Hash-based indexing scheme for references to network devices.
 	DevMapHash
+	// StructOps - This map holds a kernel struct with its function pointer implemented in a BPF
+	// program.
 	StructOpts
+	// RingBuf - Similar to PerfEventArray, but shared across all CPUs.
 	RingBuf
+	// InodeStorage - Specialized local storage map for inodes.
 	InodeStorage
+	// TaskStorage - Specialized local storage map for task_struct.
 	TaskStorage
+	// maxMapType - Bound enum of MapTypes, has to be last in enum.
+	maxMapType
 )
 
 // hasPerCPUValue returns true if the Map stores a value per CPU.


Test patch:
diff --git a/features/map_test.go b/features/map_test.go
new file mode 100644
index 0000000..71b416b
--- /dev/null
+++ b/features/map_test.go
@@ -0,0 +1,87 @@
+package features
+
+import (
+	"fmt"
+	"math"
+	"os"
+	"testing"
+
+	"github.com/cilium/ebpf"
+	"github.com/cilium/ebpf/internal/testutils"
+)
+
+var mapTypeMinVersion = map[ebpf.MapType]string{
+	ebpf.Hash:                "3.19",
+	ebpf.Array:               "3.19",
+	ebpf.ProgramArray:        "4.2",
+	ebpf.PerfEventArray:      "4.3",
+	ebpf.PerCPUHash:          "4.6",
+	ebpf.PerCPUArray:         "4.6",
+	ebpf.StackTrace:          "4.6",
+	ebpf.CGroupArray:         "4.8",
+	ebpf.LRUHash:             "4.10",
+	ebpf.LRUCPUHash:          "4.10",
+	ebpf.LPMTrie:             "4.11",
+	ebpf.ArrayOfMaps:         "4.12",
+	ebpf.HashOfMaps:          "4.12",
+	ebpf.DevMap:              "4.14",
+	ebpf.SockMap:             "4.14",
+	ebpf.CPUMap:              "4.15",
+	ebpf.XSKMap:              "4.18",
+	ebpf.SockHash:            "4.18",
+	ebpf.CGroupStorage:       "4.19",
+	ebpf.ReusePortSockArray:  "4.19",
+	ebpf.PerCPUCGroupStorage: "4.20",
+	ebpf.Queue:               "4.20",
+	ebpf.Stack:               "4.20",
+	ebpf.SkStorage:           "5.2",
+	ebpf.DevMapHash:          "5.4",
+	ebpf.StructOpts:          "5.6", // requires vmlinux BTF, skip for now
+	ebpf.RingBuf:             "5.8",
+	ebpf.InodeStorage:        "5.10",
+	ebpf.TaskStorage:         "5.11",
+}
+
+func TestHaveMapType(t *testing.T) {
+	for mt := ebpf.UnspecifiedMap + 1; mt <= mt.Max(); mt++ {
+		minVersion, ok := mapTypeMinVersion[mt]
+		if !ok {
+			// In cases were a new map type wasn't added to testCases
+			// we should make sure the test runs anyway and fails on old kernels
+			minVersion = "0.0"
+		}
+
+		feature := fmt.Sprintf("map type %s", mt.String())
+
+		t.Run(mt.String(), func(t *testing.T) {
+			// skip StructOpts test as its not implemented
+			if mt == ebpf.StructOpts {
+				t.Skip("Test for map type StructOpts requires working probe")
+			}
+
+			testutils.SkipOnOldKernel(t, minVersion, feature)
+
+			if err := HaveMapType(mt); err != nil {
+				t.Fatalf("Map type %s isn't supported even though kernel is at least %s: %v", mt.String(), minVersion, err)
+			}
+		})
+
+	}
+
+}
+
+func TestHaveMapTypeUnsupported(t *testing.T) {
+	if err := haveMapType(ebpf.MapType(math.MaxUint32)); err != ebpf.ErrNotSupported {
+		t.Fatalf("Expected ebpf.ErrNotSupported but was: %v", err)
+	}
+}
+
+func TestHaveMapTypeInvalid(t *testing.T) {
+	if err := HaveMapType(ebpf.MapType(math.MaxUint32)); err != os.ErrInvalid {
+		t.Fatalf("Expected os.ErrInvalid but was: %v", err)
+	}
+
+	if err := HaveMapType(ebpf.MapType(ebpf.StructOpts)); err == nil {
+		t.Fatal("Expected but was nil")
+	}
+}
diff --git a/link/link_test.go b/link/link_test.go
index e0151a6..14343a3 100644
--- a/link/link_test.go
+++ b/link/link_test.go
@@ -7,6 +7,8 @@ import (
 	"os"
 	"path/filepath"
 	"reflect"
+	"strings"
+	"sync"
 	"testing"
 
 	"github.com/cilium/ebpf"
@@ -15,6 +17,12 @@ import (
 	"github.com/cilium/ebpf/internal/unix"
 )
 
+var cgroup2Mount = struct {
+	once sync.Once
+	path string
+	err  error
+}{}
+
 func TestRawLink(t *testing.T) {
 	cgroup, prog := mustCgroupFixtures(t)
 
@@ -91,7 +99,7 @@ func mustCgroupFixtures(t *testing.T) (*os.File, *ebpf.Program) {
 	testutils.SkipIfNotSupported(t, haveProgAttach())
 
 	prog := mustCgroupEgressProgram(t)
-	cgdir, err := ioutil.TempDir("/sys/fs/cgroup/unified", "ebpf-link")
+	cgdir, err := ioutil.TempDir(cgroup2Path(t), "ebpf-link")
 	if err != nil {
 		prog.Close()
 		t.Fatal("Can't create cgroupv2:", err)
@@ -130,6 +138,33 @@ func mustCgroupEgressProgram(t *testing.T) *ebpf.Program {
 	return prog
 }
 
+func cgroup2Path(t *testing.T) string {
+	cgroup2Mount.once.Do(func() {
+		mounts, err := ioutil.ReadFile("/proc/mounts")
+		if err != nil {
+			cgroup2Mount.err = err
+			return
+		}
+		for _, line := range strings.Split(string(mounts), "\n") {
+			mount := strings.SplitN(line, " ", 3)
+			if mount[0] == "cgroup2" {
+				cgroup2Mount.path = mount[1]
+				return
+			}
+
+			continue
+		}
+
+		cgroup2Mount.err = errors.New("cgroup2 not mounted")
+	})
+
+	if cgroup2Mount.err != nil {
+		t.Fatal(cgroup2Mount.err)
+	}
+
+	return cgroup2Mount.path
+}
+
 type testLinkOptions struct {
 	prog       *ebpf.Program
 	loadPinned func(string, *ebpf.LoadPinOptions) (Link, error)
diff --git a/link/uprobe_test.go b/link/uprobe_test.go
index c293c8f..51db785 100644
--- a/link/uprobe_test.go
+++ b/link/uprobe_test.go
@@ -11,6 +11,7 @@ import (
 
 	"github.com/cilium/ebpf"
 	"github.com/cilium/ebpf/internal/testutils"
+	"github.com/cilium/ebpf/internal/unix"
 )
 
 var bashEx, _ = OpenExecutable("/bin/bash")
@@ -81,13 +82,41 @@ func TestUprobeExtWithOpts(t *testing.T) {
 
 	// This Uprobe is broken and will not work because the offset is not
 	// correct. This is expected since the offset is provided by the user.
-	up, err := bashEx.Uprobe("open", prog, &UprobeOptions{Offset: 0x12345})
+	up, err := bashEx.Uprobe("open", prog, &UprobeOptions{Offset: 0x1})
 	if err != nil {
 		t.Fatal(err)
 	}
 	defer up.Close()
 }
 
+func TestUprobeWithPID(t *testing.T) {
+	prog, err := ebpf.NewProgram(&kprobeSpec)
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer prog.Close()
+
+	up, err := bashEx.Uprobe(bashSym, prog, &UprobeOptions{PID: os.Getpid()})
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer up.Close()
+}
+
+func TestUprobeWithNonExistentPID(t *testing.T) {
+	prog, err := ebpf.NewProgram(&kprobeSpec)
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer prog.Close()
+
+	// trying to open a perf event on a non-existent PID will return ESRCH.
+	_, err = bashEx.Uprobe(bashSym, prog, &UprobeOptions{PID: -2})
+	if !errors.Is(err, unix.ESRCH) {
+		t.Fatalf("expected ESRCH, got %v", err)
+	}
+}
+
 func TestUretprobe(t *testing.T) {
 	c := qt.New(t)
 
@@ -118,14 +147,14 @@ func TestUprobeCreatePMU(t *testing.T) {
 	c.Assert(err, qt.IsNil)
 
 	// uprobe PMU
-	pu, err := pmuUprobe(sym.Name, bashEx.path, sym.Value, false)
+	pu, err := pmuUprobe(sym.Name, bashEx.path, sym.Value, perfAllThreads, false)
 	c.Assert(err, qt.IsNil)
 	defer pu.Close()
 
 	c.Assert(pu.typ, qt.Equals, uprobeEvent)
 
 	// uretprobe PMU
-	pr, err := pmuUprobe(sym.Name, bashEx.path, sym.Value, true)
+	pr, err := pmuUprobe(sym.Name, bashEx.path, sym.Value, perfAllThreads, true)
 	c.Assert(err, qt.IsNil)
 	defer pr.Close()
 
@@ -140,7 +169,7 @@ func TestUprobePMUUnavailable(t *testing.T) {
 	sym, err := bashEx.symbol(bashSym)
 	c.Assert(err, qt.IsNil)
 
-	pk, err := pmuUprobe(sym.Name, bashEx.path, sym.Value, false)
+	pk, err := pmuUprobe(sym.Name, bashEx.path, sym.Value, perfAllThreads, false)
 	if err == nil {
 		pk.Close()
 		t.Skipf("Kernel supports perf_uprobe PMU, not asserting error.")
@@ -162,23 +191,23 @@ func TestUprobeTraceFS(t *testing.T) {
 	ssym := uprobeSanitizedSymbol(sym.Name)
 
 	// Open and close tracefs u(ret)probes, checking all errors.
-	up, err := tracefsUprobe(ssym, bashEx.path, sym.Value, false)
+	up, err := tracefsUprobe(ssym, bashEx.path, sym.Value, perfAllThreads, false)
 	c.Assert(err, qt.IsNil)
 	c.Assert(up.Close(), qt.IsNil)
 	c.Assert(up.typ, qt.Equals, uprobeEvent)
 
-	up, err = tracefsUprobe(ssym, bashEx.path, sym.Value, true)
+	up, err = tracefsUprobe(ssym, bashEx.path, sym.Value, perfAllThreads, true)
 	c.Assert(err, qt.IsNil)
 	c.Assert(up.Close(), qt.IsNil)
 	c.Assert(up.typ, qt.Equals, uretprobeEvent)
 
 	// Create two identical trace events, ensure their IDs differ.
-	u1, err := tracefsUprobe(ssym, bashEx.path, sym.Value, false)
+	u1, err := tracefsUprobe(ssym, bashEx.path, sym.Value, perfAllThreads, false)
 	c.Assert(err, qt.IsNil)
 	defer u1.Close()
 	c.Assert(u1.tracefsID, qt.Not(qt.Equals), 0)
 
-	u2, err := tracefsUprobe(ssym, bashEx.path, sym.Value, false)
+	u2, err := tracefsUprobe(ssym, bashEx.path, sym.Value, perfAllThreads, false)
 	c.Assert(err, qt.IsNil)
 	defer u2.Close()
 	c.Assert(u2.tracefsID, qt.Not(qt.Equals), 0)
@@ -323,3 +352,32 @@ func TestUprobeProgramCall(t *testing.T) {
 	// Assert that this time the value has not been updated.
 	assertMapValue(t, m, 0, 0)
 }
+
+func TestUprobeProgramWrongPID(t *testing.T) {
+	m, p := newUpdaterMapProg(t, ebpf.Kprobe)
+
+	// Load the '/bin/bash' executable.
+	ex, err := OpenExecutable("/bin/bash")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	// Open Uprobe on '/bin/bash' for the symbol 'main'
+	// and attach it to the ebpf program created above.
+	// Create the perf-event with the current process' PID
+	// to make sure the event is not fired when we will try
+	// to trigger the program execution via exec.
+	u, err := ex.Uprobe("main", p, &UprobeOptions{PID: os.Getpid()})
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer u.Close()
+
+	// Trigger ebpf program call.
+	if err := exec.Command("/bin/bash", "--help").Run(); err != nil {
+		t.Fatal(err)
+	}
+
+	// Assert that the value at index 0 is still 0.
+	assertMapValue(t, m, 0, 0)
+}