Browse code

vendor: github.com/cilium/ebpf v0.17.3

full diff: https://github.com/cilium/ebpf/compare/v0.16.0...v0.17.3

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>

Sebastiaan van Stijn authored on 2025/03/01 00:11:43
Showing 87 changed files
... ...
@@ -142,7 +142,7 @@ require (
142 142
 	github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect
143 143
 	github.com/cenkalti/backoff/v4 v4.3.0 // indirect
144 144
 	github.com/cespare/xxhash/v2 v2.3.0 // indirect
145
-	github.com/cilium/ebpf v0.16.0 // indirect
145
+	github.com/cilium/ebpf v0.17.3 // indirect
146 146
 	github.com/container-storage-interface/spec v1.5.0 // indirect
147 147
 	github.com/containerd/console v1.0.4 // indirect
148 148
 	github.com/containerd/errdefs/pkg v0.3.0 // indirect
... ...
@@ -230,7 +230,6 @@ require (
230 230
 	go.uber.org/multierr v1.8.0 // indirect
231 231
 	go.uber.org/zap v1.21.0 // indirect
232 232
 	golang.org/x/crypto v0.35.0 // indirect
233
-	golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f // indirect
234 233
 	golang.org/x/oauth2 v0.27.0 // indirect
235 234
 	golang.org/x/tools v0.27.0 // indirect
236 235
 	google.golang.org/api v0.155.0 // indirect
... ...
@@ -105,8 +105,8 @@ github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA
105 105
 github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
106 106
 github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
107 107
 github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
108
-github.com/cilium/ebpf v0.16.0 h1:+BiEnHL6Z7lXnlGUsXQPPAE7+kenAd4ES8MQ5min0Ok=
109
-github.com/cilium/ebpf v0.16.0/go.mod h1:L7u2Blt2jMM/vLAVgjxluxtBKlz3/GWjB0dMOEngfwE=
108
+github.com/cilium/ebpf v0.17.3 h1:FnP4r16PWYSE4ux6zN+//jMcW4nMVRvuTLVTvCjyyjg=
109
+github.com/cilium/ebpf v0.17.3/go.mod h1:G5EDHij8yiLzaqn0WjyfJHvRa+3aDlReIaLVRMvOyJk=
110 110
 github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
111 111
 github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
112 112
 github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
... ...
@@ -11,3 +11,21 @@ linters:
11 11
     - typecheck
12 12
     - unused
13 13
     - gofmt
14
+    - depguard
15
+linters-settings:
16
+  goimports:
17
+    # A comma-separated list of prefixes, which, if set, checks import paths
18
+    # with the given prefixes are grouped after 3rd-party packages.
19
+    # Default: ""
20
+    local-prefixes: github.com/cilium/ebpf
21
+  depguard:
22
+    rules:
23
+      no-x-sys-unix:
24
+        files:
25
+          # Filenames are matched against absolute paths, include **/ at the start.
26
+          - '!**/internal/unix/*.go'
27
+          - '!**/examples/**/*.go'
28
+          - '!**/docs/**/*.go'
29
+        deny:
30
+          - pkg: golang.org/x/sys/unix
31
+            desc: use internal/unix instead
... ...
@@ -9,3 +9,5 @@ ringbuf/ @florianl
9 9
 btf/ @dylandreimerink
10 10
 
11 11
 cmd/bpf2go/ @mejedi
12
+
13
+docs/ @ti-mo
... ...
@@ -39,16 +39,18 @@ TARGETS := \
39 39
 	testdata/subprog_reloc \
40 40
 	testdata/fwd_decl \
41 41
 	testdata/kconfig \
42
-	testdata/kconfig_config \
42
+	testdata/ksym \
43 43
 	testdata/kfunc \
44 44
 	testdata/invalid-kfunc \
45 45
 	testdata/kfunc-kmod \
46 46
 	testdata/constants \
47 47
 	testdata/errors \
48
+	testdata/variables \
48 49
 	btf/testdata/relocs \
49 50
 	btf/testdata/relocs_read \
50 51
 	btf/testdata/relocs_read_tgt \
51 52
 	btf/testdata/relocs_enum \
53
+	btf/testdata/tags \
52 54
 	cmd/bpf2go/testdata/minimal
53 55
 
54 56
 .PHONY: all clean container-all container-shell generate
... ...
@@ -57,7 +59,7 @@ TARGETS := \
57 57
 
58 58
 # Build all ELF binaries using a containerized LLVM toolchain.
59 59
 container-all:
60
-	+${CONTAINER_ENGINE} run --rm -t ${CONTAINER_RUN_ARGS} \
60
+	+${CONTAINER_ENGINE} run --rm -ti ${CONTAINER_RUN_ARGS} \
61 61
 		-v "${REPODIR}":/ebpf -w /ebpf --env MAKEFLAGS \
62 62
 		--env HOME="/tmp" \
63 63
 		--env BPF2GO_CC="$(CLANG)" \
... ...
@@ -53,6 +53,7 @@ This library includes the following packages:
53 53
 * [rlimit](https://pkg.go.dev/github.com/cilium/ebpf/rlimit) provides a convenient API to lift
54 54
   the `RLIMIT_MEMLOCK` constraint on kernels before 5.11.
55 55
 * [btf](https://pkg.go.dev/github.com/cilium/ebpf/btf) allows reading the BPF Type Format.
56
+* [pin](https://pkg.go.dev/github.com/cilium/ebpf/pin) provides APIs for working with pinned objects on bpffs.
56 57
 
57 58
 ## Requirements
58 59
 
... ...
@@ -5,10 +5,6 @@ package asm
5 5
 // BuiltinFunc is a built-in eBPF function.
6 6
 type BuiltinFunc int32
7 7
 
8
-func (_ BuiltinFunc) Max() BuiltinFunc {
9
-	return maxBuiltinFunc - 1
10
-}
11
-
12 8
 // eBPF built-in functions
13 9
 //
14 10
 // You can regenerate this list using the following gawk script:
... ...
@@ -237,8 +233,6 @@ const (
237 237
 	FnUserRingbufDrain
238 238
 	FnCgrpStorageGet
239 239
 	FnCgrpStorageDelete
240
-
241
-	maxBuiltinFunc
242 240
 )
243 241
 
244 242
 // Call emits a function call.
... ...
@@ -220,12 +220,11 @@ func _() {
220 220
 	_ = x[FnUserRingbufDrain-209]
221 221
 	_ = x[FnCgrpStorageGet-210]
222 222
 	_ = x[FnCgrpStorageDelete-211]
223
-	_ = x[maxBuiltinFunc-212]
224 223
 }
225 224
 
226
-const _BuiltinFunc_name = "FnUnspecFnMapLookupElemFnMapUpdateElemFnMapDeleteElemFnProbeReadFnKtimeGetNsFnTracePrintkFnGetPrandomU32FnGetSmpProcessorIdFnSkbStoreBytesFnL3CsumReplaceFnL4CsumReplaceFnTailCallFnCloneRedirectFnGetCurrentPidTgidFnGetCurrentUidGidFnGetCurrentCommFnGetCgroupClassidFnSkbVlanPushFnSkbVlanPopFnSkbGetTunnelKeyFnSkbSetTunnelKeyFnPerfEventReadFnRedirectFnGetRouteRealmFnPerfEventOutputFnSkbLoadBytesFnGetStackidFnCsumDiffFnSkbGetTunnelOptFnSkbSetTunnelOptFnSkbChangeProtoFnSkbChangeTypeFnSkbUnderCgroupFnGetHashRecalcFnGetCurrentTaskFnProbeWriteUserFnCurrentTaskUnderCgroupFnSkbChangeTailFnSkbPullDataFnCsumUpdateFnSetHashInvalidFnGetNumaNodeIdFnSkbChangeHeadFnXdpAdjustHeadFnProbeReadStrFnGetSocketCookieFnGetSocketUidFnSetHashFnSetsockoptFnSkbAdjustRoomFnRedirectMapFnSkRedirectMapFnSockMapUpdateFnXdpAdjustMetaFnPerfEventReadValueFnPerfProgReadValueFnGetsockoptFnOverrideReturnFnSockOpsCbFlagsSetFnMsgRedirectMapFnMsgApplyBytesFnMsgCorkBytesFnMsgPullDataFnBindFnXdpAdjustTailFnSkbGetXfrmStateFnGetStackFnSkbLoadBytesRelativeFnFibLookupFnSockHashUpdateFnMsgRedirectHashFnSkRedirectHashFnLwtPushEncapFnLwtSeg6StoreBytesFnLwtSeg6AdjustSrhFnLwtSeg6ActionFnRcRepeatFnRcKeydownFnSkbCgroupIdFnGetCurrentCgroupIdFnGetLocalStorageFnSkSelectReuseportFnSkbAncestorCgroupIdFnSkLookupTcpFnSkLookupUdpFnSkReleaseFnMapPushElemFnMapPopElemFnMapPeekElemFnMsgPushDataFnMsgPopDataFnRcPointerRelFnSpinLockFnSpinUnlockFnSkFullsockFnTcpSockFnSkbEcnSetCeFnGetListenerSockFnSkcLookupTcpFnTcpCheckSyncookieFnSysctlGetNameFnSysctlGetCurrentValueFnSysctlGetNewValueFnSysctlSetNewValueFnStrtolFnStrtoulFnSkStorageGetFnSkStorageDeleteFnSendSignalFnTcpGenSyncookieFnSkbOutputFnProbeReadUserFnProbeReadKernelFnProbeReadUserStrFnProbeReadKernelStrFnTcpSendAckFnSendSignalThreadFnJiffies64FnReadBranchRecordsFnGetNsCurrentPidTgidFnXdpOutputFnGetNetnsCookieFnGetCurrentAncestorCgroupIdFnSkAssignFnKtimeGetBootNsFnSeqPrintfFnSeqWriteFnSkCgroupIdFnSkAncestorCgroupIdFnRingbufOutputFnRingbufReserveFnRingbufSubmitFnRingbufDiscardFnRingbufQueryFnCsumLevelFnSkcToTcp6SockFnSkcToTcpSockFnSkcToTcpTimewaitSockFnSkcToTcpRequestSockFnSkcToUdp6SockFnGetTaskStackFnLoadHdrOptFnStoreHdrOptFnReserveHdrOptFnInodeStorageGetFnInodeStorageDeleteFnDPathFnCopyFromUserFnSnprintfBtfFnSeqPrintfBtfFnSkbCgroupClassidFnRedirectNeighFnPerCpuPtrFnThisCpuPtrFnRedirectPeerFnTaskStorageGetFnTaskStorageDeleteFnGetCurrentTaskBtfFnBprmOptsSetFnKtimeGetCoarseNsFnImaInodeHashFnSockFromFileFnCheckMtuFnForEachMapElemFnSnprintfFnSysBpfFnBtfFindByNameKindFnSysCloseFnTimerInitFnTimerSetCallbackFnTimerStartFnTimerCancelFnGetFuncIpFnGetAttachCookieFnTaskPtRegsFnGetBranchSnapshotFnTraceVprintkFnSkcToUnixSockFnKallsymsLookupNameFnFindVmaFnLoopFnStrncmpFnGetFuncArgFnGetFuncRetFnGetFuncArgCntFnGetRetvalFnSetRetvalFnXdpGetBuffLenFnXdpLoadBytesFnXdpStoreBytesFnCopyFromUserTaskFnSkbSetTstampFnImaFileHashFnKptrXchgFnMapLookupPercpuElemFnSkcToMptcpSockFnDynptrFromMemFnRingbufReserveDynptrFnRingbufSubmitDynptrFnRingbufDiscardDynptrFnDynptrReadFnDynptrWriteFnDynptrDataFnTcpRawGenSyncookieIpv4FnTcpRawGenSyncookieIpv6FnTcpRawCheckSyncookieIpv4FnTcpRawCheckSyncookieIpv6FnKtimeGetTaiNsFnUserRingbufDrainFnCgrpStorageGetFnCgrpStorageDeletemaxBuiltinFunc"
225
+const _BuiltinFunc_name = "FnUnspecFnMapLookupElemFnMapUpdateElemFnMapDeleteElemFnProbeReadFnKtimeGetNsFnTracePrintkFnGetPrandomU32FnGetSmpProcessorIdFnSkbStoreBytesFnL3CsumReplaceFnL4CsumReplaceFnTailCallFnCloneRedirectFnGetCurrentPidTgidFnGetCurrentUidGidFnGetCurrentCommFnGetCgroupClassidFnSkbVlanPushFnSkbVlanPopFnSkbGetTunnelKeyFnSkbSetTunnelKeyFnPerfEventReadFnRedirectFnGetRouteRealmFnPerfEventOutputFnSkbLoadBytesFnGetStackidFnCsumDiffFnSkbGetTunnelOptFnSkbSetTunnelOptFnSkbChangeProtoFnSkbChangeTypeFnSkbUnderCgroupFnGetHashRecalcFnGetCurrentTaskFnProbeWriteUserFnCurrentTaskUnderCgroupFnSkbChangeTailFnSkbPullDataFnCsumUpdateFnSetHashInvalidFnGetNumaNodeIdFnSkbChangeHeadFnXdpAdjustHeadFnProbeReadStrFnGetSocketCookieFnGetSocketUidFnSetHashFnSetsockoptFnSkbAdjustRoomFnRedirectMapFnSkRedirectMapFnSockMapUpdateFnXdpAdjustMetaFnPerfEventReadValueFnPerfProgReadValueFnGetsockoptFnOverrideReturnFnSockOpsCbFlagsSetFnMsgRedirectMapFnMsgApplyBytesFnMsgCorkBytesFnMsgPullDataFnBindFnXdpAdjustTailFnSkbGetXfrmStateFnGetStackFnSkbLoadBytesRelativeFnFibLookupFnSockHashUpdateFnMsgRedirectHashFnSkRedirectHashFnLwtPushEncapFnLwtSeg6StoreBytesFnLwtSeg6AdjustSrhFnLwtSeg6ActionFnRcRepeatFnRcKeydownFnSkbCgroupIdFnGetCurrentCgroupIdFnGetLocalStorageFnSkSelectReuseportFnSkbAncestorCgroupIdFnSkLookupTcpFnSkLookupUdpFnSkReleaseFnMapPushElemFnMapPopElemFnMapPeekElemFnMsgPushDataFnMsgPopDataFnRcPointerRelFnSpinLockFnSpinUnlockFnSkFullsockFnTcpSockFnSkbEcnSetCeFnGetListenerSockFnSkcLookupTcpFnTcpCheckSyncookieFnSysctlGetNameFnSysctlGetCurrentValueFnSysctlGetNewValueFnSysctlSetNewValueFnStrtolFnStrtoulFnSkStorageGetFnSkStorageDeleteFnSendSignalFnTcpGenSyncookieFnSkbOutputFnProbeReadUserFnProbeReadKernelFnProbeReadUserStrFnProbeReadKernelStrFnTcpSendAckFnSendSignalThreadFnJiffies64FnReadBranchRecordsFnGetNsCurrentPidTgidFnXdpOutputFnGetNetnsCookieFnGetCurrentAncestorCgroupIdFnSkAssignFnKtimeGetBootNsFnSeqPrintfFnSeqWriteFnSkCgroupIdFnSkAncestorCgroupIdFnRingbufOutputFnRingbufReserveFnRingbufSubmitFnRingbufDiscardFnRingbufQueryFnCsumLevelFnSkcToTcp6SockFnSkcToTcpSockFnSkcToTcpTimewaitSockFnSkcToTcpRequestSockFnSkcToUdp6SockFnGetTaskStackFnLoadHdrOptFnStoreHdrOptFnReserveHdrOptFnInodeStorageGetFnInodeStorageDeleteFnDPathFnCopyFromUserFnSnprintfBtfFnSeqPrintfBtfFnSkbCgroupClassidFnRedirectNeighFnPerCpuPtrFnThisCpuPtrFnRedirectPeerFnTaskStorageGetFnTaskStorageDeleteFnGetCurrentTaskBtfFnBprmOptsSetFnKtimeGetCoarseNsFnImaInodeHashFnSockFromFileFnCheckMtuFnForEachMapElemFnSnprintfFnSysBpfFnBtfFindByNameKindFnSysCloseFnTimerInitFnTimerSetCallbackFnTimerStartFnTimerCancelFnGetFuncIpFnGetAttachCookieFnTaskPtRegsFnGetBranchSnapshotFnTraceVprintkFnSkcToUnixSockFnKallsymsLookupNameFnFindVmaFnLoopFnStrncmpFnGetFuncArgFnGetFuncRetFnGetFuncArgCntFnGetRetvalFnSetRetvalFnXdpGetBuffLenFnXdpLoadBytesFnXdpStoreBytesFnCopyFromUserTaskFnSkbSetTstampFnImaFileHashFnKptrXchgFnMapLookupPercpuElemFnSkcToMptcpSockFnDynptrFromMemFnRingbufReserveDynptrFnRingbufSubmitDynptrFnRingbufDiscardDynptrFnDynptrReadFnDynptrWriteFnDynptrDataFnTcpRawGenSyncookieIpv4FnTcpRawGenSyncookieIpv6FnTcpRawCheckSyncookieIpv4FnTcpRawCheckSyncookieIpv6FnKtimeGetTaiNsFnUserRingbufDrainFnCgrpStorageGetFnCgrpStorageDelete"
227 226
 
228
-var _BuiltinFunc_index = [...]uint16{0, 8, 23, 38, 53, 64, 76, 89, 104, 123, 138, 153, 168, 178, 193, 212, 230, 246, 264, 277, 289, 306, 323, 338, 348, 363, 380, 394, 406, 416, 433, 450, 466, 481, 497, 512, 528, 544, 568, 583, 596, 608, 624, 639, 654, 669, 683, 700, 714, 723, 735, 750, 763, 778, 793, 808, 828, 847, 859, 875, 894, 910, 925, 939, 952, 958, 973, 990, 1000, 1022, 1033, 1049, 1066, 1082, 1096, 1115, 1133, 1148, 1158, 1169, 1182, 1202, 1219, 1238, 1259, 1272, 1285, 1296, 1309, 1321, 1334, 1347, 1359, 1373, 1383, 1395, 1407, 1416, 1429, 1446, 1460, 1479, 1494, 1517, 1536, 1555, 1563, 1572, 1586, 1603, 1615, 1632, 1643, 1658, 1675, 1693, 1713, 1725, 1743, 1754, 1773, 1794, 1805, 1821, 1849, 1859, 1875, 1886, 1896, 1908, 1928, 1943, 1959, 1974, 1990, 2004, 2015, 2030, 2044, 2066, 2087, 2102, 2116, 2128, 2141, 2156, 2173, 2193, 2200, 2214, 2227, 2241, 2259, 2274, 2285, 2297, 2311, 2327, 2346, 2365, 2378, 2396, 2410, 2424, 2434, 2450, 2460, 2468, 2487, 2497, 2508, 2526, 2538, 2551, 2562, 2579, 2591, 2610, 2624, 2639, 2659, 2668, 2674, 2683, 2695, 2707, 2722, 2733, 2744, 2759, 2773, 2788, 2806, 2820, 2833, 2843, 2864, 2880, 2895, 2917, 2938, 2960, 2972, 2985, 2997, 3021, 3045, 3071, 3097, 3112, 3130, 3146, 3165, 3179}
227
+var _BuiltinFunc_index = [...]uint16{0, 8, 23, 38, 53, 64, 76, 89, 104, 123, 138, 153, 168, 178, 193, 212, 230, 246, 264, 277, 289, 306, 323, 338, 348, 363, 380, 394, 406, 416, 433, 450, 466, 481, 497, 512, 528, 544, 568, 583, 596, 608, 624, 639, 654, 669, 683, 700, 714, 723, 735, 750, 763, 778, 793, 808, 828, 847, 859, 875, 894, 910, 925, 939, 952, 958, 973, 990, 1000, 1022, 1033, 1049, 1066, 1082, 1096, 1115, 1133, 1148, 1158, 1169, 1182, 1202, 1219, 1238, 1259, 1272, 1285, 1296, 1309, 1321, 1334, 1347, 1359, 1373, 1383, 1395, 1407, 1416, 1429, 1446, 1460, 1479, 1494, 1517, 1536, 1555, 1563, 1572, 1586, 1603, 1615, 1632, 1643, 1658, 1675, 1693, 1713, 1725, 1743, 1754, 1773, 1794, 1805, 1821, 1849, 1859, 1875, 1886, 1896, 1908, 1928, 1943, 1959, 1974, 1990, 2004, 2015, 2030, 2044, 2066, 2087, 2102, 2116, 2128, 2141, 2156, 2173, 2193, 2200, 2214, 2227, 2241, 2259, 2274, 2285, 2297, 2311, 2327, 2346, 2365, 2378, 2396, 2410, 2424, 2434, 2450, 2460, 2468, 2487, 2497, 2508, 2526, 2538, 2551, 2562, 2579, 2591, 2610, 2624, 2639, 2659, 2668, 2674, 2683, 2695, 2707, 2722, 2733, 2744, 2759, 2773, 2788, 2806, 2820, 2833, 2843, 2864, 2880, 2895, 2917, 2938, 2960, 2972, 2985, 2997, 3021, 3045, 3071, 3097, 3112, 3130, 3146, 3165}
229 228
 
230 229
 func (i BuiltinFunc) String() string {
231 230
 	if i < 0 || i >= BuiltinFunc(len(_BuiltinFunc_index)-1) {
... ...
@@ -12,7 +12,6 @@ import (
12 12
 	"strings"
13 13
 
14 14
 	"github.com/cilium/ebpf/internal/sys"
15
-	"github.com/cilium/ebpf/internal/unix"
16 15
 )
17 16
 
18 17
 // InstructionSize is the size of a BPF instruction in bytes
... ...
@@ -804,7 +803,7 @@ func (insns Instructions) Tag(bo binary.ByteOrder) (string, error) {
804 804
 			return "", fmt.Errorf("instruction %d: %w", i, err)
805 805
 		}
806 806
 	}
807
-	return hex.EncodeToString(h.Sum(nil)[:unix.BPF_TAG_SIZE]), nil
807
+	return hex.EncodeToString(h.Sum(nil)[:sys.BPF_TAG_SIZE]), nil
808 808
 }
809 809
 
810 810
 // encodeFunctionReferences populates the Offset (or Constant, depending on
... ...
@@ -52,6 +52,7 @@ func _() {
52 52
 	_ = x[AttachSkReuseportSelectOrMigrate-40]
53 53
 	_ = x[AttachPerfEvent-41]
54 54
 	_ = x[AttachTraceKprobeMulti-42]
55
+	_ = x[AttachTraceKprobeSession-56]
55 56
 	_ = x[AttachLSMCgroup-43]
56 57
 	_ = x[AttachStructOps-44]
57 58
 	_ = x[AttachNetfilter-45]
... ...
@@ -67,9 +68,9 @@ func _() {
67 67
 	_ = x[AttachNetkitPeer-55]
68 68
 }
69 69
 
70
-const _AttachType_name = "NoneCGroupInetEgressCGroupInetSockCreateCGroupSockOpsSkSKBStreamParserSkSKBStreamVerdictCGroupDeviceSkMsgVerdictCGroupInet4BindCGroupInet6BindCGroupInet4ConnectCGroupInet6ConnectCGroupInet4PostBindCGroupInet6PostBindCGroupUDP4SendmsgCGroupUDP6SendmsgLircMode2FlowDissectorCGroupSysctlCGroupUDP4RecvmsgCGroupUDP6RecvmsgCGroupGetsockoptCGroupSetsockoptTraceRawTpTraceFEntryTraceFExitModifyReturnLSMMacTraceIterCgroupInet4GetPeernameCgroupInet6GetPeernameCgroupInet4GetSocknameCgroupInet6GetSocknameXDPDevMapCgroupInetSockReleaseXDPCPUMapSkLookupXDPSkSKBVerdictSkReuseportSelectSkReuseportSelectOrMigratePerfEventTraceKprobeMultiLSMCgroupStructOpsNetfilterTCXIngressTCXEgressTraceUprobeMultiCgroupUnixConnectCgroupUnixSendmsgCgroupUnixRecvmsgCgroupUnixGetpeernameCgroupUnixGetsocknameNetkitPrimaryNetkitPeer"
70
+const _AttachType_name = "NoneCGroupInetEgressCGroupInetSockCreateCGroupSockOpsSkSKBStreamParserSkSKBStreamVerdictCGroupDeviceSkMsgVerdictCGroupInet4BindCGroupInet6BindCGroupInet4ConnectCGroupInet6ConnectCGroupInet4PostBindCGroupInet6PostBindCGroupUDP4SendmsgCGroupUDP6SendmsgLircMode2FlowDissectorCGroupSysctlCGroupUDP4RecvmsgCGroupUDP6RecvmsgCGroupGetsockoptCGroupSetsockoptTraceRawTpTraceFEntryTraceFExitModifyReturnLSMMacTraceIterCgroupInet4GetPeernameCgroupInet6GetPeernameCgroupInet4GetSocknameCgroupInet6GetSocknameXDPDevMapCgroupInetSockReleaseXDPCPUMapSkLookupXDPSkSKBVerdictSkReuseportSelectSkReuseportSelectOrMigratePerfEventTraceKprobeMultiLSMCgroupStructOpsNetfilterTCXIngressTCXEgressTraceUprobeMultiCgroupUnixConnectCgroupUnixSendmsgCgroupUnixRecvmsgCgroupUnixGetpeernameCgroupUnixGetsocknameNetkitPrimaryNetkitPeerTraceKprobeSession"
71 71
 
72
-var _AttachType_index = [...]uint16{0, 4, 20, 40, 53, 70, 88, 100, 112, 127, 142, 160, 178, 197, 216, 233, 250, 259, 272, 284, 301, 318, 334, 350, 360, 371, 381, 393, 399, 408, 430, 452, 474, 496, 505, 526, 535, 543, 546, 558, 575, 601, 610, 626, 635, 644, 653, 663, 672, 688, 705, 722, 739, 760, 781, 794, 804}
72
+var _AttachType_index = [...]uint16{0, 4, 20, 40, 53, 70, 88, 100, 112, 127, 142, 160, 178, 197, 216, 233, 250, 259, 272, 284, 301, 318, 334, 350, 360, 371, 381, 393, 399, 408, 430, 452, 474, 496, 505, 526, 535, 543, 546, 558, 575, 601, 610, 626, 635, 644, 653, 663, 672, 688, 705, 722, 739, 760, 781, 794, 804, 822}
73 73
 
74 74
 func (i AttachType) String() string {
75 75
 	if i >= AttachType(len(_AttachType_index)-1) {
... ...
@@ -99,6 +99,10 @@ func (mt *mutableTypes) copy() *mutableTypes {
99 99
 		return nil
100 100
 	}
101 101
 
102
+	// Prevent concurrent modification of mt.copiedTypeIDs.
103
+	mt.mu.RLock()
104
+	defer mt.mu.RUnlock()
105
+
102 106
 	mtCopy := &mutableTypes{
103 107
 		mt.imm,
104 108
 		sync.RWMutex{},
... ...
@@ -106,10 +110,6 @@ func (mt *mutableTypes) copy() *mutableTypes {
106 106
 		make(map[Type]TypeID, len(mt.copiedTypeIDs)),
107 107
 	}
108 108
 
109
-	// Prevent concurrent modification of mt.copiedTypeIDs.
110
-	mt.mu.RLock()
111
-	defer mt.mu.RUnlock()
112
-
113 109
 	copiesOfCopies := make(map[Type]Type, len(mt.copies))
114 110
 	for orig, copy := range mt.copies {
115 111
 		// NB: We make a copy of copy, not orig, so that changes to mutable types
... ...
@@ -443,13 +443,19 @@ func fixupDatasec(types []Type, sectionSizes map[string]uint32, offsets map[symb
443 443
 		// Some Datasecs are virtual and don't have corresponding ELF sections.
444 444
 		switch name {
445 445
 		case ".ksyms":
446
-			// .ksyms describes forward declarations of kfunc signatures.
446
+			// .ksyms describes forward declarations of kfunc signatures, as well as
447
+			// references to kernel symbols.
447 448
 			// Nothing to fix up, all sizes and offsets are 0.
448 449
 			for _, vsi := range ds.Vars {
449
-				_, ok := vsi.Type.(*Func)
450
-				if !ok {
451
-					// Only Funcs are supported in the .ksyms Datasec.
452
-					return fmt.Errorf("data section %s: expected *btf.Func, not %T: %w", name, vsi.Type, ErrNotSupported)
450
+				switch t := vsi.Type.(type) {
451
+				case *Func:
452
+					continue
453
+				case *Var:
454
+					if _, ok := t.Type.(*Void); !ok {
455
+						return fmt.Errorf("data section %s: expected %s to be *Void, not %T: %w", name, vsi.Type.TypeName(), vsi.Type, ErrNotSupported)
456
+					}
457
+				default:
458
+					return fmt.Errorf("data section %s: expected to be either *btf.Func or *btf.Var, not %T: %w", name, vsi.Type, ErrNotSupported)
453 459
 				}
454 460
 			}
455 461
 
... ...
@@ -695,5 +701,13 @@ func (iter *TypesIterator) Next() bool {
695 695
 	iter.Type, ok = iter.spec.typeByID(iter.id)
696 696
 	iter.id++
697 697
 	iter.done = !ok
698
+	if !iter.done {
699
+		// Skip declTags, during unmarshaling declTags become `Tags` fields of other types.
700
+		// We keep them in the spec to avoid holes in the ID space, but for the purposes of
701
+		// iteration, they are not useful to the user.
702
+		if _, ok := iter.Type.(*declTag); ok {
703
+			return iter.Next()
704
+		}
705
+	}
698 706
 	return !iter.done
699 707
 }
... ...
@@ -39,6 +39,7 @@ const (
39 39
 	kindFloat // Float
40 40
 	// Added 5.16
41 41
 	kindDeclTag // DeclTag
42
+	// Added 5.17
42 43
 	kindTypeTag // TypeTag
43 44
 	// Added 6.0
44 45
 	kindEnum64 // Enum64
... ...
@@ -16,8 +16,8 @@ import (
16 16
 // ExtInfos contains ELF section metadata.
17 17
 type ExtInfos struct {
18 18
 	// The slices are sorted by offset in ascending order.
19
-	funcInfos       map[string]FuncInfos
20
-	lineInfos       map[string]LineInfos
19
+	funcInfos       map[string]FuncOffsets
20
+	lineInfos       map[string]LineOffsets
21 21
 	relocationInfos map[string]CORERelocationInfos
22 22
 }
23 23
 
... ...
@@ -58,9 +58,9 @@ func loadExtInfos(r io.ReaderAt, bo binary.ByteOrder, spec *Spec) (*ExtInfos, er
58 58
 		return nil, fmt.Errorf("parsing BTF function info: %w", err)
59 59
 	}
60 60
 
61
-	funcInfos := make(map[string]FuncInfos, len(btfFuncInfos))
61
+	funcInfos := make(map[string]FuncOffsets, len(btfFuncInfos))
62 62
 	for section, bfis := range btfFuncInfos {
63
-		funcInfos[section], err = newFuncInfos(bfis, spec)
63
+		funcInfos[section], err = newFuncOffsets(bfis, spec)
64 64
 		if err != nil {
65 65
 			return nil, fmt.Errorf("section %s: func infos: %w", section, err)
66 66
 		}
... ...
@@ -72,7 +72,7 @@ func loadExtInfos(r io.ReaderAt, bo binary.ByteOrder, spec *Spec) (*ExtInfos, er
72 72
 		return nil, fmt.Errorf("parsing BTF line info: %w", err)
73 73
 	}
74 74
 
75
-	lineInfos := make(map[string]LineInfos, len(btfLineInfos))
75
+	lineInfos := make(map[string]LineOffsets, len(btfLineInfos))
76 76
 	for section, blis := range btfLineInfos {
77 77
 		lineInfos[section], err = newLineInfos(blis, spec.strings)
78 78
 		if err != nil {
... ...
@@ -102,8 +102,10 @@ func loadExtInfos(r io.ReaderAt, bo binary.ByteOrder, spec *Spec) (*ExtInfos, er
102 102
 	return &ExtInfos{funcInfos, lineInfos, coreRelos}, nil
103 103
 }
104 104
 
105
-type funcInfoMeta struct{}
106
-type coreRelocationMeta struct{}
105
+type (
106
+	funcInfoMeta       struct{}
107
+	coreRelocationMeta struct{}
108
+)
107 109
 
108 110
 // Assign per-section metadata from BTF to a section's instructions.
109 111
 func (ei *ExtInfos) Assign(insns asm.Instructions, section string) {
... ...
@@ -117,20 +119,20 @@ func (ei *ExtInfos) Assign(insns asm.Instructions, section string) {
117 117
 // Assign per-instruction metadata to the instructions in insns.
118 118
 func AssignMetadataToInstructions(
119 119
 	insns asm.Instructions,
120
-	funcInfos FuncInfos,
121
-	lineInfos LineInfos,
120
+	funcInfos FuncOffsets,
121
+	lineInfos LineOffsets,
122 122
 	reloInfos CORERelocationInfos,
123 123
 ) {
124 124
 	iter := insns.Iterate()
125 125
 	for iter.Next() {
126
-		if len(funcInfos.infos) > 0 && funcInfos.infos[0].offset == iter.Offset {
127
-			*iter.Ins = WithFuncMetadata(*iter.Ins, funcInfos.infos[0].fn)
128
-			funcInfos.infos = funcInfos.infos[1:]
126
+		if len(funcInfos) > 0 && funcInfos[0].Offset == iter.Offset {
127
+			*iter.Ins = WithFuncMetadata(*iter.Ins, funcInfos[0].Func)
128
+			funcInfos = funcInfos[1:]
129 129
 		}
130 130
 
131
-		if len(lineInfos.infos) > 0 && lineInfos.infos[0].offset == iter.Offset {
132
-			*iter.Ins = iter.Ins.WithSource(lineInfos.infos[0].line)
133
-			lineInfos.infos = lineInfos.infos[1:]
131
+		if len(lineInfos) > 0 && lineInfos[0].Offset == iter.Offset {
132
+			*iter.Ins = iter.Ins.WithSource(lineInfos[0].Line)
133
+			lineInfos = lineInfos[1:]
134 134
 		}
135 135
 
136 136
 		if len(reloInfos.infos) > 0 && reloInfos.infos[0].offset == iter.Offset {
... ...
@@ -159,9 +161,9 @@ marshal:
159 159
 	var fiBuf, liBuf bytes.Buffer
160 160
 	for {
161 161
 		if fn := FuncMetadata(iter.Ins); fn != nil {
162
-			fi := &funcInfo{
163
-				fn:     fn,
164
-				offset: iter.Offset,
162
+			fi := &FuncOffset{
163
+				Func:   fn,
164
+				Offset: iter.Offset,
165 165
 			}
166 166
 			if err := fi.marshal(&fiBuf, b); err != nil {
167 167
 				return nil, nil, fmt.Errorf("write func info: %w", err)
... ...
@@ -178,9 +180,9 @@ marshal:
178 178
 				}
179 179
 			}
180 180
 
181
-			li := &lineInfo{
182
-				line:   line,
183
-				offset: iter.Offset,
181
+			li := &LineOffset{
182
+				Offset: iter.Offset,
183
+				Line:   line,
184 184
 			}
185 185
 			if err := li.marshal(&liBuf, b); err != nil {
186 186
 				return nil, nil, fmt.Errorf("write line info: %w", err)
... ...
@@ -333,17 +335,17 @@ func parseExtInfoRecordSize(r io.Reader, bo binary.ByteOrder) (uint32, error) {
333 333
 	return recordSize, nil
334 334
 }
335 335
 
336
-// FuncInfos contains a sorted list of func infos.
337
-type FuncInfos struct {
338
-	infos []funcInfo
339
-}
336
+// FuncOffsets is a sorted slice of FuncOffset.
337
+type FuncOffsets []FuncOffset
340 338
 
341 339
 // The size of a FuncInfo in BTF wire format.
342 340
 var FuncInfoSize = uint32(binary.Size(bpfFuncInfo{}))
343 341
 
344
-type funcInfo struct {
345
-	fn     *Func
346
-	offset asm.RawInstructionOffset
342
+// FuncOffset represents a [btf.Func] and its raw instruction offset within a
343
+// BPF program.
344
+type FuncOffset struct {
345
+	Offset asm.RawInstructionOffset
346
+	Func   *Func
347 347
 }
348 348
 
349 349
 type bpfFuncInfo struct {
... ...
@@ -352,7 +354,7 @@ type bpfFuncInfo struct {
352 352
 	TypeID  TypeID
353 353
 }
354 354
 
355
-func newFuncInfo(fi bpfFuncInfo, spec *Spec) (*funcInfo, error) {
355
+func newFuncOffset(fi bpfFuncInfo, spec *Spec) (*FuncOffset, error) {
356 356
 	typ, err := spec.TypeByID(fi.TypeID)
357 357
 	if err != nil {
358 358
 		return nil, err
... ...
@@ -368,31 +370,32 @@ func newFuncInfo(fi bpfFuncInfo, spec *Spec) (*funcInfo, error) {
368 368
 		return nil, fmt.Errorf("func with type ID %d doesn't have a name", fi.TypeID)
369 369
 	}
370 370
 
371
-	return &funcInfo{
372
-		fn,
371
+	return &FuncOffset{
373 372
 		asm.RawInstructionOffset(fi.InsnOff),
373
+		fn,
374 374
 	}, nil
375 375
 }
376 376
 
377
-func newFuncInfos(bfis []bpfFuncInfo, spec *Spec) (FuncInfos, error) {
378
-	fis := FuncInfos{
379
-		infos: make([]funcInfo, 0, len(bfis)),
380
-	}
377
+func newFuncOffsets(bfis []bpfFuncInfo, spec *Spec) (FuncOffsets, error) {
378
+	fos := make(FuncOffsets, 0, len(bfis))
379
+
381 380
 	for _, bfi := range bfis {
382
-		fi, err := newFuncInfo(bfi, spec)
381
+		fi, err := newFuncOffset(bfi, spec)
383 382
 		if err != nil {
384
-			return FuncInfos{}, fmt.Errorf("offset %d: %w", bfi.InsnOff, err)
383
+			return FuncOffsets{}, fmt.Errorf("offset %d: %w", bfi.InsnOff, err)
385 384
 		}
386
-		fis.infos = append(fis.infos, *fi)
385
+		fos = append(fos, *fi)
387 386
 	}
388
-	sort.Slice(fis.infos, func(i, j int) bool {
389
-		return fis.infos[i].offset <= fis.infos[j].offset
387
+	sort.Slice(fos, func(i, j int) bool {
388
+		return fos[i].Offset <= fos[j].Offset
390 389
 	})
391
-	return fis, nil
390
+	return fos, nil
392 391
 }
393 392
 
394
-// LoadFuncInfos parses BTF func info in kernel wire format.
395
-func LoadFuncInfos(reader io.Reader, bo binary.ByteOrder, recordNum uint32, spec *Spec) (FuncInfos, error) {
393
+// LoadFuncInfos parses BTF func info from kernel wire format into a
394
+// [FuncOffsets], a sorted slice of [btf.Func]s of (sub)programs within a BPF
395
+// program with their corresponding raw instruction offsets.
396
+func LoadFuncInfos(reader io.Reader, bo binary.ByteOrder, recordNum uint32, spec *Spec) (FuncOffsets, error) {
396 397
 	fis, err := parseFuncInfoRecords(
397 398
 		reader,
398 399
 		bo,
... ...
@@ -401,20 +404,20 @@ func LoadFuncInfos(reader io.Reader, bo binary.ByteOrder, recordNum uint32, spec
401 401
 		false,
402 402
 	)
403 403
 	if err != nil {
404
-		return FuncInfos{}, fmt.Errorf("parsing BTF func info: %w", err)
404
+		return FuncOffsets{}, fmt.Errorf("parsing BTF func info: %w", err)
405 405
 	}
406 406
 
407
-	return newFuncInfos(fis, spec)
407
+	return newFuncOffsets(fis, spec)
408 408
 }
409 409
 
410 410
 // marshal into the BTF wire format.
411
-func (fi *funcInfo) marshal(w *bytes.Buffer, b *Builder) error {
412
-	id, err := b.Add(fi.fn)
411
+func (fi *FuncOffset) marshal(w *bytes.Buffer, b *Builder) error {
412
+	id, err := b.Add(fi.Func)
413 413
 	if err != nil {
414 414
 		return err
415 415
 	}
416 416
 	bfi := bpfFuncInfo{
417
-		InsnOff: uint32(fi.offset),
417
+		InsnOff: uint32(fi.Offset),
418 418
 		TypeID:  id,
419 419
 	}
420 420
 	buf := make([]byte, FuncInfoSize)
... ...
@@ -515,14 +518,13 @@ func (li *Line) String() string {
515 515
 	return li.line
516 516
 }
517 517
 
518
-// LineInfos contains a sorted list of line infos.
519
-type LineInfos struct {
520
-	infos []lineInfo
521
-}
518
+// LineOffsets contains a sorted list of line infos.
519
+type LineOffsets []LineOffset
522 520
 
523
-type lineInfo struct {
524
-	line   *Line
525
-	offset asm.RawInstructionOffset
521
+// LineOffset represents a line info and its raw instruction offset.
522
+type LineOffset struct {
523
+	Offset asm.RawInstructionOffset
524
+	Line   *Line
526 525
 }
527 526
 
528 527
 // Constants for the format of bpfLineInfo.LineCol.
... ...
@@ -541,7 +543,7 @@ type bpfLineInfo struct {
541 541
 }
542 542
 
543 543
 // LoadLineInfos parses BTF line info in kernel wire format.
544
-func LoadLineInfos(reader io.Reader, bo binary.ByteOrder, recordNum uint32, spec *Spec) (LineInfos, error) {
544
+func LoadLineInfos(reader io.Reader, bo binary.ByteOrder, recordNum uint32, spec *Spec) (LineOffsets, error) {
545 545
 	lis, err := parseLineInfoRecords(
546 546
 		reader,
547 547
 		bo,
... ...
@@ -550,57 +552,55 @@ func LoadLineInfos(reader io.Reader, bo binary.ByteOrder, recordNum uint32, spec
550 550
 		false,
551 551
 	)
552 552
 	if err != nil {
553
-		return LineInfos{}, fmt.Errorf("parsing BTF line info: %w", err)
553
+		return LineOffsets{}, fmt.Errorf("parsing BTF line info: %w", err)
554 554
 	}
555 555
 
556 556
 	return newLineInfos(lis, spec.strings)
557 557
 }
558 558
 
559
-func newLineInfo(li bpfLineInfo, strings *stringTable) (lineInfo, error) {
559
+func newLineInfo(li bpfLineInfo, strings *stringTable) (LineOffset, error) {
560 560
 	line, err := strings.Lookup(li.LineOff)
561 561
 	if err != nil {
562
-		return lineInfo{}, fmt.Errorf("lookup of line: %w", err)
562
+		return LineOffset{}, fmt.Errorf("lookup of line: %w", err)
563 563
 	}
564 564
 
565 565
 	fileName, err := strings.Lookup(li.FileNameOff)
566 566
 	if err != nil {
567
-		return lineInfo{}, fmt.Errorf("lookup of filename: %w", err)
567
+		return LineOffset{}, fmt.Errorf("lookup of filename: %w", err)
568 568
 	}
569 569
 
570 570
 	lineNumber := li.LineCol >> bpfLineShift
571 571
 	lineColumn := li.LineCol & bpfColumnMax
572 572
 
573
-	return lineInfo{
573
+	return LineOffset{
574
+		asm.RawInstructionOffset(li.InsnOff),
574 575
 		&Line{
575 576
 			fileName,
576 577
 			line,
577 578
 			lineNumber,
578 579
 			lineColumn,
579 580
 		},
580
-		asm.RawInstructionOffset(li.InsnOff),
581 581
 	}, nil
582 582
 }
583 583
 
584
-func newLineInfos(blis []bpfLineInfo, strings *stringTable) (LineInfos, error) {
585
-	lis := LineInfos{
586
-		infos: make([]lineInfo, 0, len(blis)),
587
-	}
584
+func newLineInfos(blis []bpfLineInfo, strings *stringTable) (LineOffsets, error) {
585
+	lis := make([]LineOffset, 0, len(blis))
588 586
 	for _, bli := range blis {
589 587
 		li, err := newLineInfo(bli, strings)
590 588
 		if err != nil {
591
-			return LineInfos{}, fmt.Errorf("offset %d: %w", bli.InsnOff, err)
589
+			return LineOffsets{}, fmt.Errorf("offset %d: %w", bli.InsnOff, err)
592 590
 		}
593
-		lis.infos = append(lis.infos, li)
591
+		lis = append(lis, li)
594 592
 	}
595
-	sort.Slice(lis.infos, func(i, j int) bool {
596
-		return lis.infos[i].offset <= lis.infos[j].offset
593
+	sort.Slice(lis, func(i, j int) bool {
594
+		return lis[i].Offset <= lis[j].Offset
597 595
 	})
598 596
 	return lis, nil
599 597
 }
600 598
 
601 599
 // marshal writes the binary representation of the LineInfo to w.
602
-func (li *lineInfo) marshal(w *bytes.Buffer, b *Builder) error {
603
-	line := li.line
600
+func (li *LineOffset) marshal(w *bytes.Buffer, b *Builder) error {
601
+	line := li.Line
604 602
 	if line.lineNumber > bpfLineMax {
605 603
 		return fmt.Errorf("line %d exceeds %d", line.lineNumber, bpfLineMax)
606 604
 	}
... ...
@@ -620,7 +620,7 @@ func (li *lineInfo) marshal(w *bytes.Buffer, b *Builder) error {
620 620
 	}
621 621
 
622 622
 	bli := bpfLineInfo{
623
-		uint32(li.offset),
623
+		uint32(li.Offset),
624 624
 		fileNameOff,
625 625
 		lineOff,
626 626
 		(line.lineNumber << bpfLineShift) | line.lineColumn,
... ...
@@ -666,20 +666,19 @@ func parseLineInfos(r io.Reader, bo binary.ByteOrder, strings *stringTable) (map
666 666
 // These records appear after a btf_ext_info_sec header in the line_info
667 667
 // sub-section of .BTF.ext.
668 668
 func parseLineInfoRecords(r io.Reader, bo binary.ByteOrder, recordSize uint32, recordNum uint32, offsetInBytes bool) ([]bpfLineInfo, error) {
669
-	var li bpfLineInfo
670
-
671
-	if exp, got := uint32(binary.Size(li)), recordSize; exp != got {
669
+	if exp, got := uint32(binary.Size(bpfLineInfo{})), recordSize; exp != got {
672 670
 		// BTF blob's record size is longer than we know how to parse.
673 671
 		return nil, fmt.Errorf("expected LineInfo record size %d, but BTF blob contains %d", exp, got)
674 672
 	}
675 673
 
676
-	out := make([]bpfLineInfo, 0, recordNum)
677
-	for i := uint32(0); i < recordNum; i++ {
678
-		if err := binary.Read(r, bo, &li); err != nil {
679
-			return nil, fmt.Errorf("can't read line info: %v", err)
680
-		}
674
+	out := make([]bpfLineInfo, recordNum)
675
+	if err := binary.Read(r, bo, out); err != nil {
676
+		return nil, fmt.Errorf("can't read line info: %v", err)
677
+	}
681 678
 
682
-		if offsetInBytes {
679
+	if offsetInBytes {
680
+		for i := range out {
681
+			li := &out[i]
683 682
 			if li.InsnOff%asm.InstructionSize != 0 {
684 683
 				return nil, fmt.Errorf("offset %v is not aligned with instruction size", li.InsnOff)
685 684
 			}
... ...
@@ -688,8 +687,6 @@ func parseLineInfoRecords(r io.Reader, bo binary.ByteOrder, recordSize uint32, r
688 688
 			// Convert as early as possible.
689 689
 			li.InsnOff /= asm.InstructionSize
690 690
 		}
691
-
692
-		out = append(out, li)
693 691
 	}
694 692
 
695 693
 	return out, nil
... ...
@@ -799,7 +796,7 @@ func parseCORERelos(r io.Reader, bo binary.ByteOrder, strings *stringTable) (map
799 799
 			return nil, err
800 800
 		}
801 801
 
802
-		records, err := parseCOREReloRecords(r, bo, recordSize, infoHeader.NumInfo)
802
+		records, err := parseCOREReloRecords(r, bo, infoHeader.NumInfo)
803 803
 		if err != nil {
804 804
 			return nil, fmt.Errorf("section %v: %w", secName, err)
805 805
 		}
... ...
@@ -811,7 +808,7 @@ func parseCORERelos(r io.Reader, bo binary.ByteOrder, strings *stringTable) (map
811 811
 // parseCOREReloRecords parses a stream of CO-RE relocation entries into a
812 812
 // coreRelos. These records appear after a btf_ext_info_sec header in the
813 813
 // core_relos sub-section of .BTF.ext.
814
-func parseCOREReloRecords(r io.Reader, bo binary.ByteOrder, recordSize uint32, recordNum uint32) ([]bpfCORERelo, error) {
814
+func parseCOREReloRecords(r io.Reader, bo binary.ByteOrder, recordNum uint32) ([]bpfCORERelo, error) {
815 815
 	var out []bpfCORERelo
816 816
 
817 817
 	var relo bpfCORERelo
... ...
@@ -11,19 +11,19 @@ import (
11 11
 
12 12
 // haveBTF attempts to load a BTF blob containing an Int. It should pass on any
13 13
 // kernel that supports BPF_BTF_LOAD.
14
-var haveBTF = internal.NewFeatureTest("BTF", "4.18", func() error {
14
+var haveBTF = internal.NewFeatureTest("BTF", func() error {
15 15
 	// 0-length anonymous integer
16 16
 	err := probeBTF(&Int{})
17 17
 	if errors.Is(err, unix.EINVAL) || errors.Is(err, unix.EPERM) {
18 18
 		return internal.ErrNotSupported
19 19
 	}
20 20
 	return err
21
-})
21
+}, "4.18")
22 22
 
23 23
 // haveMapBTF attempts to load a minimal BTF blob containing a Var. It is
24 24
 // used as a proxy for .bss, .data and .rodata map support, which generally
25 25
 // come with a Var and Datasec. These were introduced in Linux 5.2.
26
-var haveMapBTF = internal.NewFeatureTest("Map BTF (Var/Datasec)", "5.2", func() error {
26
+var haveMapBTF = internal.NewFeatureTest("Map BTF (Var/Datasec)", func() error {
27 27
 	if err := haveBTF(); err != nil {
28 28
 		return err
29 29
 	}
... ...
@@ -40,12 +40,12 @@ var haveMapBTF = internal.NewFeatureTest("Map BTF (Var/Datasec)", "5.2", func()
40 40
 		return internal.ErrNotSupported
41 41
 	}
42 42
 	return err
43
-})
43
+}, "5.2")
44 44
 
45 45
 // haveProgBTF attempts to load a BTF blob containing a Func and FuncProto. It
46 46
 // is used as a proxy for ext_info (func_info) support, which depends on
47 47
 // Func(Proto) by definition.
48
-var haveProgBTF = internal.NewFeatureTest("Program BTF (func/line_info)", "5.0", func() error {
48
+var haveProgBTF = internal.NewFeatureTest("Program BTF (func/line_info)", func() error {
49 49
 	if err := haveBTF(); err != nil {
50 50
 		return err
51 51
 	}
... ...
@@ -60,9 +60,9 @@ var haveProgBTF = internal.NewFeatureTest("Program BTF (func/line_info)", "5.0",
60 60
 		return internal.ErrNotSupported
61 61
 	}
62 62
 	return err
63
-})
63
+}, "5.0")
64 64
 
65
-var haveFuncLinkage = internal.NewFeatureTest("BTF func linkage", "5.6", func() error {
65
+var haveFuncLinkage = internal.NewFeatureTest("BTF func linkage", func() error {
66 66
 	if err := haveProgBTF(); err != nil {
67 67
 		return err
68 68
 	}
... ...
@@ -78,9 +78,44 @@ var haveFuncLinkage = internal.NewFeatureTest("BTF func linkage", "5.6", func()
78 78
 		return internal.ErrNotSupported
79 79
 	}
80 80
 	return err
81
-})
81
+}, "5.6")
82 82
 
83
-var haveEnum64 = internal.NewFeatureTest("ENUM64", "6.0", func() error {
83
+var haveDeclTags = internal.NewFeatureTest("BTF decl tags", func() error {
84
+	if err := haveBTF(); err != nil {
85
+		return err
86
+	}
87
+
88
+	t := &Typedef{
89
+		Name: "a",
90
+		Type: &Int{},
91
+		Tags: []string{"a"},
92
+	}
93
+
94
+	err := probeBTF(t)
95
+	if errors.Is(err, unix.EINVAL) {
96
+		return internal.ErrNotSupported
97
+	}
98
+	return err
99
+}, "5.16")
100
+
101
+var haveTypeTags = internal.NewFeatureTest("BTF type tags", func() error {
102
+	if err := haveBTF(); err != nil {
103
+		return err
104
+	}
105
+
106
+	t := &TypeTag{
107
+		Type:  &Int{},
108
+		Value: "a",
109
+	}
110
+
111
+	err := probeBTF(t)
112
+	if errors.Is(err, unix.EINVAL) {
113
+		return internal.ErrNotSupported
114
+	}
115
+	return err
116
+}, "5.17")
117
+
118
+var haveEnum64 = internal.NewFeatureTest("ENUM64", func() error {
84 119
 	if err := haveBTF(); err != nil {
85 120
 		return err
86 121
 	}
... ...
@@ -97,7 +132,7 @@ var haveEnum64 = internal.NewFeatureTest("ENUM64", "6.0", func() error {
97 97
 		return internal.ErrNotSupported
98 98
 	}
99 99
 	return err
100
-})
100
+}, "6.0")
101 101
 
102 102
 func probeBTF(typ Type) error {
103 103
 	b, err := NewBuilder([]Type{typ})
... ...
@@ -161,6 +161,9 @@ func (gf *GoFormatter) writeTypeLit(typ Type, depth int) error {
161 161
 	case *Datasec:
162 162
 		err = gf.writeDatasecLit(v, depth)
163 163
 
164
+	case *Var:
165
+		err = gf.writeTypeLit(v.Type, depth)
166
+
164 167
 	default:
165 168
 		return fmt.Errorf("type %T: %w", v, ErrNotSupported)
166 169
 	}
... ...
@@ -8,7 +8,7 @@ import (
8 8
 	"sync"
9 9
 
10 10
 	"github.com/cilium/ebpf/internal"
11
-	"github.com/cilium/ebpf/internal/kallsyms"
11
+	"github.com/cilium/ebpf/internal/linux"
12 12
 )
13 13
 
14 14
 var kernelBTF = struct {
... ...
@@ -21,8 +21,6 @@ var kernelBTF = struct {
21 21
 
22 22
 // FlushKernelSpec removes any cached kernel type information.
23 23
 func FlushKernelSpec() {
24
-	kallsyms.FlushKernelModuleCache()
25
-
26 24
 	kernelBTF.Lock()
27 25
 	defer kernelBTF.Unlock()
28 26
 
... ...
@@ -130,7 +128,7 @@ func loadKernelModuleSpec(module string, base *Spec) (*Spec, error) {
130 130
 
131 131
 // findVMLinux scans multiple well-known paths for vmlinux kernel images.
132 132
 func findVMLinux() (*os.File, error) {
133
-	release, err := internal.KernelRelease()
133
+	release, err := linux.KernelRelease()
134 134
 	if err != nil {
135 135
 		return nil, err
136 136
 	}
... ...
@@ -18,6 +18,10 @@ type MarshalOptions struct {
18 18
 	Order binary.ByteOrder
19 19
 	// Remove function linkage information for compatibility with <5.6 kernels.
20 20
 	StripFuncLinkage bool
21
+	// Replace decl tags with a placeholder for compatibility with <5.16 kernels.
22
+	ReplaceDeclTags bool
23
+	// Replace TypeTags with a placeholder for compatibility with <5.17 kernels.
24
+	ReplaceTypeTags bool
21 25
 	// Replace Enum64 with a placeholder for compatibility with <6.0 kernels.
22 26
 	ReplaceEnum64 bool
23 27
 	// Prevent the "No type found" error when loading BTF without any types.
... ...
@@ -29,6 +33,8 @@ func KernelMarshalOptions() *MarshalOptions {
29 29
 	return &MarshalOptions{
30 30
 		Order:              internal.NativeEndian,
31 31
 		StripFuncLinkage:   haveFuncLinkage() != nil,
32
+		ReplaceDeclTags:    haveDeclTags() != nil,
33
+		ReplaceTypeTags:    haveTypeTags() != nil,
32 34
 		ReplaceEnum64:      haveEnum64() != nil,
33 35
 		PreventNoTypeFound: true, // All current kernels require this.
34 36
 	}
... ...
@@ -318,15 +324,7 @@ func (e *encoder) deflateType(typ Type) (err error) {
318 318
 		return errors.New("Void is implicit in BTF wire format")
319 319
 
320 320
 	case *Int:
321
-		raw.SetKind(kindInt)
322
-		raw.SetSize(v.Size)
323
-
324
-		var bi btfInt
325
-		bi.SetEncoding(v.Encoding)
326
-		// We need to set bits in addition to size, since btf_type_int_is_regular
327
-		// otherwise flags this as a bitfield.
328
-		bi.SetBits(byte(v.Size) * 8)
329
-		raw.data = bi
321
+		e.deflateInt(&raw, v)
330 322
 
331 323
 	case *Pointer:
332 324
 		raw.SetKind(kindPointer)
... ...
@@ -368,8 +366,7 @@ func (e *encoder) deflateType(typ Type) (err error) {
368 368
 		raw.SetType(e.id(v.Type))
369 369
 
370 370
 	case *Const:
371
-		raw.SetKind(kindConst)
372
-		raw.SetType(e.id(v.Type))
371
+		e.deflateConst(&raw, v)
373 372
 
374 373
 	case *Restrict:
375 374
 		raw.SetKind(kindRestrict)
... ...
@@ -404,15 +401,10 @@ func (e *encoder) deflateType(typ Type) (err error) {
404 404
 		raw.SetSize(v.Size)
405 405
 
406 406
 	case *declTag:
407
-		raw.SetKind(kindDeclTag)
408
-		raw.SetType(e.id(v.Type))
409
-		raw.data = &btfDeclTag{uint32(v.Index)}
410
-		raw.NameOff, err = e.strings.Add(v.Value)
407
+		err = e.deflateDeclTag(&raw, v)
411 408
 
412
-	case *typeTag:
413
-		raw.SetKind(kindTypeTag)
414
-		raw.SetType(e.id(v.Type))
415
-		raw.NameOff, err = e.strings.Add(v.Value)
409
+	case *TypeTag:
410
+		err = e.deflateTypeTag(&raw, v)
416 411
 
417 412
 	default:
418 413
 		return fmt.Errorf("don't know how to deflate %T", v)
... ...
@@ -425,6 +417,57 @@ func (e *encoder) deflateType(typ Type) (err error) {
425 425
 	return raw.Marshal(e.buf, e.Order)
426 426
 }
427 427
 
428
+func (e *encoder) deflateInt(raw *rawType, i *Int) {
429
+	raw.SetKind(kindInt)
430
+	raw.SetSize(i.Size)
431
+
432
+	var bi btfInt
433
+	bi.SetEncoding(i.Encoding)
434
+	// We need to set bits in addition to size, since btf_type_int_is_regular
435
+	// otherwise flags this as a bitfield.
436
+	bi.SetBits(byte(i.Size) * 8)
437
+	raw.data = bi
438
+}
439
+
440
+func (e *encoder) deflateDeclTag(raw *rawType, tag *declTag) (err error) {
441
+	// Replace a decl tag with an integer for compatibility with <5.16 kernels,
442
+	// following libbpf behaviour.
443
+	if e.ReplaceDeclTags {
444
+		typ := &Int{"decl_tag_placeholder", 1, Unsigned}
445
+		e.deflateInt(raw, typ)
446
+
447
+		// Add the placeholder type name to the string table. The encoder added the
448
+		// original type name before this call.
449
+		raw.NameOff, err = e.strings.Add(typ.TypeName())
450
+		return
451
+	}
452
+
453
+	raw.SetKind(kindDeclTag)
454
+	raw.SetType(e.id(tag.Type))
455
+	raw.data = &btfDeclTag{uint32(tag.Index)}
456
+	raw.NameOff, err = e.strings.Add(tag.Value)
457
+	return
458
+}
459
+
460
+func (e *encoder) deflateConst(raw *rawType, c *Const) {
461
+	raw.SetKind(kindConst)
462
+	raw.SetType(e.id(c.Type))
463
+}
464
+
465
+func (e *encoder) deflateTypeTag(raw *rawType, tag *TypeTag) (err error) {
466
+	// Replace a type tag with a const qualifier for compatibility with <5.17
467
+	// kernels, following libbpf behaviour.
468
+	if e.ReplaceTypeTags {
469
+		e.deflateConst(raw, &Const{tag.Type})
470
+		return
471
+	}
472
+
473
+	raw.SetKind(kindTypeTag)
474
+	raw.SetType(e.id(tag.Type))
475
+	raw.NameOff, err = e.strings.Add(tag.Value)
476
+	return
477
+}
478
+
428 479
 func (e *encoder) deflateUnion(raw *rawType, union *Union) (err error) {
429 480
 	raw.SetKind(kindUnion)
430 481
 	raw.SetSize(union.Size)
... ...
@@ -521,7 +564,7 @@ func (e *encoder) deflateEnum64(raw *rawType, enum *Enum) (err error) {
521 521
 			})
522 522
 		}
523 523
 
524
-		return e.deflateUnion(raw, &Union{enum.Name, enum.Size, members})
524
+		return e.deflateUnion(raw, &Union{enum.Name, enum.Size, members, nil})
525 525
 	}
526 526
 
527 527
 	raw.SetKind(kindEnum64)
... ...
@@ -40,9 +40,12 @@ func children(typ Type, yield func(child *Type) bool) bool {
40 40
 	// Explicitly type switch on the most common types to allow the inliner to
41 41
 	// do its work. This avoids allocating intermediate slices from walk() on
42 42
 	// the heap.
43
+	var tags []string
43 44
 	switch v := typ.(type) {
44
-	case *Void, *Int, *Enum, *Fwd, *Float:
45
+	case *Void, *Int, *Enum, *Fwd, *Float, *declTag:
45 46
 		// No children to traverse.
47
+		// declTags is declared as a leaf type since it's parsed into .Tags fields of other types
48
+		// during unmarshaling.
46 49
 	case *Pointer:
47 50
 		if !yield(&v.Target) {
48 51
 			return false
... ...
@@ -59,17 +62,32 @@ func children(typ Type, yield func(child *Type) bool) bool {
59 59
 			if !yield(&v.Members[i].Type) {
60 60
 				return false
61 61
 			}
62
+			for _, t := range v.Members[i].Tags {
63
+				var tag Type = &declTag{v, t, i}
64
+				if !yield(&tag) {
65
+					return false
66
+				}
67
+			}
62 68
 		}
69
+		tags = v.Tags
63 70
 	case *Union:
64 71
 		for i := range v.Members {
65 72
 			if !yield(&v.Members[i].Type) {
66 73
 				return false
67 74
 			}
75
+			for _, t := range v.Members[i].Tags {
76
+				var tag Type = &declTag{v, t, i}
77
+				if !yield(&tag) {
78
+					return false
79
+				}
80
+			}
68 81
 		}
82
+		tags = v.Tags
69 83
 	case *Typedef:
70 84
 		if !yield(&v.Type) {
71 85
 			return false
72 86
 		}
87
+		tags = v.Tags
73 88
 	case *Volatile:
74 89
 		if !yield(&v.Type) {
75 90
 			return false
... ...
@@ -86,6 +104,20 @@ func children(typ Type, yield func(child *Type) bool) bool {
86 86
 		if !yield(&v.Type) {
87 87
 			return false
88 88
 		}
89
+		if fp, ok := v.Type.(*FuncProto); ok {
90
+			for i := range fp.Params {
91
+				if len(v.ParamTags) <= i {
92
+					continue
93
+				}
94
+				for _, t := range v.ParamTags[i] {
95
+					var tag Type = &declTag{v, t, i}
96
+					if !yield(&tag) {
97
+						return false
98
+					}
99
+				}
100
+			}
101
+		}
102
+		tags = v.Tags
89 103
 	case *FuncProto:
90 104
 		if !yield(&v.Return) {
91 105
 			return false
... ...
@@ -99,17 +131,14 @@ func children(typ Type, yield func(child *Type) bool) bool {
99 99
 		if !yield(&v.Type) {
100 100
 			return false
101 101
 		}
102
+		tags = v.Tags
102 103
 	case *Datasec:
103 104
 		for i := range v.Vars {
104 105
 			if !yield(&v.Vars[i].Type) {
105 106
 				return false
106 107
 			}
107 108
 		}
108
-	case *declTag:
109
-		if !yield(&v.Type) {
110
-			return false
111
-		}
112
-	case *typeTag:
109
+	case *TypeTag:
113 110
 		if !yield(&v.Type) {
114 111
 			return false
115 112
 		}
... ...
@@ -119,5 +148,12 @@ func children(typ Type, yield func(child *Type) bool) bool {
119 119
 		panic(fmt.Sprintf("don't know how to walk Type %T", v))
120 120
 	}
121 121
 
122
+	for _, t := range tags {
123
+		var tag Type = &declTag{typ, t, -1}
124
+		if !yield(&tag) {
125
+			return false
126
+		}
127
+	}
128
+
122 129
 	return true
123 130
 }
... ...
@@ -67,7 +67,7 @@ var (
67 67
 	_ Type = (*Datasec)(nil)
68 68
 	_ Type = (*Float)(nil)
69 69
 	_ Type = (*declTag)(nil)
70
-	_ Type = (*typeTag)(nil)
70
+	_ Type = (*TypeTag)(nil)
71 71
 	_ Type = (*cycle)(nil)
72 72
 )
73 73
 
... ...
@@ -169,6 +169,7 @@ type Struct struct {
169 169
 	// The size of the struct including padding, in bytes
170 170
 	Size    uint32
171 171
 	Members []Member
172
+	Tags    []string
172 173
 }
173 174
 
174 175
 func (s *Struct) Format(fs fmt.State, verb rune) {
... ...
@@ -182,6 +183,7 @@ func (s *Struct) size() uint32 { return s.Size }
182 182
 func (s *Struct) copy() Type {
183 183
 	cpy := *s
184 184
 	cpy.Members = copyMembers(s.Members)
185
+	cpy.Tags = copyTags(cpy.Tags)
185 186
 	return &cpy
186 187
 }
187 188
 
... ...
@@ -195,6 +197,7 @@ type Union struct {
195 195
 	// The size of the union including padding, in bytes.
196 196
 	Size    uint32
197 197
 	Members []Member
198
+	Tags    []string
198 199
 }
199 200
 
200 201
 func (u *Union) Format(fs fmt.State, verb rune) {
... ...
@@ -208,6 +211,7 @@ func (u *Union) size() uint32 { return u.Size }
208 208
 func (u *Union) copy() Type {
209 209
 	cpy := *u
210 210
 	cpy.Members = copyMembers(u.Members)
211
+	cpy.Tags = copyTags(cpy.Tags)
211 212
 	return &cpy
212 213
 }
213 214
 
... ...
@@ -218,6 +222,18 @@ func (u *Union) members() []Member {
218 218
 func copyMembers(orig []Member) []Member {
219 219
 	cpy := make([]Member, len(orig))
220 220
 	copy(cpy, orig)
221
+	for i, member := range cpy {
222
+		cpy[i].Tags = copyTags(member.Tags)
223
+	}
224
+	return cpy
225
+}
226
+
227
+func copyTags(orig []string) []string {
228
+	if orig == nil { // preserve nil vs zero-len slice distinction
229
+		return nil
230
+	}
231
+	cpy := make([]string, len(orig))
232
+	copy(cpy, orig)
221 233
 	return cpy
222 234
 }
223 235
 
... ...
@@ -247,6 +263,7 @@ type Member struct {
247 247
 	Type         Type
248 248
 	Offset       Bits
249 249
 	BitfieldSize Bits
250
+	Tags         []string
250 251
 }
251 252
 
252 253
 // Enum lists possible values.
... ...
@@ -334,6 +351,7 @@ func (f *Fwd) matches(typ Type) bool {
334 334
 type Typedef struct {
335 335
 	Name string
336 336
 	Type Type
337
+	Tags []string
337 338
 }
338 339
 
339 340
 func (td *Typedef) Format(fs fmt.State, verb rune) {
... ...
@@ -344,6 +362,7 @@ func (td *Typedef) TypeName() string { return td.Name }
344 344
 
345 345
 func (td *Typedef) copy() Type {
346 346
 	cpy := *td
347
+	cpy.Tags = copyTags(td.Tags)
347 348
 	return &cpy
348 349
 }
349 350
 
... ...
@@ -403,6 +422,12 @@ type Func struct {
403 403
 	Name    string
404 404
 	Type    Type
405 405
 	Linkage FuncLinkage
406
+	Tags    []string
407
+	// ParamTags holds a list of tags for each parameter of the FuncProto to which `Type` points.
408
+	// If no tags are present for any param, the outer slice will be nil/len(ParamTags)==0.
409
+	// If at least 1 param has a tag, the outer slice will have the same length as the number of params.
410
+	// The inner slice contains the tags and may be nil/len(ParamTags[i])==0 if no tags are present for that param.
411
+	ParamTags [][]string
406 412
 }
407 413
 
408 414
 func FuncMetadata(ins *asm.Instruction) *Func {
... ...
@@ -424,6 +449,14 @@ func (f *Func) TypeName() string { return f.Name }
424 424
 
425 425
 func (f *Func) copy() Type {
426 426
 	cpy := *f
427
+	cpy.Tags = copyTags(f.Tags)
428
+	if f.ParamTags != nil { // preserve nil vs zero-len slice distinction
429
+		ptCopy := make([][]string, len(f.ParamTags))
430
+		for i, tags := range f.ParamTags {
431
+			ptCopy[i] = copyTags(tags)
432
+		}
433
+		cpy.ParamTags = ptCopy
434
+	}
427 435
 	return &cpy
428 436
 }
429 437
 
... ...
@@ -456,6 +489,7 @@ type Var struct {
456 456
 	Name    string
457 457
 	Type    Type
458 458
 	Linkage VarLinkage
459
+	Tags    []string
459 460
 }
460 461
 
461 462
 func (v *Var) Format(fs fmt.State, verb rune) {
... ...
@@ -466,6 +500,7 @@ func (v *Var) TypeName() string { return v.Name }
466 466
 
467 467
 func (v *Var) copy() Type {
468 468
 	cpy := *v
469
+	cpy.Tags = copyTags(v.Tags)
469 470
 	return &cpy
470 471
 }
471 472
 
... ...
@@ -540,19 +575,25 @@ func (dt *declTag) copy() Type {
540 540
 	return &cpy
541 541
 }
542 542
 
543
-// typeTag associates metadata with a type.
544
-type typeTag struct {
543
+// TypeTag associates metadata with a pointer type. Tag types act as a custom
544
+// modifier(const, restrict, volatile) for the target type. Unlike declTags,
545
+// TypeTags are ordered so the order in which they are added matters.
546
+//
547
+// One of their uses is to mark pointers as `__kptr` meaning a pointer points
548
+// to kernel memory. Adding a `__kptr` to pointers in map values allows you
549
+// to store pointers to kernel memory in maps.
550
+type TypeTag struct {
545 551
 	Type  Type
546 552
 	Value string
547 553
 }
548 554
 
549
-func (tt *typeTag) Format(fs fmt.State, verb rune) {
555
+func (tt *TypeTag) Format(fs fmt.State, verb rune) {
550 556
 	formatType(fs, verb, tt, "type=", tt.Type, "value=", tt.Value)
551 557
 }
552 558
 
553
-func (tt *typeTag) TypeName() string { return "" }
554
-func (tt *typeTag) qualify() Type    { return tt.Type }
555
-func (tt *typeTag) copy() Type {
559
+func (tt *TypeTag) TypeName() string { return "" }
560
+func (tt *TypeTag) qualify() Type    { return tt.Type }
561
+func (tt *TypeTag) copy() Type {
556 562
 	cpy := *tt
557 563
 	return &cpy
558 564
 }
... ...
@@ -591,7 +632,7 @@ var (
591 591
 	_ qualifier = (*Const)(nil)
592 592
 	_ qualifier = (*Restrict)(nil)
593 593
 	_ qualifier = (*Volatile)(nil)
594
-	_ qualifier = (*typeTag)(nil)
594
+	_ qualifier = (*TypeTag)(nil)
595 595
 )
596 596
 
597 597
 var errUnsizedType = errors.New("type is unsized")
... ...
@@ -918,7 +959,7 @@ func readAndInflateTypes(r io.Reader, bo binary.ByteOrder, typeLen uint32, rawSt
918 918
 			if err != nil {
919 919
 				return nil, fmt.Errorf("struct %s (id %d): %w", name, id, err)
920 920
 			}
921
-			typ = &Struct{name, header.Size(), members}
921
+			typ = &Struct{name, header.Size(), members, nil}
922 922
 
923 923
 		case kindUnion:
924 924
 			vlen := header.Vlen()
... ...
@@ -935,7 +976,7 @@ func readAndInflateTypes(r io.Reader, bo binary.ByteOrder, typeLen uint32, rawSt
935 935
 			if err != nil {
936 936
 				return nil, fmt.Errorf("union %s (id %d): %w", name, id, err)
937 937
 			}
938
-			typ = &Union{name, header.Size(), members}
938
+			typ = &Union{name, header.Size(), members, nil}
939 939
 
940 940
 		case kindEnum:
941 941
 			vlen := header.Vlen()
... ...
@@ -968,7 +1009,7 @@ func readAndInflateTypes(r io.Reader, bo binary.ByteOrder, typeLen uint32, rawSt
968 968
 			typ = &Fwd{name, header.FwdKind()}
969 969
 
970 970
 		case kindTypedef:
971
-			typedef := &Typedef{name, nil}
971
+			typedef := &Typedef{name, nil, nil}
972 972
 			fixup(header.Type(), &typedef.Type)
973 973
 			typ = typedef
974 974
 
... ...
@@ -988,7 +1029,7 @@ func readAndInflateTypes(r io.Reader, bo binary.ByteOrder, typeLen uint32, rawSt
988 988
 			typ = restrict
989 989
 
990 990
 		case kindFunc:
991
-			fn := &Func{name, nil, header.Linkage()}
991
+			fn := &Func{name, nil, header.Linkage(), nil, nil}
992 992
 			fixup(header.Type(), &fn.Type)
993 993
 			typ = fn
994 994
 
... ...
@@ -1030,7 +1071,7 @@ func readAndInflateTypes(r io.Reader, bo binary.ByteOrder, typeLen uint32, rawSt
1030 1030
 				return nil, fmt.Errorf("can't read btfVariable, id: %d: %w", id, err)
1031 1031
 			}
1032 1032
 
1033
-			v := &Var{name, nil, VarLinkage(bVariable.Linkage)}
1033
+			v := &Var{name, nil, VarLinkage(bVariable.Linkage), nil}
1034 1034
 			fixup(header.Type(), &v.Type)
1035 1035
 			typ = v
1036 1036
 
... ...
@@ -1081,7 +1122,7 @@ func readAndInflateTypes(r io.Reader, bo binary.ByteOrder, typeLen uint32, rawSt
1081 1081
 			declTags = append(declTags, dt)
1082 1082
 
1083 1083
 		case kindTypeTag:
1084
-			tt := &typeTag{nil, name}
1084
+			tt := &TypeTag{nil, name}
1085 1085
 			fixup(header.Type(), &tt.Type)
1086 1086
 			typ = tt
1087 1087
 
... ...
@@ -1142,26 +1183,69 @@ func readAndInflateTypes(r io.Reader, bo binary.ByteOrder, typeLen uint32, rawSt
1142 1142
 
1143 1143
 	for _, dt := range declTags {
1144 1144
 		switch t := dt.Type.(type) {
1145
-		case *Var, *Typedef:
1145
+		case *Var:
1146
+			if dt.Index != -1 {
1147
+				return nil, fmt.Errorf("type %s: component idx %d is not -1", dt, dt.Index)
1148
+			}
1149
+			t.Tags = append(t.Tags, dt.Value)
1150
+
1151
+		case *Typedef:
1146 1152
 			if dt.Index != -1 {
1147
-				return nil, fmt.Errorf("type %s: index %d is not -1", dt, dt.Index)
1153
+				return nil, fmt.Errorf("type %s: component idx %d is not -1", dt, dt.Index)
1148 1154
 			}
1155
+			t.Tags = append(t.Tags, dt.Value)
1149 1156
 
1150 1157
 		case composite:
1151
-			if dt.Index >= len(t.members()) {
1152
-				return nil, fmt.Errorf("type %s: index %d exceeds members of %s", dt, dt.Index, t)
1158
+			if dt.Index >= 0 {
1159
+				members := t.members()
1160
+				if dt.Index >= len(members) {
1161
+					return nil, fmt.Errorf("type %s: component idx %d exceeds members of %s", dt, dt.Index, t)
1162
+				}
1163
+
1164
+				members[dt.Index].Tags = append(members[dt.Index].Tags, dt.Value)
1165
+				continue
1166
+			}
1167
+
1168
+			if dt.Index == -1 {
1169
+				switch t2 := t.(type) {
1170
+				case *Struct:
1171
+					t2.Tags = append(t2.Tags, dt.Value)
1172
+				case *Union:
1173
+					t2.Tags = append(t2.Tags, dt.Value)
1174
+				}
1175
+
1176
+				continue
1153 1177
 			}
1154 1178
 
1179
+			return nil, fmt.Errorf("type %s: decl tag for type %s has invalid component idx", dt, t)
1180
+
1155 1181
 		case *Func:
1156 1182
 			fp, ok := t.Type.(*FuncProto)
1157 1183
 			if !ok {
1158 1184
 				return nil, fmt.Errorf("type %s: %s is not a FuncProto", dt, t.Type)
1159 1185
 			}
1160 1186
 
1161
-			if dt.Index >= len(fp.Params) {
1162
-				return nil, fmt.Errorf("type %s: index %d exceeds params of %s", dt, dt.Index, t)
1187
+			// Ensure the number of argument tag lists equals the number of arguments
1188
+			if len(t.ParamTags) == 0 {
1189
+				t.ParamTags = make([][]string, len(fp.Params))
1190
+			}
1191
+
1192
+			if dt.Index >= 0 {
1193
+				if dt.Index >= len(fp.Params) {
1194
+					return nil, fmt.Errorf("type %s: component idx %d exceeds params of %s", dt, dt.Index, t)
1195
+				}
1196
+
1197
+				t.ParamTags[dt.Index] = append(t.ParamTags[dt.Index], dt.Value)
1198
+				continue
1163 1199
 			}
1164 1200
 
1201
+			if dt.Index == -1 {
1202
+				t.Tags = append(t.Tags, dt.Value)
1203
+				continue
1204
+			}
1205
+
1206
+			return nil, fmt.Errorf("type %s: decl tag for type %s has invalid component idx", dt, t)
1207
+
1165 1208
 		default:
1166 1209
 			return nil, fmt.Errorf("type %s: decl tag for type %s is not supported", dt, t)
1167 1210
 		}
... ...
@@ -1207,6 +1291,20 @@ func UnderlyingType(typ Type) Type {
1207 1207
 	return &cycle{typ}
1208 1208
 }
1209 1209
 
1210
+// QualifiedType returns the type with all qualifiers removed.
1211
+func QualifiedType(typ Type) Type {
1212
+	result := typ
1213
+	for depth := 0; depth <= maxResolveDepth; depth++ {
1214
+		switch v := (result).(type) {
1215
+		case qualifier:
1216
+			result = v.qualify()
1217
+		default:
1218
+			return result
1219
+		}
1220
+	}
1221
+	return &cycle{typ}
1222
+}
1223
+
1210 1224
 // As returns typ if is of type T. Otherwise it peels qualifiers and Typedefs
1211 1225
 // until it finds a T.
1212 1226
 //
... ...
@@ -12,7 +12,7 @@ func datasecResolveWorkaround(b *Builder, ds *Datasec) error {
12 12
 		}
13 13
 
14 14
 		switch v.Type.(type) {
15
-		case *Typedef, *Volatile, *Const, *Restrict, *typeTag:
15
+		case *Typedef, *Volatile, *Const, *Restrict, *TypeTag:
16 16
 			// NB: We must never call Add on a Datasec, otherwise we risk
17 17
 			// infinite recursion.
18 18
 			_, err := b.Add(v.Type)
... ...
@@ -10,8 +10,10 @@ import (
10 10
 	"github.com/cilium/ebpf/asm"
11 11
 	"github.com/cilium/ebpf/btf"
12 12
 	"github.com/cilium/ebpf/internal"
13
+	"github.com/cilium/ebpf/internal/kallsyms"
13 14
 	"github.com/cilium/ebpf/internal/kconfig"
14
-	"github.com/cilium/ebpf/internal/sysenc"
15
+	"github.com/cilium/ebpf/internal/linux"
16
+	"github.com/cilium/ebpf/internal/sys"
15 17
 )
16 18
 
17 19
 // CollectionOptions control loading a collection into the kernel.
... ...
@@ -38,6 +40,11 @@ type CollectionSpec struct {
38 38
 	Maps     map[string]*MapSpec
39 39
 	Programs map[string]*ProgramSpec
40 40
 
41
+	// Variables refer to global variables declared in the ELF. They can be read
42
+	// and modified freely before loading the Collection. Modifying them after
43
+	// loading has no effect on a running eBPF program.
44
+	Variables map[string]*VariableSpec
45
+
41 46
 	// Types holds type information about Maps and Programs.
42 47
 	// Modifications to Types are currently undefined behaviour.
43 48
 	Types *btf.Spec
... ...
@@ -56,6 +63,7 @@ func (cs *CollectionSpec) Copy() *CollectionSpec {
56 56
 	cpy := CollectionSpec{
57 57
 		Maps:      make(map[string]*MapSpec, len(cs.Maps)),
58 58
 		Programs:  make(map[string]*ProgramSpec, len(cs.Programs)),
59
+		Variables: make(map[string]*VariableSpec, len(cs.Variables)),
59 60
 		ByteOrder: cs.ByteOrder,
60 61
 		Types:     cs.Types.Copy(),
61 62
 	}
... ...
@@ -68,6 +76,10 @@ func (cs *CollectionSpec) Copy() *CollectionSpec {
68 68
 		cpy.Programs[name] = spec.Copy()
69 69
 	}
70 70
 
71
+	for name, spec := range cs.Variables {
72
+		cpy.Variables[name] = spec.copy(&cpy)
73
+	}
74
+
71 75
 	return &cpy
72 76
 }
73 77
 
... ...
@@ -134,65 +146,24 @@ func (m *MissingConstantsError) Error() string {
134 134
 // From Linux 5.5 the verifier will use constants to eliminate dead code.
135 135
 //
136 136
 // Returns an error wrapping [MissingConstantsError] if a constant doesn't exist.
137
+//
138
+// Deprecated: Use [CollectionSpec.Variables] to interact with constants instead.
139
+// RewriteConstants is now a wrapper around the VariableSpec API.
137 140
 func (cs *CollectionSpec) RewriteConstants(consts map[string]interface{}) error {
138
-	replaced := make(map[string]bool)
139
-
140
-	for name, spec := range cs.Maps {
141
-		if !strings.HasPrefix(name, ".rodata") {
141
+	var missing []string
142
+	for n, c := range consts {
143
+		v, ok := cs.Variables[n]
144
+		if !ok {
145
+			missing = append(missing, n)
142 146
 			continue
143 147
 		}
144 148
 
145
-		b, ds, err := spec.dataSection()
146
-		if errors.Is(err, errMapNoBTFValue) {
147
-			// Data sections without a BTF Datasec are valid, but don't support
148
-			// constant replacements.
149
-			continue
150
-		}
151
-		if err != nil {
152
-			return fmt.Errorf("map %s: %w", name, err)
149
+		if !v.Constant() {
150
+			return fmt.Errorf("variable %s is not a constant", n)
153 151
 		}
154 152
 
155
-		// MapSpec.Copy() performs a shallow copy. Fully copy the byte slice
156
-		// to avoid any changes affecting other copies of the MapSpec.
157
-		cpy := make([]byte, len(b))
158
-		copy(cpy, b)
159
-
160
-		for _, v := range ds.Vars {
161
-			vname := v.Type.TypeName()
162
-			replacement, ok := consts[vname]
163
-			if !ok {
164
-				continue
165
-			}
166
-
167
-			if _, ok := v.Type.(*btf.Var); !ok {
168
-				return fmt.Errorf("section %s: unexpected type %T for variable %s", name, v.Type, vname)
169
-			}
170
-
171
-			if replaced[vname] {
172
-				return fmt.Errorf("section %s: duplicate variable %s", name, vname)
173
-			}
174
-
175
-			if int(v.Offset+v.Size) > len(cpy) {
176
-				return fmt.Errorf("section %s: offset %d(+%d) for variable %s is out of bounds", name, v.Offset, v.Size, vname)
177
-			}
178
-
179
-			b, err := sysenc.Marshal(replacement, int(v.Size))
180
-			if err != nil {
181
-				return fmt.Errorf("marshaling constant replacement %s: %w", vname, err)
182
-			}
183
-
184
-			b.CopyTo(cpy[v.Offset : v.Offset+v.Size])
185
-
186
-			replaced[vname] = true
187
-		}
188
-
189
-		spec.Contents[0] = MapKV{Key: uint32(0), Value: cpy}
190
-	}
191
-
192
-	var missing []string
193
-	for c := range consts {
194
-		if !replaced[c] {
195
-			missing = append(missing, c)
153
+		if err := v.Set(c); err != nil {
154
+			return fmt.Errorf("rewriting constant %s: %w", n, err)
196 155
 		}
197 156
 	}
198 157
 
... ...
@@ -210,25 +181,23 @@ func (cs *CollectionSpec) RewriteConstants(consts map[string]interface{}) error
210 210
 // if this sounds useful.
211 211
 //
212 212
 // 'to' must be a pointer to a struct. A field of the
213
-// struct is updated with values from Programs or Maps if it
214
-// has an `ebpf` tag and its type is *ProgramSpec or *MapSpec.
213
+// struct is updated with values from Programs, Maps or Variables if it
214
+// has an `ebpf` tag and its type is *ProgramSpec, *MapSpec or *VariableSpec.
215 215
 // The tag's value specifies the name of the program or map as
216 216
 // found in the CollectionSpec.
217 217
 //
218 218
 //	struct {
219
-//	    Foo     *ebpf.ProgramSpec `ebpf:"xdp_foo"`
220
-//	    Bar     *ebpf.MapSpec     `ebpf:"bar_map"`
219
+//	    Foo     *ebpf.ProgramSpec  `ebpf:"xdp_foo"`
220
+//	    Bar     *ebpf.MapSpec      `ebpf:"bar_map"`
221
+//	    Var     *ebpf.VariableSpec `ebpf:"some_var"`
221 222
 //	    Ignored int
222 223
 //	}
223 224
 //
224 225
 // Returns an error if any of the eBPF objects can't be found, or
225
-// if the same MapSpec or ProgramSpec is assigned multiple times.
226
+// if the same Spec is assigned multiple times.
226 227
 func (cs *CollectionSpec) Assign(to interface{}) error {
227
-	// Assign() only supports assigning ProgramSpecs and MapSpecs,
228
-	// so doesn't load any resources into the kernel.
229 228
 	getValue := func(typ reflect.Type, name string) (interface{}, error) {
230 229
 		switch typ {
231
-
232 230
 		case reflect.TypeOf((*ProgramSpec)(nil)):
233 231
 			if p := cs.Programs[name]; p != nil {
234 232
 				return p, nil
... ...
@@ -241,6 +210,12 @@ func (cs *CollectionSpec) Assign(to interface{}) error {
241 241
 			}
242 242
 			return nil, fmt.Errorf("missing map %q", name)
243 243
 
244
+		case reflect.TypeOf((*VariableSpec)(nil)):
245
+			if v := cs.Variables[name]; v != nil {
246
+				return v, nil
247
+			}
248
+			return nil, fmt.Errorf("missing variable %q", name)
249
+
244 250
 		default:
245 251
 			return nil, fmt.Errorf("unsupported type %s", typ)
246 252
 		}
... ...
@@ -286,6 +261,7 @@ func (cs *CollectionSpec) LoadAndAssign(to interface{}, opts *CollectionOptions)
286 286
 	// Support assigning Programs and Maps, lazy-loading the required objects.
287 287
 	assignedMaps := make(map[string]bool)
288 288
 	assignedProgs := make(map[string]bool)
289
+	assignedVars := make(map[string]bool)
289 290
 
290 291
 	getValue := func(typ reflect.Type, name string) (interface{}, error) {
291 292
 		switch typ {
... ...
@@ -298,6 +274,10 @@ func (cs *CollectionSpec) LoadAndAssign(to interface{}, opts *CollectionOptions)
298 298
 			assignedMaps[name] = true
299 299
 			return loader.loadMap(name)
300 300
 
301
+		case reflect.TypeOf((*Variable)(nil)):
302
+			assignedVars[name] = true
303
+			return loader.loadVariable(name)
304
+
301 305
 		default:
302 306
 			return nil, fmt.Errorf("unsupported type %s", typ)
303 307
 		}
... ...
@@ -338,15 +318,22 @@ func (cs *CollectionSpec) LoadAndAssign(to interface{}, opts *CollectionOptions)
338 338
 	for p := range assignedProgs {
339 339
 		delete(loader.programs, p)
340 340
 	}
341
+	for p := range assignedVars {
342
+		delete(loader.vars, p)
343
+	}
341 344
 
342 345
 	return nil
343 346
 }
344 347
 
345
-// Collection is a collection of Programs and Maps associated
346
-// with their symbols
348
+// Collection is a collection of live BPF resources present in the kernel.
347 349
 type Collection struct {
348 350
 	Programs map[string]*Program
349 351
 	Maps     map[string]*Map
352
+
353
+	// Variables contains global variables used by the Collection's program(s). On
354
+	// kernels older than 5.5, most interactions with Variables return
355
+	// [ErrNotSupported].
356
+	Variables map[string]*Variable
350 357
 }
351 358
 
352 359
 // NewCollection creates a Collection from the given spec, creating and
... ...
@@ -387,19 +374,26 @@ func NewCollectionWithOptions(spec *CollectionSpec, opts CollectionOptions) (*Co
387 387
 		}
388 388
 	}
389 389
 
390
+	for varName := range spec.Variables {
391
+		if _, err := loader.loadVariable(varName); err != nil {
392
+			return nil, err
393
+		}
394
+	}
395
+
390 396
 	// Maps can contain Program and Map stubs, so populate them after
391 397
 	// all Maps and Programs have been successfully loaded.
392 398
 	if err := loader.populateDeferredMaps(); err != nil {
393 399
 		return nil, err
394 400
 	}
395 401
 
396
-	// Prevent loader.cleanup from closing maps and programs.
397
-	maps, progs := loader.maps, loader.programs
398
-	loader.maps, loader.programs = nil, nil
402
+	// Prevent loader.cleanup from closing maps, programs and vars.
403
+	maps, progs, vars := loader.maps, loader.programs, loader.vars
404
+	loader.maps, loader.programs, loader.vars = nil, nil, nil
399 405
 
400 406
 	return &Collection{
401 407
 		progs,
402 408
 		maps,
409
+		vars,
403 410
 	}, nil
404 411
 }
405 412
 
... ...
@@ -408,6 +402,7 @@ type collectionLoader struct {
408 408
 	opts     *CollectionOptions
409 409
 	maps     map[string]*Map
410 410
 	programs map[string]*Program
411
+	vars     map[string]*Variable
411 412
 }
412 413
 
413 414
 func newCollectionLoader(coll *CollectionSpec, opts *CollectionOptions) (*collectionLoader, error) {
... ...
@@ -416,15 +411,14 @@ func newCollectionLoader(coll *CollectionSpec, opts *CollectionOptions) (*collec
416 416
 	}
417 417
 
418 418
 	// Check for existing MapSpecs in the CollectionSpec for all provided replacement maps.
419
-	for name, m := range opts.MapReplacements {
420
-		spec, ok := coll.Maps[name]
421
-		if !ok {
419
+	for name := range opts.MapReplacements {
420
+		if _, ok := coll.Maps[name]; !ok {
422 421
 			return nil, fmt.Errorf("replacement map %s not found in CollectionSpec", name)
423 422
 		}
423
+	}
424 424
 
425
-		if err := spec.Compatible(m); err != nil {
426
-			return nil, fmt.Errorf("using replacement map %s: %w", spec.Name, err)
427
-		}
425
+	if err := populateKallsyms(coll.Programs); err != nil {
426
+		return nil, fmt.Errorf("populating kallsyms caches: %w", err)
428 427
 	}
429 428
 
430 429
 	return &collectionLoader{
... ...
@@ -432,9 +426,49 @@ func newCollectionLoader(coll *CollectionSpec, opts *CollectionOptions) (*collec
432 432
 		opts,
433 433
 		make(map[string]*Map),
434 434
 		make(map[string]*Program),
435
+		make(map[string]*Variable),
435 436
 	}, nil
436 437
 }
437 438
 
439
+// populateKallsyms populates kallsyms caches, making lookups cheaper later on
440
+// during individual program loading. Since we have less context available
441
+// at those stages, we batch the lookups here instead to avoid redundant work.
442
+func populateKallsyms(progs map[string]*ProgramSpec) error {
443
+	// Look up associated kernel modules for all symbols referenced by
444
+	// ProgramSpec.AttachTo for program types that support attaching to kmods.
445
+	mods := make(map[string]string)
446
+	for _, p := range progs {
447
+		if p.AttachTo != "" && p.targetsKernelModule() {
448
+			mods[p.AttachTo] = ""
449
+		}
450
+	}
451
+	if len(mods) != 0 {
452
+		if err := kallsyms.AssignModules(mods); err != nil {
453
+			return fmt.Errorf("getting modules from kallsyms: %w", err)
454
+		}
455
+	}
456
+
457
+	// Look up addresses of all kernel symbols referenced by all programs.
458
+	addrs := make(map[string]uint64)
459
+	for _, p := range progs {
460
+		iter := p.Instructions.Iterate()
461
+		for iter.Next() {
462
+			ins := iter.Ins
463
+			meta, _ := ins.Metadata.Get(ksymMetaKey{}).(*ksymMeta)
464
+			if meta != nil {
465
+				addrs[meta.Name] = 0
466
+			}
467
+		}
468
+	}
469
+	if len(addrs) != 0 {
470
+		if err := kallsyms.AssignAddresses(addrs); err != nil {
471
+			return fmt.Errorf("getting addresses from kallsyms: %w", err)
472
+		}
473
+	}
474
+
475
+	return nil
476
+}
477
+
438 478
 // close all resources left over in the collectionLoader.
439 479
 func (cl *collectionLoader) close() {
440 480
 	for _, m := range cl.maps {
... ...
@@ -455,7 +489,22 @@ func (cl *collectionLoader) loadMap(mapName string) (*Map, error) {
455 455
 		return nil, fmt.Errorf("missing map %s", mapName)
456 456
 	}
457 457
 
458
+	mapSpec = mapSpec.Copy()
459
+
460
+	// Defer setting the mmapable flag on maps until load time. This avoids the
461
+	// MapSpec having different flags on some kernel versions. Also avoid running
462
+	// syscalls during ELF loading, so platforms like wasm can also parse an ELF.
463
+	if isDataSection(mapSpec.Name) && haveMmapableMaps() == nil {
464
+		mapSpec.Flags |= sys.BPF_F_MMAPABLE
465
+	}
466
+
458 467
 	if replaceMap, ok := cl.opts.MapReplacements[mapName]; ok {
468
+		// Check compatibility with the replacement map after setting
469
+		// feature-dependent map flags.
470
+		if err := mapSpec.Compatible(replaceMap); err != nil {
471
+			return nil, fmt.Errorf("using replacement map %s: %w", mapSpec.Name, err)
472
+		}
473
+
459 474
 		// Clone the map to avoid closing user's map later on.
460 475
 		m, err := replaceMap.Clone()
461 476
 		if err != nil {
... ...
@@ -537,6 +586,58 @@ func (cl *collectionLoader) loadProgram(progName string) (*Program, error) {
537 537
 	return prog, nil
538 538
 }
539 539
 
540
+func (cl *collectionLoader) loadVariable(varName string) (*Variable, error) {
541
+	if v := cl.vars[varName]; v != nil {
542
+		return v, nil
543
+	}
544
+
545
+	varSpec := cl.coll.Variables[varName]
546
+	if varSpec == nil {
547
+		return nil, fmt.Errorf("unknown variable %s", varName)
548
+	}
549
+
550
+	// Get the key of the VariableSpec's MapSpec in the CollectionSpec.
551
+	var mapName string
552
+	for n, ms := range cl.coll.Maps {
553
+		if ms == varSpec.m {
554
+			mapName = n
555
+			break
556
+		}
557
+	}
558
+	if mapName == "" {
559
+		return nil, fmt.Errorf("variable %s: underlying MapSpec %s was removed from CollectionSpec", varName, varSpec.m.Name)
560
+	}
561
+
562
+	m, err := cl.loadMap(mapName)
563
+	if err != nil {
564
+		return nil, fmt.Errorf("variable %s: %w", varName, err)
565
+	}
566
+
567
+	// If the kernel is too old or the underlying map was created without
568
+	// BPF_F_MMAPABLE, [Map.Memory] will return ErrNotSupported. In this case,
569
+	// emit a Variable with a nil Memory. This keeps Collection{Spec}.Variables
570
+	// consistent across systems with different feature sets without breaking
571
+	// LoadAndAssign.
572
+	mm, err := m.Memory()
573
+	if err != nil && !errors.Is(err, ErrNotSupported) {
574
+		return nil, fmt.Errorf("variable %s: getting memory for map %s: %w", varName, mapName, err)
575
+	}
576
+
577
+	v, err := newVariable(
578
+		varSpec.name,
579
+		varSpec.offset,
580
+		varSpec.size,
581
+		varSpec.t,
582
+		mm,
583
+	)
584
+	if err != nil {
585
+		return nil, fmt.Errorf("variable %s: %w", varName, err)
586
+	}
587
+
588
+	cl.vars[varName] = v
589
+	return v, nil
590
+}
591
+
540 592
 // populateDeferredMaps iterates maps holding programs or other maps and loads
541 593
 // any dependencies. Populates all maps in cl and freezes them if specified.
542 594
 func (cl *collectionLoader) populateDeferredMaps() error {
... ...
@@ -603,6 +704,7 @@ func resolveKconfig(m *MapSpec) error {
603 603
 
604 604
 	type configInfo struct {
605 605
 		offset uint32
606
+		size   uint32
606 607
 		typ    btf.Type
607 608
 	}
608 609
 
... ...
@@ -619,7 +721,7 @@ func resolveKconfig(m *MapSpec) error {
619 619
 				return fmt.Errorf("variable %s must be a 32 bits integer, got %s", n, v.Type)
620 620
 			}
621 621
 
622
-			kv, err := internal.KernelVersion()
622
+			kv, err := linux.KernelVersion()
623 623
 			if err != nil {
624 624
 				return fmt.Errorf("getting kernel version: %w", err)
625 625
 			}
... ...
@@ -644,6 +746,7 @@ func resolveKconfig(m *MapSpec) error {
644 644
 		default: // Catch CONFIG_*.
645 645
 			configs[n] = configInfo{
646 646
 				offset: vsi.Offset,
647
+				size:   vsi.Size,
647 648
 				typ:    v.Type,
648 649
 			}
649 650
 		}
... ...
@@ -651,7 +754,7 @@ func resolveKconfig(m *MapSpec) error {
651 651
 
652 652
 	// We only parse kconfig file if a CONFIG_* variable was found.
653 653
 	if len(configs) > 0 {
654
-		f, err := kconfig.Find()
654
+		f, err := linux.FindKConfig()
655 655
 		if err != nil {
656 656
 			return fmt.Errorf("cannot find a kconfig file: %w", err)
657 657
 		}
... ...
@@ -670,10 +773,10 @@ func resolveKconfig(m *MapSpec) error {
670 670
 		for n, info := range configs {
671 671
 			value, ok := kernelConfig[n]
672 672
 			if !ok {
673
-				return fmt.Errorf("config option %q does not exists for this kernel", n)
673
+				return fmt.Errorf("config option %q does not exist on this kernel", n)
674 674
 			}
675 675
 
676
-			err := kconfig.PutValue(data[info.offset:], info.typ, value)
676
+			err := kconfig.PutValue(data[info.offset:info.offset+info.size], info.typ, value)
677 677
 			if err != nil {
678 678
 				return fmt.Errorf("problem adding value for %s: %w", n, err)
679 679
 			}
... ...
@@ -723,6 +826,7 @@ func LoadCollection(file string) (*Collection, error) {
723 723
 func (coll *Collection) Assign(to interface{}) error {
724 724
 	assignedMaps := make(map[string]bool)
725 725
 	assignedProgs := make(map[string]bool)
726
+	assignedVars := make(map[string]bool)
726 727
 
727 728
 	// Assign() only transfers already-loaded Maps and Programs. No extra
728 729
 	// loading is done.
... ...
@@ -743,6 +847,13 @@ func (coll *Collection) Assign(to interface{}) error {
743 743
 			}
744 744
 			return nil, fmt.Errorf("missing map %q", name)
745 745
 
746
+		case reflect.TypeOf((*Variable)(nil)):
747
+			if v := coll.Variables[name]; v != nil {
748
+				assignedVars[name] = true
749
+				return v, nil
750
+			}
751
+			return nil, fmt.Errorf("missing variable %q", name)
752
+
746 753
 		default:
747 754
 			return nil, fmt.Errorf("unsupported type %s", typ)
748 755
 		}
... ...
@@ -759,6 +870,9 @@ func (coll *Collection) Assign(to interface{}) error {
759 759
 	for m := range assignedMaps {
760 760
 		delete(coll.Maps, m)
761 761
 	}
762
+	for s := range assignedVars {
763
+		delete(coll.Variables, s)
764
+	}
762 765
 
763 766
 	return nil
764 767
 }
... ...
@@ -16,7 +16,6 @@ import (
16 16
 	"github.com/cilium/ebpf/btf"
17 17
 	"github.com/cilium/ebpf/internal"
18 18
 	"github.com/cilium/ebpf/internal/sys"
19
-	"github.com/cilium/ebpf/internal/unix"
20 19
 )
21 20
 
22 21
 type kconfigMetaKey struct{}
... ...
@@ -33,6 +32,13 @@ type kfuncMeta struct {
33 33
 	Func    *btf.Func
34 34
 }
35 35
 
36
+type ksymMetaKey struct{}
37
+
38
+type ksymMeta struct {
39
+	Binding elf.SymBind
40
+	Name    string
41
+}
42
+
36 43
 // elfCode is a convenience to reduce the amount of arguments that have to
37 44
 // be passed around explicitly. You should treat its contents as immutable.
38 45
 type elfCode struct {
... ...
@@ -43,7 +49,9 @@ type elfCode struct {
43 43
 	btf      *btf.Spec
44 44
 	extInfo  *btf.ExtInfos
45 45
 	maps     map[string]*MapSpec
46
+	vars     map[string]*VariableSpec
46 47
 	kfuncs   map[string]*btf.Func
48
+	ksyms    map[string]struct{}
47 49
 	kconfig  *MapSpec
48 50
 }
49 51
 
... ...
@@ -71,7 +79,7 @@ func LoadCollectionSpecFromReader(rd io.ReaderAt) (*CollectionSpec, error) {
71 71
 
72 72
 	// Checks if the ELF file is for BPF data.
73 73
 	// Old LLVM versions set e_machine to EM_NONE.
74
-	if f.File.Machine != unix.EM_NONE && f.File.Machine != elf.EM_BPF {
74
+	if f.File.Machine != elf.EM_NONE && f.File.Machine != elf.EM_BPF {
75 75
 		return nil, fmt.Errorf("unexpected machine type for BPF ELF: %s", f.File.Machine)
76 76
 	}
77 77
 
... ...
@@ -101,7 +109,7 @@ func LoadCollectionSpecFromReader(rd io.ReaderAt) (*CollectionSpec, error) {
101 101
 			sections[idx] = newElfSection(sec, mapSection)
102 102
 		case sec.Name == ".maps":
103 103
 			sections[idx] = newElfSection(sec, btfMapSection)
104
-		case sec.Name == ".bss" || sec.Name == ".data" || strings.HasPrefix(sec.Name, ".rodata"):
104
+		case isDataSection(sec.Name):
105 105
 			sections[idx] = newElfSection(sec, dataSection)
106 106
 		case sec.Type == elf.SHT_REL:
107 107
 			// Store relocations under the section index of the target
... ...
@@ -134,7 +142,9 @@ func LoadCollectionSpecFromReader(rd io.ReaderAt) (*CollectionSpec, error) {
134 134
 		btf:         btfSpec,
135 135
 		extInfo:     btfExtInfo,
136 136
 		maps:        make(map[string]*MapSpec),
137
+		vars:        make(map[string]*VariableSpec),
137 138
 		kfuncs:      make(map[string]*btf.Func),
139
+		ksyms:       make(map[string]struct{}),
138 140
 	}
139 141
 
140 142
 	symbols, err := f.Symbols()
... ...
@@ -174,7 +184,7 @@ func LoadCollectionSpecFromReader(rd io.ReaderAt) (*CollectionSpec, error) {
174 174
 		return nil, fmt.Errorf("load programs: %w", err)
175 175
 	}
176 176
 
177
-	return &CollectionSpec{ec.maps, progs, btfSpec, ec.ByteOrder}, nil
177
+	return &CollectionSpec{ec.maps, progs, ec.vars, btfSpec, ec.ByteOrder}, nil
178 178
 }
179 179
 
180 180
 func loadLicense(sec *elf.Section) (string, error) {
... ...
@@ -201,6 +211,18 @@ func loadVersion(sec *elf.Section, bo binary.ByteOrder) (uint32, error) {
201 201
 	return version, nil
202 202
 }
203 203
 
204
+func isDataSection(name string) bool {
205
+	return name == ".bss" || strings.HasPrefix(name, ".data") || strings.HasPrefix(name, ".rodata")
206
+}
207
+
208
+func isConstantDataSection(name string) bool {
209
+	return strings.HasPrefix(name, ".rodata")
210
+}
211
+
212
+func isKconfigSection(name string) bool {
213
+	return name == ".kconfig"
214
+}
215
+
204 216
 type elfSectionKind int
205 217
 
206 218
 const (
... ...
@@ -506,7 +528,7 @@ func (ec *elfCode) relocateInstruction(ins *asm.Instruction, rel elf.Symbol) err
506 506
 
507 507
 		case elf.STT_OBJECT:
508 508
 			// LLVM 9 emits OBJECT-LOCAL symbols for anonymous constants.
509
-			if bind != elf.STB_GLOBAL && bind != elf.STB_LOCAL {
509
+			if bind != elf.STB_GLOBAL && bind != elf.STB_LOCAL && bind != elf.STB_WEAK {
510 510
 				return fmt.Errorf("direct load: %s: %w: %s", name, errUnsupportedBinding, bind)
511 511
 			}
512 512
 
... ...
@@ -614,6 +636,8 @@ func (ec *elfCode) relocateInstruction(ins *asm.Instruction, rel elf.Symbol) err
614 614
 		}
615 615
 
616 616
 		kf := ec.kfuncs[name]
617
+		_, ks := ec.ksyms[name]
618
+
617 619
 		switch {
618 620
 		// If a Call / DWordLoad instruction is found and the datasec has a btf.Func with a Name
619 621
 		// that matches the symbol name we mark the instruction as a referencing a kfunc.
... ...
@@ -634,6 +658,15 @@ func (ec *elfCode) relocateInstruction(ins *asm.Instruction, rel elf.Symbol) err
634 634
 
635 635
 			ins.Constant = 0
636 636
 
637
+		case ks && ins.OpCode.IsDWordLoad():
638
+			if bind != elf.STB_GLOBAL && bind != elf.STB_WEAK {
639
+				return fmt.Errorf("asm relocation: %s: %w: %s", name, errUnsupportedBinding, bind)
640
+			}
641
+			ins.Metadata.Set(ksymMetaKey{}, &ksymMeta{
642
+				Binding: bind,
643
+				Name:    name,
644
+			})
645
+
637 646
 		// If no kconfig map is found, this must be a symbol reference from inline
638 647
 		// asm (see testdata/loader.c:asm_relocation()) or a call to a forward
639 648
 		// function declaration (see testdata/fwd_decl.c). Don't interfere, These
... ...
@@ -980,6 +1013,13 @@ func mapSpecFromBTF(es *elfSection, vs *btf.VarSecinfo, def *btf.Struct, spec *b
980 980
 		}
981 981
 	}
982 982
 
983
+	// Some maps don't support value sizes, but annotating their map definitions
984
+	// with __type macros can still be useful, especially to let bpf2go generate
985
+	// type definitions for them.
986
+	if value != nil && !mapType.canHaveValueSize() {
987
+		valueSize = 0
988
+	}
989
+
983 990
 	return &MapSpec{
984 991
 		Name:       SanitizeName(name, -1),
985 992
 		Type:       MapType(mapType),
... ...
@@ -1092,12 +1132,21 @@ func (ec *elfCode) loadDataSections() error {
1092 1092
 			continue
1093 1093
 		}
1094 1094
 
1095
-		if sec.references == 0 {
1096
-			// Prune data sections which are not referenced by any
1097
-			// instructions.
1095
+		// If a section has no references, it will be freed as soon as the
1096
+		// Collection closes, so creating and populating it is wasteful. If it has
1097
+		// no symbols, it is likely an ephemeral section used during compilation
1098
+		// that wasn't sanitized by the bpf linker. (like .rodata.str1.1)
1099
+		//
1100
+		// No symbols means no VariableSpecs can be generated from it, making it
1101
+		// pointless to emit a data section for.
1102
+		if sec.references == 0 && len(sec.symbols) == 0 {
1098 1103
 			continue
1099 1104
 		}
1100 1105
 
1106
+		if sec.Size > math.MaxUint32 {
1107
+			return fmt.Errorf("data section %s: contents exceed maximum size", sec.Name)
1108
+		}
1109
+
1101 1110
 		mapSpec := &MapSpec{
1102 1111
 			Name:       SanitizeName(sec.Name, -1),
1103 1112
 			Type:       Array,
... ...
@@ -1106,6 +1155,10 @@ func (ec *elfCode) loadDataSections() error {
1106 1106
 			MaxEntries: 1,
1107 1107
 		}
1108 1108
 
1109
+		if isConstantDataSection(sec.Name) {
1110
+			mapSpec.Flags = sys.BPF_F_RDONLY_PROG
1111
+		}
1112
+
1109 1113
 		switch sec.Type {
1110 1114
 		// Only open the section if we know there's actual data to be read.
1111 1115
 		case elf.SHT_PROGBITS:
... ...
@@ -1113,20 +1166,56 @@ func (ec *elfCode) loadDataSections() error {
1113 1113
 			if err != nil {
1114 1114
 				return fmt.Errorf("data section %s: can't get contents: %w", sec.Name, err)
1115 1115
 			}
1116
-
1117
-			if uint64(len(data)) > math.MaxUint32 {
1118
-				return fmt.Errorf("data section %s: contents exceed maximum size", sec.Name)
1119
-			}
1120 1116
 			mapSpec.Contents = []MapKV{{uint32(0), data}}
1121 1117
 
1122 1118
 		case elf.SHT_NOBITS:
1123
-			// NOBITS sections like .bss contain only zeroes, and since data sections
1124
-			// are Arrays, the kernel already preallocates them. Skip reading zeroes
1125
-			// from the ELF.
1119
+			// NOBITS sections like .bss contain only zeroes and are not allocated in
1120
+			// the ELF. Since data sections are Arrays, the kernel can preallocate
1121
+			// them. Don't attempt reading zeroes from the ELF, instead allocate the
1122
+			// zeroed memory to support getting and setting VariableSpecs for sections
1123
+			// like .bss.
1124
+			mapSpec.Contents = []MapKV{{uint32(0), make([]byte, sec.Size)}}
1125
+
1126 1126
 		default:
1127 1127
 			return fmt.Errorf("data section %s: unknown section type %s", sec.Name, sec.Type)
1128 1128
 		}
1129 1129
 
1130
+		for off, sym := range sec.symbols {
1131
+			// Skip symbols marked with the 'hidden' attribute.
1132
+			if elf.ST_VISIBILITY(sym.Other) == elf.STV_HIDDEN ||
1133
+				elf.ST_VISIBILITY(sym.Other) == elf.STV_INTERNAL {
1134
+				continue
1135
+			}
1136
+
1137
+			// Only accept symbols with global or weak bindings. The common
1138
+			// alternative is STB_LOCAL, which are either function-scoped or declared
1139
+			// 'static'.
1140
+			if elf.ST_BIND(sym.Info) != elf.STB_GLOBAL &&
1141
+				elf.ST_BIND(sym.Info) != elf.STB_WEAK {
1142
+				continue
1143
+			}
1144
+
1145
+			if ec.vars[sym.Name] != nil {
1146
+				return fmt.Errorf("data section %s: duplicate variable %s", sec.Name, sym.Name)
1147
+			}
1148
+
1149
+			// Skip symbols starting with a dot, they are compiler-internal symbols
1150
+			// emitted by clang 11 and earlier and are not cleaned up by the bpf
1151
+			// compiler backend (e.g. symbols named .Lconstinit.1 in sections like
1152
+			// .rodata.cst32). Variables in C cannot start with a dot, so filter these
1153
+			// out.
1154
+			if strings.HasPrefix(sym.Name, ".") {
1155
+				continue
1156
+			}
1157
+
1158
+			ec.vars[sym.Name] = &VariableSpec{
1159
+				name:   sym.Name,
1160
+				offset: off,
1161
+				size:   sym.Size,
1162
+				m:      mapSpec,
1163
+			}
1164
+		}
1165
+
1130 1166
 		// It is possible for a data section to exist without a corresponding BTF Datasec
1131 1167
 		// if it only contains anonymous values like macro-defined arrays.
1132 1168
 		if ec.btf != nil {
... ...
@@ -1135,12 +1224,38 @@ func (ec *elfCode) loadDataSections() error {
1135 1135
 				// Assign the spec's key and BTF only if the Datasec lookup was successful.
1136 1136
 				mapSpec.Key = &btf.Void{}
1137 1137
 				mapSpec.Value = ds
1138
-			}
1139
-		}
1140 1138
 
1141
-		if strings.HasPrefix(sec.Name, ".rodata") {
1142
-			mapSpec.Flags = unix.BPF_F_RDONLY_PROG
1143
-			mapSpec.Freeze = true
1139
+				// Populate VariableSpecs with type information, if available.
1140
+				for _, v := range ds.Vars {
1141
+					name := v.Type.TypeName()
1142
+					if name == "" {
1143
+						return fmt.Errorf("data section %s: anonymous variable %v", sec.Name, v)
1144
+					}
1145
+
1146
+					vt, ok := v.Type.(*btf.Var)
1147
+					if !ok {
1148
+						return fmt.Errorf("data section %s: unexpected type %T for variable %s", sec.Name, v.Type, name)
1149
+					}
1150
+
1151
+					ev := ec.vars[name]
1152
+					if ev == nil {
1153
+						// Hidden symbols appear in the BTF Datasec but don't receive a VariableSpec.
1154
+						continue
1155
+					}
1156
+
1157
+					if uint64(v.Offset) != ev.offset {
1158
+						return fmt.Errorf("data section %s: variable %s datasec offset (%d) doesn't match ELF symbol offset (%d)", sec.Name, name, v.Offset, ev.offset)
1159
+					}
1160
+
1161
+					if uint64(v.Size) != ev.size {
1162
+						return fmt.Errorf("data section %s: variable %s size in datasec (%d) doesn't match ELF symbol size (%d)", sec.Name, name, v.Size, ev.size)
1163
+					}
1164
+
1165
+					// Decouple the Var in the VariableSpec from the underlying DataSec in
1166
+					// the MapSpec to avoid modifications from affecting map loads later on.
1167
+					ev.t = btf.Copy(vt).(*btf.Var)
1168
+				}
1169
+			}
1144 1170
 		}
1145 1171
 
1146 1172
 		ec.maps[sec.Name] = mapSpec
... ...
@@ -1175,8 +1290,7 @@ func (ec *elfCode) loadKconfigSection() error {
1175 1175
 		KeySize:    uint32(4),
1176 1176
 		ValueSize:  ds.Size,
1177 1177
 		MaxEntries: 1,
1178
-		Flags:      unix.BPF_F_RDONLY_PROG,
1179
-		Freeze:     true,
1178
+		Flags:      sys.BPF_F_RDONLY_PROG,
1180 1179
 		Key:        &btf.Int{Size: 4},
1181 1180
 		Value:      ds,
1182 1181
 	}
... ...
@@ -1201,8 +1315,14 @@ func (ec *elfCode) loadKsymsSection() error {
1201 1201
 	}
1202 1202
 
1203 1203
 	for _, v := range ds.Vars {
1204
-		// we have already checked the .ksyms Datasec to only contain Func Vars.
1205
-		ec.kfuncs[v.Type.TypeName()] = v.Type.(*btf.Func)
1204
+		switch t := v.Type.(type) {
1205
+		case *btf.Func:
1206
+			ec.kfuncs[t.TypeName()] = t
1207
+		case *btf.Var:
1208
+			ec.ksyms[t.TypeName()] = struct{}{}
1209
+		default:
1210
+			return fmt.Errorf("unexpected variable type in .ksyms: %T", v)
1211
+		}
1206 1212
 	}
1207 1213
 
1208 1214
 	return nil
... ...
@@ -1266,10 +1386,10 @@ func getProgType(sectionName string) (ProgramType, AttachType, uint32, string) {
1266 1266
 
1267 1267
 		var flags uint32
1268 1268
 		if t.flags&_SEC_SLEEPABLE > 0 {
1269
-			flags |= unix.BPF_F_SLEEPABLE
1269
+			flags |= sys.BPF_F_SLEEPABLE
1270 1270
 		}
1271 1271
 		if t.flags&_SEC_XDP_FRAGS > 0 {
1272
-			flags |= unix.BPF_F_XDP_HAS_FRAGS
1272
+			flags |= sys.BPF_F_XDP_HAS_FRAGS
1273 1273
 		}
1274 1274
 		if t.flags&_SEC_EXP_ATTACH_OPT > 0 {
1275 1275
 			if programType == XDP {
... ...
@@ -18,6 +18,7 @@ var elfSectionDefs = []libbpfElfSectionDef{
18 18
 	{"uretprobe.s+", sys.BPF_PROG_TYPE_KPROBE, 0, _SEC_SLEEPABLE},
19 19
 	{"kprobe.multi+", sys.BPF_PROG_TYPE_KPROBE, sys.BPF_TRACE_KPROBE_MULTI, _SEC_NONE},
20 20
 	{"kretprobe.multi+", sys.BPF_PROG_TYPE_KPROBE, sys.BPF_TRACE_KPROBE_MULTI, _SEC_NONE},
21
+	{"kprobe.session+", sys.BPF_PROG_TYPE_KPROBE, sys.BPF_TRACE_KPROBE_SESSION, _SEC_NONE},
21 22
 	{"uprobe.multi+", sys.BPF_PROG_TYPE_KPROBE, sys.BPF_TRACE_UPROBE_MULTI, _SEC_NONE},
22 23
 	{"uretprobe.multi+", sys.BPF_PROG_TYPE_KPROBE, sys.BPF_TRACE_UPROBE_MULTI, _SEC_NONE},
23 24
 	{"uprobe.multi.s+", sys.BPF_PROG_TYPE_KPROBE, sys.BPF_TRACE_UPROBE_MULTI, _SEC_SLEEPABLE},
... ...
@@ -69,6 +70,7 @@ var elfSectionDefs = []libbpfElfSectionDef{
69 69
 	{"sockops", sys.BPF_PROG_TYPE_SOCK_OPS, sys.BPF_CGROUP_SOCK_OPS, _SEC_ATTACHABLE_OPT},
70 70
 	{"sk_skb/stream_parser", sys.BPF_PROG_TYPE_SK_SKB, sys.BPF_SK_SKB_STREAM_PARSER, _SEC_ATTACHABLE_OPT},
71 71
 	{"sk_skb/stream_verdict", sys.BPF_PROG_TYPE_SK_SKB, sys.BPF_SK_SKB_STREAM_VERDICT, _SEC_ATTACHABLE_OPT},
72
+	{"sk_skb/verdict", sys.BPF_PROG_TYPE_SK_SKB, sys.BPF_SK_SKB_VERDICT, _SEC_ATTACHABLE_OPT},
72 73
 	{"sk_skb", sys.BPF_PROG_TYPE_SK_SKB, 0, _SEC_NONE},
73 74
 	{"sk_msg", sys.BPF_PROG_TYPE_SK_MSG, sys.BPF_SK_MSG_VERDICT, _SEC_ATTACHABLE_OPT},
74 75
 	{"lirc_mode2", sys.BPF_PROG_TYPE_LIRC_MODE2, sys.BPF_LIRC_MODE2, _SEC_ATTACHABLE_OPT},
... ...
@@ -8,10 +8,10 @@ import (
8 8
 	"fmt"
9 9
 	"io"
10 10
 	"os"
11
+	"reflect"
11 12
 	"strings"
12 13
 	"syscall"
13 14
 	"time"
14
-	"unsafe"
15 15
 
16 16
 	"github.com/cilium/ebpf/asm"
17 17
 	"github.com/cilium/ebpf/btf"
... ...
@@ -39,53 +39,83 @@ import (
39 39
 
40 40
 // MapInfo describes a map.
41 41
 type MapInfo struct {
42
-	Type       MapType
43
-	id         MapID
44
-	KeySize    uint32
45
-	ValueSize  uint32
42
+	// Type of the map.
43
+	Type MapType
44
+	// KeySize is the size of the map key in bytes.
45
+	KeySize uint32
46
+	// ValueSize is the size of the map value in bytes.
47
+	ValueSize uint32
48
+	// MaxEntries is the maximum number of entries the map can hold. Its meaning
49
+	// is map-specific.
46 50
 	MaxEntries uint32
47
-	Flags      uint32
51
+	// Flags used during map creation.
52
+	Flags uint32
48 53
 	// Name as supplied by user space at load time. Available from 4.15.
49 54
 	Name string
50 55
 
51
-	btf btf.ID
56
+	id       MapID
57
+	btf      btf.ID
58
+	mapExtra uint64
59
+	memlock  uint64
60
+	frozen   bool
52 61
 }
53 62
 
63
+// newMapInfoFromFd queries map information about the given fd. [sys.ObjInfo] is
64
+// attempted first, supplementing any missing values with information from
65
+// /proc/self/fdinfo. Ignores EINVAL from ObjInfo as well as ErrNotSupported
66
+// from reading fdinfo (indicating the file exists, but no fields of interest
67
+// were found). If both fail, an error is always returned.
54 68
 func newMapInfoFromFd(fd *sys.FD) (*MapInfo, error) {
55 69
 	var info sys.MapInfo
56
-	err := sys.ObjInfo(fd, &info)
57
-	if errors.Is(err, syscall.EINVAL) {
58
-		return newMapInfoFromProc(fd)
59
-	}
60
-	if err != nil {
61
-		return nil, err
70
+	err1 := sys.ObjInfo(fd, &info)
71
+	// EINVAL means the kernel doesn't support BPF_OBJ_GET_INFO_BY_FD. Continue
72
+	// with fdinfo if that's the case.
73
+	if err1 != nil && !errors.Is(err1, unix.EINVAL) {
74
+		return nil, fmt.Errorf("getting object info: %w", err1)
62 75
 	}
63 76
 
64
-	return &MapInfo{
77
+	mi := &MapInfo{
65 78
 		MapType(info.Type),
66
-		MapID(info.Id),
67 79
 		info.KeySize,
68 80
 		info.ValueSize,
69 81
 		info.MaxEntries,
70 82
 		uint32(info.MapFlags),
71 83
 		unix.ByteSliceToString(info.Name[:]),
84
+		MapID(info.Id),
72 85
 		btf.ID(info.BtfId),
73
-	}, nil
86
+		info.MapExtra,
87
+		0,
88
+		false,
89
+	}
90
+
91
+	// Supplement OBJ_INFO with data from /proc/self/fdinfo. It contains fields
92
+	// like memlock and frozen that are not present in OBJ_INFO.
93
+	err2 := readMapInfoFromProc(fd, mi)
94
+	if err2 != nil && !errors.Is(err2, ErrNotSupported) {
95
+		return nil, fmt.Errorf("getting map info from fdinfo: %w", err2)
96
+	}
97
+
98
+	if err1 != nil && err2 != nil {
99
+		return nil, fmt.Errorf("ObjInfo and fdinfo both failed: objinfo: %w, fdinfo: %w", err1, err2)
100
+	}
101
+
102
+	return mi, nil
74 103
 }
75 104
 
76
-func newMapInfoFromProc(fd *sys.FD) (*MapInfo, error) {
77
-	var mi MapInfo
78
-	err := scanFdInfo(fd, map[string]interface{}{
105
+// readMapInfoFromProc queries map information about the given fd from
106
+// /proc/self/fdinfo. It only writes data into fields that have a zero value.
107
+func readMapInfoFromProc(fd *sys.FD, mi *MapInfo) error {
108
+	return scanFdInfo(fd, map[string]interface{}{
79 109
 		"map_type":    &mi.Type,
110
+		"map_id":      &mi.id,
80 111
 		"key_size":    &mi.KeySize,
81 112
 		"value_size":  &mi.ValueSize,
82 113
 		"max_entries": &mi.MaxEntries,
83 114
 		"map_flags":   &mi.Flags,
115
+		"map_extra":   &mi.mapExtra,
116
+		"memlock":     &mi.memlock,
117
+		"frozen":      &mi.frozen,
84 118
 	})
85
-	if err != nil {
86
-		return nil, err
87
-	}
88
-	return &mi, nil
89 119
 }
90 120
 
91 121
 // ID returns the map ID.
... ...
@@ -109,6 +139,35 @@ func (mi *MapInfo) BTFID() (btf.ID, bool) {
109 109
 	return mi.btf, mi.btf > 0
110 110
 }
111 111
 
112
+// MapExtra returns an opaque field whose meaning is map-specific.
113
+//
114
+// Available from 5.16.
115
+//
116
+// The bool return value indicates whether this optional field is available and
117
+// populated, if it was specified during Map creation.
118
+func (mi *MapInfo) MapExtra() (uint64, bool) {
119
+	return mi.mapExtra, mi.mapExtra > 0
120
+}
121
+
122
+// Memlock returns an approximate number of bytes allocated to this map.
123
+//
124
+// Available from 4.10.
125
+//
126
+// The bool return value indicates whether this optional field is available.
127
+func (mi *MapInfo) Memlock() (uint64, bool) {
128
+	return mi.memlock, mi.memlock > 0
129
+}
130
+
131
+// Frozen indicates whether [Map.Freeze] was called on this map. If true,
132
+// modifications from user space are not allowed.
133
+//
134
+// Available from 5.2. Requires access to procfs.
135
+//
136
+// If the kernel doesn't support map freezing, this field will always be false.
137
+func (mi *MapInfo) Frozen() bool {
138
+	return mi.frozen
139
+}
140
+
112 141
 // programStats holds statistics of a program.
113 142
 type programStats struct {
114 143
 	// Total accumulated runtime of the program ins ns.
... ...
@@ -120,6 +179,40 @@ type programStats struct {
120 120
 	recursionMisses uint64
121 121
 }
122 122
 
123
+// programJitedInfo holds information about JITed info of a program.
124
+type programJitedInfo struct {
125
+	// ksyms holds the ksym addresses of the BPF program, including those of its
126
+	// subprograms.
127
+	//
128
+	// Available from 4.18.
129
+	ksyms    []uint64
130
+	numKsyms uint32
131
+
132
+	// insns holds the JITed machine native instructions of the program,
133
+	// including those of its subprograms.
134
+	//
135
+	// Available from 4.13.
136
+	insns    []byte
137
+	numInsns uint32
138
+
139
+	// lineInfos holds the JITed line infos, which are kernel addresses.
140
+	//
141
+	// Available from 5.0.
142
+	lineInfos    []uint64
143
+	numLineInfos uint32
144
+
145
+	// lineInfoRecSize is the size of a single line info record.
146
+	//
147
+	// Available from 5.0.
148
+	lineInfoRecSize uint32
149
+
150
+	// funcLens holds the insns length of each function.
151
+	//
152
+	// Available from 4.18.
153
+	funcLens    []uint32
154
+	numFuncLens uint32
155
+}
156
+
123 157
 // ProgramInfo describes a program.
124 158
 type ProgramInfo struct {
125 159
 	Type ProgramType
... ...
@@ -133,9 +226,14 @@ type ProgramInfo struct {
133 133
 	haveCreatedByUID bool
134 134
 	btf              btf.ID
135 135
 	stats            *programStats
136
+	loadTime         time.Duration
137
+
138
+	maps                 []MapID
139
+	insns                []byte
140
+	jitedSize            uint32
141
+	verifiedInstructions uint32
136 142
 
137
-	maps  []MapID
138
-	insns []byte
143
+	jitedInfo programJitedInfo
139 144
 
140 145
 	lineInfos    []byte
141 146
 	numLineInfos uint32
... ...
@@ -164,6 +262,9 @@ func newProgramInfoFromFd(fd *sys.FD) (*ProgramInfo, error) {
164 164
 			runCount:        info.RunCnt,
165 165
 			recursionMisses: info.RecursionMisses,
166 166
 		},
167
+		jitedSize:            info.JitedProgLen,
168
+		loadTime:             time.Duration(info.LoadTime),
169
+		verifiedInstructions: info.VerifiedInsns,
167 170
 	}
168 171
 
169 172
 	// Start with a clean struct for the second call, otherwise we may get EFAULT.
... ...
@@ -174,7 +275,7 @@ func newProgramInfoFromFd(fd *sys.FD) (*ProgramInfo, error) {
174 174
 	if info.NrMapIds > 0 {
175 175
 		pi.maps = make([]MapID, info.NrMapIds)
176 176
 		info2.NrMapIds = info.NrMapIds
177
-		info2.MapIds = sys.NewPointer(unsafe.Pointer(&pi.maps[0]))
177
+		info2.MapIds = sys.NewSlicePointer(pi.maps)
178 178
 		makeSecondCall = true
179 179
 	} else if haveProgramInfoMapIDs() == nil {
180 180
 		// This program really has no associated maps.
... ...
@@ -215,6 +316,40 @@ func newProgramInfoFromFd(fd *sys.FD) (*ProgramInfo, error) {
215 215
 		makeSecondCall = true
216 216
 	}
217 217
 
218
+	pi.jitedInfo.lineInfoRecSize = info.JitedLineInfoRecSize
219
+	if info.JitedProgLen > 0 {
220
+		pi.jitedInfo.numInsns = info.JitedProgLen
221
+		pi.jitedInfo.insns = make([]byte, info.JitedProgLen)
222
+		info2.JitedProgLen = info.JitedProgLen
223
+		info2.JitedProgInsns = sys.NewSlicePointer(pi.jitedInfo.insns)
224
+		makeSecondCall = true
225
+	}
226
+
227
+	if info.NrJitedFuncLens > 0 {
228
+		pi.jitedInfo.numFuncLens = info.NrJitedFuncLens
229
+		pi.jitedInfo.funcLens = make([]uint32, info.NrJitedFuncLens)
230
+		info2.NrJitedFuncLens = info.NrJitedFuncLens
231
+		info2.JitedFuncLens = sys.NewSlicePointer(pi.jitedInfo.funcLens)
232
+		makeSecondCall = true
233
+	}
234
+
235
+	if info.NrJitedLineInfo > 0 {
236
+		pi.jitedInfo.numLineInfos = info.NrJitedLineInfo
237
+		pi.jitedInfo.lineInfos = make([]uint64, info.NrJitedLineInfo)
238
+		info2.NrJitedLineInfo = info.NrJitedLineInfo
239
+		info2.JitedLineInfo = sys.NewSlicePointer(pi.jitedInfo.lineInfos)
240
+		info2.JitedLineInfoRecSize = info.JitedLineInfoRecSize
241
+		makeSecondCall = true
242
+	}
243
+
244
+	if info.NrJitedKsyms > 0 {
245
+		pi.jitedInfo.numKsyms = info.NrJitedKsyms
246
+		pi.jitedInfo.ksyms = make([]uint64, info.NrJitedKsyms)
247
+		info2.JitedKsyms = sys.NewSlicePointer(pi.jitedInfo.ksyms)
248
+		info2.NrJitedKsyms = info.NrJitedKsyms
249
+		makeSecondCall = true
250
+	}
251
+
218 252
 	if makeSecondCall {
219 253
 		if err := sys.ObjInfo(fd, &info2); err != nil {
220 254
 			return nil, err
... ...
@@ -230,7 +365,7 @@ func newProgramInfoFromProc(fd *sys.FD) (*ProgramInfo, error) {
230 230
 		"prog_type": &info.Type,
231 231
 		"prog_tag":  &info.Tag,
232 232
 	})
233
-	if errors.Is(err, errMissingFields) {
233
+	if errors.Is(err, ErrNotSupported) {
234 234
 		return nil, &internal.UnsupportedFeatureError{
235 235
 			Name:           "reading program info from /proc/self/fdinfo",
236 236
 			MinimumVersion: internal.Version{4, 10, 0},
... ...
@@ -305,6 +440,52 @@ func (pi *ProgramInfo) RecursionMisses() (uint64, bool) {
305 305
 	return 0, false
306 306
 }
307 307
 
308
+// btfSpec returns the BTF spec associated with the program.
309
+func (pi *ProgramInfo) btfSpec() (*btf.Spec, error) {
310
+	id, ok := pi.BTFID()
311
+	if !ok {
312
+		return nil, fmt.Errorf("program created without BTF or unsupported kernel: %w", ErrNotSupported)
313
+	}
314
+
315
+	h, err := btf.NewHandleFromID(id)
316
+	if err != nil {
317
+		return nil, fmt.Errorf("get BTF handle: %w", err)
318
+	}
319
+	defer h.Close()
320
+
321
+	spec, err := h.Spec(nil)
322
+	if err != nil {
323
+		return nil, fmt.Errorf("get BTF spec: %w", err)
324
+	}
325
+
326
+	return spec, nil
327
+}
328
+
329
+// LineInfos returns the BTF line information of the program.
330
+//
331
+// Available from 5.0.
332
+//
333
+// Requires CAP_SYS_ADMIN or equivalent for reading BTF information. Returns
334
+// ErrNotSupported if the program was created without BTF or if the kernel
335
+// doesn't support the field.
336
+func (pi *ProgramInfo) LineInfos() (btf.LineOffsets, error) {
337
+	if len(pi.lineInfos) == 0 {
338
+		return nil, fmt.Errorf("insufficient permissions or unsupported kernel: %w", ErrNotSupported)
339
+	}
340
+
341
+	spec, err := pi.btfSpec()
342
+	if err != nil {
343
+		return nil, err
344
+	}
345
+
346
+	return btf.LoadLineInfos(
347
+		bytes.NewReader(pi.lineInfos),
348
+		internal.NativeEndian,
349
+		pi.numLineInfos,
350
+		spec,
351
+	)
352
+}
353
+
308 354
 // Instructions returns the 'xlated' instruction stream of the program
309 355
 // after it has been verified and rewritten by the kernel. These instructions
310 356
 // cannot be loaded back into the kernel as-is, this is mainly used for
... ...
@@ -391,6 +572,29 @@ func (pi *ProgramInfo) Instructions() (asm.Instructions, error) {
391 391
 	return insns, nil
392 392
 }
393 393
 
394
+// JitedSize returns the size of the program's JIT-compiled machine code in bytes, which is the
395
+// actual code executed on the host's CPU. This field requires the BPF JIT compiler to be enabled.
396
+//
397
+// Available from 4.13. Reading this metadata requires CAP_BPF or equivalent.
398
+func (pi *ProgramInfo) JitedSize() (uint32, error) {
399
+	if pi.jitedSize == 0 {
400
+		return 0, fmt.Errorf("insufficient permissions, unsupported kernel, or JIT compiler disabled: %w", ErrNotSupported)
401
+	}
402
+	return pi.jitedSize, nil
403
+}
404
+
405
+// TranslatedSize returns the size of the program's translated instructions in bytes, after it has
406
+// been verified and rewritten by the kernel.
407
+//
408
+// Available from 4.13. Reading this metadata requires CAP_BPF or equivalent.
409
+func (pi *ProgramInfo) TranslatedSize() (int, error) {
410
+	insns := len(pi.insns)
411
+	if insns == 0 {
412
+		return 0, fmt.Errorf("insufficient permissions or unsupported kernel: %w", ErrNotSupported)
413
+	}
414
+	return insns, nil
415
+}
416
+
394 417
 // MapIDs returns the maps related to the program.
395 418
 //
396 419
 // Available from 4.15.
... ...
@@ -400,6 +604,106 @@ func (pi *ProgramInfo) MapIDs() ([]MapID, bool) {
400 400
 	return pi.maps, pi.maps != nil
401 401
 }
402 402
 
403
+// LoadTime returns when the program was loaded since boot time.
404
+//
405
+// Available from 4.15.
406
+//
407
+// The bool return value indicates whether this optional field is available.
408
+func (pi *ProgramInfo) LoadTime() (time.Duration, bool) {
409
+	// loadTime and NrMapIds were introduced in the same kernel version.
410
+	return pi.loadTime, pi.loadTime > 0
411
+}
412
+
413
+// VerifiedInstructions returns the number verified instructions in the program.
414
+//
415
+// Available from 5.16.
416
+//
417
+// The bool return value indicates whether this optional field is available.
418
+func (pi *ProgramInfo) VerifiedInstructions() (uint32, bool) {
419
+	return pi.verifiedInstructions, pi.verifiedInstructions > 0
420
+}
421
+
422
+// JitedKsymAddrs returns the ksym addresses of the BPF program, including its
423
+// subprograms. The addresses correspond to their symbols in /proc/kallsyms.
424
+//
425
+// Available from 4.18. Note that before 5.x, this field can be empty for
426
+// programs without subprograms (bpf2bpf calls).
427
+//
428
+// The bool return value indicates whether this optional field is available.
429
+//
430
+// When a kernel address can't fit into uintptr (which is usually the case when
431
+// running 32 bit program on a 64 bit kernel), this returns an empty slice and
432
+// a false.
433
+func (pi *ProgramInfo) JitedKsymAddrs() ([]uintptr, bool) {
434
+	ksyms := make([]uintptr, 0, len(pi.jitedInfo.ksyms))
435
+	if cap(ksyms) == 0 {
436
+		return ksyms, false
437
+	}
438
+	// Check if a kernel address fits into uintptr (it might not when
439
+	// using a 32 bit binary on a 64 bit kernel). This check should work
440
+	// with any kernel address, since they have 1s at the highest bits.
441
+	if a := pi.jitedInfo.ksyms[0]; uint64(uintptr(a)) != a {
442
+		return nil, false
443
+	}
444
+	for _, ksym := range pi.jitedInfo.ksyms {
445
+		ksyms = append(ksyms, uintptr(ksym))
446
+	}
447
+	return ksyms, true
448
+}
449
+
450
+// JitedInsns returns the JITed machine native instructions of the program.
451
+//
452
+// Available from 4.13.
453
+//
454
+// The bool return value indicates whether this optional field is available.
455
+func (pi *ProgramInfo) JitedInsns() ([]byte, bool) {
456
+	return pi.jitedInfo.insns, len(pi.jitedInfo.insns) > 0
457
+}
458
+
459
+// JitedLineInfos returns the JITed line infos of the program.
460
+//
461
+// Available from 5.0.
462
+//
463
+// The bool return value indicates whether this optional field is available.
464
+func (pi *ProgramInfo) JitedLineInfos() ([]uint64, bool) {
465
+	return pi.jitedInfo.lineInfos, len(pi.jitedInfo.lineInfos) > 0
466
+}
467
+
468
+// JitedFuncLens returns the insns length of each function in the JITed program.
469
+//
470
+// Available from 4.18.
471
+//
472
+// The bool return value indicates whether this optional field is available.
473
+func (pi *ProgramInfo) JitedFuncLens() ([]uint32, bool) {
474
+	return pi.jitedInfo.funcLens, len(pi.jitedInfo.funcLens) > 0
475
+}
476
+
477
+// FuncInfos returns the offset and function information of all (sub)programs in
478
+// a BPF program.
479
+//
480
+// Available from 5.0.
481
+//
482
+// Requires CAP_SYS_ADMIN or equivalent for reading BTF information. Returns
483
+// ErrNotSupported if the program was created without BTF or if the kernel
484
+// doesn't support the field.
485
+func (pi *ProgramInfo) FuncInfos() (btf.FuncOffsets, error) {
486
+	if len(pi.funcInfos) == 0 {
487
+		return nil, fmt.Errorf("insufficient permissions or unsupported kernel: %w", ErrNotSupported)
488
+	}
489
+
490
+	spec, err := pi.btfSpec()
491
+	if err != nil {
492
+		return nil, err
493
+	}
494
+
495
+	return btf.LoadFuncInfos(
496
+		bytes.NewReader(pi.funcInfos),
497
+		internal.NativeEndian,
498
+		pi.numFuncInfos,
499
+		spec,
500
+	)
501
+}
502
+
403 503
 func scanFdInfo(fd *sys.FD, fields map[string]interface{}) error {
404 504
 	fh, err := os.Open(fmt.Sprintf("/proc/self/fdinfo/%d", fd.Int()))
405 505
 	if err != nil {
... ...
@@ -413,8 +717,6 @@ func scanFdInfo(fd *sys.FD, fields map[string]interface{}) error {
413 413
 	return nil
414 414
 }
415 415
 
416
-var errMissingFields = errors.New("missing fields")
417
-
418 416
 func scanFdInfoReader(r io.Reader, fields map[string]interface{}) error {
419 417
 	var (
420 418
 		scanner = bufio.NewScanner(r)
... ...
@@ -433,26 +735,37 @@ func scanFdInfoReader(r io.Reader, fields map[string]interface{}) error {
433 433
 			continue
434 434
 		}
435 435
 
436
-		if n, err := fmt.Sscanln(parts[1], field); err != nil || n != 1 {
437
-			return fmt.Errorf("can't parse field %s: %v", name, err)
436
+		// If field already contains a non-zero value, don't overwrite it with fdinfo.
437
+		if zero(field) {
438
+			if n, err := fmt.Sscanln(parts[1], field); err != nil || n != 1 {
439
+				return fmt.Errorf("can't parse field %s: %v", name, err)
440
+			}
438 441
 		}
439 442
 
440 443
 		scanned++
441 444
 	}
442 445
 
443 446
 	if err := scanner.Err(); err != nil {
444
-		return err
447
+		return fmt.Errorf("scanning fdinfo: %w", err)
445 448
 	}
446 449
 
447 450
 	if len(fields) > 0 && scanned == 0 {
448 451
 		return ErrNotSupported
449 452
 	}
450 453
 
451
-	if scanned != len(fields) {
452
-		return errMissingFields
454
+	return nil
455
+}
456
+
457
+func zero(arg any) bool {
458
+	v := reflect.ValueOf(arg)
459
+
460
+	// Unwrap pointers and interfaces.
461
+	for v.Kind() == reflect.Pointer ||
462
+		v.Kind() == reflect.Interface {
463
+		v = v.Elem()
453 464
 	}
454 465
 
455
-	return nil
466
+	return v.IsZero()
456 467
 }
457 468
 
458 469
 // EnableStats starts the measuring of the runtime
... ...
@@ -471,7 +784,7 @@ func EnableStats(which uint32) (io.Closer, error) {
471 471
 	return fd, nil
472 472
 }
473 473
 
474
-var haveProgramInfoMapIDs = internal.NewFeatureTest("map IDs in program info", "4.15", func() error {
474
+var haveProgramInfoMapIDs = internal.NewFeatureTest("map IDs in program info", func() error {
475 475
 	prog, err := progLoad(asm.Instructions{
476 476
 		asm.LoadImm(asm.R0, 0, asm.DWord),
477 477
 		asm.Return(),
... ...
@@ -496,4 +809,4 @@ var haveProgramInfoMapIDs = internal.NewFeatureTest("map IDs in program info", "
496 496
 	}
497 497
 
498 498
 	return err
499
-})
499
+}, "4.15")
500 500
deleted file mode 100644
... ...
@@ -1,60 +0,0 @@
1
-package internal
2
-
3
-import (
4
-	"errors"
5
-	"io"
6
-	_ "unsafe"
7
-)
8
-
9
-type auxvPairReader interface {
10
-	Close() error
11
-	ReadAuxvPair() (uint64, uint64, error)
12
-}
13
-
14
-// See https://elixir.bootlin.com/linux/v6.5.5/source/include/uapi/linux/auxvec.h
15
-const (
16
-	_AT_NULL         = 0  // End of vector
17
-	_AT_SYSINFO_EHDR = 33 // Offset to vDSO blob in process image
18
-)
19
-
20
-//go:linkname runtime_getAuxv runtime.getAuxv
21
-func runtime_getAuxv() []uintptr
22
-
23
-type auxvRuntimeReader struct {
24
-	data  []uintptr
25
-	index int
26
-}
27
-
28
-func (r *auxvRuntimeReader) Close() error {
29
-	return nil
30
-}
31
-
32
-func (r *auxvRuntimeReader) ReadAuxvPair() (uint64, uint64, error) {
33
-	if r.index >= len(r.data)+2 {
34
-		return 0, 0, io.EOF
35
-	}
36
-
37
-	// we manually add the (_AT_NULL, _AT_NULL) pair at the end
38
-	// that is not provided by the go runtime
39
-	var tag, value uintptr
40
-	if r.index+1 < len(r.data) {
41
-		tag, value = r.data[r.index], r.data[r.index+1]
42
-	} else {
43
-		tag, value = _AT_NULL, _AT_NULL
44
-	}
45
-	r.index += 2
46
-	return uint64(tag), uint64(value), nil
47
-}
48
-
49
-func newAuxvRuntimeReader() (auxvPairReader, error) {
50
-	data := runtime_getAuxv()
51
-
52
-	if len(data)%2 != 0 {
53
-		return nil, errors.New("malformed auxv passed from runtime")
54
-	}
55
-
56
-	return &auxvRuntimeReader{
57
-		data:  data,
58
-		index: 0,
59
-	}, nil
60
-}
... ...
@@ -23,7 +23,7 @@ func ErrorWithLog(source string, err error, log []byte) *VerifierError {
23 23
 
24 24
 	log = bytes.Trim(log, whitespace)
25 25
 	if len(log) == 0 {
26
-		return &VerifierError{source, err, nil, false}
26
+		return &VerifierError{source, err, nil}
27 27
 	}
28 28
 
29 29
 	logLines := bytes.Split(log, []byte{'\n'})
... ...
@@ -34,7 +34,7 @@ func ErrorWithLog(source string, err error, log []byte) *VerifierError {
34 34
 		lines = append(lines, string(bytes.TrimRight(line, whitespace)))
35 35
 	}
36 36
 
37
-	return &VerifierError{source, err, lines, false}
37
+	return &VerifierError{source, err, lines}
38 38
 }
39 39
 
40 40
 // VerifierError includes information from the eBPF verifier.
... ...
@@ -46,8 +46,6 @@ type VerifierError struct {
46 46
 	Cause error
47 47
 	// The verifier output split into lines.
48 48
 	Log []string
49
-	// Deprecated: the log is never truncated anymore.
50
-	Truncated bool
51 49
 }
52 50
 
53 51
 func (le *VerifierError) Unwrap() error {
... ...
@@ -3,15 +3,25 @@ package internal
3 3
 import (
4 4
 	"errors"
5 5
 	"fmt"
6
+	"runtime"
7
+	"strings"
6 8
 	"sync"
7 9
 )
8 10
 
9
-// ErrNotSupported indicates that a feature is not supported by the current kernel.
11
+// ErrNotSupported indicates that a feature is not supported.
10 12
 var ErrNotSupported = errors.New("not supported")
11 13
 
14
+// ErrNotSupportedOnOS indicates that a feature is not supported on the current
15
+// operating system.
16
+var ErrNotSupportedOnOS = fmt.Errorf("%w on %s", ErrNotSupported, runtime.GOOS)
17
+
12 18
 // UnsupportedFeatureError is returned by FeatureTest() functions.
13 19
 type UnsupportedFeatureError struct {
14
-	// The minimum Linux mainline version required for this feature.
20
+	// The minimum version required for this feature.
21
+	//
22
+	// On Linux this refers to the mainline kernel version, on other platforms
23
+	// to the version of the runtime.
24
+	//
15 25
 	// Used for the error string, and for sanity checking during testing.
16 26
 	MinimumVersion Version
17 27
 
... ...
@@ -58,11 +68,44 @@ type FeatureTest struct {
58 58
 type FeatureTestFn func() error
59 59
 
60 60
 // NewFeatureTest is a convenient way to create a single [FeatureTest].
61
-func NewFeatureTest(name, version string, fn FeatureTestFn) func() error {
61
+//
62
+// versions specifies in which version of a BPF runtime a feature appeared.
63
+// The format is "GOOS:Major.Minor[.Patch]". GOOS may be omitted when targeting
64
+// Linux. Returns [ErrNotSupportedOnOS] if there is no version specified for the
65
+// current OS.
66
+func NewFeatureTest(name string, fn FeatureTestFn, versions ...string) func() error {
67
+	const nativePrefix = runtime.GOOS + ":"
68
+
69
+	if len(versions) == 0 {
70
+		return func() error {
71
+			return fmt.Errorf("feature test %q: no versions specified", name)
72
+		}
73
+	}
74
+
62 75
 	ft := &FeatureTest{
63
-		Name:    name,
64
-		Version: version,
65
-		Fn:      fn,
76
+		Name: name,
77
+		Fn:   fn,
78
+	}
79
+
80
+	for _, version := range versions {
81
+		if strings.HasPrefix(version, nativePrefix) {
82
+			ft.Version = strings.TrimPrefix(version, nativePrefix)
83
+			break
84
+		}
85
+
86
+		if OnLinux && !strings.ContainsRune(version, ':') {
87
+			// Allow version numbers without a GOOS prefix on Linux.
88
+			ft.Version = version
89
+			break
90
+		}
91
+	}
92
+
93
+	if ft.Version == "" {
94
+		return func() error {
95
+			// We don't return an UnsupportedFeatureError here, since that will
96
+			// trigger version checks which don't make sense.
97
+			return fmt.Errorf("%s: %w", name, ErrNotSupportedOnOS)
98
+		}
66 99
 	}
67 100
 
68 101
 	return ft.execute
69 102
new file mode 100644
... ...
@@ -0,0 +1,7 @@
0
+package internal
1
+
2
+import "runtime"
3
+
4
+const (
5
+	OnLinux = runtime.GOOS == "linux"
6
+)
0 7
new file mode 100644
... ...
@@ -0,0 +1,20 @@
0
+package kallsyms
1
+
2
+import "sync"
3
+
4
+type cache[K, V comparable] struct {
5
+	m sync.Map
6
+}
7
+
8
+func (c *cache[K, V]) Load(key K) (value V, _ bool) {
9
+	v, ok := c.m.Load(key)
10
+	if !ok {
11
+		return value, false
12
+	}
13
+	value = v.(V)
14
+	return value, true
15
+}
16
+
17
+func (c *cache[K, V]) Store(key K, value V) {
18
+	c.m.Store(key, value)
19
+}
... ...
@@ -1,74 +1,289 @@
1 1
 package kallsyms
2 2
 
3 3
 import (
4
-	"bufio"
5
-	"bytes"
4
+	"errors"
5
+	"fmt"
6 6
 	"io"
7 7
 	"os"
8
-	"sync"
8
+	"slices"
9
+	"strconv"
10
+	"strings"
11
+
12
+	"github.com/cilium/ebpf/internal"
9 13
 )
10 14
 
11
-var kernelModules struct {
12
-	sync.RWMutex
13
-	// function to kernel module mapping
14
-	kmods map[string]string
15
+var errAmbiguousKsym = errors.New("multiple kernel symbols with the same name")
16
+
17
+var symAddrs cache[string, uint64]
18
+var symModules cache[string, string]
19
+
20
+// Module returns the kernel module providing the given symbol in the kernel, if
21
+// any. Returns an empty string and no error if the symbol is not present in the
22
+// kernel. Only function symbols are considered. Returns an error if multiple
23
+// symbols with the same name were found.
24
+//
25
+// Consider [AssignModules] if you need to resolve multiple symbols, as it will
26
+// only perform one iteration over /proc/kallsyms.
27
+func Module(name string) (string, error) {
28
+	if name == "" {
29
+		return "", nil
30
+	}
31
+
32
+	if mod, ok := symModules.Load(name); ok {
33
+		return mod, nil
34
+	}
35
+
36
+	request := map[string]string{name: ""}
37
+	if err := AssignModules(request); err != nil {
38
+		return "", err
39
+	}
40
+
41
+	return request[name], nil
15 42
 }
16 43
 
17
-// KernelModule returns the kernel module, if any, a probe-able function is contained in.
18
-func KernelModule(fn string) (string, error) {
19
-	kernelModules.RLock()
20
-	kmods := kernelModules.kmods
21
-	kernelModules.RUnlock()
44
+// AssignModules looks up the kernel module providing each given symbol, if any,
45
+// and assigns them to their corresponding values in the symbols map. Only
46
+// function symbols are considered. Results of all lookups are cached,
47
+// successful or otherwise.
48
+//
49
+// Any symbols missing in the kernel are ignored. Returns an error if multiple
50
+// symbols with a given name were found.
51
+func AssignModules(symbols map[string]string) error {
52
+	if !internal.OnLinux {
53
+		return fmt.Errorf("read /proc/kallsyms: %w", internal.ErrNotSupportedOnOS)
54
+	}
22 55
 
23
-	if kmods == nil {
24
-		kernelModules.Lock()
25
-		defer kernelModules.Unlock()
26
-		kmods = kernelModules.kmods
56
+	if len(symbols) == 0 {
57
+		return nil
27 58
 	}
28 59
 
29
-	if kmods != nil {
30
-		return kmods[fn], nil
60
+	// Attempt to fetch symbols from cache.
61
+	request := make(map[string]string)
62
+	for name := range symbols {
63
+		if mod, ok := symModules.Load(name); ok {
64
+			symbols[name] = mod
65
+			continue
66
+		}
67
+
68
+		// Mark the symbol to be read from /proc/kallsyms.
69
+		request[name] = ""
70
+	}
71
+	if len(request) == 0 {
72
+		// All symbols satisfied from cache.
73
+		return nil
31 74
 	}
32 75
 
33 76
 	f, err := os.Open("/proc/kallsyms")
34 77
 	if err != nil {
35
-		return "", err
78
+		return err
36 79
 	}
37 80
 	defer f.Close()
38
-	kmods, err = loadKernelModuleMapping(f)
39
-	if err != nil {
40
-		return "", err
81
+
82
+	if err := assignModules(f, request); err != nil {
83
+		return fmt.Errorf("assigning symbol modules: %w", err)
41 84
 	}
42 85
 
43
-	kernelModules.kmods = kmods
44
-	return kmods[fn], nil
86
+	// Update the cache with the new symbols. Cache all requested symbols, even if
87
+	// they're missing or don't belong to a module.
88
+	for name, mod := range request {
89
+		symModules.Store(name, mod)
90
+		symbols[name] = mod
91
+	}
92
+
93
+	return nil
45 94
 }
46 95
 
47
-// FlushKernelModuleCache removes any cached information about function to kernel module mapping.
48
-func FlushKernelModuleCache() {
49
-	kernelModules.Lock()
50
-	defer kernelModules.Unlock()
96
+// assignModules assigns kernel symbol modules read from f to values requested
97
+// by symbols. Always scans the whole input to make sure the user didn't request
98
+// an ambiguous symbol.
99
+func assignModules(f io.Reader, symbols map[string]string) error {
100
+	if len(symbols) == 0 {
101
+		return nil
102
+	}
103
+
104
+	found := make(map[string]struct{})
105
+	r := newReader(f)
106
+	for r.Line() {
107
+		// Only look for function symbols in the kernel's text section (tT).
108
+		s, err, skip := parseSymbol(r, []rune{'t', 'T'})
109
+		if err != nil {
110
+			return fmt.Errorf("parsing kallsyms line: %w", err)
111
+		}
112
+		if skip {
113
+			continue
114
+		}
115
+
116
+		if _, requested := symbols[s.name]; !requested {
117
+			continue
118
+		}
119
+
120
+		if _, ok := found[s.name]; ok {
121
+			// We've already seen this symbol. Return an error to avoid silently
122
+			// attaching to a symbol in the wrong module. libbpf also rejects
123
+			// referring to ambiguous symbols.
124
+			//
125
+			// We can't simply check if we already have a value for the given symbol,
126
+			// since many won't have an associated kernel module.
127
+			return fmt.Errorf("symbol %s: duplicate found at address 0x%x (module %q): %w",
128
+				s.name, s.addr, s.mod, errAmbiguousKsym)
129
+		}
130
+
131
+		symbols[s.name] = s.mod
132
+		found[s.name] = struct{}{}
133
+	}
134
+	if err := r.Err(); err != nil {
135
+		return fmt.Errorf("reading kallsyms: %w", err)
136
+	}
137
+
138
+	return nil
139
+}
140
+
141
+// Address returns the address of the given symbol in the kernel. Returns 0 and
142
+// no error if the symbol is not present. Returns an error if multiple addresses
143
+// were found for a symbol.
144
+//
145
+// Consider [AssignAddresses] if you need to resolve multiple symbols, as it
146
+// will only perform one iteration over /proc/kallsyms.
147
+func Address(symbol string) (uint64, error) {
148
+	if symbol == "" {
149
+		return 0, nil
150
+	}
151
+
152
+	if addr, ok := symAddrs.Load(symbol); ok {
153
+		return addr, nil
154
+	}
51 155
 
52
-	kernelModules.kmods = nil
156
+	request := map[string]uint64{symbol: 0}
157
+	if err := AssignAddresses(request); err != nil {
158
+		return 0, err
159
+	}
160
+
161
+	return request[symbol], nil
53 162
 }
54 163
 
55
-func loadKernelModuleMapping(f io.Reader) (map[string]string, error) {
56
-	mods := make(map[string]string)
57
-	scanner := bufio.NewScanner(f)
58
-	for scanner.Scan() {
59
-		fields := bytes.Fields(scanner.Bytes())
60
-		if len(fields) < 4 {
164
+// AssignAddresses looks up the addresses of the requested symbols in the kernel
165
+// and assigns them to their corresponding values in the symbols map. Results
166
+// of all lookups are cached, successful or otherwise.
167
+//
168
+// Any symbols missing in the kernel are ignored. Returns an error if multiple
169
+// addresses were found for a symbol.
170
+func AssignAddresses(symbols map[string]uint64) error {
171
+	if !internal.OnLinux {
172
+		return fmt.Errorf("read /proc/kallsyms: %w", internal.ErrNotSupportedOnOS)
173
+	}
174
+
175
+	if len(symbols) == 0 {
176
+		return nil
177
+	}
178
+
179
+	// Attempt to fetch symbols from cache.
180
+	request := make(map[string]uint64)
181
+	for name := range symbols {
182
+		if addr, ok := symAddrs.Load(name); ok {
183
+			symbols[name] = addr
61 184
 			continue
62 185
 		}
63
-		switch string(fields[1]) {
64
-		case "t", "T":
65
-			mods[string(fields[2])] = string(bytes.Trim(fields[3], "[]"))
66
-		default:
186
+
187
+		// Mark the symbol to be read from /proc/kallsyms.
188
+		request[name] = 0
189
+	}
190
+	if len(request) == 0 {
191
+		// All symbols satisfied from cache.
192
+		return nil
193
+	}
194
+
195
+	f, err := os.Open("/proc/kallsyms")
196
+	if err != nil {
197
+		return err
198
+	}
199
+	defer f.Close()
200
+
201
+	if err := assignAddresses(f, request); err != nil {
202
+		return fmt.Errorf("loading symbol addresses: %w", err)
203
+	}
204
+
205
+	// Update the cache with the new symbols. Cache all requested symbols even if
206
+	// they weren't found, to avoid repeated lookups.
207
+	for name, addr := range request {
208
+		symAddrs.Store(name, addr)
209
+		symbols[name] = addr
210
+	}
211
+
212
+	return nil
213
+}
214
+
215
+// assignAddresses assigns kernel symbol addresses read from f to values
216
+// requested by symbols. Always scans the whole input to make sure the user
217
+// didn't request an ambiguous symbol.
218
+func assignAddresses(f io.Reader, symbols map[string]uint64) error {
219
+	if len(symbols) == 0 {
220
+		return nil
221
+	}
222
+	r := newReader(f)
223
+	for r.Line() {
224
+		s, err, skip := parseSymbol(r, nil)
225
+		if err != nil {
226
+			return fmt.Errorf("parsing kallsyms line: %w", err)
227
+		}
228
+		if skip {
67 229
 			continue
68 230
 		}
231
+
232
+		existing, requested := symbols[s.name]
233
+		if existing != 0 {
234
+			// Multiple addresses for a symbol have been found. Return a friendly
235
+			// error to avoid silently attaching to the wrong symbol. libbpf also
236
+			// rejects referring to ambiguous symbols.
237
+			return fmt.Errorf("symbol %s(0x%x): duplicate found at address 0x%x: %w", s.name, existing, s.addr, errAmbiguousKsym)
238
+		}
239
+		if requested {
240
+			symbols[s.name] = s.addr
241
+		}
69 242
 	}
70
-	if scanner.Err() != nil {
71
-		return nil, scanner.Err()
243
+	if err := r.Err(); err != nil {
244
+		return fmt.Errorf("reading kallsyms: %w", err)
72 245
 	}
73
-	return mods, nil
246
+
247
+	return nil
248
+}
249
+
250
+type ksym struct {
251
+	addr uint64
252
+	name string
253
+	mod  string
254
+}
255
+
256
+// parseSymbol parses a line from /proc/kallsyms into an address, type, name and
257
+// module. Skip will be true if the symbol doesn't match any of the given symbol
258
+// types. See `man 1 nm` for all available types.
259
+//
260
+// Example line: `ffffffffc1682010 T nf_nat_init  [nf_nat]`
261
+func parseSymbol(r *reader, types []rune) (s ksym, err error, skip bool) {
262
+	for i := 0; r.Word(); i++ {
263
+		switch i {
264
+		// Address of the symbol.
265
+		case 0:
266
+			s.addr, err = strconv.ParseUint(r.Text(), 16, 64)
267
+			if err != nil {
268
+				return s, fmt.Errorf("parsing address: %w", err), false
269
+			}
270
+		// Type of the symbol. Assume the character is ASCII-encoded by converting
271
+		// it directly to a rune, since it's a fixed field controlled by the kernel.
272
+		case 1:
273
+			if len(types) > 0 && !slices.Contains(types, rune(r.Bytes()[0])) {
274
+				return s, nil, true
275
+			}
276
+		// Name of the symbol.
277
+		case 2:
278
+			s.name = r.Text()
279
+		// Kernel module the symbol is provided by.
280
+		case 3:
281
+			s.mod = strings.Trim(r.Text(), "[]")
282
+		// Ignore any future fields.
283
+		default:
284
+			break
285
+		}
286
+	}
287
+
288
+	return
74 289
 }
75 290
new file mode 100644
... ...
@@ -0,0 +1,118 @@
0
+package kallsyms
1
+
2
+import (
3
+	"bufio"
4
+	"io"
5
+	"unicode"
6
+	"unicode/utf8"
7
+)
8
+
9
+// reader is a line and word-oriented reader built for reading /proc/kallsyms.
10
+// It takes an io.Reader and iterates its contents line by line, then word by
11
+// word.
12
+//
13
+// It's designed to allow partial reading of lines without paying the cost of
14
+// allocating objects that will never be accessed, resulting in less work for
15
+// the garbage collector.
16
+type reader struct {
17
+	s    *bufio.Scanner
18
+	line []byte
19
+	word []byte
20
+
21
+	err error
22
+}
23
+
24
+func newReader(r io.Reader) *reader {
25
+	return &reader{
26
+		s: bufio.NewScanner(r),
27
+	}
28
+}
29
+
30
+// Bytes returns the current word as a byte slice.
31
+func (r *reader) Bytes() []byte {
32
+	return r.word
33
+}
34
+
35
+// Text returns the output of Bytes as a string.
36
+func (r *reader) Text() string {
37
+	return string(r.Bytes())
38
+}
39
+
40
+// Line advances the reader to the next line in the input. Calling Line resets
41
+// the current word, making [reader.Bytes] and [reader.Text] return empty
42
+// values. Follow this up with a call to [reader.Word].
43
+//
44
+// Like [bufio.Scanner], [reader.Err] needs to be checked after Line returns
45
+// false to determine if an error occurred during reading.
46
+//
47
+// Returns true if Line can be called again. Returns false if all lines in the
48
+// input have been read.
49
+func (r *reader) Line() bool {
50
+	for r.s.Scan() {
51
+		line := r.s.Bytes()
52
+		if len(line) == 0 {
53
+			continue
54
+		}
55
+
56
+		r.line = line
57
+		r.word = nil
58
+
59
+		return true
60
+	}
61
+	if err := r.s.Err(); err != nil {
62
+		r.err = err
63
+	}
64
+
65
+	return false
66
+}
67
+
68
+// Word advances the reader to the next word in the current line.
69
+//
70
+// Returns true if a word is found and Word should be called again. Returns
71
+// false when all words on the line have been read.
72
+func (r *reader) Word() bool {
73
+	if len(r.line) == 0 {
74
+		return false
75
+	}
76
+
77
+	// Find next word start, skipping leading spaces.
78
+	start := 0
79
+	for width := 0; start < len(r.line); start += width {
80
+		var c rune
81
+		c, width = utf8.DecodeRune(r.line[start:])
82
+		if !unicode.IsSpace(c) {
83
+			break
84
+		}
85
+	}
86
+
87
+	// Whitespace scanning reached the end of the line due to trailing whitespace,
88
+	// meaning there are no more words to read
89
+	if start == len(r.line) {
90
+		return false
91
+	}
92
+
93
+	// Find next word end.
94
+	for width, i := 0, start; i < len(r.line); i += width {
95
+		var c rune
96
+		c, width = utf8.DecodeRune(r.line[i:])
97
+		if unicode.IsSpace(c) {
98
+			r.word = r.line[start:i]
99
+			r.line = r.line[i:]
100
+			return true
101
+		}
102
+	}
103
+
104
+	// The line contains data, but no end-of-word boundary was found. This is the
105
+	// last, unterminated word in the line.
106
+	if len(r.line) > start {
107
+		r.word = r.line[start:]
108
+		r.line = nil
109
+		return true
110
+	}
111
+
112
+	return false
113
+}
114
+
115
+func (r *reader) Err() error {
116
+	return r.err
117
+}
... ...
@@ -1,3 +1,4 @@
1
+// Package kconfig implements a parser for the format of Linux's .config file.
1 2
 package kconfig
2 3
 
3 4
 import (
... ...
@@ -7,7 +8,6 @@ import (
7 7
 	"fmt"
8 8
 	"io"
9 9
 	"math"
10
-	"os"
11 10
 	"strconv"
12 11
 	"strings"
13 12
 
... ...
@@ -15,30 +15,6 @@ import (
15 15
 	"github.com/cilium/ebpf/internal"
16 16
 )
17 17
 
18
-// Find find a kconfig file on the host.
19
-// It first reads from /boot/config- of the current running kernel and tries
20
-// /proc/config.gz if nothing was found in /boot.
21
-// If none of the file provide a kconfig, it returns an error.
22
-func Find() (*os.File, error) {
23
-	kernelRelease, err := internal.KernelRelease()
24
-	if err != nil {
25
-		return nil, fmt.Errorf("cannot get kernel release: %w", err)
26
-	}
27
-
28
-	path := "/boot/config-" + kernelRelease
29
-	f, err := os.Open(path)
30
-	if err == nil {
31
-		return f, nil
32
-	}
33
-
34
-	f, err = os.Open("/proc/config.gz")
35
-	if err == nil {
36
-		return f, nil
37
-	}
38
-
39
-	return nil, fmt.Errorf("neither %s nor /proc/config.gz provide a kconfig", path)
40
-}
41
-
42 18
 // Parse parses the kconfig file for which a reader is given.
43 19
 // All the CONFIG_* which are in filter and which are set set will be
44 20
 // put in the returned map as key with their corresponding value as map value.
... ...
@@ -127,12 +103,13 @@ func PutValue(data []byte, typ btf.Type, value string) error {
127 127
 	switch value {
128 128
 	case "y", "n", "m":
129 129
 		return putValueTri(data, typ, value)
130
-	default:
131
-		if strings.HasPrefix(value, `"`) {
132
-			return putValueString(data, typ, value)
133
-		}
134
-		return putValueNumber(data, typ, value)
135 130
 	}
131
+
132
+	if strings.HasPrefix(value, `"`) {
133
+		return putValueString(data, typ, value)
134
+	}
135
+
136
+	return putValueNumber(data, typ, value)
136 137
 }
137 138
 
138 139
 // Golang translation of libbpf_tristate enum:
... ...
@@ -169,6 +146,10 @@ func putValueTri(data []byte, typ btf.Type, value string) error {
169 169
 			return fmt.Errorf("cannot use enum %q, only libbpf_tristate is supported", v.Name)
170 170
 		}
171 171
 
172
+		if len(data) != 4 {
173
+			return fmt.Errorf("expected enum value to occupy 4 bytes in datasec, got: %d", len(data))
174
+		}
175
+
172 176
 		var tri triState
173 177
 		switch value {
174 178
 		case "y":
... ...
@@ -178,10 +159,10 @@ func putValueTri(data []byte, typ btf.Type, value string) error {
178 178
 		case "n":
179 179
 			tri = TriNo
180 180
 		default:
181
-			return fmt.Errorf("value %q is not support for libbpf_tristate", value)
181
+			return fmt.Errorf("value %q is not supported for libbpf_tristate", value)
182 182
 		}
183 183
 
184
-		internal.NativeEndian.PutUint64(data, uint64(tri))
184
+		internal.NativeEndian.PutUint32(data, uint32(tri))
185 185
 	default:
186 186
 		return fmt.Errorf("cannot add number value, expected btf.Int or btf.Enum, got: %T", v)
187 187
 	}
188 188
new file mode 100644
... ...
@@ -0,0 +1,62 @@
0
+package linux
1
+
2
+import (
3
+	"fmt"
4
+	"io"
5
+
6
+	"github.com/cilium/ebpf/internal"
7
+	"github.com/cilium/ebpf/internal/unix"
8
+)
9
+
10
+type auxvPairReader interface {
11
+	Close() error
12
+	ReadAuxvPair() (uint64, uint64, error)
13
+}
14
+
15
+// See https://elixir.bootlin.com/linux/v6.5.5/source/include/uapi/linux/auxvec.h
16
+const (
17
+	_AT_NULL         = 0  // End of vector
18
+	_AT_SYSINFO_EHDR = 33 // Offset to vDSO blob in process image
19
+)
20
+
21
+type auxvRuntimeReader struct {
22
+	data  [][2]uintptr
23
+	index int
24
+}
25
+
26
+func (r *auxvRuntimeReader) Close() error {
27
+	return nil
28
+}
29
+
30
+func (r *auxvRuntimeReader) ReadAuxvPair() (uint64, uint64, error) {
31
+	if r.index >= len(r.data)+2 {
32
+		return 0, 0, io.EOF
33
+	}
34
+
35
+	// we manually add the (_AT_NULL, _AT_NULL) pair at the end
36
+	// that is not provided by the go runtime
37
+	var tag, value uintptr
38
+	if r.index < len(r.data) {
39
+		tag, value = r.data[r.index][0], r.data[r.index][1]
40
+	} else {
41
+		tag, value = _AT_NULL, _AT_NULL
42
+	}
43
+	r.index += 1
44
+	return uint64(tag), uint64(value), nil
45
+}
46
+
47
+func newAuxvRuntimeReader() (auxvPairReader, error) {
48
+	if !internal.OnLinux {
49
+		return nil, fmt.Errorf("read auxv from runtime: %w", internal.ErrNotSupportedOnOS)
50
+	}
51
+
52
+	data, err := unix.Auxv()
53
+	if err != nil {
54
+		return nil, fmt.Errorf("read auxv from runtime: %w", err)
55
+	}
56
+
57
+	return &auxvRuntimeReader{
58
+		data:  data,
59
+		index: 0,
60
+	}, nil
61
+}
0 62
new file mode 100644
... ...
@@ -0,0 +1,2 @@
0
+// Package linux contains OS specific wrappers around package unix.
1
+package linux
0 2
new file mode 100644
... ...
@@ -0,0 +1,31 @@
0
+package linux
1
+
2
+import (
3
+	"fmt"
4
+	"os"
5
+)
6
+
7
+// FindKConfig searches for a kconfig file on the host.
8
+//
9
+// It first reads from /boot/config- of the current running kernel and tries
10
+// /proc/config.gz if nothing was found in /boot.
11
+// If none of the file provide a kconfig, it returns an error.
12
+func FindKConfig() (*os.File, error) {
13
+	kernelRelease, err := KernelRelease()
14
+	if err != nil {
15
+		return nil, fmt.Errorf("cannot get kernel release: %w", err)
16
+	}
17
+
18
+	path := "/boot/config-" + kernelRelease
19
+	f, err := os.Open(path)
20
+	if err == nil {
21
+		return f, nil
22
+	}
23
+
24
+	f, err = os.Open("/proc/config.gz")
25
+	if err == nil {
26
+		return f, nil
27
+	}
28
+
29
+	return nil, fmt.Errorf("neither %s nor /proc/config.gz provide a kconfig", path)
30
+}
0 31
new file mode 100644
... ...
@@ -0,0 +1,43 @@
0
+package linux
1
+
2
+import (
3
+	"runtime"
4
+)
5
+
6
+// PlatformPrefix returns the platform-dependent syscall wrapper prefix used by
7
+// the linux kernel.
8
+//
9
+// Based on https://github.com/golang/go/blob/master/src/go/build/syslist.go
10
+// and https://github.com/libbpf/libbpf/blob/master/src/libbpf.c#L10047
11
+func PlatformPrefix() string {
12
+	switch runtime.GOARCH {
13
+	case "386":
14
+		return "__ia32_"
15
+	case "amd64", "amd64p32":
16
+		return "__x64_"
17
+
18
+	case "arm", "armbe":
19
+		return "__arm_"
20
+	case "arm64", "arm64be":
21
+		return "__arm64_"
22
+
23
+	case "mips", "mipsle", "mips64", "mips64le", "mips64p32", "mips64p32le":
24
+		return "__mips_"
25
+
26
+	case "s390":
27
+		return "__s390_"
28
+	case "s390x":
29
+		return "__s390x_"
30
+
31
+	case "riscv", "riscv64":
32
+		return "__riscv_"
33
+
34
+	case "ppc":
35
+		return "__powerpc_"
36
+	case "ppc64", "ppc64le":
37
+		return "__powerpc64_"
38
+
39
+	default:
40
+		return ""
41
+	}
42
+}
0 43
new file mode 100644
... ...
@@ -0,0 +1,23 @@
0
+package linux
1
+
2
+import (
3
+	"unsafe"
4
+
5
+	"github.com/cilium/ebpf/internal/unix"
6
+)
7
+
8
+func FSType(path string) (int64, error) {
9
+	var statfs unix.Statfs_t
10
+	if err := unix.Statfs(path, &statfs); err != nil {
11
+		return 0, err
12
+	}
13
+
14
+	fsType := int64(statfs.Type)
15
+	if unsafe.Sizeof(statfs.Type) == 4 {
16
+		// We're on a 32 bit arch, where statfs.Type is int32. bpfFSType is a
17
+		// negative number when interpreted as int32 so we need to cast via
18
+		// uint32 to avoid sign extension.
19
+		fsType = int64(uint32(statfs.Type))
20
+	}
21
+	return fsType, nil
22
+}
0 23
new file mode 100644
... ...
@@ -0,0 +1,144 @@
0
+package linux
1
+
2
+import (
3
+	"debug/elf"
4
+	"encoding/binary"
5
+	"errors"
6
+	"fmt"
7
+	"io"
8
+	"math"
9
+	"os"
10
+
11
+	"github.com/cilium/ebpf/internal"
12
+	"github.com/cilium/ebpf/internal/unix"
13
+)
14
+
15
+var (
16
+	errAuxvNoVDSO = errors.New("no vdso address found in auxv")
17
+)
18
+
19
+// vdsoVersion returns the LINUX_VERSION_CODE embedded in the vDSO library
20
+// linked into the current process image.
21
+func vdsoVersion() (uint32, error) {
22
+	av, err := newAuxvRuntimeReader()
23
+	if err != nil {
24
+		return 0, err
25
+	}
26
+
27
+	defer av.Close()
28
+
29
+	vdsoAddr, err := vdsoMemoryAddress(av)
30
+	if err != nil {
31
+		return 0, fmt.Errorf("finding vDSO memory address: %w", err)
32
+	}
33
+
34
+	// Use /proc/self/mem rather than unsafe.Pointer tricks.
35
+	mem, err := os.Open("/proc/self/mem")
36
+	if err != nil {
37
+		return 0, fmt.Errorf("opening mem: %w", err)
38
+	}
39
+	defer mem.Close()
40
+
41
+	// Open ELF at provided memory address, as offset into /proc/self/mem.
42
+	c, err := vdsoLinuxVersionCode(io.NewSectionReader(mem, int64(vdsoAddr), math.MaxInt64))
43
+	if err != nil {
44
+		return 0, fmt.Errorf("reading linux version code: %w", err)
45
+	}
46
+
47
+	return c, nil
48
+}
49
+
50
+// vdsoMemoryAddress returns the memory address of the vDSO library
51
+// linked into the current process image. r is an io.Reader into an auxv blob.
52
+func vdsoMemoryAddress(r auxvPairReader) (uintptr, error) {
53
+	// Loop through all tag/value pairs in auxv until we find `AT_SYSINFO_EHDR`,
54
+	// the address of a page containing the virtual Dynamic Shared Object (vDSO).
55
+	for {
56
+		tag, value, err := r.ReadAuxvPair()
57
+		if err != nil {
58
+			return 0, err
59
+		}
60
+
61
+		switch tag {
62
+		case _AT_SYSINFO_EHDR:
63
+			if value != 0 {
64
+				return uintptr(value), nil
65
+			}
66
+			return 0, fmt.Errorf("invalid vDSO address in auxv")
67
+		// _AT_NULL is always the last tag/val pair in the aux vector
68
+		// and can be treated like EOF.
69
+		case _AT_NULL:
70
+			return 0, errAuxvNoVDSO
71
+		}
72
+	}
73
+}
74
+
75
+// format described at https://www.man7.org/linux/man-pages/man5/elf.5.html in section 'Notes (Nhdr)'
76
+type elfNoteHeader struct {
77
+	NameSize int32
78
+	DescSize int32
79
+	Type     int32
80
+}
81
+
82
+// vdsoLinuxVersionCode returns the LINUX_VERSION_CODE embedded in
83
+// the ELF notes section of the binary provided by the reader.
84
+func vdsoLinuxVersionCode(r io.ReaderAt) (uint32, error) {
85
+	hdr, err := internal.NewSafeELFFile(r)
86
+	if err != nil {
87
+		return 0, fmt.Errorf("reading vDSO ELF: %w", err)
88
+	}
89
+
90
+	sections := hdr.SectionsByType(elf.SHT_NOTE)
91
+	if len(sections) == 0 {
92
+		return 0, fmt.Errorf("no note section found in vDSO ELF")
93
+	}
94
+
95
+	for _, sec := range sections {
96
+		sr := sec.Open()
97
+		var n elfNoteHeader
98
+
99
+		// Read notes until we find one named 'Linux'.
100
+		for {
101
+			if err := binary.Read(sr, hdr.ByteOrder, &n); err != nil {
102
+				if errors.Is(err, io.EOF) {
103
+					// We looked at all the notes in this section
104
+					break
105
+				}
106
+				return 0, fmt.Errorf("reading note header: %w", err)
107
+			}
108
+
109
+			// If a note name is defined, it follows the note header.
110
+			var name string
111
+			if n.NameSize > 0 {
112
+				// Read the note name, aligned to 4 bytes.
113
+				buf := make([]byte, internal.Align(n.NameSize, 4))
114
+				if err := binary.Read(sr, hdr.ByteOrder, &buf); err != nil {
115
+					return 0, fmt.Errorf("reading note name: %w", err)
116
+				}
117
+
118
+				// Read nul-terminated string.
119
+				name = unix.ByteSliceToString(buf[:n.NameSize])
120
+			}
121
+
122
+			// If a note descriptor is defined, it follows the name.
123
+			// It is possible for a note to have a descriptor but not a name.
124
+			if n.DescSize > 0 {
125
+				// LINUX_VERSION_CODE is a uint32 value.
126
+				if name == "Linux" && n.DescSize == 4 && n.Type == 0 {
127
+					var version uint32
128
+					if err := binary.Read(sr, hdr.ByteOrder, &version); err != nil {
129
+						return 0, fmt.Errorf("reading note descriptor: %w", err)
130
+					}
131
+					return version, nil
132
+				}
133
+
134
+				// Discard the note descriptor if it exists but we're not interested in it.
135
+				if _, err := io.CopyN(io.Discard, sr, int64(internal.Align(n.DescSize, 4))); err != nil {
136
+					return 0, err
137
+				}
138
+			}
139
+		}
140
+	}
141
+
142
+	return 0, fmt.Errorf("no Linux note in ELF")
143
+}
0 144
new file mode 100644
... ...
@@ -0,0 +1,34 @@
0
+package linux
1
+
2
+import (
3
+	"fmt"
4
+	"sync"
5
+
6
+	"github.com/cilium/ebpf/internal"
7
+	"github.com/cilium/ebpf/internal/unix"
8
+)
9
+
10
+// KernelVersion returns the version of the currently running kernel.
11
+var KernelVersion = sync.OnceValues(detectKernelVersion)
12
+
13
+// detectKernelVersion returns the version of the running kernel.
14
+func detectKernelVersion() (internal.Version, error) {
15
+	vc, err := vdsoVersion()
16
+	if err != nil {
17
+		return internal.Version{}, err
18
+	}
19
+	return internal.NewVersionFromCode(vc), nil
20
+}
21
+
22
+// KernelRelease returns the release string of the running kernel.
23
+// Its format depends on the Linux distribution and corresponds to directory
24
+// names in /lib/modules by convention. Some examples are 5.15.17-1-lts and
25
+// 4.19.0-16-amd64.
26
+func KernelRelease() (string, error) {
27
+	var uname unix.Utsname
28
+	if err := unix.Uname(&uname); err != nil {
29
+		return "", fmt.Errorf("uname failed: %w", err)
30
+	}
31
+
32
+	return unix.ByteSliceToString(uname.Release[:]), nil
33
+}
... ...
@@ -1,13 +1,33 @@
1 1
 package internal
2 2
 
3
-import "golang.org/x/exp/constraints"
4
-
5 3
 // Align returns 'n' updated to 'alignment' boundary.
6
-func Align[I constraints.Integer](n, alignment I) I {
4
+func Align[I Integer](n, alignment I) I {
7 5
 	return (n + alignment - 1) / alignment * alignment
8 6
 }
9 7
 
10 8
 // IsPow returns true if n is a power of two.
11
-func IsPow[I constraints.Integer](n I) bool {
9
+func IsPow[I Integer](n I) bool {
12 10
 	return n != 0 && (n&(n-1)) == 0
13 11
 }
12
+
13
+// Between returns the value clamped between a and b.
14
+func Between[I Integer](val, a, b I) I {
15
+	lower, upper := a, b
16
+	if lower > upper {
17
+		upper, lower = a, b
18
+	}
19
+
20
+	val = min(val, upper)
21
+	return max(val, lower)
22
+}
23
+
24
+// Integer represents all possible integer types.
25
+// Remove when x/exp/constraints is moved to the standard library.
26
+type Integer interface {
27
+	~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr
28
+}
29
+
30
+// List of integer types known by the Go compiler. Used by TestIntegerConstraint
31
+// to warn if a new integer type is introduced. Remove when x/exp/constraints
32
+// is moved to the standard library.
33
+var integers = []string{"int", "int8", "int16", "int32", "int64", "uint", "uint8", "uint16", "uint32", "uint64", "uintptr"}
14 34
deleted file mode 100644
... ...
@@ -1,65 +0,0 @@
1
-package internal
2
-
3
-import (
4
-	"errors"
5
-	"fmt"
6
-	"os"
7
-	"path/filepath"
8
-	"runtime"
9
-
10
-	"github.com/cilium/ebpf/internal/sys"
11
-	"github.com/cilium/ebpf/internal/unix"
12
-)
13
-
14
-func Pin(currentPath, newPath string, fd *sys.FD) error {
15
-	if newPath == "" {
16
-		return errors.New("given pinning path cannot be empty")
17
-	}
18
-	if currentPath == newPath {
19
-		return nil
20
-	}
21
-
22
-	fsType, err := FSType(filepath.Dir(newPath))
23
-	if err != nil {
24
-		return err
25
-	}
26
-	if fsType != unix.BPF_FS_MAGIC {
27
-		return fmt.Errorf("%s is not on a bpf filesystem", newPath)
28
-	}
29
-
30
-	defer runtime.KeepAlive(fd)
31
-
32
-	if currentPath == "" {
33
-		return sys.ObjPin(&sys.ObjPinAttr{
34
-			Pathname: sys.NewStringPointer(newPath),
35
-			BpfFd:    fd.Uint(),
36
-		})
37
-	}
38
-
39
-	// Renameat2 is used instead of os.Rename to disallow the new path replacing
40
-	// an existing path.
41
-	err = unix.Renameat2(unix.AT_FDCWD, currentPath, unix.AT_FDCWD, newPath, unix.RENAME_NOREPLACE)
42
-	if err == nil {
43
-		// Object is now moved to the new pinning path.
44
-		return nil
45
-	}
46
-	if !os.IsNotExist(err) {
47
-		return fmt.Errorf("unable to move pinned object to new path %v: %w", newPath, err)
48
-	}
49
-	// Internal state not in sync with the file system so let's fix it.
50
-	return sys.ObjPin(&sys.ObjPinAttr{
51
-		Pathname: sys.NewStringPointer(newPath),
52
-		BpfFd:    fd.Uint(),
53
-	})
54
-}
55
-
56
-func Unpin(pinnedPath string) error {
57
-	if pinnedPath == "" {
58
-		return nil
59
-	}
60
-	err := os.Remove(pinnedPath)
61
-	if err == nil || os.IsNotExist(err) {
62
-		return nil
63
-	}
64
-	return err
65
-}
66 1
deleted file mode 100644
... ...
@@ -1,43 +0,0 @@
1
-package internal
2
-
3
-import (
4
-	"runtime"
5
-)
6
-
7
-// PlatformPrefix returns the platform-dependent syscall wrapper prefix used by
8
-// the linux kernel.
9
-//
10
-// Based on https://github.com/golang/go/blob/master/src/go/build/syslist.go
11
-// and https://github.com/libbpf/libbpf/blob/master/src/libbpf.c#L10047
12
-func PlatformPrefix() string {
13
-	switch runtime.GOARCH {
14
-	case "386":
15
-		return "__ia32_"
16
-	case "amd64", "amd64p32":
17
-		return "__x64_"
18
-
19
-	case "arm", "armbe":
20
-		return "__arm_"
21
-	case "arm64", "arm64be":
22
-		return "__arm64_"
23
-
24
-	case "mips", "mipsle", "mips64", "mips64le", "mips64p32", "mips64p32le":
25
-		return "__mips_"
26
-
27
-	case "s390":
28
-		return "__s390_"
29
-	case "s390x":
30
-		return "__s390x_"
31
-
32
-	case "riscv", "riscv64":
33
-		return "__riscv_"
34
-
35
-	case "ppc":
36
-		return "__powerpc_"
37
-	case "ppc64", "ppc64le":
38
-		return "__powerpc64_"
39
-
40
-	default:
41
-		return ""
42
-	}
43
-}
44 1
deleted file mode 100644
... ...
@@ -1,23 +0,0 @@
1
-package internal
2
-
3
-import (
4
-	"unsafe"
5
-
6
-	"github.com/cilium/ebpf/internal/unix"
7
-)
8
-
9
-func FSType(path string) (int64, error) {
10
-	var statfs unix.Statfs_t
11
-	if err := unix.Statfs(path, &statfs); err != nil {
12
-		return 0, err
13
-	}
14
-
15
-	fsType := int64(statfs.Type)
16
-	if unsafe.Sizeof(statfs.Type) == 4 {
17
-		// We're on a 32 bit arch, where statfs.Type is int32. bpfFSType is a
18
-		// negative number when interpreted as int32 so we need to cast via
19
-		// uint32 to avoid sign extension.
20
-		fsType = int64(uint32(statfs.Type))
21
-	}
22
-	return fsType, nil
23
-}
... ...
@@ -4,9 +4,12 @@ import (
4 4
 	"fmt"
5 5
 	"math"
6 6
 	"os"
7
+	"path/filepath"
7 8
 	"runtime"
8 9
 	"strconv"
10
+	"strings"
9 11
 
12
+	"github.com/cilium/ebpf/internal/testutils/fdtrace"
10 13
 	"github.com/cilium/ebpf/internal/unix"
11 14
 )
12 15
 
... ...
@@ -17,15 +20,7 @@ type FD struct {
17 17
 }
18 18
 
19 19
 func newFD(value int) *FD {
20
-	if onLeakFD != nil {
21
-		// Attempt to store the caller's stack for the given fd value.
22
-		// Panic if fds contains an existing stack for the fd.
23
-		old, exist := fds.LoadOrStore(value, callersFrames())
24
-		if exist {
25
-			f := old.(*runtime.Frames)
26
-			panic(fmt.Sprintf("found existing stack for fd %d:\n%s", value, FormatFrames(f)))
27
-		}
28
-	}
20
+	fdtrace.TraceFD(value, 1)
29 21
 
30 22
 	fd := &FD{value}
31 23
 	runtime.SetFinalizer(fd, (*FD).finalize)
... ...
@@ -39,13 +34,7 @@ func (fd *FD) finalize() {
39 39
 		return
40 40
 	}
41 41
 
42
-	// Invoke the fd leak callback. Calls LoadAndDelete to guarantee the callback
43
-	// is invoked at most once for one sys.FD allocation, runtime.Frames can only
44
-	// be unwound once.
45
-	f, ok := fds.LoadAndDelete(fd.Int())
46
-	if ok && onLeakFD != nil {
47
-		onLeakFD(f.(*runtime.Frames))
48
-	}
42
+	fdtrace.LeakFD(fd.raw)
49 43
 
50 44
 	_ = fd.Close()
51 45
 }
... ...
@@ -92,12 +81,15 @@ func (fd *FD) Close() error {
92 92
 		return nil
93 93
 	}
94 94
 
95
-	return unix.Close(fd.disown())
95
+	return unix.Close(fd.Disown())
96 96
 }
97 97
 
98
-func (fd *FD) disown() int {
99
-	value := int(fd.raw)
100
-	fds.Delete(int(value))
98
+// Disown destroys the FD and returns its raw file descriptor without closing
99
+// it. After this call, the underlying fd is no longer tied to the FD's
100
+// lifecycle.
101
+func (fd *FD) Disown() int {
102
+	value := fd.raw
103
+	fdtrace.ForgetFD(value)
101 104
 	fd.raw = -1
102 105
 
103 106
 	runtime.SetFinalizer(fd, nil)
... ...
@@ -129,5 +121,45 @@ func (fd *FD) File(name string) *os.File {
129 129
 		return nil
130 130
 	}
131 131
 
132
-	return os.NewFile(uintptr(fd.disown()), name)
132
+	return os.NewFile(uintptr(fd.Disown()), name)
133
+}
134
+
135
+// ObjGetTyped wraps [ObjGet] with a readlink call to extract the type of the
136
+// underlying bpf object.
137
+func ObjGetTyped(attr *ObjGetAttr) (*FD, ObjType, error) {
138
+	fd, err := ObjGet(attr)
139
+	if err != nil {
140
+		return nil, 0, err
141
+	}
142
+
143
+	typ, err := readType(fd)
144
+	if err != nil {
145
+		_ = fd.Close()
146
+		return nil, 0, fmt.Errorf("reading fd type: %w", err)
147
+	}
148
+
149
+	return fd, typ, nil
150
+}
151
+
152
+// readType returns the bpf object type of the file descriptor by calling
153
+// readlink(3). Returns an error if the file descriptor does not represent a bpf
154
+// object.
155
+func readType(fd *FD) (ObjType, error) {
156
+	s, err := os.Readlink(filepath.Join("/proc/self/fd/", fd.String()))
157
+	if err != nil {
158
+		return 0, fmt.Errorf("readlink fd %d: %w", fd.Int(), err)
159
+	}
160
+
161
+	s = strings.TrimPrefix(s, "anon_inode:")
162
+
163
+	switch s {
164
+	case "bpf-map":
165
+		return BPF_TYPE_MAP, nil
166
+	case "bpf-prog":
167
+		return BPF_TYPE_PROG, nil
168
+	case "bpf-link":
169
+		return BPF_TYPE_LINK, nil
170
+	}
171
+
172
+	return 0, fmt.Errorf("unknown type %s of fd %d", s, fd.Int())
133 173
 }
134 174
deleted file mode 100644
... ...
@@ -1,93 +0,0 @@
1
-package sys
2
-
3
-import (
4
-	"bytes"
5
-	"fmt"
6
-	"runtime"
7
-	"sync"
8
-)
9
-
10
-// OnLeakFD controls tracing [FD] lifetime to detect resources that are not
11
-// closed by Close().
12
-//
13
-// If fn is not nil, tracing is enabled for all FDs created going forward. fn is
14
-// invoked for all FDs that are closed by the garbage collector instead of an
15
-// explicit Close() by a caller. Calling OnLeakFD twice with a non-nil fn
16
-// (without disabling tracing in the meantime) will cause a panic.
17
-//
18
-// If fn is nil, tracing will be disabled. Any FDs that have not been closed are
19
-// considered to be leaked, fn will be invoked for them, and the process will be
20
-// terminated.
21
-//
22
-// fn will be invoked at most once for every unique sys.FD allocation since a
23
-// runtime.Frames can only be unwound once.
24
-func OnLeakFD(fn func(*runtime.Frames)) {
25
-	// Enable leak tracing if new fn is provided.
26
-	if fn != nil {
27
-		if onLeakFD != nil {
28
-			panic("OnLeakFD called twice with non-nil fn")
29
-		}
30
-
31
-		onLeakFD = fn
32
-		return
33
-	}
34
-
35
-	// fn is nil past this point.
36
-
37
-	if onLeakFD == nil {
38
-		return
39
-	}
40
-
41
-	// Call onLeakFD for all open fds.
42
-	if fs := flushFrames(); len(fs) != 0 {
43
-		for _, f := range fs {
44
-			onLeakFD(f)
45
-		}
46
-	}
47
-
48
-	onLeakFD = nil
49
-}
50
-
51
-var onLeakFD func(*runtime.Frames)
52
-
53
-// fds is a registry of all file descriptors wrapped into sys.fds that were
54
-// created while an fd tracer was active.
55
-var fds sync.Map // map[int]*runtime.Frames
56
-
57
-// flushFrames removes all elements from fds and returns them as a slice. This
58
-// deals with the fact that a runtime.Frames can only be unwound once using
59
-// Next().
60
-func flushFrames() []*runtime.Frames {
61
-	var frames []*runtime.Frames
62
-	fds.Range(func(key, value any) bool {
63
-		frames = append(frames, value.(*runtime.Frames))
64
-		fds.Delete(key)
65
-		return true
66
-	})
67
-	return frames
68
-}
69
-
70
-func callersFrames() *runtime.Frames {
71
-	c := make([]uintptr, 32)
72
-
73
-	// Skip runtime.Callers and this function.
74
-	i := runtime.Callers(2, c)
75
-	if i == 0 {
76
-		return nil
77
-	}
78
-
79
-	return runtime.CallersFrames(c)
80
-}
81
-
82
-// FormatFrames formats a runtime.Frames as a human-readable string.
83
-func FormatFrames(fs *runtime.Frames) string {
84
-	var b bytes.Buffer
85
-	for {
86
-		f, more := fs.Next()
87
-		b.WriteString(fmt.Sprintf("\t%s+%#x\n\t\t%s:%d\n", f.Function, f.PC-f.Entry, f.File, f.Line))
88
-		if !more {
89
-			break
90
-		}
91
-	}
92
-	return b.String()
93
-}
94 1
deleted file mode 100644
... ...
@@ -1,53 +0,0 @@
1
-// Code generated by "stringer -type MapFlags"; DO NOT EDIT.
2
-
3
-package sys
4
-
5
-import "strconv"
6
-
7
-func _() {
8
-	// An "invalid array index" compiler error signifies that the constant values have changed.
9
-	// Re-run the stringer command to generate them again.
10
-	var x [1]struct{}
11
-	_ = x[BPF_F_NO_PREALLOC-1]
12
-	_ = x[BPF_F_NO_COMMON_LRU-2]
13
-	_ = x[BPF_F_NUMA_NODE-4]
14
-	_ = x[BPF_F_RDONLY-8]
15
-	_ = x[BPF_F_WRONLY-16]
16
-	_ = x[BPF_F_STACK_BUILD_ID-32]
17
-	_ = x[BPF_F_ZERO_SEED-64]
18
-	_ = x[BPF_F_RDONLY_PROG-128]
19
-	_ = x[BPF_F_WRONLY_PROG-256]
20
-	_ = x[BPF_F_CLONE-512]
21
-	_ = x[BPF_F_MMAPABLE-1024]
22
-	_ = x[BPF_F_PRESERVE_ELEMS-2048]
23
-	_ = x[BPF_F_INNER_MAP-4096]
24
-	_ = x[BPF_F_LINK-8192]
25
-	_ = x[BPF_F_PATH_FD-16384]
26
-}
27
-
28
-const _MapFlags_name = "BPF_F_NO_PREALLOCBPF_F_NO_COMMON_LRUBPF_F_NUMA_NODEBPF_F_RDONLYBPF_F_WRONLYBPF_F_STACK_BUILD_IDBPF_F_ZERO_SEEDBPF_F_RDONLY_PROGBPF_F_WRONLY_PROGBPF_F_CLONEBPF_F_MMAPABLEBPF_F_PRESERVE_ELEMSBPF_F_INNER_MAPBPF_F_LINKBPF_F_PATH_FD"
29
-
30
-var _MapFlags_map = map[MapFlags]string{
31
-	1:     _MapFlags_name[0:17],
32
-	2:     _MapFlags_name[17:36],
33
-	4:     _MapFlags_name[36:51],
34
-	8:     _MapFlags_name[51:63],
35
-	16:    _MapFlags_name[63:75],
36
-	32:    _MapFlags_name[75:95],
37
-	64:    _MapFlags_name[95:110],
38
-	128:   _MapFlags_name[110:127],
39
-	256:   _MapFlags_name[127:144],
40
-	512:   _MapFlags_name[144:155],
41
-	1024:  _MapFlags_name[155:169],
42
-	2048:  _MapFlags_name[169:189],
43
-	4096:  _MapFlags_name[189:204],
44
-	8192:  _MapFlags_name[204:214],
45
-	16384: _MapFlags_name[214:227],
46
-}
47
-
48
-func (i MapFlags) String() string {
49
-	if str, ok := _MapFlags_map[i]; ok {
50
-		return str
51
-	}
52
-	return "MapFlags(" + strconv.FormatInt(int64(i), 10) + ")"
53
-}
54 1
new file mode 100644
... ...
@@ -0,0 +1,65 @@
0
+package sys
1
+
2
+import (
3
+	"errors"
4
+	"fmt"
5
+	"os"
6
+	"path/filepath"
7
+	"runtime"
8
+
9
+	"github.com/cilium/ebpf/internal/linux"
10
+	"github.com/cilium/ebpf/internal/unix"
11
+)
12
+
13
+func Pin(currentPath, newPath string, fd *FD) error {
14
+	if newPath == "" {
15
+		return errors.New("given pinning path cannot be empty")
16
+	}
17
+	if currentPath == newPath {
18
+		return nil
19
+	}
20
+
21
+	fsType, err := linux.FSType(filepath.Dir(newPath))
22
+	if err != nil {
23
+		return err
24
+	}
25
+	if fsType != unix.BPF_FS_MAGIC {
26
+		return fmt.Errorf("%s is not on a bpf filesystem", newPath)
27
+	}
28
+
29
+	defer runtime.KeepAlive(fd)
30
+
31
+	if currentPath == "" {
32
+		return ObjPin(&ObjPinAttr{
33
+			Pathname: NewStringPointer(newPath),
34
+			BpfFd:    fd.Uint(),
35
+		})
36
+	}
37
+
38
+	// Renameat2 is used instead of os.Rename to disallow the new path replacing
39
+	// an existing path.
40
+	err = unix.Renameat2(unix.AT_FDCWD, currentPath, unix.AT_FDCWD, newPath, unix.RENAME_NOREPLACE)
41
+	if err == nil {
42
+		// Object is now moved to the new pinning path.
43
+		return nil
44
+	}
45
+	if !os.IsNotExist(err) {
46
+		return fmt.Errorf("unable to move pinned object to new path %v: %w", newPath, err)
47
+	}
48
+	// Internal state not in sync with the file system so let's fix it.
49
+	return ObjPin(&ObjPinAttr{
50
+		Pathname: NewStringPointer(newPath),
51
+		BpfFd:    fd.Uint(),
52
+	})
53
+}
54
+
55
+func Unpin(pinnedPath string) error {
56
+	if pinnedPath == "" {
57
+		return nil
58
+	}
59
+	err := os.Remove(pinnedPath)
60
+	if err == nil || os.IsNotExist(err) {
61
+		return nil
62
+	}
63
+	return err
64
+}
... ...
@@ -11,13 +11,13 @@ func NewPointer(ptr unsafe.Pointer) Pointer {
11 11
 	return Pointer{ptr: ptr}
12 12
 }
13 13
 
14
-// NewSlicePointer creates a 64-bit pointer from a byte slice.
15
-func NewSlicePointer(buf []byte) Pointer {
14
+// NewSlicePointer creates a 64-bit pointer from a slice.
15
+func NewSlicePointer[T comparable](buf []T) Pointer {
16 16
 	if len(buf) == 0 {
17 17
 		return Pointer{}
18 18
 	}
19 19
 
20
-	return Pointer{ptr: unsafe.Pointer(&buf[0])}
20
+	return Pointer{ptr: unsafe.Pointer(unsafe.SliceData(buf))}
21 21
 }
22 22
 
23 23
 // NewSlicePointerLen creates a 64-bit pointer from a byte slice.
... ...
@@ -2,7 +2,6 @@ package sys
2 2
 
3 3
 import (
4 4
 	"runtime"
5
-	"syscall"
6 5
 	"unsafe"
7 6
 
8 7
 	"github.com/cilium/ebpf/internal/unix"
... ...
@@ -11,7 +10,7 @@ import (
11 11
 // ENOTSUPP is a Linux internal error code that has leaked into UAPI.
12 12
 //
13 13
 // It is not the same as ENOTSUP or EOPNOTSUPP.
14
-const ENOTSUPP = syscall.Errno(524)
14
+const ENOTSUPP = unix.Errno(524)
15 15
 
16 16
 // BPF wraps SYS_BPF.
17 17
 //
... ...
@@ -133,12 +132,12 @@ func ObjInfo(fd *FD, info Info) error {
133 133
 
134 134
 // BPFObjName is a null-terminated string made up of
135 135
 // 'A-Za-z0-9_' characters.
136
-type ObjName [unix.BPF_OBJ_NAME_LEN]byte
136
+type ObjName [BPF_OBJ_NAME_LEN]byte
137 137
 
138 138
 // NewObjName truncates the result if it is too long.
139 139
 func NewObjName(name string) ObjName {
140 140
 	var result ObjName
141
-	copy(result[:unix.BPF_OBJ_NAME_LEN-1], name)
141
+	copy(result[:BPF_OBJ_NAME_LEN-1], name)
142 142
 	return result
143 143
 }
144 144
 
... ...
@@ -160,29 +159,6 @@ type BTFID uint32
160 160
 // TypeID identifies a type in a BTF blob.
161 161
 type TypeID uint32
162 162
 
163
-// MapFlags control map behaviour.
164
-type MapFlags uint32
165
-
166
-//go:generate go run golang.org/x/tools/cmd/stringer@latest -type MapFlags
167
-
168
-const (
169
-	BPF_F_NO_PREALLOC MapFlags = 1 << iota
170
-	BPF_F_NO_COMMON_LRU
171
-	BPF_F_NUMA_NODE
172
-	BPF_F_RDONLY
173
-	BPF_F_WRONLY
174
-	BPF_F_STACK_BUILD_ID
175
-	BPF_F_ZERO_SEED
176
-	BPF_F_RDONLY_PROG
177
-	BPF_F_WRONLY_PROG
178
-	BPF_F_CLONE
179
-	BPF_F_MMAPABLE
180
-	BPF_F_PRESERVE_ELEMS
181
-	BPF_F_INNER_MAP
182
-	BPF_F_LINK
183
-	BPF_F_PATH_FD
184
-)
185
-
186 163
 // Flags used by bpf_mprog.
187 164
 const (
188 165
 	BPF_F_REPLACE = 1 << (iota + 2)
... ...
@@ -192,12 +168,22 @@ const (
192 192
 	BPF_F_LINK_MPROG = 1 << 13 // aka BPF_F_LINK
193 193
 )
194 194
 
195
-// wrappedErrno wraps syscall.Errno to prevent direct comparisons with
195
+// Flags used by BPF_PROG_LOAD.
196
+const (
197
+	BPF_F_SLEEPABLE          = 1 << 4
198
+	BPF_F_XDP_HAS_FRAGS      = 1 << 5
199
+	BPF_F_XDP_DEV_BOUND_ONLY = 1 << 6
200
+)
201
+
202
+const BPF_TAG_SIZE = 8
203
+const BPF_OBJ_NAME_LEN = 16
204
+
205
+// wrappedErrno wraps [unix.Errno] to prevent direct comparisons with
196 206
 // syscall.E* or unix.E* constants.
197 207
 //
198 208
 // You should never export an error of this type.
199 209
 type wrappedErrno struct {
200
-	syscall.Errno
210
+	unix.Errno
201 211
 }
202 212
 
203 213
 func (we wrappedErrno) Unwrap() error {
... ...
@@ -213,10 +199,10 @@ func (we wrappedErrno) Error() string {
213 213
 
214 214
 type syscallError struct {
215 215
 	error
216
-	errno syscall.Errno
216
+	errno unix.Errno
217 217
 }
218 218
 
219
-func Error(err error, errno syscall.Errno) error {
219
+func Error(err error, errno unix.Errno) error {
220 220
 	return &syscallError{err, errno}
221 221
 }
222 222
 
... ...
@@ -6,6 +6,176 @@ import (
6 6
 	"unsafe"
7 7
 )
8 8
 
9
+const (
10
+	BPF_ADJ_ROOM_ENCAP_L2_MASK                 = 255
11
+	BPF_ADJ_ROOM_ENCAP_L2_SHIFT                = 56
12
+	BPF_ANY                                    = 0
13
+	BPF_CSUM_LEVEL_DEC                         = 2
14
+	BPF_CSUM_LEVEL_INC                         = 1
15
+	BPF_CSUM_LEVEL_QUERY                       = 0
16
+	BPF_CSUM_LEVEL_RESET                       = 3
17
+	BPF_EXIST                                  = 2
18
+	BPF_FIB_LKUP_RET_BLACKHOLE                 = 1
19
+	BPF_FIB_LKUP_RET_FRAG_NEEDED               = 8
20
+	BPF_FIB_LKUP_RET_FWD_DISABLED              = 5
21
+	BPF_FIB_LKUP_RET_NOT_FWDED                 = 4
22
+	BPF_FIB_LKUP_RET_NO_NEIGH                  = 7
23
+	BPF_FIB_LKUP_RET_NO_SRC_ADDR               = 9
24
+	BPF_FIB_LKUP_RET_PROHIBIT                  = 3
25
+	BPF_FIB_LKUP_RET_SUCCESS                   = 0
26
+	BPF_FIB_LKUP_RET_UNREACHABLE               = 2
27
+	BPF_FIB_LKUP_RET_UNSUPP_LWT                = 6
28
+	BPF_FIB_LOOKUP_DIRECT                      = 1
29
+	BPF_FIB_LOOKUP_MARK                        = 32
30
+	BPF_FIB_LOOKUP_OUTPUT                      = 2
31
+	BPF_FIB_LOOKUP_SKIP_NEIGH                  = 4
32
+	BPF_FIB_LOOKUP_SRC                         = 16
33
+	BPF_FIB_LOOKUP_TBID                        = 8
34
+	BPF_FLOW_DISSECTOR_F_PARSE_1ST_FRAG        = 1
35
+	BPF_FLOW_DISSECTOR_F_STOP_AT_ENCAP         = 4
36
+	BPF_FLOW_DISSECTOR_F_STOP_AT_FLOW_LABEL    = 2
37
+	BPF_F_ADJ_ROOM_DECAP_L3_IPV4               = 128
38
+	BPF_F_ADJ_ROOM_DECAP_L3_IPV6               = 256
39
+	BPF_F_ADJ_ROOM_ENCAP_L2_ETH                = 64
40
+	BPF_F_ADJ_ROOM_ENCAP_L3_IPV4               = 2
41
+	BPF_F_ADJ_ROOM_ENCAP_L3_IPV6               = 4
42
+	BPF_F_ADJ_ROOM_ENCAP_L4_GRE                = 8
43
+	BPF_F_ADJ_ROOM_ENCAP_L4_UDP                = 16
44
+	BPF_F_ADJ_ROOM_FIXED_GSO                   = 1
45
+	BPF_F_ADJ_ROOM_NO_CSUM_RESET               = 32
46
+	BPF_F_BPRM_SECUREEXEC                      = 1
47
+	BPF_F_BROADCAST                            = 8
48
+	BPF_F_CLONE                                = 512
49
+	BPF_F_CTXLEN_MASK                          = 4503595332403200
50
+	BPF_F_CURRENT_CPU                          = 4294967295
51
+	BPF_F_CURRENT_NETNS                        = 18446744073709551615
52
+	BPF_F_DONT_FRAGMENT                        = 4
53
+	BPF_F_EXCLUDE_INGRESS                      = 16
54
+	BPF_F_FAST_STACK_CMP                       = 512
55
+	BPF_F_GET_BRANCH_RECORDS_SIZE              = 1
56
+	BPF_F_HDR_FIELD_MASK                       = 15
57
+	BPF_F_INDEX_MASK                           = 4294967295
58
+	BPF_F_INGRESS                              = 1
59
+	BPF_F_INNER_MAP                            = 4096
60
+	BPF_F_INVALIDATE_HASH                      = 2
61
+	BPF_F_KPROBE_MULTI_RETURN                  = 1
62
+	BPF_F_LINK                                 = 8192
63
+	BPF_F_LOCK                                 = 4
64
+	BPF_F_MARK_ENFORCE                         = 64
65
+	BPF_F_MARK_MANGLED_0                       = 32
66
+	BPF_F_MMAPABLE                             = 1024
67
+	BPF_F_NEIGH                                = 2
68
+	BPF_F_NEXTHOP                              = 8
69
+	BPF_F_NO_COMMON_LRU                        = 2
70
+	BPF_F_NO_PREALLOC                          = 1
71
+	BPF_F_NO_TUNNEL_KEY                        = 16
72
+	BPF_F_NO_USER_CONV                         = 262144
73
+	BPF_F_NUMA_NODE                            = 4
74
+	BPF_F_PATH_FD                              = 16384
75
+	BPF_F_PEER                                 = 4
76
+	BPF_F_PRESERVE_ELEMS                       = 2048
77
+	BPF_F_PSEUDO_HDR                           = 16
78
+	BPF_F_RDONLY                               = 8
79
+	BPF_F_RDONLY_PROG                          = 128
80
+	BPF_F_RECOMPUTE_CSUM                       = 1
81
+	BPF_F_REUSE_STACKID                        = 1024
82
+	BPF_F_SEGV_ON_FAULT                        = 131072
83
+	BPF_F_SEQ_NUMBER                           = 8
84
+	BPF_F_SKIP_FIELD_MASK                      = 255
85
+	BPF_F_STACK_BUILD_ID                       = 32
86
+	BPF_F_SYSCTL_BASE_NAME                     = 1
87
+	BPF_F_TIMER_ABS                            = 1
88
+	BPF_F_TIMER_CPU_PIN                        = 2
89
+	BPF_F_TOKEN_FD                             = 65536
90
+	BPF_F_TUNINFO_FLAGS                        = 16
91
+	BPF_F_TUNINFO_IPV6                         = 1
92
+	BPF_F_UPROBE_MULTI_RETURN                  = 1
93
+	BPF_F_USER_BUILD_ID                        = 2048
94
+	BPF_F_USER_STACK                           = 256
95
+	BPF_F_VTYPE_BTF_OBJ_FD                     = 32768
96
+	BPF_F_WRONLY                               = 16
97
+	BPF_F_WRONLY_PROG                          = 256
98
+	BPF_F_ZERO_CSUM_TX                         = 2
99
+	BPF_F_ZERO_SEED                            = 64
100
+	BPF_LOAD_HDR_OPT_TCP_SYN                   = 1
101
+	BPF_LOCAL_STORAGE_GET_F_CREATE             = 1
102
+	BPF_MAX_LOOPS                              = 8388608
103
+	BPF_MAX_TRAMP_LINKS                        = 38
104
+	BPF_NOEXIST                                = 1
105
+	BPF_RB_AVAIL_DATA                          = 0
106
+	BPF_RB_CONS_POS                            = 2
107
+	BPF_RB_FORCE_WAKEUP                        = 2
108
+	BPF_RB_NO_WAKEUP                           = 1
109
+	BPF_RB_PROD_POS                            = 3
110
+	BPF_RB_RING_SIZE                           = 1
111
+	BPF_REG_0                                  = 0
112
+	BPF_REG_1                                  = 1
113
+	BPF_REG_10                                 = 10
114
+	BPF_REG_2                                  = 2
115
+	BPF_REG_3                                  = 3
116
+	BPF_REG_4                                  = 4
117
+	BPF_REG_5                                  = 5
118
+	BPF_REG_6                                  = 6
119
+	BPF_REG_7                                  = 7
120
+	BPF_REG_8                                  = 8
121
+	BPF_REG_9                                  = 9
122
+	BPF_RINGBUF_BUSY_BIT                       = 2147483648
123
+	BPF_RINGBUF_DISCARD_BIT                    = 1073741824
124
+	BPF_RINGBUF_HDR_SZ                         = 8
125
+	BPF_SKB_CLOCK_MONOTONIC                    = 1
126
+	BPF_SKB_CLOCK_REALTIME                     = 0
127
+	BPF_SKB_CLOCK_TAI                          = 2
128
+	BPF_SKB_TSTAMP_DELIVERY_MONO               = 1
129
+	BPF_SKB_TSTAMP_UNSPEC                      = 0
130
+	BPF_SK_LOOKUP_F_NO_REUSEPORT               = 2
131
+	BPF_SK_LOOKUP_F_REPLACE                    = 1
132
+	BPF_SK_STORAGE_GET_F_CREATE                = 1
133
+	BPF_SOCK_OPS_ACTIVE_ESTABLISHED_CB         = 4
134
+	BPF_SOCK_OPS_ALL_CB_FLAGS                  = 127
135
+	BPF_SOCK_OPS_BASE_RTT                      = 7
136
+	BPF_SOCK_OPS_HDR_OPT_LEN_CB                = 14
137
+	BPF_SOCK_OPS_NEEDS_ECN                     = 6
138
+	BPF_SOCK_OPS_PARSE_ALL_HDR_OPT_CB_FLAG     = 16
139
+	BPF_SOCK_OPS_PARSE_HDR_OPT_CB              = 13
140
+	BPF_SOCK_OPS_PARSE_UNKNOWN_HDR_OPT_CB_FLAG = 32
141
+	BPF_SOCK_OPS_PASSIVE_ESTABLISHED_CB        = 5
142
+	BPF_SOCK_OPS_RETRANS_CB                    = 9
143
+	BPF_SOCK_OPS_RETRANS_CB_FLAG               = 2
144
+	BPF_SOCK_OPS_RTO_CB                        = 8
145
+	BPF_SOCK_OPS_RTO_CB_FLAG                   = 1
146
+	BPF_SOCK_OPS_RTT_CB                        = 12
147
+	BPF_SOCK_OPS_RTT_CB_FLAG                   = 8
148
+	BPF_SOCK_OPS_RWND_INIT                     = 2
149
+	BPF_SOCK_OPS_STATE_CB                      = 10
150
+	BPF_SOCK_OPS_STATE_CB_FLAG                 = 4
151
+	BPF_SOCK_OPS_TCP_CONNECT_CB                = 3
152
+	BPF_SOCK_OPS_TCP_LISTEN_CB                 = 11
153
+	BPF_SOCK_OPS_TIMEOUT_INIT                  = 1
154
+	BPF_SOCK_OPS_VOID                          = 0
155
+	BPF_SOCK_OPS_WRITE_HDR_OPT_CB              = 15
156
+	BPF_SOCK_OPS_WRITE_HDR_OPT_CB_FLAG         = 64
157
+	BPF_TASK_ITER_ALL_PROCS                    = 0
158
+	BPF_TASK_ITER_ALL_THREADS                  = 1
159
+	BPF_TASK_ITER_PROC_THREADS                 = 2
160
+	BPF_TCP_BOUND_INACTIVE                     = 13
161
+	BPF_TCP_CLOSE                              = 7
162
+	BPF_TCP_CLOSE_WAIT                         = 8
163
+	BPF_TCP_CLOSING                            = 11
164
+	BPF_TCP_ESTABLISHED                        = 1
165
+	BPF_TCP_FIN_WAIT1                          = 4
166
+	BPF_TCP_FIN_WAIT2                          = 5
167
+	BPF_TCP_LAST_ACK                           = 9
168
+	BPF_TCP_LISTEN                             = 10
169
+	BPF_TCP_MAX_STATES                         = 14
170
+	BPF_TCP_NEW_SYN_RECV                       = 12
171
+	BPF_TCP_SYN_RECV                           = 3
172
+	BPF_TCP_SYN_SENT                           = 2
173
+	BPF_TCP_TIME_WAIT                          = 6
174
+	BPF_WRITE_HDR_TCP_CURRENT_MSS              = 1
175
+	BPF_WRITE_HDR_TCP_SYNACK_COOKIE            = 2
176
+	BPF_XFRM_STATE_OPTS_SZ                     = 36
177
+)
178
+
9 179
 type AdjRoomMode uint32
10 180
 
11 181
 const (
... ...
@@ -72,7 +242,8 @@ const (
72 72
 	BPF_CGROUP_UNIX_GETSOCKNAME        AttachType = 53
73 73
 	BPF_NETKIT_PRIMARY                 AttachType = 54
74 74
 	BPF_NETKIT_PEER                    AttachType = 55
75
-	__MAX_BPF_ATTACH_TYPE              AttachType = 56
75
+	BPF_TRACE_KPROBE_SESSION           AttachType = 56
76
+	__MAX_BPF_ATTACH_TYPE              AttachType = 57
76 77
 )
77 78
 
78 79
 type Cmd uint32
... ...
@@ -115,6 +286,8 @@ const (
115 115
 	BPF_ITER_CREATE                 Cmd = 33
116 116
 	BPF_LINK_DETACH                 Cmd = 34
117 117
 	BPF_PROG_BIND_MAP               Cmd = 35
118
+	BPF_TOKEN_CREATE                Cmd = 36
119
+	__MAX_BPF_CMD                   Cmd = 37
118 120
 )
119 121
 
120 122
 type FunctionId uint32
... ...
@@ -359,7 +532,8 @@ const (
359 359
 	BPF_LINK_TYPE_TCX            LinkType = 11
360 360
 	BPF_LINK_TYPE_UPROBE_MULTI   LinkType = 12
361 361
 	BPF_LINK_TYPE_NETKIT         LinkType = 13
362
-	__MAX_BPF_LINK_TYPE          LinkType = 14
362
+	BPF_LINK_TYPE_SOCKMAP        LinkType = 14
363
+	__MAX_BPF_LINK_TYPE          LinkType = 15
363 364
 )
364 365
 
365 366
 type MapType uint32
... ...
@@ -400,6 +574,17 @@ const (
400 400
 	BPF_MAP_TYPE_BLOOM_FILTER                     MapType = 30
401 401
 	BPF_MAP_TYPE_USER_RINGBUF                     MapType = 31
402 402
 	BPF_MAP_TYPE_CGRP_STORAGE                     MapType = 32
403
+	BPF_MAP_TYPE_ARENA                            MapType = 33
404
+	__MAX_BPF_MAP_TYPE                            MapType = 34
405
+)
406
+
407
+type ObjType uint32
408
+
409
+const (
410
+	BPF_TYPE_UNSPEC ObjType = 0
411
+	BPF_TYPE_PROG   ObjType = 1
412
+	BPF_TYPE_MAP    ObjType = 2
413
+	BPF_TYPE_LINK   ObjType = 3
403 414
 )
404 415
 
405 416
 type PerfEventType uint32
... ...
@@ -450,6 +635,7 @@ const (
450 450
 	BPF_PROG_TYPE_SK_LOOKUP               ProgType = 30
451 451
 	BPF_PROG_TYPE_SYSCALL                 ProgType = 31
452 452
 	BPF_PROG_TYPE_NETFILTER               ProgType = 32
453
+	__MAX_BPF_PROG_TYPE                   ProgType = 33
453 454
 )
454 455
 
455 456
 type RetCode uint32
... ...
@@ -537,7 +723,7 @@ type MapInfo struct {
537 537
 	KeySize               uint32
538 538
 	ValueSize             uint32
539 539
 	MaxEntries            uint32
540
-	MapFlags              MapFlags
540
+	MapFlags              uint32
541 541
 	Name                  ObjName
542 542
 	Ifindex               uint32
543 543
 	BtfVmlinuxValueTypeId TypeID
... ...
@@ -546,7 +732,7 @@ type MapInfo struct {
546 546
 	BtfId                 uint32
547 547
 	BtfKeyTypeId          TypeID
548 548
 	BtfValueTypeId        TypeID
549
-	_                     [4]byte
549
+	BtfVmlinuxId          uint32
550 550
 	MapExtra              uint64
551 551
 }
552 552
 
... ...
@@ -556,7 +742,7 @@ type ProgInfo struct {
556 556
 	Tag                  [8]uint8
557 557
 	JitedProgLen         uint32
558 558
 	XlatedProgLen        uint32
559
-	JitedProgInsns       uint64
559
+	JitedProgInsns       Pointer
560 560
 	XlatedProgInsns      Pointer
561 561
 	LoadTime             uint64
562 562
 	CreatedByUid         uint32
... ...
@@ -569,15 +755,15 @@ type ProgInfo struct {
569 569
 	NetnsIno             uint64
570 570
 	NrJitedKsyms         uint32
571 571
 	NrJitedFuncLens      uint32
572
-	JitedKsyms           uint64
573
-	JitedFuncLens        uint64
572
+	JitedKsyms           Pointer
573
+	JitedFuncLens        Pointer
574 574
 	BtfId                BTFID
575 575
 	FuncInfoRecSize      uint32
576 576
 	FuncInfo             Pointer
577 577
 	NrFuncInfo           uint32
578 578
 	NrLineInfo           uint32
579 579
 	LineInfo             Pointer
580
-	JitedLineInfo        uint64
580
+	JitedLineInfo        Pointer
581 581
 	NrJitedLineInfo      uint32
582 582
 	LineInfoRecSize      uint32
583 583
 	JitedLineInfoRecSize uint32
... ...
@@ -643,6 +829,8 @@ type BtfLoadAttr struct {
643 643
 	BtfLogSize     uint32
644 644
 	BtfLogLevel    uint32
645 645
 	BtfLogTrueSize uint32
646
+	BtfFlags       uint32
647
+	BtfTokenFd     int32
646 648
 }
647 649
 
648 650
 func BtfLoad(attr *BtfLoadAttr) (*FD, error) {
... ...
@@ -886,7 +1074,7 @@ type MapCreateAttr struct {
886 886
 	KeySize               uint32
887 887
 	ValueSize             uint32
888 888
 	MaxEntries            uint32
889
-	MapFlags              MapFlags
889
+	MapFlags              uint32
890 890
 	InnerMapFd            uint32
891 891
 	NumaNode              uint32
892 892
 	MapName               ObjName
... ...
@@ -896,6 +1084,8 @@ type MapCreateAttr struct {
896 896
 	BtfValueTypeId        TypeID
897 897
 	BtfVmlinuxValueTypeId TypeID
898 898
 	MapExtra              uint64
899
+	ValueTypeBtfObjFd     int32
900
+	MapTokenFd            int32
899 901
 }
900 902
 
901 903
 func MapCreate(attr *MapCreateAttr) (*FD, error) {
... ...
@@ -1189,6 +1379,8 @@ type ProgLoadAttr struct {
1189 1189
 	CoreRelos          Pointer
1190 1190
 	CoreReloRecSize    uint32
1191 1191
 	LogTrueSize        uint32
1192
+	ProgTokenFd        int32
1193
+	_                  [4]byte
1192 1194
 }
1193 1195
 
1194 1196
 func ProgLoad(attr *ProgLoadAttr) (*FD, error) {
... ...
@@ -1246,6 +1438,7 @@ type RawTracepointOpenAttr struct {
1246 1246
 	Name   Pointer
1247 1247
 	ProgFd uint32
1248 1248
 	_      [4]byte
1249
+	Cookie uint64
1249 1250
 }
1250 1251
 
1251 1252
 func RawTracepointOpen(attr *RawTracepointOpenAttr) (*FD, error) {
... ...
@@ -1287,19 +1480,20 @@ type KprobeLinkInfo struct {
1287 1287
 	Offset        uint32
1288 1288
 	Addr          uint64
1289 1289
 	Missed        uint64
1290
-	_             [8]byte
1290
+	Cookie        uint64
1291 1291
 }
1292 1292
 
1293 1293
 type KprobeMultiLinkInfo struct {
1294
-	Type   LinkType
1295
-	Id     LinkID
1296
-	ProgId uint32
1297
-	_      [4]byte
1298
-	Addrs  Pointer
1299
-	Count  uint32
1300
-	Flags  uint32
1301
-	Missed uint64
1302
-	_      [24]byte
1294
+	Type    LinkType
1295
+	Id      LinkID
1296
+	ProgId  uint32
1297
+	_       [4]byte
1298
+	Addrs   Pointer
1299
+	Count   uint32
1300
+	Flags   uint32
1301
+	Missed  uint64
1302
+	Cookies uint64
1303
+	_       [16]byte
1303 1304
 }
1304 1305
 
1305 1306
 type NetNsLinkInfo struct {
... ...
@@ -51,12 +51,12 @@ func SyscallOutput(dst any, size int) Buffer {
51 51
 //
52 52
 // Returns the number of copied bytes.
53 53
 func (b Buffer) CopyTo(dst []byte) int {
54
-	return copy(dst, b.unsafeBytes())
54
+	return copy(dst, b.Bytes())
55 55
 }
56 56
 
57 57
 // AppendTo appends the buffer onto dst.
58 58
 func (b Buffer) AppendTo(dst []byte) []byte {
59
-	return append(dst, b.unsafeBytes()...)
59
+	return append(dst, b.Bytes()...)
60 60
 }
61 61
 
62 62
 // Pointer returns the location where a syscall should write.
... ...
@@ -72,10 +72,12 @@ func (b Buffer) Unmarshal(data any) error {
72 72
 		return nil
73 73
 	}
74 74
 
75
-	return Unmarshal(data, b.unsafeBytes())
75
+	return Unmarshal(data, b.Bytes())
76 76
 }
77 77
 
78
-func (b Buffer) unsafeBytes() []byte {
78
+// Bytes returns the buffer as a byte slice. Returns nil if the Buffer was
79
+// created using UnsafeBuffer or by zero-copy unmarshaling.
80
+func (b Buffer) Bytes() []byte {
79 81
 	if b.size == syscallPointerOnly {
80 82
 		return nil
81 83
 	}
82 84
new file mode 100644
... ...
@@ -0,0 +1,103 @@
0
+package fdtrace
1
+
2
+import (
3
+	"bytes"
4
+	"fmt"
5
+	"os"
6
+	"runtime"
7
+	"sync"
8
+	"sync/atomic"
9
+)
10
+
11
+// foundLeak is atomic since the GC may collect objects in parallel.
12
+var foundLeak atomic.Bool
13
+
14
+func onLeakFD(fs *runtime.Frames) {
15
+	foundLeak.Store(true)
16
+	fmt.Fprintln(os.Stderr, "leaked fd created at:")
17
+	fmt.Fprintln(os.Stderr, formatFrames(fs))
18
+}
19
+
20
+// fds is a registry of all file descriptors wrapped into sys.fds that were
21
+// created while an fd tracer was active.
22
+var fds *sync.Map // map[int]*runtime.Frames
23
+
24
+// TraceFD associates raw with the current execution stack.
25
+//
26
+// skip controls how many entries of the stack the function should skip.
27
+func TraceFD(raw int, skip int) {
28
+	if fds == nil {
29
+		return
30
+	}
31
+
32
+	// Attempt to store the caller's stack for the given fd value.
33
+	// Panic if fds contains an existing stack for the fd.
34
+	old, exist := fds.LoadOrStore(raw, callersFrames(skip))
35
+	if exist {
36
+		f := old.(*runtime.Frames)
37
+		panic(fmt.Sprintf("found existing stack for fd %d:\n%s", raw, formatFrames(f)))
38
+	}
39
+}
40
+
41
+// ForgetFD removes any existing association for raw.
42
+func ForgetFD(raw int) {
43
+	if fds != nil {
44
+		fds.Delete(raw)
45
+	}
46
+}
47
+
48
+// LeakFD indicates that raw was leaked.
49
+//
50
+// Calling the function with a value that was not passed to [TraceFD] before
51
+// is undefined.
52
+func LeakFD(raw int) {
53
+	if fds == nil {
54
+		return
55
+	}
56
+
57
+	// Invoke the fd leak callback. Calls LoadAndDelete to guarantee the callback
58
+	// is invoked at most once for one sys.FD allocation, runtime.Frames can only
59
+	// be unwound once.
60
+	f, ok := fds.LoadAndDelete(raw)
61
+	if ok {
62
+		onLeakFD(f.(*runtime.Frames))
63
+	}
64
+}
65
+
66
+// flushFrames removes all elements from fds and returns them as a slice. This
67
+// deals with the fact that a runtime.Frames can only be unwound once using
68
+// Next().
69
+func flushFrames() []*runtime.Frames {
70
+	var frames []*runtime.Frames
71
+	fds.Range(func(key, value any) bool {
72
+		frames = append(frames, value.(*runtime.Frames))
73
+		fds.Delete(key)
74
+		return true
75
+	})
76
+	return frames
77
+}
78
+
79
+func callersFrames(skip int) *runtime.Frames {
80
+	c := make([]uintptr, 32)
81
+
82
+	// Skip runtime.Callers and this function.
83
+	i := runtime.Callers(skip+2, c)
84
+	if i == 0 {
85
+		return nil
86
+	}
87
+
88
+	return runtime.CallersFrames(c)
89
+}
90
+
91
+// formatFrames formats a runtime.Frames as a human-readable string.
92
+func formatFrames(fs *runtime.Frames) string {
93
+	var b bytes.Buffer
94
+	for {
95
+		f, more := fs.Next()
96
+		b.WriteString(fmt.Sprintf("\t%s+%#x\n\t\t%s:%d\n", f.Function, f.PC-f.Entry, f.File, f.Line))
97
+		if !more {
98
+			break
99
+		}
100
+	}
101
+	return b.String()
102
+}
0 103
new file mode 100644
... ...
@@ -0,0 +1,31 @@
0
+package fdtrace
1
+
2
+import (
3
+	"os"
4
+	"sync"
5
+)
6
+
7
+type testingM interface {
8
+	Run() int
9
+}
10
+
11
+// TestMain runs m with fd tracing enabled.
12
+//
13
+// The function calls [os.Exit] and does not return.
14
+func TestMain(m testingM) {
15
+	fds = new(sync.Map)
16
+
17
+	ret := m.Run()
18
+
19
+	if fs := flushFrames(); len(fs) != 0 {
20
+		for _, f := range fs {
21
+			onLeakFD(f)
22
+		}
23
+	}
24
+
25
+	if foundLeak.Load() {
26
+		ret = 99
27
+	}
28
+
29
+	os.Exit(ret)
30
+}
... ...
@@ -12,6 +12,7 @@ import (
12 12
 	"syscall"
13 13
 
14 14
 	"github.com/cilium/ebpf/internal"
15
+	"github.com/cilium/ebpf/internal/linux"
15 16
 	"github.com/cilium/ebpf/internal/unix"
16 17
 )
17 18
 
... ...
@@ -112,6 +113,10 @@ func sanitizeTracefsPath(path ...string) (string, error) {
112 112
 // but may be also be available at /sys/kernel/debug/tracing if debugfs is mounted.
113 113
 // The available tracefs paths will depends on distribution choices.
114 114
 var getTracefsPath = sync.OnceValues(func() (string, error) {
115
+	if !internal.OnLinux {
116
+		return "", fmt.Errorf("tracefs: %w", internal.ErrNotSupportedOnOS)
117
+	}
118
+
115 119
 	for _, p := range []struct {
116 120
 		path   string
117 121
 		fsType int64
... ...
@@ -121,7 +126,7 @@ var getTracefsPath = sync.OnceValues(func() (string, error) {
121 121
 		// RHEL/CentOS
122 122
 		{"/sys/kernel/debug/tracing", unix.DEBUGFS_MAGIC},
123 123
 	} {
124
-		if fsType, err := internal.FSType(p.path); err == nil && fsType == p.fsType {
124
+		if fsType, err := linux.FSType(p.path); err == nil && fsType == p.fsType {
125 125
 			return p.path, nil
126 126
 		}
127 127
 	}
... ...
@@ -213,7 +218,10 @@ func NewEvent(args ProbeArgs) (*Event, error) {
213 213
 	if err == nil {
214 214
 		return nil, fmt.Errorf("trace event %s/%s: %w", args.Group, eventName, os.ErrExist)
215 215
 	}
216
-	if err != nil && !errors.Is(err, os.ErrNotExist) {
216
+	if errors.Is(err, unix.EINVAL) {
217
+		return nil, fmt.Errorf("trace event %s/%s: %w (unknown symbol?)", args.Group, eventName, err)
218
+	}
219
+	if !errors.Is(err, os.ErrNotExist) {
217 220
 		return nil, fmt.Errorf("checking trace event %s/%s: %w", args.Group, eventName, err)
218 221
 	}
219 222
 
220 223
new file mode 100644
... ...
@@ -0,0 +1,29 @@
0
+package unix
1
+
2
+import (
3
+	"syscall"
4
+
5
+	linux "golang.org/x/sys/unix"
6
+)
7
+
8
+type Errno = syscall.Errno
9
+
10
+const (
11
+	E2BIG      = linux.E2BIG
12
+	EACCES     = linux.EACCES
13
+	EAGAIN     = linux.EAGAIN
14
+	EBADF      = linux.EBADF
15
+	EEXIST     = linux.EEXIST
16
+	EFAULT     = linux.EFAULT
17
+	EILSEQ     = linux.EILSEQ
18
+	EINTR      = linux.EINTR
19
+	EINVAL     = linux.EINVAL
20
+	ENODEV     = linux.ENODEV
21
+	ENOENT     = linux.ENOENT
22
+	ENOSPC     = linux.ENOSPC
23
+	EOPNOTSUPP = linux.EOPNOTSUPP
24
+	EPERM      = linux.EPERM
25
+	EPOLLIN    = linux.EPOLLIN
26
+	ESRCH      = linux.ESRCH
27
+	ESTALE     = linux.ESTALE
28
+)
0 29
new file mode 100644
... ...
@@ -0,0 +1,29 @@
0
+//go:build !linux && !windows
1
+
2
+package unix
3
+
4
+import "syscall"
5
+
6
+type Errno = syscall.Errno
7
+
8
+// Errnos are distinct and non-zero.
9
+const (
10
+	E2BIG Errno = iota + 1
11
+	EACCES
12
+	EAGAIN
13
+	EBADF
14
+	EEXIST
15
+	EFAULT
16
+	EILSEQ
17
+	EINTR
18
+	EINVAL
19
+	ENODEV
20
+	ENOENT
21
+	ENOSPC
22
+	ENOTSUP
23
+	ENOTSUPP
24
+	EOPNOTSUPP
25
+	EPERM
26
+	ESRCH
27
+	ESTALE
28
+)
0 29
new file mode 100644
... ...
@@ -0,0 +1,59 @@
0
+// Code generated by "stringer -type=Errno -tags=windows -output=errno_string_windows.go"; DO NOT EDIT.
1
+
2
+package unix
3
+
4
+import "strconv"
5
+
6
+func _() {
7
+	// An "invalid array index" compiler error signifies that the constant values have changed.
8
+	// Re-run the stringer command to generate them again.
9
+	var x [1]struct{}
10
+	_ = x[EPERM-1]
11
+	_ = x[ENOENT-2]
12
+	_ = x[ESRCH-3]
13
+	_ = x[EINTR-4]
14
+	_ = x[E2BIG-7]
15
+	_ = x[EBADF-9]
16
+	_ = x[EAGAIN-11]
17
+	_ = x[EACCES-13]
18
+	_ = x[EFAULT-14]
19
+	_ = x[EEXIST-17]
20
+	_ = x[ENODEV-19]
21
+	_ = x[EINVAL-22]
22
+	_ = x[ENOSPC-28]
23
+	_ = x[EILSEQ-42]
24
+	_ = x[ENOTSUP-129]
25
+	_ = x[EOPNOTSUPP-130]
26
+	_ = x[ENOTSUPP-536870912]
27
+	_ = x[ESTALE-536870913]
28
+}
29
+
30
+const _Errno_name = "EPERMENOENTESRCHEINTRE2BIGEBADFEAGAINEACCESEFAULTEEXISTENODEVEINVALENOSPCEILSEQENOTSUPEOPNOTSUPPENOTSUPPESTALE"
31
+
32
+var _Errno_map = map[Errno]string{
33
+	1:         _Errno_name[0:5],
34
+	2:         _Errno_name[5:11],
35
+	3:         _Errno_name[11:16],
36
+	4:         _Errno_name[16:21],
37
+	7:         _Errno_name[21:26],
38
+	9:         _Errno_name[26:31],
39
+	11:        _Errno_name[31:37],
40
+	13:        _Errno_name[37:43],
41
+	14:        _Errno_name[43:49],
42
+	17:        _Errno_name[49:55],
43
+	19:        _Errno_name[55:61],
44
+	22:        _Errno_name[61:67],
45
+	28:        _Errno_name[67:73],
46
+	42:        _Errno_name[73:79],
47
+	129:       _Errno_name[79:86],
48
+	130:       _Errno_name[86:96],
49
+	536870912: _Errno_name[96:104],
50
+	536870913: _Errno_name[104:110],
51
+}
52
+
53
+func (i Errno) String() string {
54
+	if str, ok := _Errno_map[i]; ok {
55
+		return str
56
+	}
57
+	return "Errno(" + strconv.FormatInt(int64(i), 10) + ")"
58
+}
0 59
new file mode 100644
... ...
@@ -0,0 +1,78 @@
0
+package unix
1
+
2
+// The code in this file is derived from syscall_unix.go in the Go source code,
3
+// licensed under the MIT license.
4
+
5
+import (
6
+	"errors"
7
+	"os"
8
+	"syscall"
9
+)
10
+
11
+//go:generate go run golang.org/x/tools/cmd/stringer@latest -type=Errno -tags=windows -output=errno_string_windows.go
12
+
13
+// Windows specific constants for Unix errnos.
14
+//
15
+// The values do not always match Linux, for example EILSEQ and EOPNOTSUPP.
16
+//
17
+// See https://learn.microsoft.com/en-us/cpp/c-runtime-library/errno-constants?view=msvc-170
18
+const (
19
+	EPERM       Errno = 1
20
+	ENOENT      Errno = 2
21
+	ESRCH       Errno = 3
22
+	EINTR       Errno = 4
23
+	E2BIG       Errno = 7
24
+	EBADF       Errno = 9
25
+	EAGAIN      Errno = 11
26
+	EACCES      Errno = 13
27
+	EFAULT      Errno = 14
28
+	EEXIST      Errno = 17
29
+	ENODEV      Errno = 19
30
+	EINVAL      Errno = 22
31
+	ENFILE      Errno = 23
32
+	EMFILE      Errno = 24
33
+	ENOSPC      Errno = 28
34
+	ENOSYS      Errno = 40
35
+	ENOTEMPTY   Errno = 41
36
+	EILSEQ      Errno = 42
37
+	ENOTSUP     Errno = 129
38
+	EOPNOTSUPP  Errno = 130
39
+	ETIMEDOUT   Errno = 138
40
+	EWOULDBLOCK Errno = 140
41
+)
42
+
43
+// These constants do not exist on Windows and therefore have a non-zero
44
+// dummy value.
45
+const (
46
+	ENOTSUPP Errno = Errno(syscall.APPLICATION_ERROR) + iota
47
+	ESTALE
48
+)
49
+
50
+// Errno is a Windows compatibility shim for Unix errnos.
51
+type Errno uintptr
52
+
53
+func (e Errno) Error() string {
54
+	return e.String()
55
+}
56
+
57
+func (e Errno) Is(target error) bool {
58
+	switch target {
59
+	case os.ErrPermission:
60
+		return e == EACCES || e == EPERM
61
+	case os.ErrExist:
62
+		return e == EEXIST || e == ENOTEMPTY
63
+	case os.ErrNotExist:
64
+		return e == ENOENT
65
+	case errors.ErrUnsupported:
66
+		return e == ENOSYS || e == ENOTSUP || e == EOPNOTSUPP
67
+	}
68
+	return false
69
+}
70
+
71
+func (e Errno) Temporary() bool {
72
+	return e == EINTR || e == EMFILE || e == ENFILE || e.Timeout()
73
+}
74
+
75
+func (e Errno) Timeout() bool {
76
+	return e == EAGAIN || e == EWOULDBLOCK || e == ETIMEDOUT
77
+}
0 78
new file mode 100644
... ...
@@ -0,0 +1,23 @@
0
+package unix
1
+
2
+import (
3
+	"fmt"
4
+	"runtime"
5
+	"strings"
6
+
7
+	"github.com/cilium/ebpf/internal"
8
+)
9
+
10
+// errNonLinux returns an error which wraps [internal.ErrNotSupportedOnOS] and
11
+// includes the name of the calling function.
12
+func errNonLinux() error {
13
+	name := "unknown"
14
+	pc, _, _, ok := runtime.Caller(1)
15
+	if ok {
16
+		name = runtime.FuncForPC(pc).Name()
17
+		if pos := strings.LastIndexByte(name, '.'); pos != -1 {
18
+			name = name[pos+1:]
19
+		}
20
+	}
21
+	return fmt.Errorf("unix: %s: %w", name, internal.ErrNotSupportedOnOS)
22
+}
0 23
new file mode 100644
... ...
@@ -0,0 +1,11 @@
0
+//go:build !linux && !windows
1
+
2
+package unix
3
+
4
+func BytePtrFromString(s string) (*byte, error) {
5
+	return nil, errNonLinux()
6
+}
7
+
8
+func ByteSliceToString(s []byte) string {
9
+	return ""
10
+}
0 11
new file mode 100644
... ...
@@ -0,0 +1,19 @@
0
+package unix
1
+
2
+import (
3
+	"syscall"
4
+
5
+	"golang.org/x/sys/windows"
6
+)
7
+
8
+func BytePtrFromString(s string) (*byte, error) {
9
+	p, err := windows.BytePtrFromString(s)
10
+	if err == syscall.EINVAL {
11
+		err = EINVAL
12
+	}
13
+	return p, err
14
+}
15
+
16
+func ByteSliceToString(s []byte) string {
17
+	return windows.ByteSliceToString(s)
18
+}
... ...
@@ -9,26 +9,6 @@ import (
9 9
 )
10 10
 
11 11
 const (
12
-	ENOENT     = linux.ENOENT
13
-	EEXIST     = linux.EEXIST
14
-	EAGAIN     = linux.EAGAIN
15
-	ENOSPC     = linux.ENOSPC
16
-	EINVAL     = linux.EINVAL
17
-	EPOLLIN    = linux.EPOLLIN
18
-	EINTR      = linux.EINTR
19
-	EPERM      = linux.EPERM
20
-	ESRCH      = linux.ESRCH
21
-	ENODEV     = linux.ENODEV
22
-	EBADF      = linux.EBADF
23
-	E2BIG      = linux.E2BIG
24
-	EFAULT     = linux.EFAULT
25
-	EACCES     = linux.EACCES
26
-	EILSEQ     = linux.EILSEQ
27
-	EOPNOTSUPP = linux.EOPNOTSUPP
28
-	ESTALE     = linux.ESTALE
29
-)
30
-
31
-const (
32 12
 	BPF_F_NO_PREALLOC         = linux.BPF_F_NO_PREALLOC
33 13
 	BPF_F_NUMA_NODE           = linux.BPF_F_NUMA_NODE
34 14
 	BPF_F_RDONLY              = linux.BPF_F_RDONLY
... ...
@@ -81,15 +61,16 @@ const (
81 81
 	SO_DETACH_BPF             = linux.SO_DETACH_BPF
82 82
 	SOL_SOCKET                = linux.SOL_SOCKET
83 83
 	SIGPROF                   = linux.SIGPROF
84
+	SIGUSR1                   = linux.SIGUSR1
84 85
 	SIG_BLOCK                 = linux.SIG_BLOCK
85 86
 	SIG_UNBLOCK               = linux.SIG_UNBLOCK
86
-	EM_NONE                   = linux.EM_NONE
87
-	EM_BPF                    = linux.EM_BPF
88 87
 	BPF_FS_MAGIC              = linux.BPF_FS_MAGIC
89 88
 	TRACEFS_MAGIC             = linux.TRACEFS_MAGIC
90 89
 	DEBUGFS_MAGIC             = linux.DEBUGFS_MAGIC
91 90
 	BPF_RB_NO_WAKEUP          = linux.BPF_RB_NO_WAKEUP
92 91
 	BPF_RB_FORCE_WAKEUP       = linux.BPF_RB_FORCE_WAKEUP
92
+	AF_UNSPEC                 = linux.AF_UNSPEC
93
+	IFF_UP                    = linux.IFF_UP
93 94
 )
94 95
 
95 96
 type Statfs_t = linux.Statfs_t
... ...
@@ -214,3 +195,7 @@ func SchedSetaffinity(pid int, set *CPUSet) error {
214 214
 func SchedGetaffinity(pid int, set *CPUSet) error {
215 215
 	return linux.SchedGetaffinity(pid, set)
216 216
 }
217
+
218
+func Auxv() ([][2]uintptr, error) {
219
+	return linux.Auxv()
220
+}
... ...
@@ -3,33 +3,9 @@
3 3
 package unix
4 4
 
5 5
 import (
6
-	"fmt"
7
-	"runtime"
8 6
 	"syscall"
9 7
 )
10 8
 
11
-var errNonLinux = fmt.Errorf("unsupported platform %s/%s", runtime.GOOS, runtime.GOARCH)
12
-
13
-// Errnos are distinct and non-zero.
14
-const (
15
-	ENOENT syscall.Errno = iota + 1
16
-	EEXIST
17
-	EAGAIN
18
-	ENOSPC
19
-	EINVAL
20
-	EINTR
21
-	EPERM
22
-	ESRCH
23
-	ENODEV
24
-	EBADF
25
-	E2BIG
26
-	EFAULT
27
-	EACCES
28
-	EILSEQ
29
-	EOPNOTSUPP
30
-	ESTALE
31
-)
32
-
33 9
 // Constants are distinct to avoid breaking switch statements.
34 10
 const (
35 11
 	BPF_F_NO_PREALLOC = iota
... ...
@@ -84,16 +60,17 @@ const (
84 84
 	SO_DETACH_BPF
85 85
 	SOL_SOCKET
86 86
 	SIGPROF
87
+	SIGUSR1
87 88
 	SIG_BLOCK
88 89
 	SIG_UNBLOCK
89
-	EM_NONE
90
-	EM_BPF
91 90
 	BPF_FS_MAGIC
92 91
 	TRACEFS_MAGIC
93 92
 	DEBUGFS_MAGIC
94 93
 	BPF_RB_NO_WAKEUP
95 94
 	BPF_RB_FORCE_WAKEUP
96 95
 	BPF_F_LOCK
96
+	AF_UNSPEC
97
+	IFF_UP
97 98
 )
98 99
 
99 100
 type Statfs_t struct {
... ...
@@ -136,28 +113,28 @@ type Sigset_t struct {
136 136
 	Val [4]uint64
137 137
 }
138 138
 
139
-func Syscall(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err syscall.Errno) {
140
-	return 0, 0, syscall.ENOTSUP
139
+func Syscall(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err Errno) {
140
+	return 0, 0, ENOTSUP
141 141
 }
142 142
 
143 143
 func PthreadSigmask(how int, set, oldset *Sigset_t) error {
144
-	return errNonLinux
144
+	return errNonLinux()
145 145
 }
146 146
 
147 147
 func FcntlInt(fd uintptr, cmd, arg int) (int, error) {
148
-	return -1, errNonLinux
148
+	return -1, errNonLinux()
149 149
 }
150 150
 
151 151
 func IoctlSetInt(fd int, req uint, value int) error {
152
-	return errNonLinux
152
+	return errNonLinux()
153 153
 }
154 154
 
155 155
 func Statfs(path string, buf *Statfs_t) error {
156
-	return errNonLinux
156
+	return errNonLinux()
157 157
 }
158 158
 
159 159
 func Close(fd int) (err error) {
160
-	return errNonLinux
160
+	return errNonLinux()
161 161
 }
162 162
 
163 163
 type EpollEvent struct {
... ...
@@ -167,23 +144,23 @@ type EpollEvent struct {
167 167
 }
168 168
 
169 169
 func EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error) {
170
-	return 0, errNonLinux
170
+	return 0, errNonLinux()
171 171
 }
172 172
 
173 173
 func EpollCtl(epfd int, op int, fd int, event *EpollEvent) (err error) {
174
-	return errNonLinux
174
+	return errNonLinux()
175 175
 }
176 176
 
177 177
 func Eventfd(initval uint, flags int) (fd int, err error) {
178
-	return 0, errNonLinux
178
+	return 0, errNonLinux()
179 179
 }
180 180
 
181 181
 func Write(fd int, p []byte) (n int, err error) {
182
-	return 0, errNonLinux
182
+	return 0, errNonLinux()
183 183
 }
184 184
 
185 185
 func EpollCreate1(flag int) (fd int, err error) {
186
-	return 0, errNonLinux
186
+	return 0, errNonLinux()
187 187
 }
188 188
 
189 189
 type PerfEventMmapPage struct {
... ...
@@ -213,15 +190,15 @@ type PerfEventMmapPage struct {
213 213
 }
214 214
 
215 215
 func SetNonblock(fd int, nonblocking bool) (err error) {
216
-	return errNonLinux
216
+	return errNonLinux()
217 217
 }
218 218
 
219 219
 func Mmap(fd int, offset int64, length int, prot int, flags int) (data []byte, err error) {
220
-	return []byte{}, errNonLinux
220
+	return []byte{}, errNonLinux()
221 221
 }
222 222
 
223 223
 func Munmap(b []byte) (err error) {
224
-	return errNonLinux
224
+	return errNonLinux()
225 225
 }
226 226
 
227 227
 type PerfEventAttr struct {
... ...
@@ -246,7 +223,7 @@ type PerfEventAttr struct {
246 246
 }
247 247
 
248 248
 func PerfEventOpen(attr *PerfEventAttr, pid int, cpu int, groupFd int, flags int) (fd int, err error) {
249
-	return 0, errNonLinux
249
+	return 0, errNonLinux()
250 250
 }
251 251
 
252 252
 type Utsname struct {
... ...
@@ -255,7 +232,7 @@ type Utsname struct {
255 255
 }
256 256
 
257 257
 func Uname(buf *Utsname) (err error) {
258
-	return errNonLinux
258
+	return errNonLinux()
259 259
 }
260 260
 
261 261
 func Getpid() int {
... ...
@@ -267,35 +244,27 @@ func Gettid() int {
267 267
 }
268 268
 
269 269
 func Tgkill(tgid int, tid int, sig syscall.Signal) (err error) {
270
-	return errNonLinux
271
-}
272
-
273
-func BytePtrFromString(s string) (*byte, error) {
274
-	return nil, errNonLinux
275
-}
276
-
277
-func ByteSliceToString(s []byte) string {
278
-	return ""
270
+	return errNonLinux()
279 271
 }
280 272
 
281 273
 func Renameat2(olddirfd int, oldpath string, newdirfd int, newpath string, flags uint) error {
282
-	return errNonLinux
274
+	return errNonLinux()
283 275
 }
284 276
 
285 277
 func Prlimit(pid, resource int, new, old *Rlimit) error {
286
-	return errNonLinux
278
+	return errNonLinux()
287 279
 }
288 280
 
289 281
 func Open(path string, mode int, perm uint32) (int, error) {
290
-	return -1, errNonLinux
282
+	return -1, errNonLinux()
291 283
 }
292 284
 
293 285
 func Fstat(fd int, stat *Stat_t) error {
294
-	return errNonLinux
286
+	return errNonLinux()
295 287
 }
296 288
 
297 289
 func SetsockoptInt(fd, level, opt, value int) error {
298
-	return errNonLinux
290
+	return errNonLinux()
299 291
 }
300 292
 
301 293
 type CPUSet struct{}
... ...
@@ -303,9 +272,13 @@ type CPUSet struct{}
303 303
 func (*CPUSet) Set(int) {}
304 304
 
305 305
 func SchedSetaffinity(pid int, set *CPUSet) error {
306
-	return errNonLinux
306
+	return errNonLinux()
307 307
 }
308 308
 
309 309
 func SchedGetaffinity(pid int, set *CPUSet) error {
310
-	return errNonLinux
310
+	return errNonLinux()
311
+}
312
+
313
+func Auxv() ([][2]uintptr, error) {
314
+	return nil, errNonLinux()
311 315
 }
312 316
deleted file mode 100644
... ...
@@ -1,143 +0,0 @@
1
-package internal
2
-
3
-import (
4
-	"debug/elf"
5
-	"encoding/binary"
6
-	"errors"
7
-	"fmt"
8
-	"io"
9
-	"math"
10
-	"os"
11
-
12
-	"github.com/cilium/ebpf/internal/unix"
13
-)
14
-
15
-var (
16
-	errAuxvNoVDSO = errors.New("no vdso address found in auxv")
17
-)
18
-
19
-// vdsoVersion returns the LINUX_VERSION_CODE embedded in the vDSO library
20
-// linked into the current process image.
21
-func vdsoVersion() (uint32, error) {
22
-	av, err := newAuxvRuntimeReader()
23
-	if err != nil {
24
-		return 0, err
25
-	}
26
-
27
-	defer av.Close()
28
-
29
-	vdsoAddr, err := vdsoMemoryAddress(av)
30
-	if err != nil {
31
-		return 0, fmt.Errorf("finding vDSO memory address: %w", err)
32
-	}
33
-
34
-	// Use /proc/self/mem rather than unsafe.Pointer tricks.
35
-	mem, err := os.Open("/proc/self/mem")
36
-	if err != nil {
37
-		return 0, fmt.Errorf("opening mem: %w", err)
38
-	}
39
-	defer mem.Close()
40
-
41
-	// Open ELF at provided memory address, as offset into /proc/self/mem.
42
-	c, err := vdsoLinuxVersionCode(io.NewSectionReader(mem, int64(vdsoAddr), math.MaxInt64))
43
-	if err != nil {
44
-		return 0, fmt.Errorf("reading linux version code: %w", err)
45
-	}
46
-
47
-	return c, nil
48
-}
49
-
50
-// vdsoMemoryAddress returns the memory address of the vDSO library
51
-// linked into the current process image. r is an io.Reader into an auxv blob.
52
-func vdsoMemoryAddress(r auxvPairReader) (uintptr, error) {
53
-	// Loop through all tag/value pairs in auxv until we find `AT_SYSINFO_EHDR`,
54
-	// the address of a page containing the virtual Dynamic Shared Object (vDSO).
55
-	for {
56
-		tag, value, err := r.ReadAuxvPair()
57
-		if err != nil {
58
-			return 0, err
59
-		}
60
-
61
-		switch tag {
62
-		case _AT_SYSINFO_EHDR:
63
-			if value != 0 {
64
-				return uintptr(value), nil
65
-			}
66
-			return 0, fmt.Errorf("invalid vDSO address in auxv")
67
-		// _AT_NULL is always the last tag/val pair in the aux vector
68
-		// and can be treated like EOF.
69
-		case _AT_NULL:
70
-			return 0, errAuxvNoVDSO
71
-		}
72
-	}
73
-}
74
-
75
-// format described at https://www.man7.org/linux/man-pages/man5/elf.5.html in section 'Notes (Nhdr)'
76
-type elfNoteHeader struct {
77
-	NameSize int32
78
-	DescSize int32
79
-	Type     int32
80
-}
81
-
82
-// vdsoLinuxVersionCode returns the LINUX_VERSION_CODE embedded in
83
-// the ELF notes section of the binary provided by the reader.
84
-func vdsoLinuxVersionCode(r io.ReaderAt) (uint32, error) {
85
-	hdr, err := NewSafeELFFile(r)
86
-	if err != nil {
87
-		return 0, fmt.Errorf("reading vDSO ELF: %w", err)
88
-	}
89
-
90
-	sections := hdr.SectionsByType(elf.SHT_NOTE)
91
-	if len(sections) == 0 {
92
-		return 0, fmt.Errorf("no note section found in vDSO ELF")
93
-	}
94
-
95
-	for _, sec := range sections {
96
-		sr := sec.Open()
97
-		var n elfNoteHeader
98
-
99
-		// Read notes until we find one named 'Linux'.
100
-		for {
101
-			if err := binary.Read(sr, hdr.ByteOrder, &n); err != nil {
102
-				if errors.Is(err, io.EOF) {
103
-					// We looked at all the notes in this section
104
-					break
105
-				}
106
-				return 0, fmt.Errorf("reading note header: %w", err)
107
-			}
108
-
109
-			// If a note name is defined, it follows the note header.
110
-			var name string
111
-			if n.NameSize > 0 {
112
-				// Read the note name, aligned to 4 bytes.
113
-				buf := make([]byte, Align(n.NameSize, 4))
114
-				if err := binary.Read(sr, hdr.ByteOrder, &buf); err != nil {
115
-					return 0, fmt.Errorf("reading note name: %w", err)
116
-				}
117
-
118
-				// Read nul-terminated string.
119
-				name = unix.ByteSliceToString(buf[:n.NameSize])
120
-			}
121
-
122
-			// If a note descriptor is defined, it follows the name.
123
-			// It is possible for a note to have a descriptor but not a name.
124
-			if n.DescSize > 0 {
125
-				// LINUX_VERSION_CODE is a uint32 value.
126
-				if name == "Linux" && n.DescSize == 4 && n.Type == 0 {
127
-					var version uint32
128
-					if err := binary.Read(sr, hdr.ByteOrder, &version); err != nil {
129
-						return 0, fmt.Errorf("reading note descriptor: %w", err)
130
-					}
131
-					return version, nil
132
-				}
133
-
134
-				// Discard the note descriptor if it exists but we're not interested in it.
135
-				if _, err := io.CopyN(io.Discard, sr, int64(Align(n.DescSize, 4))); err != nil {
136
-					return 0, err
137
-				}
138
-			}
139
-		}
140
-	}
141
-
142
-	return 0, fmt.Errorf("no Linux note in ELF")
143
-}
... ...
@@ -2,9 +2,6 @@ package internal
2 2
 
3 3
 import (
4 4
 	"fmt"
5
-	"sync"
6
-
7
-	"github.com/cilium/ebpf/internal/unix"
8 5
 )
9 6
 
10 7
 const (
... ...
@@ -78,30 +75,3 @@ func (v Version) Kernel() uint32 {
78 78
 	// each other when overflowing 8 bits.
79 79
 	return uint32(uint8(v[0]))<<16 | uint32(uint8(v[1]))<<8 | uint32(uint8(s))
80 80
 }
81
-
82
-// KernelVersion returns the version of the currently running kernel.
83
-var KernelVersion = sync.OnceValues(func() (Version, error) {
84
-	return detectKernelVersion()
85
-})
86
-
87
-// detectKernelVersion returns the version of the running kernel.
88
-func detectKernelVersion() (Version, error) {
89
-	vc, err := vdsoVersion()
90
-	if err != nil {
91
-		return Version{}, err
92
-	}
93
-	return NewVersionFromCode(vc), nil
94
-}
95
-
96
-// KernelRelease returns the release string of the running kernel.
97
-// Its format depends on the Linux distribution and corresponds to directory
98
-// names in /lib/modules by convention. Some examples are 5.15.17-1-lts and
99
-// 4.19.0-16-amd64.
100
-func KernelRelease() (string, error) {
101
-	var uname unix.Utsname
102
-	if err := unix.Uname(&uname); err != nil {
103
-		return "", fmt.Errorf("uname failed: %w", err)
104
-	}
105
-
106
-	return unix.ByteSliceToString(uname.Release[:]), nil
107
-}
... ...
@@ -10,6 +10,7 @@ import (
10 10
 
11 11
 	"github.com/cilium/ebpf"
12 12
 	"github.com/cilium/ebpf/internal"
13
+	"github.com/cilium/ebpf/internal/linux"
13 14
 	"github.com/cilium/ebpf/internal/sys"
14 15
 	"github.com/cilium/ebpf/internal/tracefs"
15 16
 	"github.com/cilium/ebpf/internal/unix"
... ...
@@ -60,6 +61,9 @@ func (ko *KprobeOptions) cookie() uint64 {
60 60
 // platform's syscall prefix (e.g. __x64_) to support attaching to syscalls
61 61
 // in a portable fashion.
62 62
 //
63
+// On kernels 6.11 and later, setting a kprobe on a nonexistent symbol using
64
+// tracefs incorrectly returns [unix.EINVAL] instead of [os.ErrNotExist].
65
+//
63 66
 // The returned Link may implement [PerfEvent].
64 67
 func Kprobe(symbol string, prog *ebpf.Program, opts *KprobeOptions) (Link, error) {
65 68
 	k, err := kprobe(symbol, prog, opts, false)
... ...
@@ -91,7 +95,7 @@ func Kprobe(symbol string, prog *ebpf.Program, opts *KprobeOptions) (Link, error
91 91
 // in a portable fashion.
92 92
 //
93 93
 // On kernels 5.10 and earlier, setting a kretprobe on a nonexistent symbol
94
-// incorrectly returns unix.EINVAL instead of os.ErrNotExist.
94
+// incorrectly returns [unix.EINVAL] instead of [os.ErrNotExist].
95 95
 //
96 96
 // The returned Link may implement [PerfEvent].
97 97
 func Kretprobe(symbol string, prog *ebpf.Program, opts *KprobeOptions) (Link, error) {
... ...
@@ -169,7 +173,7 @@ func kprobe(symbol string, prog *ebpf.Program, opts *KprobeOptions, ret bool) (*
169 169
 	// Use kprobe PMU if the kernel has it available.
170 170
 	tp, err := pmuProbe(args)
171 171
 	if errors.Is(err, os.ErrNotExist) || errors.Is(err, unix.EINVAL) {
172
-		if prefix := internal.PlatformPrefix(); prefix != "" {
172
+		if prefix := linux.PlatformPrefix(); prefix != "" {
173 173
 			args.Symbol = prefix + symbol
174 174
 			tp, err = pmuProbe(args)
175 175
 		}
... ...
@@ -177,7 +181,7 @@ func kprobe(symbol string, prog *ebpf.Program, opts *KprobeOptions, ret bool) (*
177 177
 	if err == nil {
178 178
 		return tp, nil
179 179
 	}
180
-	if err != nil && !errors.Is(err, ErrNotSupported) {
180
+	if !errors.Is(err, ErrNotSupported) {
181 181
 		return nil, fmt.Errorf("creating perf_kprobe PMU (arch-specific fallback for %q): %w", symbol, err)
182 182
 	}
183 183
 
... ...
@@ -185,7 +189,7 @@ func kprobe(symbol string, prog *ebpf.Program, opts *KprobeOptions, ret bool) (*
185 185
 	args.Symbol = symbol
186 186
 	tp, err = tracefsProbe(args)
187 187
 	if errors.Is(err, os.ErrNotExist) || errors.Is(err, unix.EINVAL) {
188
-		if prefix := internal.PlatformPrefix(); prefix != "" {
188
+		if prefix := linux.PlatformPrefix(); prefix != "" {
189 189
 			args.Symbol = prefix + symbol
190 190
 			tp, err = tracefsProbe(args)
191 191
 		}
... ...
@@ -37,6 +37,14 @@ type KprobeMultiOptions struct {
37 37
 	// Each Cookie is assigned to the Symbol or Address specified at the
38 38
 	// corresponding slice index.
39 39
 	Cookies []uint64
40
+
41
+	// Session must be true when attaching Programs with the
42
+	// [ebpf.AttachTraceKprobeSession] attach type.
43
+	//
44
+	// This makes a Kprobe execute on both function entry and return. The entry
45
+	// program can share a cookie value with the return program and can decide
46
+	// whether the return program gets executed.
47
+	Session bool
40 48
 }
41 49
 
42 50
 // KprobeMulti attaches the given eBPF program to the entry point of a given set
... ...
@@ -60,7 +68,7 @@ func KprobeMulti(prog *ebpf.Program, opts KprobeMultiOptions) (Link, error) {
60 60
 //
61 61
 // Requires at least Linux 5.18.
62 62
 func KretprobeMulti(prog *ebpf.Program, opts KprobeMultiOptions) (Link, error) {
63
-	return kprobeMulti(prog, opts, unix.BPF_F_KPROBE_MULTI_RETURN)
63
+	return kprobeMulti(prog, opts, sys.BPF_F_KPROBE_MULTI_RETURN)
64 64
 }
65 65
 
66 66
 func kprobeMulti(prog *ebpf.Program, opts KprobeMultiOptions, flags uint32) (Link, error) {
... ...
@@ -82,9 +90,14 @@ func kprobeMulti(prog *ebpf.Program, opts KprobeMultiOptions, flags uint32) (Lin
82 82
 		return nil, fmt.Errorf("Cookies must be exactly Symbols or Addresses in length: %w", errInvalidInput)
83 83
 	}
84 84
 
85
+	attachType := sys.BPF_TRACE_KPROBE_MULTI
86
+	if opts.Session {
87
+		attachType = sys.BPF_TRACE_KPROBE_SESSION
88
+	}
89
+
85 90
 	attr := &sys.LinkCreateKprobeMultiAttr{
86 91
 		ProgFd:           uint32(prog.FD()),
87
-		AttachType:       sys.BPF_TRACE_KPROBE_MULTI,
92
+		AttachType:       attachType,
88 93
 		KprobeMultiFlags: flags,
89 94
 	}
90 95
 
... ...
@@ -103,21 +116,31 @@ func kprobeMulti(prog *ebpf.Program, opts KprobeMultiOptions, flags uint32) (Lin
103 103
 	}
104 104
 
105 105
 	fd, err := sys.LinkCreateKprobeMulti(attr)
106
+	if err == nil {
107
+		return &kprobeMultiLink{RawLink{fd, ""}}, nil
108
+	}
109
+
106 110
 	if errors.Is(err, unix.ESRCH) {
107 111
 		return nil, fmt.Errorf("couldn't find one or more symbols: %w", os.ErrNotExist)
108 112
 	}
109
-	if errors.Is(err, unix.EINVAL) {
110
-		return nil, fmt.Errorf("%w (missing kernel symbol or prog's AttachType not AttachTraceKprobeMulti?)", err)
111
-	}
112 113
 
113
-	if err != nil {
114
+	if opts.Session {
115
+		if haveFeatErr := haveBPFLinkKprobeSession(); haveFeatErr != nil {
116
+			return nil, haveFeatErr
117
+		}
118
+	} else {
114 119
 		if haveFeatErr := haveBPFLinkKprobeMulti(); haveFeatErr != nil {
115 120
 			return nil, haveFeatErr
116 121
 		}
117
-		return nil, err
118 122
 	}
119 123
 
120
-	return &kprobeMultiLink{RawLink{fd, ""}}, nil
124
+	// Check EINVAL after running feature probes, since it's also returned when
125
+	// the kernel doesn't support the multi/session attach types.
126
+	if errors.Is(err, unix.EINVAL) {
127
+		return nil, fmt.Errorf("%w (missing kernel symbol or prog's AttachType not %s?)", err, ebpf.AttachType(attachType))
128
+	}
129
+
130
+	return nil, err
121 131
 }
122 132
 
123 133
 type kprobeMultiLink struct {
... ...
@@ -126,7 +149,7 @@ type kprobeMultiLink struct {
126 126
 
127 127
 var _ Link = (*kprobeMultiLink)(nil)
128 128
 
129
-func (kml *kprobeMultiLink) Update(prog *ebpf.Program) error {
129
+func (kml *kprobeMultiLink) Update(_ *ebpf.Program) error {
130 130
 	return fmt.Errorf("update kprobe_multi: %w", ErrNotSupported)
131 131
 }
132 132
 
... ...
@@ -149,7 +172,7 @@ func (kml *kprobeMultiLink) Info() (*Info, error) {
149 149
 	}, nil
150 150
 }
151 151
 
152
-var haveBPFLinkKprobeMulti = internal.NewFeatureTest("bpf_link_kprobe_multi", "5.18", func() error {
152
+var haveBPFLinkKprobeMulti = internal.NewFeatureTest("bpf_link_kprobe_multi", func() error {
153 153
 	prog, err := ebpf.NewProgram(&ebpf.ProgramSpec{
154 154
 		Name: "probe_kpm_link",
155 155
 		Type: ebpf.Kprobe,
... ...
@@ -188,4 +211,45 @@ var haveBPFLinkKprobeMulti = internal.NewFeatureTest("bpf_link_kprobe_multi", "5
188 188
 	fd.Close()
189 189
 
190 190
 	return nil
191
-})
191
+}, "5.18")
192
+
193
+var haveBPFLinkKprobeSession = internal.NewFeatureTest("bpf_link_kprobe_session", func() error {
194
+	prog, err := ebpf.NewProgram(&ebpf.ProgramSpec{
195
+		Name: "probe_kps_link",
196
+		Type: ebpf.Kprobe,
197
+		Instructions: asm.Instructions{
198
+			asm.Mov.Imm(asm.R0, 0),
199
+			asm.Return(),
200
+		},
201
+		AttachType: ebpf.AttachTraceKprobeSession,
202
+		License:    "MIT",
203
+	})
204
+	if errors.Is(err, unix.E2BIG) {
205
+		// Kernel doesn't support AttachType field.
206
+		return internal.ErrNotSupported
207
+	}
208
+	if err != nil {
209
+		return err
210
+	}
211
+	defer prog.Close()
212
+
213
+	fd, err := sys.LinkCreateKprobeMulti(&sys.LinkCreateKprobeMultiAttr{
214
+		ProgFd:     uint32(prog.FD()),
215
+		AttachType: sys.BPF_TRACE_KPROBE_SESSION,
216
+		Count:      1,
217
+		Syms:       sys.NewStringSlicePointer([]string{"vprintk"}),
218
+	})
219
+	switch {
220
+	case errors.Is(err, unix.EINVAL):
221
+		return internal.ErrNotSupported
222
+	// If CONFIG_FPROBE isn't set.
223
+	case errors.Is(err, unix.EOPNOTSUPP):
224
+		return internal.ErrNotSupported
225
+	case err != nil:
226
+		return err
227
+	}
228
+
229
+	fd.Close()
230
+
231
+	return nil
232
+}, "6.10")
... ...
@@ -78,7 +78,9 @@ func NewFromID(id ID) (Link, error) {
78 78
 	return wrapRawLink(&RawLink{fd, ""})
79 79
 }
80 80
 
81
-// LoadPinnedLink loads a link that was persisted into a bpffs.
81
+// LoadPinnedLink loads a Link from a pin (file) on the BPF virtual filesystem.
82
+//
83
+// Requires at least Linux 5.7.
82 84
 func LoadPinnedLink(fileName string, opts *ebpf.LoadPinOptions) (Link, error) {
83 85
 	raw, err := loadPinnedRawLink(fileName, opts)
84 86
 	if err != nil {
... ...
@@ -350,7 +352,7 @@ func AttachRawLink(opts RawLinkOptions) (*RawLink, error) {
350 350
 }
351 351
 
352 352
 func loadPinnedRawLink(fileName string, opts *ebpf.LoadPinOptions) (*RawLink, error) {
353
-	fd, err := sys.ObjGet(&sys.ObjGetAttr{
353
+	fd, typ, err := sys.ObjGetTyped(&sys.ObjGetAttr{
354 354
 		Pathname:  sys.NewStringPointer(fileName),
355 355
 		FileFlags: opts.Marshal(),
356 356
 	})
... ...
@@ -358,6 +360,11 @@ func loadPinnedRawLink(fileName string, opts *ebpf.LoadPinOptions) (*RawLink, er
358 358
 		return nil, fmt.Errorf("load pinned link: %w", err)
359 359
 	}
360 360
 
361
+	if typ != sys.BPF_TYPE_LINK {
362
+		_ = fd.Close()
363
+		return nil, fmt.Errorf("%s is not a Link", fileName)
364
+	}
365
+
361 366
 	return &RawLink{fd, fileName}, nil
362 367
 }
363 368
 
... ...
@@ -380,7 +387,7 @@ func (l *RawLink) Close() error {
380 380
 // Calling Close on a pinned Link will not break the link
381 381
 // until the pin is removed.
382 382
 func (l *RawLink) Pin(fileName string) error {
383
-	if err := internal.Pin(l.pinnedPath, fileName, l.fd); err != nil {
383
+	if err := sys.Pin(l.pinnedPath, fileName, l.fd); err != nil {
384 384
 		return err
385 385
 	}
386 386
 	l.pinnedPath = fileName
... ...
@@ -389,7 +396,7 @@ func (l *RawLink) Pin(fileName string) error {
389 389
 
390 390
 // Unpin implements the Link interface.
391 391
 func (l *RawLink) Unpin() error {
392
-	if err := internal.Unpin(l.pinnedPath); err != nil {
392
+	if err := sys.Unpin(l.pinnedPath); err != nil {
393 393
 		return err
394 394
 	}
395 395
 	l.pinnedPath = ""
... ...
@@ -63,7 +63,7 @@ func AttachNetfilter(opts NetfilterOptions) (Link, error) {
63 63
 	return &netfilterLink{RawLink{fd, ""}}, nil
64 64
 }
65 65
 
66
-func (*netfilterLink) Update(new *ebpf.Program) error {
66
+func (*netfilterLink) Update(_ *ebpf.Program) error {
67 67
 	return fmt.Errorf("netfilter update: %w", ErrNotSupported)
68 68
 }
69 69
 
... ...
@@ -115,7 +115,7 @@ func (pl *perfEventLink) Close() error {
115 115
 	return nil
116 116
 }
117 117
 
118
-func (pl *perfEventLink) Update(prog *ebpf.Program) error {
118
+func (pl *perfEventLink) Update(_ *ebpf.Program) error {
119 119
 	return fmt.Errorf("perf event link update: %w", ErrNotSupported)
120 120
 }
121 121
 
... ...
@@ -185,7 +185,7 @@ func (pi *perfEventIoctl) isLink() {}
185 185
 //
186 186
 // Detaching a program from a perf event is currently not possible, so a
187 187
 // program replacement mechanism cannot be implemented for perf events.
188
-func (pi *perfEventIoctl) Update(prog *ebpf.Program) error {
188
+func (pi *perfEventIoctl) Update(_ *ebpf.Program) error {
189 189
 	return fmt.Errorf("perf event ioctl update: %w", ErrNotSupported)
190 190
 }
191 191
 
... ...
@@ -303,7 +303,7 @@ func openTracepointPerfEvent(tid uint64, pid int) (*sys.FD, error) {
303 303
 //
304 304
 // https://elixir.bootlin.com/linux/v5.16.8/source/kernel/bpf/syscall.c#L4307
305 305
 // https://github.com/torvalds/linux/commit/b89fbfbb854c9afc3047e8273cc3a694650b802e
306
-var haveBPFLinkPerfEvent = internal.NewFeatureTest("bpf_link_perf_event", "5.15", func() error {
306
+var haveBPFLinkPerfEvent = internal.NewFeatureTest("bpf_link_perf_event", func() error {
307 307
 	prog, err := ebpf.NewProgram(&ebpf.ProgramSpec{
308 308
 		Name: "probe_bpf_perf_link",
309 309
 		Type: ebpf.Kprobe,
... ...
@@ -329,4 +329,4 @@ var haveBPFLinkPerfEvent = internal.NewFeatureTest("bpf_link_perf_event", "5.15"
329 329
 		return nil
330 330
 	}
331 331
 	return err
332
-})
332
+}, "5.15")
... ...
@@ -30,7 +30,7 @@ const (
30 30
 	NetkitType        = sys.BPF_LINK_TYPE_NETKIT
31 31
 )
32 32
 
33
-var haveProgAttach = internal.NewFeatureTest("BPF_PROG_ATTACH", "4.10", func() error {
33
+var haveProgAttach = internal.NewFeatureTest("BPF_PROG_ATTACH", func() error {
34 34
 	prog, err := ebpf.NewProgram(&ebpf.ProgramSpec{
35 35
 		Type:    ebpf.CGroupSKB,
36 36
 		License: "MIT",
... ...
@@ -48,9 +48,9 @@ var haveProgAttach = internal.NewFeatureTest("BPF_PROG_ATTACH", "4.10", func() e
48 48
 	// have the syscall.
49 49
 	prog.Close()
50 50
 	return nil
51
-})
51
+}, "4.10")
52 52
 
53
-var haveProgAttachReplace = internal.NewFeatureTest("BPF_PROG_ATTACH atomic replacement of MULTI progs", "5.5", func() error {
53
+var haveProgAttachReplace = internal.NewFeatureTest("BPF_PROG_ATTACH atomic replacement of MULTI progs", func() error {
54 54
 	if err := haveProgAttach(); err != nil {
55 55
 		return err
56 56
 	}
... ...
@@ -90,9 +90,9 @@ var haveProgAttachReplace = internal.NewFeatureTest("BPF_PROG_ATTACH atomic repl
90 90
 		return nil
91 91
 	}
92 92
 	return err
93
-})
93
+}, "5.5")
94 94
 
95
-var haveBPFLink = internal.NewFeatureTest("bpf_link", "5.7", func() error {
95
+var haveBPFLink = internal.NewFeatureTest("bpf_link", func() error {
96 96
 	attr := sys.LinkCreateAttr{
97 97
 		// This is a hopefully invalid file descriptor, which triggers EBADF.
98 98
 		TargetFd:   ^uint32(0),
... ...
@@ -107,9 +107,9 @@ var haveBPFLink = internal.NewFeatureTest("bpf_link", "5.7", func() error {
107 107
 		return nil
108 108
 	}
109 109
 	return err
110
-})
110
+}, "5.7")
111 111
 
112
-var haveProgQuery = internal.NewFeatureTest("BPF_PROG_QUERY", "4.15", func() error {
112
+var haveProgQuery = internal.NewFeatureTest("BPF_PROG_QUERY", func() error {
113 113
 	attr := sys.ProgQueryAttr{
114 114
 		// We rely on this being checked during the syscall.
115 115
 		// With an otherwise correct payload we expect EBADF here
... ...
@@ -127,9 +127,9 @@ var haveProgQuery = internal.NewFeatureTest("BPF_PROG_QUERY", "4.15", func() err
127 127
 		return ErrNotSupported
128 128
 	}
129 129
 	return errors.New("syscall succeeded unexpectedly")
130
-})
130
+}, "4.15")
131 131
 
132
-var haveTCX = internal.NewFeatureTest("tcx", "6.6", func() error {
132
+var haveTCX = internal.NewFeatureTest("tcx", func() error {
133 133
 	prog, err := ebpf.NewProgram(&ebpf.ProgramSpec{
134 134
 		Type:    ebpf.SchedCLS,
135 135
 		License: "MIT",
... ...
@@ -162,9 +162,9 @@ var haveTCX = internal.NewFeatureTest("tcx", "6.6", func() error {
162 162
 		return ErrNotSupported
163 163
 	}
164 164
 	return errors.New("syscall succeeded unexpectedly")
165
-})
165
+}, "6.6")
166 166
 
167
-var haveNetkit = internal.NewFeatureTest("netkit", "6.7", func() error {
167
+var haveNetkit = internal.NewFeatureTest("netkit", func() error {
168 168
 	prog, err := ebpf.NewProgram(&ebpf.ProgramSpec{
169 169
 		Type:    ebpf.SchedCLS,
170 170
 		License: "MIT",
... ...
@@ -197,4 +197,4 @@ var haveNetkit = internal.NewFeatureTest("netkit", "6.7", func() error {
197 197
 		return ErrNotSupported
198 198
 	}
199 199
 	return errors.New("syscall succeeded unexpectedly")
200
-})
200
+}, "6.7")
... ...
@@ -14,7 +14,7 @@ type tracing struct {
14 14
 	RawLink
15 15
 }
16 16
 
17
-func (f *tracing) Update(new *ebpf.Program) error {
17
+func (f *tracing) Update(_ *ebpf.Program) error {
18 18
 	return fmt.Errorf("tracing update: %w", ErrNotSupported)
19 19
 }
20 20
 
... ...
@@ -16,7 +16,7 @@ var (
16 16
 	uprobeRefCtrOffsetPMUPath = "/sys/bus/event_source/devices/uprobe/format/ref_ctr_offset"
17 17
 	// elixir.bootlin.com/linux/v5.15-rc7/source/kernel/events/core.c#L9799
18 18
 	uprobeRefCtrOffsetShift = 32
19
-	haveRefCtrOffsetPMU     = internal.NewFeatureTest("RefCtrOffsetPMU", "4.20", func() error {
19
+	haveRefCtrOffsetPMU     = internal.NewFeatureTest("RefCtrOffsetPMU", func() error {
20 20
 		_, err := os.Stat(uprobeRefCtrOffsetPMUPath)
21 21
 		if errors.Is(err, os.ErrNotExist) {
22 22
 			return internal.ErrNotSupported
... ...
@@ -25,7 +25,7 @@ var (
25 25
 			return err
26 26
 		}
27 27
 		return nil
28
-	})
28
+	}, "4.20")
29 29
 
30 30
 	// ErrNoSymbol indicates that the given symbol was not found
31 31
 	// in the ELF symbols table.
... ...
@@ -321,7 +321,7 @@ func (ex *Executable) uprobe(symbol string, prog *ebpf.Program, opts *UprobeOpti
321 321
 	if err == nil {
322 322
 		return tp, nil
323 323
 	}
324
-	if err != nil && !errors.Is(err, ErrNotSupported) {
324
+	if !errors.Is(err, ErrNotSupported) {
325 325
 		return nil, fmt.Errorf("creating perf_uprobe PMU: %w", err)
326 326
 	}
327 327
 
... ...
@@ -47,7 +47,7 @@ func (ex *Executable) UretprobeMulti(symbols []string, prog *ebpf.Program, opts
47 47
 	// The return probe is not limited for symbols entry, so there's no special
48 48
 	// setup for return uprobes (other than the extra flag). The symbols, opts.Offsets
49 49
 	// and opts.Addresses arrays follow the same logic as for entry uprobes.
50
-	return ex.uprobeMulti(symbols, prog, opts, unix.BPF_F_UPROBE_MULTI_RETURN)
50
+	return ex.uprobeMulti(symbols, prog, opts, sys.BPF_F_UPROBE_MULTI_RETURN)
51 51
 }
52 52
 
53 53
 func (ex *Executable) uprobeMulti(symbols []string, prog *ebpf.Program, opts *UprobeMultiOptions, flags uint32) (Link, error) {
... ...
@@ -99,8 +99,11 @@ func (ex *Executable) uprobeMulti(symbols []string, prog *ebpf.Program, opts *Up
99 99
 	if errors.Is(err, unix.ESRCH) {
100 100
 		return nil, fmt.Errorf("%w (specified pid not found?)", os.ErrNotExist)
101 101
 	}
102
+	// Since Linux commit 46ba0e49b642 ("bpf: fix multi-uprobe PID filtering
103
+	// logic"), if the provided pid overflows MaxInt32 (turning it negative), the
104
+	// kernel will return EINVAL instead of ESRCH.
102 105
 	if errors.Is(err, unix.EINVAL) {
103
-		return nil, fmt.Errorf("%w (missing symbol or prog's AttachType not AttachTraceUprobeMulti?)", err)
106
+		return nil, fmt.Errorf("%w (invalid pid, missing symbol or prog's AttachType not AttachTraceUprobeMulti?)", err)
104 107
 	}
105 108
 
106 109
 	if err != nil {
... ...
@@ -168,11 +171,11 @@ type uprobeMultiLink struct {
168 168
 
169 169
 var _ Link = (*uprobeMultiLink)(nil)
170 170
 
171
-func (kml *uprobeMultiLink) Update(prog *ebpf.Program) error {
171
+func (kml *uprobeMultiLink) Update(_ *ebpf.Program) error {
172 172
 	return fmt.Errorf("update uprobe_multi: %w", ErrNotSupported)
173 173
 }
174 174
 
175
-var haveBPFLinkUprobeMulti = internal.NewFeatureTest("bpf_link_uprobe_multi", "6.6", func() error {
175
+var haveBPFLinkUprobeMulti = internal.NewFeatureTest("bpf_link_uprobe_multi", func() error {
176 176
 	prog, err := ebpf.NewProgram(&ebpf.ProgramSpec{
177 177
 		Name: "probe_upm_link",
178 178
 		Type: ebpf.Kprobe,
... ...
@@ -213,4 +216,4 @@ var haveBPFLinkUprobeMulti = internal.NewFeatureTest("bpf_link_uprobe_multi", "6
213 213
 	// should not happen
214 214
 	fd.Close()
215 215
 	return errors.New("successfully attached uprobe_multi to /, kernel bug?")
216
-})
216
+}, "6.6")
... ...
@@ -9,10 +9,12 @@ import (
9 9
 	"io/fs"
10 10
 	"math"
11 11
 	"slices"
12
+	"strings"
12 13
 
13 14
 	"github.com/cilium/ebpf/asm"
14 15
 	"github.com/cilium/ebpf/btf"
15 16
 	"github.com/cilium/ebpf/internal"
17
+	"github.com/cilium/ebpf/internal/kallsyms"
16 18
 )
17 19
 
18 20
 // handles stores handle objects to avoid gc cleanup
... ...
@@ -205,13 +207,19 @@ func flattenPrograms(progs map[string]*ProgramSpec, names []string) {
205 205
 // dependencies of each program.
206 206
 func flattenInstructions(name string, progs map[string]*ProgramSpec, refs map[*ProgramSpec][]string) asm.Instructions {
207 207
 	prog := progs[name]
208
+	progRefs := refs[prog]
209
+
210
+	if len(progRefs) == 0 {
211
+		// No references, nothing to do.
212
+		return prog.Instructions
213
+	}
208 214
 
209 215
 	insns := make(asm.Instructions, len(prog.Instructions))
210 216
 	copy(insns, prog.Instructions)
211 217
 
212 218
 	// Add all direct references of prog to the list of to be linked programs.
213
-	pending := make([]string, len(refs[prog]))
214
-	copy(pending, refs[prog])
219
+	pending := make([]string, len(progRefs))
220
+	copy(pending, progRefs)
215 221
 
216 222
 	// All references for which we've appended instructions.
217 223
 	linked := make(map[string]bool)
... ...
@@ -457,3 +465,42 @@ func resolveKconfigReferences(insns asm.Instructions) (_ *Map, err error) {
457 457
 
458 458
 	return kconfig, nil
459 459
 }
460
+
461
+func resolveKsymReferences(insns asm.Instructions) error {
462
+	var missing []string
463
+
464
+	iter := insns.Iterate()
465
+	for iter.Next() {
466
+		ins := iter.Ins
467
+		meta, _ := ins.Metadata.Get(ksymMetaKey{}).(*ksymMeta)
468
+		if meta == nil {
469
+			continue
470
+		}
471
+
472
+		addr, err := kallsyms.Address(meta.Name)
473
+		if err != nil {
474
+			return fmt.Errorf("resolve ksym %s: %w", meta.Name, err)
475
+		}
476
+		if addr != 0 {
477
+			ins.Constant = int64(addr)
478
+			continue
479
+		}
480
+
481
+		if meta.Binding == elf.STB_WEAK {
482
+			// A weak ksym variable in eBPF C means its resolution is optional.
483
+			// Set a zero constant explicitly for clarity.
484
+			ins.Constant = 0
485
+			continue
486
+		}
487
+
488
+		if !slices.Contains(missing, meta.Name) {
489
+			missing = append(missing, meta.Name)
490
+		}
491
+	}
492
+
493
+	if len(missing) > 0 {
494
+		return fmt.Errorf("kernel is missing symbol: %s", strings.Join(missing, ","))
495
+	}
496
+
497
+	return nil
498
+}
... ...
@@ -66,16 +66,13 @@ type MapSpec struct {
66 66
 	Pinning PinType
67 67
 
68 68
 	// Specify numa node during map creation
69
-	// (effective only if unix.BPF_F_NUMA_NODE flag is set,
69
+	// (effective only if sys.BPF_F_NUMA_NODE flag is set,
70 70
 	// which can be imported from golang.org/x/sys/unix)
71 71
 	NumaNode uint32
72 72
 
73 73
 	// The initial contents of the map. May be nil.
74 74
 	Contents []MapKV
75 75
 
76
-	// Whether to freeze a map after setting its initial contents.
77
-	Freeze bool
78
-
79 76
 	// InnerMap is used as a template for ArrayOfMaps and HashOfMaps
80 77
 	InnerMap *MapSpec
81 78
 
... ...
@@ -161,6 +158,17 @@ func (spec *MapSpec) fixupMagicFields() (*MapSpec, error) {
161 161
 			// behaviour in the past.
162 162
 			spec.MaxEntries = n
163 163
 		}
164
+
165
+	case CPUMap:
166
+		n, err := PossibleCPU()
167
+		if err != nil {
168
+			return nil, fmt.Errorf("fixup cpu map: %w", err)
169
+		}
170
+
171
+		if n := uint32(n); spec.MaxEntries == 0 || spec.MaxEntries > n {
172
+			// Perform clamping similar to PerfEventArray.
173
+			spec.MaxEntries = n
174
+		}
164 175
 	}
165 176
 
166 177
 	return spec, nil
... ...
@@ -190,6 +198,14 @@ func (ms *MapSpec) dataSection() ([]byte, *btf.Datasec, error) {
190 190
 	return value, ds, nil
191 191
 }
192 192
 
193
+func (ms *MapSpec) readOnly() bool {
194
+	return (ms.Flags & sys.BPF_F_RDONLY_PROG) > 0
195
+}
196
+
197
+func (ms *MapSpec) writeOnly() bool {
198
+	return (ms.Flags & sys.BPF_F_WRONLY_PROG) > 0
199
+}
200
+
193 201
 // MapKV is used to initialize the contents of a Map.
194 202
 type MapKV struct {
195 203
 	Key   interface{}
... ...
@@ -222,7 +238,7 @@ func (ms *MapSpec) Compatible(m *Map) error {
222 222
 
223 223
 	// BPF_F_RDONLY_PROG is set unconditionally for devmaps. Explicitly allow this
224 224
 	// mismatch.
225
-	if !((ms.Type == DevMap || ms.Type == DevMapHash) && m.flags^ms.Flags == unix.BPF_F_RDONLY_PROG) &&
225
+	if !((ms.Type == DevMap || ms.Type == DevMapHash) && m.flags^ms.Flags == sys.BPF_F_RDONLY_PROG) &&
226 226
 		m.flags != ms.Flags {
227 227
 		diffs = append(diffs, fmt.Sprintf("Flags: %d changed to %d", m.flags, ms.Flags))
228 228
 	}
... ...
@@ -254,6 +270,8 @@ type Map struct {
254 254
 	pinnedPath string
255 255
 	// Per CPU maps return values larger than the size in the spec
256 256
 	fullValueSize int
257
+
258
+	memory *Memory
257 259
 }
258 260
 
259 261
 // NewMapFromFD creates a map from a raw fd.
... ...
@@ -359,7 +377,7 @@ func newMapWithOptions(spec *MapSpec, opts MapOptions) (_ *Map, err error) {
359 359
 			return nil, errors.New("inner maps cannot be pinned")
360 360
 		}
361 361
 
362
-		template, err := spec.InnerMap.createMap(nil, opts)
362
+		template, err := spec.InnerMap.createMap(nil)
363 363
 		if err != nil {
364 364
 			return nil, fmt.Errorf("inner map: %w", err)
365 365
 		}
... ...
@@ -371,7 +389,7 @@ func newMapWithOptions(spec *MapSpec, opts MapOptions) (_ *Map, err error) {
371 371
 		innerFd = template.fd
372 372
 	}
373 373
 
374
-	m, err := spec.createMap(innerFd, opts)
374
+	m, err := spec.createMap(innerFd)
375 375
 	if err != nil {
376 376
 		return nil, err
377 377
 	}
... ...
@@ -387,9 +405,54 @@ func newMapWithOptions(spec *MapSpec, opts MapOptions) (_ *Map, err error) {
387 387
 	return m, nil
388 388
 }
389 389
 
390
+// Memory returns a memory-mapped region for the Map. The Map must have been
391
+// created with the BPF_F_MMAPABLE flag. Repeated calls to Memory return the
392
+// same mapping. Callers are responsible for coordinating access to Memory.
393
+func (m *Map) Memory() (*Memory, error) {
394
+	if m.memory != nil {
395
+		return m.memory, nil
396
+	}
397
+
398
+	if m.flags&sys.BPF_F_MMAPABLE == 0 {
399
+		return nil, fmt.Errorf("Map was not created with the BPF_F_MMAPABLE flag: %w", ErrNotSupported)
400
+	}
401
+
402
+	size, err := m.memorySize()
403
+	if err != nil {
404
+		return nil, err
405
+	}
406
+
407
+	mm, err := newMemory(m.FD(), size)
408
+	if err != nil {
409
+		return nil, fmt.Errorf("creating new Memory: %w", err)
410
+	}
411
+
412
+	m.memory = mm
413
+
414
+	return mm, nil
415
+}
416
+
417
+func (m *Map) memorySize() (int, error) {
418
+	switch m.Type() {
419
+	case Array:
420
+		// In Arrays, values are always laid out on 8-byte boundaries regardless of
421
+		// architecture. Multiply by MaxEntries and align the result to the host's
422
+		// page size.
423
+		size := int(internal.Align(m.ValueSize(), 8) * m.MaxEntries())
424
+		size = internal.Align(size, os.Getpagesize())
425
+		return size, nil
426
+	case Arena:
427
+		// For Arenas, MaxEntries denotes the maximum number of pages available to
428
+		// the arena.
429
+		return int(m.MaxEntries()) * os.Getpagesize(), nil
430
+	}
431
+
432
+	return 0, fmt.Errorf("determine memory size of map type %s: %w", m.Type(), ErrNotSupported)
433
+}
434
+
390 435
 // createMap validates the spec's properties and creates the map in the kernel
391 436
 // using the given opts. It does not populate or freeze the map.
392
-func (spec *MapSpec) createMap(inner *sys.FD, opts MapOptions) (_ *Map, err error) {
437
+func (spec *MapSpec) createMap(inner *sys.FD) (_ *Map, err error) {
393 438
 	closeOnError := func(closer io.Closer) {
394 439
 		if err != nil {
395 440
 			closer.Close()
... ...
@@ -416,7 +479,7 @@ func (spec *MapSpec) createMap(inner *sys.FD, opts MapOptions) (_ *Map, err erro
416 416
 		KeySize:    spec.KeySize,
417 417
 		ValueSize:  spec.ValueSize,
418 418
 		MaxEntries: spec.MaxEntries,
419
-		MapFlags:   sys.MapFlags(spec.Flags),
419
+		MapFlags:   spec.Flags,
420 420
 		NumaNode:   spec.NumaNode,
421 421
 	}
422 422
 
... ...
@@ -474,32 +537,32 @@ func handleMapCreateError(attr sys.MapCreateAttr, spec *MapSpec, err error) erro
474 474
 	if errors.Is(err, unix.EINVAL) && spec.Type == UnspecifiedMap {
475 475
 		return fmt.Errorf("map create: cannot use type %s", UnspecifiedMap)
476 476
 	}
477
-	if errors.Is(err, unix.EINVAL) && spec.Flags&unix.BPF_F_NO_PREALLOC > 0 {
477
+	if errors.Is(err, unix.EINVAL) && spec.Flags&sys.BPF_F_NO_PREALLOC > 0 {
478 478
 		return fmt.Errorf("map create: %w (noPrealloc flag may be incompatible with map type %s)", err, spec.Type)
479 479
 	}
480 480
 
481
-	switch spec.Type {
482
-	case ArrayOfMaps, HashOfMaps:
481
+	if spec.Type.canStoreMap() {
483 482
 		if haveFeatErr := haveNestedMaps(); haveFeatErr != nil {
484 483
 			return fmt.Errorf("map create: %w", haveFeatErr)
485 484
 		}
486 485
 	}
487
-	if spec.Flags&(unix.BPF_F_RDONLY_PROG|unix.BPF_F_WRONLY_PROG) > 0 || spec.Freeze {
486
+
487
+	if spec.readOnly() || spec.writeOnly() {
488 488
 		if haveFeatErr := haveMapMutabilityModifiers(); haveFeatErr != nil {
489 489
 			return fmt.Errorf("map create: %w", haveFeatErr)
490 490
 		}
491 491
 	}
492
-	if spec.Flags&unix.BPF_F_MMAPABLE > 0 {
492
+	if spec.Flags&sys.BPF_F_MMAPABLE > 0 {
493 493
 		if haveFeatErr := haveMmapableMaps(); haveFeatErr != nil {
494 494
 			return fmt.Errorf("map create: %w", haveFeatErr)
495 495
 		}
496 496
 	}
497
-	if spec.Flags&unix.BPF_F_INNER_MAP > 0 {
497
+	if spec.Flags&sys.BPF_F_INNER_MAP > 0 {
498 498
 		if haveFeatErr := haveInnerMaps(); haveFeatErr != nil {
499 499
 			return fmt.Errorf("map create: %w", haveFeatErr)
500 500
 		}
501 501
 	}
502
-	if spec.Flags&unix.BPF_F_NO_PREALLOC > 0 {
502
+	if spec.Flags&sys.BPF_F_NO_PREALLOC > 0 {
503 503
 		if haveFeatErr := haveNoPreallocMaps(); haveFeatErr != nil {
504 504
 			return fmt.Errorf("map create: %w", haveFeatErr)
505 505
 		}
... ...
@@ -530,6 +593,7 @@ func newMap(fd *sys.FD, name string, typ MapType, keySize, valueSize, maxEntries
530 530
 		flags,
531 531
 		"",
532 532
 		int(valueSize),
533
+		nil,
533 534
 	}
534 535
 
535 536
 	if !typ.hasPerCPUValue() {
... ...
@@ -577,7 +641,12 @@ func (m *Map) Flags() uint32 {
577 577
 	return m.flags
578 578
 }
579 579
 
580
-// Info returns metadata about the map.
580
+// Info returns metadata about the map. This was first introduced in Linux 4.5,
581
+// but newer kernels support more MapInfo fields with the introduction of more
582
+// features. See [MapInfo] and its methods for more details.
583
+//
584
+// Returns an error wrapping ErrNotSupported if the kernel supports neither
585
+// BPF_OBJ_GET_INFO_BY_FD nor reading map information from /proc/self/fdinfo.
581 586
 func (m *Map) Info() (*MapInfo, error) {
582 587
 	return newMapInfoFromFd(m.fd)
583 588
 }
... ...
@@ -604,7 +673,7 @@ func (m *Map) Handle() (*btf.Handle, error) {
604 604
 type MapLookupFlags uint64
605 605
 
606 606
 // LookupLock look up the value of a spin-locked map.
607
-const LookupLock MapLookupFlags = unix.BPF_F_LOCK
607
+const LookupLock MapLookupFlags = sys.BPF_F_LOCK
608 608
 
609 609
 // Lookup retrieves a value from a Map.
610 610
 //
... ...
@@ -1336,6 +1405,7 @@ func (m *Map) Clone() (*Map, error) {
1336 1336
 		m.flags,
1337 1337
 		"",
1338 1338
 		m.fullValueSize,
1339
+		nil,
1339 1340
 	}, nil
1340 1341
 }
1341 1342
 
... ...
@@ -1349,7 +1419,7 @@ func (m *Map) Clone() (*Map, error) {
1349 1349
 // This requires bpffs to be mounted above fileName.
1350 1350
 // See https://docs.cilium.io/en/stable/network/kubernetes/configuration/#mounting-bpffs-with-systemd
1351 1351
 func (m *Map) Pin(fileName string) error {
1352
-	if err := internal.Pin(m.pinnedPath, fileName, m.fd); err != nil {
1352
+	if err := sys.Pin(m.pinnedPath, fileName, m.fd); err != nil {
1353 1353
 		return err
1354 1354
 	}
1355 1355
 	m.pinnedPath = fileName
... ...
@@ -1362,7 +1432,7 @@ func (m *Map) Pin(fileName string) error {
1362 1362
 //
1363 1363
 // Unpinning an unpinned Map returns nil.
1364 1364
 func (m *Map) Unpin() error {
1365
-	if err := internal.Unpin(m.pinnedPath); err != nil {
1365
+	if err := sys.Unpin(m.pinnedPath); err != nil {
1366 1366
 		return err
1367 1367
 	}
1368 1368
 	m.pinnedPath = ""
... ...
@@ -1400,7 +1470,7 @@ func (m *Map) finalize(spec *MapSpec) error {
1400 1400
 		}
1401 1401
 	}
1402 1402
 
1403
-	if spec.Freeze {
1403
+	if isConstantDataSection(spec.Name) || isKconfigSection(spec.Name) {
1404 1404
 		if err := m.Freeze(); err != nil {
1405 1405
 			return fmt.Errorf("freezing map: %w", err)
1406 1406
 		}
... ...
@@ -1501,9 +1571,11 @@ func (m *Map) unmarshalValue(value any, buf sysenc.Buffer) error {
1501 1501
 	return buf.Unmarshal(value)
1502 1502
 }
1503 1503
 
1504
-// LoadPinnedMap loads a Map from a BPF file.
1504
+// LoadPinnedMap opens a Map from a pin (file) on the BPF virtual filesystem.
1505
+//
1506
+// Requires at least Linux 4.5.
1505 1507
 func LoadPinnedMap(fileName string, opts *LoadPinOptions) (*Map, error) {
1506
-	fd, err := sys.ObjGet(&sys.ObjGetAttr{
1508
+	fd, typ, err := sys.ObjGetTyped(&sys.ObjGetAttr{
1507 1509
 		Pathname:  sys.NewStringPointer(fileName),
1508 1510
 		FileFlags: opts.Marshal(),
1509 1511
 	})
... ...
@@ -1511,6 +1583,11 @@ func LoadPinnedMap(fileName string, opts *LoadPinOptions) (*Map, error) {
1511 1511
 		return nil, err
1512 1512
 	}
1513 1513
 
1514
+	if typ != sys.BPF_TYPE_MAP {
1515
+		_ = fd.Close()
1516
+		return nil, fmt.Errorf("%s is not a Map", fileName)
1517
+	}
1518
+
1514 1519
 	m, err := newMapFromFD(fd)
1515 1520
 	if err == nil {
1516 1521
 		m.pinnedPath = fileName
... ...
@@ -1530,6 +1607,10 @@ func unmarshalMap(buf sysenc.Buffer) (*Map, error) {
1530 1530
 
1531 1531
 // marshalMap marshals the fd of a map into a buffer in host endianness.
1532 1532
 func marshalMap(m *Map, length int) ([]byte, error) {
1533
+	if m == nil {
1534
+		return nil, errors.New("can't marshal a nil Map")
1535
+	}
1536
+
1533 1537
 	if length != 4 {
1534 1538
 		return nil, fmt.Errorf("can't marshal map to %d bytes", length)
1535 1539
 	}
1536 1540
new file mode 100644
... ...
@@ -0,0 +1,145 @@
0
+package ebpf
1
+
2
+import (
3
+	"errors"
4
+	"fmt"
5
+	"io"
6
+	"runtime"
7
+
8
+	"github.com/cilium/ebpf/internal/unix"
9
+)
10
+
11
+// Memory is the building block for accessing the memory of specific bpf map
12
+// types (Array and Arena at the time of writing) without going through the bpf
13
+// syscall interface.
14
+//
15
+// Given the fd of a bpf map created with the BPF_F_MMAPABLE flag, a shared
16
+// 'file'-based memory-mapped region can be allocated in the process' address
17
+// space, exposing the bpf map's memory by simply accessing a memory location.
18
+
19
+var ErrReadOnly = errors.New("resource is read-only")
20
+
21
+// Memory implements accessing a Map's memory without making any syscalls.
22
+// Pay attention to the difference between Go and C struct alignment rules. Use
23
+// [structs.HostLayout] on supported Go versions to help with alignment.
24
+//
25
+// Note on memory coherence: avoid using packed structs in memory shared between
26
+// user space and eBPF C programs. This drops a struct's memory alignment to 1,
27
+// forcing the compiler to use single-byte loads and stores for field accesses.
28
+// This may lead to partially-written data to be observed from user space.
29
+//
30
+// On most architectures, the memmove implementation used by Go's copy() will
31
+// access data in word-sized chunks. If paired with a matching access pattern on
32
+// the eBPF C side (and if using default memory alignment), accessing shared
33
+// memory without atomics or other synchronization primitives should be sound
34
+// for individual values. For accesses beyond a single value, the usual
35
+// concurrent programming rules apply.
36
+type Memory struct {
37
+	b  []byte
38
+	ro bool
39
+}
40
+
41
+func newMemory(fd, size int) (*Memory, error) {
42
+	// Typically, maps created with BPF_F_RDONLY_PROG remain writable from user
43
+	// space until frozen. As a security precaution, the kernel doesn't allow
44
+	// mapping bpf map memory as read-write into user space if the bpf map was
45
+	// frozen, or if it was created using the RDONLY_PROG flag.
46
+	//
47
+	// The user would be able to write to the map after freezing (since the kernel
48
+	// can't change the protection mode of an already-mapped page), while the
49
+	// verifier assumes the contents to be immutable.
50
+	b, err := unix.Mmap(fd, 0, size, unix.PROT_READ|unix.PROT_WRITE, unix.MAP_SHARED)
51
+
52
+	// If the map is frozen when an rw mapping is requested, expect EPERM. If the
53
+	// map was created with BPF_F_RDONLY_PROG, expect EACCES.
54
+	var ro bool
55
+	if errors.Is(err, unix.EPERM) || errors.Is(err, unix.EACCES) {
56
+		ro = true
57
+		b, err = unix.Mmap(fd, 0, size, unix.PROT_READ, unix.MAP_SHARED)
58
+	}
59
+	if err != nil {
60
+		return nil, fmt.Errorf("setting up memory-mapped region: %w", err)
61
+	}
62
+
63
+	mm := &Memory{
64
+		b,
65
+		ro,
66
+	}
67
+	runtime.SetFinalizer(mm, (*Memory).close)
68
+
69
+	return mm, nil
70
+}
71
+
72
+func (mm *Memory) close() {
73
+	if err := unix.Munmap(mm.b); err != nil {
74
+		panic(fmt.Errorf("unmapping memory: %w", err))
75
+	}
76
+	mm.b = nil
77
+}
78
+
79
+// Size returns the size of the memory-mapped region in bytes.
80
+func (mm *Memory) Size() int {
81
+	return len(mm.b)
82
+}
83
+
84
+// ReadOnly returns true if the memory-mapped region is read-only.
85
+func (mm *Memory) ReadOnly() bool {
86
+	return mm.ro
87
+}
88
+
89
+// bounds returns true if an access at off of the given size is within bounds.
90
+func (mm *Memory) bounds(off uint64, size uint64) bool {
91
+	return off+size < uint64(len(mm.b))
92
+}
93
+
94
+// ReadAt implements [io.ReaderAt]. Useful for creating a new [io.OffsetWriter].
95
+//
96
+// See [Memory] for details around memory coherence.
97
+func (mm *Memory) ReadAt(p []byte, off int64) (int, error) {
98
+	if mm.b == nil {
99
+		return 0, fmt.Errorf("memory-mapped region closed")
100
+	}
101
+
102
+	if p == nil {
103
+		return 0, fmt.Errorf("input buffer p is nil")
104
+	}
105
+
106
+	if off < 0 || off >= int64(len(mm.b)) {
107
+		return 0, fmt.Errorf("read offset out of range")
108
+	}
109
+
110
+	n := copy(p, mm.b[off:])
111
+	if n < len(p) {
112
+		return n, io.EOF
113
+	}
114
+
115
+	return n, nil
116
+}
117
+
118
+// WriteAt implements [io.WriterAt]. Useful for creating a new
119
+// [io.SectionReader].
120
+//
121
+// See [Memory] for details around memory coherence.
122
+func (mm *Memory) WriteAt(p []byte, off int64) (int, error) {
123
+	if mm.b == nil {
124
+		return 0, fmt.Errorf("memory-mapped region closed")
125
+	}
126
+	if mm.ro {
127
+		return 0, fmt.Errorf("memory-mapped region not writable: %w", ErrReadOnly)
128
+	}
129
+
130
+	if p == nil {
131
+		return 0, fmt.Errorf("output buffer p is nil")
132
+	}
133
+
134
+	if off < 0 || off >= int64(len(mm.b)) {
135
+		return 0, fmt.Errorf("write offset out of range")
136
+	}
137
+
138
+	n := copy(mm.b[off:], p)
139
+	if n < len(p) {
140
+		return n, io.EOF
141
+	}
142
+
143
+	return n, nil
144
+}
... ...
@@ -2,3 +2,4 @@
2 2
   base = "docs/"
3 3
   publish = "site/"
4 4
   command = "mkdocs build"
5
+  environment = { PYTHON_VERSION = "3.13" }
... ...
@@ -16,6 +16,7 @@ import (
16 16
 	"github.com/cilium/ebpf/btf"
17 17
 	"github.com/cilium/ebpf/internal"
18 18
 	"github.com/cilium/ebpf/internal/kallsyms"
19
+	"github.com/cilium/ebpf/internal/linux"
19 20
 	"github.com/cilium/ebpf/internal/sys"
20 21
 	"github.com/cilium/ebpf/internal/sysenc"
21 22
 	"github.com/cilium/ebpf/internal/unix"
... ...
@@ -46,14 +47,15 @@ const (
46 46
 	outputPad = 256 + 2
47 47
 )
48 48
 
49
-// Deprecated: the correct log size is now detected automatically and this
50
-// constant is unused.
51
-const DefaultVerifierLogSize = 64 * 1024
52
-
53 49
 // minVerifierLogSize is the default number of bytes allocated for the
54 50
 // verifier log.
55 51
 const minVerifierLogSize = 64 * 1024
56 52
 
53
+// maxVerifierLogSize is the maximum size of verifier log buffer the kernel
54
+// will accept before returning EINVAL. May be increased to MaxUint32 in the
55
+// future, but avoid the unnecessary EINVAL for now.
56
+const maxVerifierLogSize = math.MaxUint32 >> 2
57
+
57 58
 // ProgramOptions control loading a program into the kernel.
58 59
 type ProgramOptions struct {
59 60
 	// Bitmap controlling the detail emitted by the kernel's eBPF verifier log.
... ...
@@ -73,9 +75,10 @@ type ProgramOptions struct {
73 73
 	// attempt at loading the program.
74 74
 	LogLevel LogLevel
75 75
 
76
-	// Deprecated: the correct log buffer size is determined automatically
77
-	// and this field is ignored.
78
-	LogSize int
76
+	// Starting size of the verifier log buffer. If the verifier log is larger
77
+	// than this size, the buffer will be grown to fit the entire log. Leave at
78
+	// its default value unless troubleshooting.
79
+	LogSizeStart uint32
79 80
 
80 81
 	// Disables the verifier log completely, regardless of other options.
81 82
 	LogDisabled bool
... ...
@@ -162,26 +165,35 @@ func (ps *ProgramSpec) Tag() (string, error) {
162 162
 	return ps.Instructions.Tag(internal.NativeEndian)
163 163
 }
164 164
 
165
-// KernelModule returns the kernel module, if any, the AttachTo function is contained in.
166
-func (ps *ProgramSpec) KernelModule() (string, error) {
165
+// kernelModule returns the kernel module providing the symbol in
166
+// ProgramSpec.AttachTo, if any. Returns an empty string if the symbol is not
167
+// present or not part of a kernel module.
168
+func (ps *ProgramSpec) kernelModule() (string, error) {
169
+	if ps.targetsKernelModule() {
170
+		return kallsyms.Module(ps.AttachTo)
171
+	}
172
+
173
+	return "", nil
174
+}
175
+
176
+// targetsKernelModule returns true if the program supports being attached to a
177
+// symbol provided by a kernel module.
178
+func (ps *ProgramSpec) targetsKernelModule() bool {
167 179
 	if ps.AttachTo == "" {
168
-		return "", nil
180
+		return false
169 181
 	}
170 182
 
171 183
 	switch ps.Type {
172
-	default:
173
-		return "", nil
174 184
 	case Tracing:
175 185
 		switch ps.AttachType {
176
-		default:
177
-			return "", nil
178
-		case AttachTraceFEntry:
179
-		case AttachTraceFExit:
186
+		case AttachTraceFEntry, AttachTraceFExit:
187
+			return true
180 188
 		}
181
-		fallthrough
182 189
 	case Kprobe:
183
-		return kallsyms.KernelModule(ps.AttachTo)
190
+		return true
184 191
 	}
192
+
193
+	return false
185 194
 }
186 195
 
187 196
 // VerifierError is returned by [NewProgram] and [NewProgramWithOptions] if a
... ...
@@ -261,7 +273,7 @@ func newProgramWithOptions(spec *ProgramSpec, opts ProgramOptions) (*Program, er
261 261
 	// Overwrite Kprobe program version if set to zero or the magic version constant.
262 262
 	kv := spec.KernelVersion
263 263
 	if spec.Type == Kprobe && (kv == 0 || kv == internal.MagicKernelVersion) {
264
-		v, err := internal.KernelVersion()
264
+		v, err := linux.KernelVersion()
265 265
 		if err != nil {
266 266
 			return nil, fmt.Errorf("detecting kernel version: %w", err)
267 267
 		}
... ...
@@ -283,7 +295,7 @@ func newProgramWithOptions(spec *ProgramSpec, opts ProgramOptions) (*Program, er
283 283
 	insns := make(asm.Instructions, len(spec.Instructions))
284 284
 	copy(insns, spec.Instructions)
285 285
 
286
-	kmodName, err := spec.KernelModule()
286
+	kmodName, err := spec.kernelModule()
287 287
 	if err != nil {
288 288
 		return nil, fmt.Errorf("kernel module search: %w", err)
289 289
 	}
... ...
@@ -344,6 +356,10 @@ func newProgramWithOptions(spec *ProgramSpec, opts ProgramOptions) (*Program, er
344 344
 	}
345 345
 	defer kconfig.Close()
346 346
 
347
+	if err := resolveKsymReferences(insns); err != nil {
348
+		return nil, fmt.Errorf("resolve .ksyms: %w", err)
349
+	}
350
+
347 351
 	if err := fixupAndValidate(insns); err != nil {
348 352
 		return nil, err
349 353
 	}
... ...
@@ -395,9 +411,10 @@ func newProgramWithOptions(spec *ProgramSpec, opts ProgramOptions) (*Program, er
395 395
 
396 396
 	// The caller requested a specific verifier log level. Set up the log buffer
397 397
 	// so that there is a chance of loading the program in a single shot.
398
+	logSize := internal.Between(opts.LogSizeStart, minVerifierLogSize, maxVerifierLogSize)
398 399
 	var logBuf []byte
399 400
 	if !opts.LogDisabled && opts.LogLevel != 0 {
400
-		logBuf = make([]byte, minVerifierLogSize)
401
+		logBuf = make([]byte, logSize)
401 402
 		attr.LogLevel = opts.LogLevel
402 403
 		attr.LogSize = uint32(len(logBuf))
403 404
 		attr.LogBuf = sys.NewSlicePointer(logBuf)
... ...
@@ -431,12 +448,11 @@ func newProgramWithOptions(spec *ProgramSpec, opts ProgramOptions) (*Program, er
431 431
 			attr.LogLevel = LogLevelBranch
432 432
 		}
433 433
 
434
-		// Make an educated guess how large the buffer should be. Start
435
-		// at minVerifierLogSize and then double the size.
436
-		logSize := uint32(max(len(logBuf)*2, minVerifierLogSize))
437
-		if int(logSize) < len(logBuf) {
438
-			return nil, errors.New("overflow while probing log buffer size")
439
-		}
434
+		// Make an educated guess how large the buffer should be by multiplying.
435
+		// Ensure the size doesn't overflow.
436
+		const factor = 2
437
+		logSize = internal.Between(logSize, minVerifierLogSize, maxVerifierLogSize/factor)
438
+		logSize *= factor
440 439
 
441 440
 		if attr.LogTrueSize != 0 {
442 441
 			// The kernel has given us a hint how large the log buffer has to be.
... ...
@@ -462,6 +478,12 @@ func newProgramWithOptions(spec *ProgramSpec, opts ProgramOptions) (*Program, er
462 462
 			return nil, fmt.Errorf("load program: %w (MEMLOCK may be too low, consider rlimit.RemoveMemlock)", err)
463 463
 		}
464 464
 
465
+	case errors.Is(err, unix.EFAULT):
466
+		// EFAULT is returned when the kernel hits a verifier bug, and always
467
+		// overrides ENOSPC, defeating the buffer growth strategy. Warn the user
468
+		// that they may need to increase the buffer size manually.
469
+		return nil, fmt.Errorf("load program: %w (hit verifier bug, increase LogSizeStart to fit the log and check dmesg)", err)
470
+
465 471
 	case errors.Is(err, unix.EINVAL):
466 472
 		if bytes.Contains(tail, coreBadCall) {
467 473
 			err = errBadRelocation
... ...
@@ -598,7 +620,7 @@ func (p *Program) Clone() (*Program, error) {
598 598
 // This requires bpffs to be mounted above fileName.
599 599
 // See https://docs.cilium.io/en/stable/network/kubernetes/configuration/#mounting-bpffs-with-systemd
600 600
 func (p *Program) Pin(fileName string) error {
601
-	if err := internal.Pin(p.pinnedPath, fileName, p.fd); err != nil {
601
+	if err := sys.Pin(p.pinnedPath, fileName, p.fd); err != nil {
602 602
 		return err
603 603
 	}
604 604
 	p.pinnedPath = fileName
... ...
@@ -611,7 +633,7 @@ func (p *Program) Pin(fileName string) error {
611 611
 //
612 612
 // Unpinning an unpinned Program returns nil.
613 613
 func (p *Program) Unpin() error {
614
-	if err := internal.Unpin(p.pinnedPath); err != nil {
614
+	if err := sys.Unpin(p.pinnedPath); err != nil {
615 615
 		return err
616 616
 	}
617 617
 	p.pinnedPath = ""
... ...
@@ -699,6 +721,10 @@ func (p *Program) Test(in []byte) (uint32, []byte, error) {
699 699
 //
700 700
 // Note: the same restrictions from Test apply.
701 701
 func (p *Program) Run(opts *RunOptions) (uint32, error) {
702
+	if opts == nil {
703
+		opts = &RunOptions{}
704
+	}
705
+
702 706
 	ret, _, err := p.run(opts)
703 707
 	if err != nil {
704 708
 		return ret, fmt.Errorf("run program: %w", err)
... ...
@@ -732,7 +758,7 @@ func (p *Program) Benchmark(in []byte, repeat int, reset func()) (uint32, time.D
732 732
 	return ret, total, nil
733 733
 }
734 734
 
735
-var haveProgRun = internal.NewFeatureTest("BPF_PROG_RUN", "4.12", func() error {
735
+var haveProgRun = internal.NewFeatureTest("BPF_PROG_RUN", func() error {
736 736
 	prog, err := NewProgram(&ProgramSpec{
737 737
 		// SocketFilter does not require privileges on newer kernels.
738 738
 		Type: SocketFilter,
... ...
@@ -774,7 +800,7 @@ var haveProgRun = internal.NewFeatureTest("BPF_PROG_RUN", "4.12", func() error {
774 774
 	}
775 775
 
776 776
 	return err
777
-})
777
+}, "4.12")
778 778
 
779 779
 func (p *Program) run(opts *RunOptions) (uint32, time.Duration, error) {
780 780
 	if uint(len(opts.Data)) > math.MaxUint32 {
... ...
@@ -883,6 +909,10 @@ func unmarshalProgram(buf sysenc.Buffer) (*Program, error) {
883 883
 }
884 884
 
885 885
 func marshalProgram(p *Program, length int) ([]byte, error) {
886
+	if p == nil {
887
+		return nil, errors.New("can't marshal a nil Program")
888
+	}
889
+
886 890
 	if length != 4 {
887 891
 		return nil, fmt.Errorf("can't marshal program to %d bytes", length)
888 892
 	}
... ...
@@ -892,11 +922,12 @@ func marshalProgram(p *Program, length int) ([]byte, error) {
892 892
 	return buf, nil
893 893
 }
894 894
 
895
-// LoadPinnedProgram loads a Program from a BPF file.
895
+// LoadPinnedProgram loads a Program from a pin (file) on the BPF virtual
896
+// filesystem.
896 897
 //
897 898
 // Requires at least Linux 4.11.
898 899
 func LoadPinnedProgram(fileName string, opts *LoadPinOptions) (*Program, error) {
899
-	fd, err := sys.ObjGet(&sys.ObjGetAttr{
900
+	fd, typ, err := sys.ObjGetTyped(&sys.ObjGetAttr{
900 901
 		Pathname:  sys.NewStringPointer(fileName),
901 902
 		FileFlags: opts.Marshal(),
902 903
 	})
... ...
@@ -904,6 +935,11 @@ func LoadPinnedProgram(fileName string, opts *LoadPinOptions) (*Program, error)
904 904
 		return nil, err
905 905
 	}
906 906
 
907
+	if typ != sys.BPF_TYPE_PROG {
908
+		_ = fd.Close()
909
+		return nil, fmt.Errorf("%s is not a Program", fileName)
910
+	}
911
+
907 912
 	info, err := newProgramInfoFromFd(fd)
908 913
 	if err != nil {
909 914
 		_ = fd.Close()
... ...
@@ -10,6 +10,7 @@ import (
10 10
 
11 11
 	"github.com/cilium/ebpf/asm"
12 12
 	"github.com/cilium/ebpf/internal"
13
+	"github.com/cilium/ebpf/internal/linux"
13 14
 	"github.com/cilium/ebpf/internal/sys"
14 15
 	"github.com/cilium/ebpf/internal/tracefs"
15 16
 	"github.com/cilium/ebpf/internal/unix"
... ...
@@ -60,7 +61,7 @@ func progLoad(insns asm.Instructions, typ ProgramType, license string) (*sys.FD,
60 60
 	})
61 61
 }
62 62
 
63
-var haveNestedMaps = internal.NewFeatureTest("nested maps", "4.12", func() error {
63
+var haveNestedMaps = internal.NewFeatureTest("nested maps", func() error {
64 64
 	_, err := sys.MapCreate(&sys.MapCreateAttr{
65 65
 		MapType:    sys.MapType(ArrayOfMaps),
66 66
 		KeySize:    4,
... ...
@@ -76,9 +77,9 @@ var haveNestedMaps = internal.NewFeatureTest("nested maps", "4.12", func() error
76 76
 		return nil
77 77
 	}
78 78
 	return err
79
-})
79
+}, "4.12")
80 80
 
81
-var haveMapMutabilityModifiers = internal.NewFeatureTest("read- and write-only maps", "5.2", func() error {
81
+var haveMapMutabilityModifiers = internal.NewFeatureTest("read- and write-only maps", func() error {
82 82
 	// This checks BPF_F_RDONLY_PROG and BPF_F_WRONLY_PROG. Since
83 83
 	// BPF_MAP_FREEZE appeared in 5.2 as well we don't do a separate check.
84 84
 	m, err := sys.MapCreate(&sys.MapCreateAttr{
... ...
@@ -86,39 +87,39 @@ var haveMapMutabilityModifiers = internal.NewFeatureTest("read- and write-only m
86 86
 		KeySize:    4,
87 87
 		ValueSize:  4,
88 88
 		MaxEntries: 1,
89
-		MapFlags:   unix.BPF_F_RDONLY_PROG,
89
+		MapFlags:   sys.BPF_F_RDONLY_PROG,
90 90
 	})
91 91
 	if err != nil {
92 92
 		return internal.ErrNotSupported
93 93
 	}
94 94
 	_ = m.Close()
95 95
 	return nil
96
-})
96
+}, "5.2")
97 97
 
98
-var haveMmapableMaps = internal.NewFeatureTest("mmapable maps", "5.5", func() error {
98
+var haveMmapableMaps = internal.NewFeatureTest("mmapable maps", func() error {
99 99
 	// This checks BPF_F_MMAPABLE, which appeared in 5.5 for array maps.
100 100
 	m, err := sys.MapCreate(&sys.MapCreateAttr{
101 101
 		MapType:    sys.MapType(Array),
102 102
 		KeySize:    4,
103 103
 		ValueSize:  4,
104 104
 		MaxEntries: 1,
105
-		MapFlags:   unix.BPF_F_MMAPABLE,
105
+		MapFlags:   sys.BPF_F_MMAPABLE,
106 106
 	})
107 107
 	if err != nil {
108 108
 		return internal.ErrNotSupported
109 109
 	}
110 110
 	_ = m.Close()
111 111
 	return nil
112
-})
112
+}, "5.5")
113 113
 
114
-var haveInnerMaps = internal.NewFeatureTest("inner maps", "5.10", func() error {
114
+var haveInnerMaps = internal.NewFeatureTest("inner maps", func() error {
115 115
 	// This checks BPF_F_INNER_MAP, which appeared in 5.10.
116 116
 	m, err := sys.MapCreate(&sys.MapCreateAttr{
117 117
 		MapType:    sys.MapType(Array),
118 118
 		KeySize:    4,
119 119
 		ValueSize:  4,
120 120
 		MaxEntries: 1,
121
-		MapFlags:   unix.BPF_F_INNER_MAP,
121
+		MapFlags:   sys.BPF_F_INNER_MAP,
122 122
 	})
123 123
 
124 124
 	if err != nil {
... ...
@@ -126,16 +127,16 @@ var haveInnerMaps = internal.NewFeatureTest("inner maps", "5.10", func() error {
126 126
 	}
127 127
 	_ = m.Close()
128 128
 	return nil
129
-})
129
+}, "5.10")
130 130
 
131
-var haveNoPreallocMaps = internal.NewFeatureTest("prealloc maps", "4.6", func() error {
131
+var haveNoPreallocMaps = internal.NewFeatureTest("prealloc maps", func() error {
132 132
 	// This checks BPF_F_NO_PREALLOC, which appeared in 4.6.
133 133
 	m, err := sys.MapCreate(&sys.MapCreateAttr{
134 134
 		MapType:    sys.MapType(Hash),
135 135
 		KeySize:    4,
136 136
 		ValueSize:  4,
137 137
 		MaxEntries: 1,
138
-		MapFlags:   unix.BPF_F_NO_PREALLOC,
138
+		MapFlags:   sys.BPF_F_NO_PREALLOC,
139 139
 	})
140 140
 
141 141
 	if err != nil {
... ...
@@ -143,7 +144,7 @@ var haveNoPreallocMaps = internal.NewFeatureTest("prealloc maps", "4.6", func()
143 143
 	}
144 144
 	_ = m.Close()
145 145
 	return nil
146
-})
146
+}, "4.6")
147 147
 
148 148
 func wrapMapError(err error) error {
149 149
 	if err == nil {
... ...
@@ -169,7 +170,7 @@ func wrapMapError(err error) error {
169 169
 	return err
170 170
 }
171 171
 
172
-var haveObjName = internal.NewFeatureTest("object names", "4.15", func() error {
172
+var haveObjName = internal.NewFeatureTest("object names", func() error {
173 173
 	attr := sys.MapCreateAttr{
174 174
 		MapType:    sys.MapType(Array),
175 175
 		KeySize:    4,
... ...
@@ -178,16 +179,24 @@ var haveObjName = internal.NewFeatureTest("object names", "4.15", func() error {
178 178
 		MapName:    sys.NewObjName("feature_test"),
179 179
 	}
180 180
 
181
+	// Tolerate EPERM as this runs during ELF loading which is potentially
182
+	// unprivileged. Only EINVAL is conclusive, thrown from CHECK_ATTR.
181 183
 	fd, err := sys.MapCreate(&attr)
182
-	if err != nil {
184
+	if errors.Is(err, unix.EPERM) {
185
+		return nil
186
+	}
187
+	if errors.Is(err, unix.EINVAL) {
183 188
 		return internal.ErrNotSupported
184 189
 	}
190
+	if err != nil {
191
+		return err
192
+	}
185 193
 
186 194
 	_ = fd.Close()
187 195
 	return nil
188
-})
196
+}, "4.15")
189 197
 
190
-var objNameAllowsDot = internal.NewFeatureTest("dot in object names", "5.2", func() error {
198
+var objNameAllowsDot = internal.NewFeatureTest("dot in object names", func() error {
191 199
 	if err := haveObjName(); err != nil {
192 200
 		return err
193 201
 	}
... ...
@@ -200,16 +209,25 @@ var objNameAllowsDot = internal.NewFeatureTest("dot in object names", "5.2", fun
200 200
 		MapName:    sys.NewObjName(".test"),
201 201
 	}
202 202
 
203
+	// Tolerate EPERM, otherwise MapSpec.Name has its dots removed when run by
204
+	// unprivileged tools. (bpf2go, other code gen). Only EINVAL is conclusive,
205
+	// thrown from bpf_obj_name_cpy().
203 206
 	fd, err := sys.MapCreate(&attr)
204
-	if err != nil {
207
+	if errors.Is(err, unix.EPERM) {
208
+		return nil
209
+	}
210
+	if errors.Is(err, unix.EINVAL) {
205 211
 		return internal.ErrNotSupported
206 212
 	}
213
+	if err != nil {
214
+		return err
215
+	}
207 216
 
208 217
 	_ = fd.Close()
209 218
 	return nil
210
-})
219
+}, "5.2")
211 220
 
212
-var haveBatchAPI = internal.NewFeatureTest("map batch api", "5.6", func() error {
221
+var haveBatchAPI = internal.NewFeatureTest("map batch api", func() error {
213 222
 	var maxEntries uint32 = 2
214 223
 	attr := sys.MapCreateAttr{
215 224
 		MapType:    sys.MapType(Hash),
... ...
@@ -239,9 +257,9 @@ var haveBatchAPI = internal.NewFeatureTest("map batch api", "5.6", func() error
239 239
 		return internal.ErrNotSupported
240 240
 	}
241 241
 	return nil
242
-})
242
+}, "5.6")
243 243
 
244
-var haveProbeReadKernel = internal.NewFeatureTest("bpf_probe_read_kernel", "5.5", func() error {
244
+var haveProbeReadKernel = internal.NewFeatureTest("bpf_probe_read_kernel", func() error {
245 245
 	insns := asm.Instructions{
246 246
 		asm.Mov.Reg(asm.R1, asm.R10),
247 247
 		asm.Add.Imm(asm.R1, -8),
... ...
@@ -257,9 +275,9 @@ var haveProbeReadKernel = internal.NewFeatureTest("bpf_probe_read_kernel", "5.5"
257 257
 	}
258 258
 	_ = fd.Close()
259 259
 	return nil
260
-})
260
+}, "5.5")
261 261
 
262
-var haveBPFToBPFCalls = internal.NewFeatureTest("bpf2bpf calls", "4.16", func() error {
262
+var haveBPFToBPFCalls = internal.NewFeatureTest("bpf2bpf calls", func() error {
263 263
 	insns := asm.Instructions{
264 264
 		asm.Call.Label("prog2").WithSymbol("prog1"),
265 265
 		asm.Return(),
... ...
@@ -273,10 +291,10 @@ var haveBPFToBPFCalls = internal.NewFeatureTest("bpf2bpf calls", "4.16", func()
273 273
 	}
274 274
 	_ = fd.Close()
275 275
 	return nil
276
-})
276
+}, "4.16")
277 277
 
278
-var haveSyscallWrapper = internal.NewFeatureTest("syscall wrapper", "4.17", func() error {
279
-	prefix := internal.PlatformPrefix()
278
+var haveSyscallWrapper = internal.NewFeatureTest("syscall wrapper", func() error {
279
+	prefix := linux.PlatformPrefix()
280 280
 	if prefix == "" {
281 281
 		return fmt.Errorf("unable to find the platform prefix for (%s)", runtime.GOARCH)
282 282
 	}
... ...
@@ -302,9 +320,9 @@ var haveSyscallWrapper = internal.NewFeatureTest("syscall wrapper", "4.17", func
302 302
 	}
303 303
 
304 304
 	return evt.Close()
305
-})
305
+}, "4.17")
306 306
 
307
-var haveProgramExtInfos = internal.NewFeatureTest("program ext_infos", "5.0", func() error {
307
+var haveProgramExtInfos = internal.NewFeatureTest("program ext_infos", func() error {
308 308
 	insns := asm.Instructions{
309 309
 		asm.Mov.Imm(asm.R0, 0),
310 310
 		asm.Return(),
... ...
@@ -334,4 +352,4 @@ var haveProgramExtInfos = internal.NewFeatureTest("program ext_infos", "5.0", fu
334 334
 	}
335 335
 
336 336
 	return err
337
-})
337
+}, "5.0")
... ...
@@ -2,7 +2,6 @@ package ebpf
2 2
 
3 3
 import (
4 4
 	"github.com/cilium/ebpf/internal/sys"
5
-	"github.com/cilium/ebpf/internal/unix"
6 5
 )
7 6
 
8 7
 //go:generate go run golang.org/x/tools/cmd/stringer@latest -output types_string.go -type=MapType,ProgramType,PinType
... ...
@@ -95,6 +94,14 @@ const (
95 95
 	InodeStorage
96 96
 	// TaskStorage - Specialized local storage map for task_struct.
97 97
 	TaskStorage
98
+	// BloomFilter - Space-efficient data structure to quickly test whether an element exists in a set.
99
+	BloomFilter
100
+	// UserRingbuf - The reverse of RingBuf, used to send messages from user space to BPF programs.
101
+	UserRingbuf
102
+	// CgroupStorage - Store data keyed on a cgroup. If the cgroup disappears, the key is automatically removed.
103
+	CgroupStorage
104
+	// Arena - Sparse shared memory region between a BPF program and user space.
105
+	Arena
98 106
 )
99 107
 
100 108
 // hasPerCPUValue returns true if the Map stores a value per CPU.
... ...
@@ -120,6 +127,21 @@ func (mt MapType) canStoreProgram() bool {
120 120
 	return mt == ProgramArray
121 121
 }
122 122
 
123
+// canHaveValueSize returns true if the map type supports setting a value size.
124
+func (mt MapType) canHaveValueSize() bool {
125
+	switch mt {
126
+	case RingBuf, Arena:
127
+		return false
128
+
129
+	// Special-case perf events since they require a value size of either 0 or 4
130
+	// for historical reasons. Let the library fix this up later.
131
+	case PerfEventArray:
132
+		return false
133
+	}
134
+
135
+	return true
136
+}
137
+
123 138
 // ProgramType of the eBPF program
124 139
 type ProgramType uint32
125 140
 
... ...
@@ -214,6 +236,7 @@ const (
214 214
 	AttachSkReuseportSelectOrMigrate = AttachType(sys.BPF_SK_REUSEPORT_SELECT_OR_MIGRATE)
215 215
 	AttachPerfEvent                  = AttachType(sys.BPF_PERF_EVENT)
216 216
 	AttachTraceKprobeMulti           = AttachType(sys.BPF_TRACE_KPROBE_MULTI)
217
+	AttachTraceKprobeSession         = AttachType(sys.BPF_TRACE_KPROBE_SESSION)
217 218
 	AttachLSMCgroup                  = AttachType(sys.BPF_LSM_CGROUP)
218 219
 	AttachStructOps                  = AttachType(sys.BPF_STRUCT_OPS)
219 220
 	AttachNetfilter                  = AttachType(sys.BPF_NETFILTER)
... ...
@@ -263,10 +286,10 @@ func (lpo *LoadPinOptions) Marshal() uint32 {
263 263
 
264 264
 	flags := lpo.Flags
265 265
 	if lpo.ReadOnly {
266
-		flags |= unix.BPF_F_RDONLY
266
+		flags |= sys.BPF_F_RDONLY
267 267
 	}
268 268
 	if lpo.WriteOnly {
269
-		flags |= unix.BPF_F_WRONLY
269
+		flags |= sys.BPF_F_WRONLY
270 270
 	}
271 271
 	return flags
272 272
 }
... ...
@@ -38,11 +38,15 @@ func _() {
38 38
 	_ = x[RingBuf-27]
39 39
 	_ = x[InodeStorage-28]
40 40
 	_ = x[TaskStorage-29]
41
+	_ = x[BloomFilter-30]
42
+	_ = x[UserRingbuf-31]
43
+	_ = x[CgroupStorage-32]
44
+	_ = x[Arena-33]
41 45
 }
42 46
 
43
-const _MapType_name = "UnspecifiedMapHashArrayProgramArrayPerfEventArrayPerCPUHashPerCPUArrayStackTraceCGroupArrayLRUHashLRUCPUHashLPMTrieArrayOfMapsHashOfMapsDevMapSockMapCPUMapXSKMapSockHashCGroupStorageReusePortSockArrayPerCPUCGroupStorageQueueStackSkStorageDevMapHashStructOpsMapRingBufInodeStorageTaskStorage"
47
+const _MapType_name = "UnspecifiedMapHashArrayProgramArrayPerfEventArrayPerCPUHashPerCPUArrayStackTraceCGroupArrayLRUHashLRUCPUHashLPMTrieArrayOfMapsHashOfMapsDevMapSockMapCPUMapXSKMapSockHashCGroupStorageReusePortSockArrayPerCPUCGroupStorageQueueStackSkStorageDevMapHashStructOpsMapRingBufInodeStorageTaskStorageBloomFilterUserRingbufCgroupStorageArena"
44 48
 
45
-var _MapType_index = [...]uint16{0, 14, 18, 23, 35, 49, 59, 70, 80, 91, 98, 108, 115, 126, 136, 142, 149, 155, 161, 169, 182, 200, 219, 224, 229, 238, 248, 260, 267, 279, 290}
49
+var _MapType_index = [...]uint16{0, 14, 18, 23, 35, 49, 59, 70, 80, 91, 98, 108, 115, 126, 136, 142, 149, 155, 161, 169, 182, 200, 219, 224, 229, 238, 248, 260, 267, 279, 290, 301, 312, 325, 330}
46 50
 
47 51
 func (i MapType) String() string {
48 52
 	if i >= MapType(len(_MapType_index)-1) {
49 53
new file mode 100644
... ...
@@ -0,0 +1,230 @@
0
+package ebpf
1
+
2
+import (
3
+	"fmt"
4
+	"io"
5
+
6
+	"github.com/cilium/ebpf/btf"
7
+	"github.com/cilium/ebpf/internal/sysenc"
8
+)
9
+
10
+// VariableSpec is a convenience wrapper for modifying global variables of a
11
+// CollectionSpec before loading it into the kernel.
12
+//
13
+// All operations on a VariableSpec's underlying MapSpec are performed in the
14
+// host's native endianness.
15
+type VariableSpec struct {
16
+	name   string
17
+	offset uint64
18
+	size   uint64
19
+
20
+	m *MapSpec
21
+	t *btf.Var
22
+}
23
+
24
+// Set sets the value of the VariableSpec to the provided input using the host's
25
+// native endianness.
26
+func (s *VariableSpec) Set(in any) error {
27
+	buf, err := sysenc.Marshal(in, int(s.size))
28
+	if err != nil {
29
+		return fmt.Errorf("marshaling value %s: %w", s.name, err)
30
+	}
31
+
32
+	b, _, err := s.m.dataSection()
33
+	if err != nil {
34
+		return fmt.Errorf("getting data section of map %s: %w", s.m.Name, err)
35
+	}
36
+
37
+	if int(s.offset+s.size) > len(b) {
38
+		return fmt.Errorf("offset %d(+%d) for variable %s is out of bounds", s.offset, s.size, s.name)
39
+	}
40
+
41
+	// MapSpec.Copy() performs a shallow copy. Fully copy the byte slice
42
+	// to avoid any changes affecting other copies of the MapSpec.
43
+	cpy := make([]byte, len(b))
44
+	copy(cpy, b)
45
+
46
+	buf.CopyTo(cpy[s.offset : s.offset+s.size])
47
+
48
+	s.m.Contents[0] = MapKV{Key: uint32(0), Value: cpy}
49
+
50
+	return nil
51
+}
52
+
53
+// Get writes the value of the VariableSpec to the provided output using the
54
+// host's native endianness.
55
+func (s *VariableSpec) Get(out any) error {
56
+	b, _, err := s.m.dataSection()
57
+	if err != nil {
58
+		return fmt.Errorf("getting data section of map %s: %w", s.m.Name, err)
59
+	}
60
+
61
+	if int(s.offset+s.size) > len(b) {
62
+		return fmt.Errorf("offset %d(+%d) for variable %s is out of bounds", s.offset, s.size, s.name)
63
+	}
64
+
65
+	if err := sysenc.Unmarshal(out, b[s.offset:s.offset+s.size]); err != nil {
66
+		return fmt.Errorf("unmarshaling value: %w", err)
67
+	}
68
+
69
+	return nil
70
+}
71
+
72
+// Size returns the size of the variable in bytes.
73
+func (s *VariableSpec) Size() uint64 {
74
+	return s.size
75
+}
76
+
77
+// MapName returns the name of the underlying MapSpec.
78
+func (s *VariableSpec) MapName() string {
79
+	return s.m.Name
80
+}
81
+
82
+// Offset returns the offset of the variable in the underlying MapSpec.
83
+func (s *VariableSpec) Offset() uint64 {
84
+	return s.offset
85
+}
86
+
87
+// Constant returns true if the VariableSpec represents a variable that is
88
+// read-only from the perspective of the BPF program.
89
+func (s *VariableSpec) Constant() bool {
90
+	return s.m.readOnly()
91
+}
92
+
93
+// Type returns the [btf.Var] representing the variable in its data section.
94
+// This is useful for inspecting the variable's decl tags and the type
95
+// information of the inner type.
96
+//
97
+// Returns nil if the original ELF object did not contain BTF information.
98
+func (s *VariableSpec) Type() *btf.Var {
99
+	return s.t
100
+}
101
+
102
+func (s *VariableSpec) String() string {
103
+	return fmt.Sprintf("%s (type=%v, map=%s, offset=%d, size=%d)", s.name, s.t, s.m.Name, s.offset, s.size)
104
+}
105
+
106
+// copy returns a new VariableSpec with the same values as the original,
107
+// but with a different underlying MapSpec. This is useful when copying a
108
+// CollectionSpec. Returns nil if a MapSpec with the same name is not found.
109
+func (s *VariableSpec) copy(cpy *CollectionSpec) *VariableSpec {
110
+	out := &VariableSpec{
111
+		name:   s.name,
112
+		offset: s.offset,
113
+		size:   s.size,
114
+	}
115
+	if s.t != nil {
116
+		out.t = btf.Copy(s.t).(*btf.Var)
117
+	}
118
+
119
+	// Attempt to find a MapSpec with the same name in the copied CollectionSpec.
120
+	for _, m := range cpy.Maps {
121
+		if m.Name == s.m.Name {
122
+			out.m = m
123
+			return out
124
+		}
125
+	}
126
+
127
+	return nil
128
+}
129
+
130
+// Variable is a convenience wrapper for modifying global variables of a
131
+// Collection after loading it into the kernel. Operations on a Variable are
132
+// performed using direct memory access, bypassing the BPF map syscall API.
133
+//
134
+// On kernels older than 5.5, most interactions with Variable return
135
+// [ErrNotSupported].
136
+type Variable struct {
137
+	name   string
138
+	offset uint64
139
+	size   uint64
140
+	t      *btf.Var
141
+
142
+	mm *Memory
143
+}
144
+
145
+func newVariable(name string, offset, size uint64, t *btf.Var, mm *Memory) (*Variable, error) {
146
+	if mm != nil {
147
+		if int(offset+size) > mm.Size() {
148
+			return nil, fmt.Errorf("offset %d(+%d) is out of bounds", offset, size)
149
+		}
150
+	}
151
+
152
+	return &Variable{
153
+		name:   name,
154
+		offset: offset,
155
+		size:   size,
156
+		t:      t,
157
+		mm:     mm,
158
+	}, nil
159
+}
160
+
161
+// Size returns the size of the variable.
162
+func (v *Variable) Size() uint64 {
163
+	return v.size
164
+}
165
+
166
+// ReadOnly returns true if the Variable represents a variable that is read-only
167
+// after loading the Collection into the kernel.
168
+//
169
+// On systems without BPF_F_MMAPABLE support, ReadOnly always returns true.
170
+func (v *Variable) ReadOnly() bool {
171
+	if v.mm == nil {
172
+		return true
173
+	}
174
+	return v.mm.ReadOnly()
175
+}
176
+
177
+// Type returns the [btf.Var] representing the variable in its data section.
178
+// This is useful for inspecting the variable's decl tags and the type
179
+// information of the inner type.
180
+//
181
+// Returns nil if the original ELF object did not contain BTF information.
182
+func (v *Variable) Type() *btf.Var {
183
+	return v.t
184
+}
185
+
186
+func (v *Variable) String() string {
187
+	return fmt.Sprintf("%s (type=%v)", v.name, v.t)
188
+}
189
+
190
+// Set the value of the Variable to the provided input. The input must marshal
191
+// to the same length as the size of the Variable.
192
+func (v *Variable) Set(in any) error {
193
+	if v.mm == nil {
194
+		return fmt.Errorf("variable %s: direct access requires Linux 5.5 or later: %w", v.name, ErrNotSupported)
195
+	}
196
+
197
+	if v.ReadOnly() {
198
+		return fmt.Errorf("variable %s: %w", v.name, ErrReadOnly)
199
+	}
200
+
201
+	buf, err := sysenc.Marshal(in, int(v.size))
202
+	if err != nil {
203
+		return fmt.Errorf("marshaling value %s: %w", v.name, err)
204
+	}
205
+
206
+	if _, err := v.mm.WriteAt(buf.Bytes(), int64(v.offset)); err != nil {
207
+		return fmt.Errorf("writing value to %s: %w", v.name, err)
208
+	}
209
+
210
+	return nil
211
+}
212
+
213
+// Get writes the value of the Variable to the provided output. The output must
214
+// be a pointer to a value whose size matches the Variable.
215
+func (v *Variable) Get(out any) error {
216
+	if v.mm == nil {
217
+		return fmt.Errorf("variable %s: direct access requires Linux 5.5 or later: %w", v.name, ErrNotSupported)
218
+	}
219
+
220
+	if !v.mm.bounds(v.offset, v.size) {
221
+		return fmt.Errorf("variable %s: access out of bounds: %w", v.name, io.EOF)
222
+	}
223
+
224
+	if err := sysenc.Unmarshal(out, v.mm.b[v.offset:v.offset+v.size]); err != nil {
225
+		return fmt.Errorf("unmarshaling value %s: %w", v.name, err)
226
+	}
227
+
228
+	return nil
229
+}
0 230
deleted file mode 100644
... ...
@@ -1,27 +0,0 @@
1
-Copyright 2009 The Go Authors.
2
-
3
-Redistribution and use in source and binary forms, with or without
4
-modification, are permitted provided that the following conditions are
5
-met:
6
-
7
-   * Redistributions of source code must retain the above copyright
8
-notice, this list of conditions and the following disclaimer.
9
-   * Redistributions in binary form must reproduce the above
10
-copyright notice, this list of conditions and the following disclaimer
11
-in the documentation and/or other materials provided with the
12
-distribution.
13
-   * Neither the name of Google LLC nor the names of its
14
-contributors may be used to endorse or promote products derived from
15
-this software without specific prior written permission.
16
-
17
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18
-"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19
-LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20
-A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21
-OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22
-SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23
-LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24
-DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25
-THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26
-(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27
-OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 1
deleted file mode 100644
... ...
@@ -1,22 +0,0 @@
1
-Additional IP Rights Grant (Patents)
2
-
3
-"This implementation" means the copyrightable works distributed by
4
-Google as part of the Go project.
5
-
6
-Google hereby grants to You a perpetual, worldwide, non-exclusive,
7
-no-charge, royalty-free, irrevocable (except as stated in this section)
8
-patent license to make, have made, use, offer to sell, sell, import,
9
-transfer and otherwise run, modify and propagate the contents of this
10
-implementation of Go, where such license applies only to those patent
11
-claims, both currently owned or controlled by Google and acquired in
12
-the future, licensable by Google that are necessarily infringed by this
13
-implementation of Go.  This grant does not include claims that would be
14
-infringed only as a consequence of further modification of this
15
-implementation.  If you or your agent or exclusive licensee institute or
16
-order or agree to the institution of patent litigation against any
17
-entity (including a cross-claim or counterclaim in a lawsuit) alleging
18
-that this implementation of Go or any code incorporated within this
19
-implementation of Go constitutes direct or contributory patent
20
-infringement, or inducement of patent infringement, then any patent
21
-rights granted to you under this License for this implementation of Go
22
-shall terminate as of the date such litigation is filed.
23 1
deleted file mode 100644
... ...
@@ -1,50 +0,0 @@
1
-// Copyright 2021 The Go Authors. All rights reserved.
2
-// Use of this source code is governed by a BSD-style
3
-// license that can be found in the LICENSE file.
4
-
5
-// Package constraints defines a set of useful constraints to be used
6
-// with type parameters.
7
-package constraints
8
-
9
-// Signed is a constraint that permits any signed integer type.
10
-// If future releases of Go add new predeclared signed integer types,
11
-// this constraint will be modified to include them.
12
-type Signed interface {
13
-	~int | ~int8 | ~int16 | ~int32 | ~int64
14
-}
15
-
16
-// Unsigned is a constraint that permits any unsigned integer type.
17
-// If future releases of Go add new predeclared unsigned integer types,
18
-// this constraint will be modified to include them.
19
-type Unsigned interface {
20
-	~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr
21
-}
22
-
23
-// Integer is a constraint that permits any integer type.
24
-// If future releases of Go add new predeclared integer types,
25
-// this constraint will be modified to include them.
26
-type Integer interface {
27
-	Signed | Unsigned
28
-}
29
-
30
-// Float is a constraint that permits any floating-point type.
31
-// If future releases of Go add new predeclared floating-point types,
32
-// this constraint will be modified to include them.
33
-type Float interface {
34
-	~float32 | ~float64
35
-}
36
-
37
-// Complex is a constraint that permits any complex numeric type.
38
-// If future releases of Go add new predeclared complex numeric types,
39
-// this constraint will be modified to include them.
40
-type Complex interface {
41
-	~complex64 | ~complex128
42
-}
43
-
44
-// Ordered is a constraint that permits any ordered type: any type
45
-// that supports the operators < <= >= >.
46
-// If future releases of Go add new ordered types,
47
-// this constraint will be modified to include them.
48
-type Ordered interface {
49
-	Integer | Float | ~string
50
-}
... ...
@@ -258,16 +258,18 @@ github.com/cenkalti/backoff/v4
258 258
 # github.com/cespare/xxhash/v2 v2.3.0
259 259
 ## explicit; go 1.11
260 260
 github.com/cespare/xxhash/v2
261
-# github.com/cilium/ebpf v0.16.0
262
-## explicit; go 1.21
261
+# github.com/cilium/ebpf v0.17.3
262
+## explicit; go 1.22
263 263
 github.com/cilium/ebpf
264 264
 github.com/cilium/ebpf/asm
265 265
 github.com/cilium/ebpf/btf
266 266
 github.com/cilium/ebpf/internal
267 267
 github.com/cilium/ebpf/internal/kallsyms
268 268
 github.com/cilium/ebpf/internal/kconfig
269
+github.com/cilium/ebpf/internal/linux
269 270
 github.com/cilium/ebpf/internal/sys
270 271
 github.com/cilium/ebpf/internal/sysenc
272
+github.com/cilium/ebpf/internal/testutils/fdtrace
271 273
 github.com/cilium/ebpf/internal/tracefs
272 274
 github.com/cilium/ebpf/internal/unix
273 275
 github.com/cilium/ebpf/link
... ...
@@ -1403,9 +1405,6 @@ golang.org/x/crypto/pkcs12/internal/rc2
1403 1403
 golang.org/x/crypto/salsa20/salsa
1404 1404
 golang.org/x/crypto/ssh
1405 1405
 golang.org/x/crypto/ssh/internal/bcrypt_pbkdf
1406
-# golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f
1407
-## explicit; go 1.22.0
1408
-golang.org/x/exp/constraints
1409 1406
 # golang.org/x/mod v0.22.0
1410 1407
 ## explicit; go 1.22.0
1411 1408
 golang.org/x/mod/internal/lazyregexp