deps: bump cilium/ebpf to v0.17.3

It has a fix for runc issue 4594.

Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
This commit is contained in:
Kir Kolyshkin
2025-02-12 19:27:20 -08:00
parent 0f88286077
commit 79a4ac0553
82 changed files with 3229 additions and 1021 deletions

5
go.mod
View File

@@ -9,7 +9,7 @@ toolchain go1.22.4
require (
github.com/checkpoint-restore/go-criu/v6 v6.3.0
github.com/cilium/ebpf v0.16.0
github.com/cilium/ebpf v0.17.3
github.com/containerd/console v1.0.4
github.com/coreos/go-systemd/v22 v22.5.0
github.com/cyphar/filepath-securejoin v0.4.1
@@ -31,7 +31,7 @@ require (
google.golang.org/protobuf v1.36.5
)
// https://github.com/cilium/ebpf/pull/1660
// https://github.com/opencontainers/runc/issues/4594
exclude (
github.com/cilium/ebpf v0.17.0
github.com/cilium/ebpf v0.17.1
@@ -42,5 +42,4 @@ require (
github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/vishvananda/netns v0.0.4 // indirect
golang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2 // indirect
)

6
go.sum
View File

@@ -1,8 +1,8 @@
github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/checkpoint-restore/go-criu/v6 v6.3.0 h1:mIdrSO2cPNWQY1truPg6uHLXyKHk3Z5Odx4wjKOASzA=
github.com/checkpoint-restore/go-criu/v6 v6.3.0/go.mod h1:rrRTN/uSwY2X+BPRl/gkulo9gsKOSAeVp9/K2tv7xZI=
github.com/cilium/ebpf v0.16.0 h1:+BiEnHL6Z7lXnlGUsXQPPAE7+kenAd4ES8MQ5min0Ok=
github.com/cilium/ebpf v0.16.0/go.mod h1:L7u2Blt2jMM/vLAVgjxluxtBKlz3/GWjB0dMOEngfwE=
github.com/cilium/ebpf v0.17.3 h1:FnP4r16PWYSE4ux6zN+//jMcW4nMVRvuTLVTvCjyyjg=
github.com/cilium/ebpf v0.17.3/go.mod h1:G5EDHij8yiLzaqn0WjyfJHvRa+3aDlReIaLVRMvOyJk=
github.com/containerd/console v1.0.4 h1:F2g4+oChYvBTsASRTz8NP6iIAi97J3TtSAsLbIFn4ro=
github.com/containerd/console v1.0.4/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
@@ -81,8 +81,6 @@ github.com/vishvananda/netlink v1.3.0 h1:X7l42GfcV4S6E4vHTsw48qbrV+9PVojNfIhZcwQ
github.com/vishvananda/netlink v1.3.0/go.mod h1:i6NetklAujEcC6fK0JPjT8qSwWyO0HLn4UKG+hGqeJs=
github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8=
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
golang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2 h1:Jvc7gsqn21cJHCmAWx0LiimpP18LZmUxkT5Mp7EZ1mI=
golang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=

View File

@@ -11,3 +11,21 @@ linters:
- typecheck
- unused
- gofmt
- depguard
linters-settings:
goimports:
# A comma-separated list of prefixes, which, if set, checks import paths
# with the given prefixes are grouped after 3rd-party packages.
# Default: ""
local-prefixes: github.com/cilium/ebpf
depguard:
rules:
no-x-sys-unix:
files:
# Filenames are matched against absolute paths, include **/ at the start.
- '!**/internal/unix/*.go'
- '!**/examples/**/*.go'
- '!**/docs/**/*.go'
deny:
- pkg: golang.org/x/sys/unix
desc: use internal/unix instead

View File

@@ -9,3 +9,5 @@ ringbuf/ @florianl
btf/ @dylandreimerink
cmd/bpf2go/ @mejedi
docs/ @ti-mo

View File

@@ -39,16 +39,18 @@ TARGETS := \
testdata/subprog_reloc \
testdata/fwd_decl \
testdata/kconfig \
testdata/kconfig_config \
testdata/ksym \
testdata/kfunc \
testdata/invalid-kfunc \
testdata/kfunc-kmod \
testdata/constants \
testdata/errors \
testdata/variables \
btf/testdata/relocs \
btf/testdata/relocs_read \
btf/testdata/relocs_read_tgt \
btf/testdata/relocs_enum \
btf/testdata/tags \
cmd/bpf2go/testdata/minimal
.PHONY: all clean container-all container-shell generate
@@ -57,7 +59,7 @@ TARGETS := \
# Build all ELF binaries using a containerized LLVM toolchain.
container-all:
+${CONTAINER_ENGINE} run --rm -t ${CONTAINER_RUN_ARGS} \
+${CONTAINER_ENGINE} run --rm -ti ${CONTAINER_RUN_ARGS} \
-v "${REPODIR}":/ebpf -w /ebpf --env MAKEFLAGS \
--env HOME="/tmp" \
--env BPF2GO_CC="$(CLANG)" \

View File

@@ -53,6 +53,7 @@ This library includes the following packages:
* [rlimit](https://pkg.go.dev/github.com/cilium/ebpf/rlimit) provides a convenient API to lift
the `RLIMIT_MEMLOCK` constraint on kernels before 5.11.
* [btf](https://pkg.go.dev/github.com/cilium/ebpf/btf) allows reading the BPF Type Format.
* [pin](https://pkg.go.dev/github.com/cilium/ebpf/pin) provides APIs for working with pinned objects on bpffs.
## Requirements

View File

@@ -5,10 +5,6 @@ package asm
// BuiltinFunc is a built-in eBPF function.
type BuiltinFunc int32
func (_ BuiltinFunc) Max() BuiltinFunc {
return maxBuiltinFunc - 1
}
// eBPF built-in functions
//
// You can regenerate this list using the following gawk script:
@@ -237,8 +233,6 @@ const (
FnUserRingbufDrain
FnCgrpStorageGet
FnCgrpStorageDelete
maxBuiltinFunc
)
// Call emits a function call.

View File

@@ -220,12 +220,11 @@ func _() {
_ = x[FnUserRingbufDrain-209]
_ = x[FnCgrpStorageGet-210]
_ = x[FnCgrpStorageDelete-211]
_ = x[maxBuiltinFunc-212]
}
const _BuiltinFunc_name = "FnUnspecFnMapLookupElemFnMapUpdateElemFnMapDeleteElemFnProbeReadFnKtimeGetNsFnTracePrintkFnGetPrandomU32FnGetSmpProcessorIdFnSkbStoreBytesFnL3CsumReplaceFnL4CsumReplaceFnTailCallFnCloneRedirectFnGetCurrentPidTgidFnGetCurrentUidGidFnGetCurrentCommFnGetCgroupClassidFnSkbVlanPushFnSkbVlanPopFnSkbGetTunnelKeyFnSkbSetTunnelKeyFnPerfEventReadFnRedirectFnGetRouteRealmFnPerfEventOutputFnSkbLoadBytesFnGetStackidFnCsumDiffFnSkbGetTunnelOptFnSkbSetTunnelOptFnSkbChangeProtoFnSkbChangeTypeFnSkbUnderCgroupFnGetHashRecalcFnGetCurrentTaskFnProbeWriteUserFnCurrentTaskUnderCgroupFnSkbChangeTailFnSkbPullDataFnCsumUpdateFnSetHashInvalidFnGetNumaNodeIdFnSkbChangeHeadFnXdpAdjustHeadFnProbeReadStrFnGetSocketCookieFnGetSocketUidFnSetHashFnSetsockoptFnSkbAdjustRoomFnRedirectMapFnSkRedirectMapFnSockMapUpdateFnXdpAdjustMetaFnPerfEventReadValueFnPerfProgReadValueFnGetsockoptFnOverrideReturnFnSockOpsCbFlagsSetFnMsgRedirectMapFnMsgApplyBytesFnMsgCorkBytesFnMsgPullDataFnBindFnXdpAdjustTailFnSkbGetXfrmStateFnGetStackFnSkbLoadBytesRelativeFnFibLookupFnSockHashUpdateFnMsgRedirectHashFnSkRedirectHashFnLwtPushEncapFnLwtSeg6StoreBytesFnLwtSeg6AdjustSrhFnLwtSeg6ActionFnRcRepeatFnRcKeydownFnSkbCgroupIdFnGetCurrentCgroupIdFnGetLocalStorageFnSkSelectReuseportFnSkbAncestorCgroupIdFnSkLookupTcpFnSkLookupUdpFnSkReleaseFnMapPushElemFnMapPopElemFnMapPeekElemFnMsgPushDataFnMsgPopDataFnRcPointerRelFnSpinLockFnSpinUnlockFnSkFullsockFnTcpSockFnSkbEcnSetCeFnGetListenerSockFnSkcLookupTcpFnTcpCheckSyncookieFnSysctlGetNameFnSysctlGetCurrentValueFnSysctlGetNewValueFnSysctlSetNewValueFnStrtolFnStrtoulFnSkStorageGetFnSkStorageDeleteFnSendSignalFnTcpGenSyncookieFnSkbOutputFnProbeReadUserFnProbeReadKernelFnProbeReadUserStrFnProbeReadKernelStrFnTcpSendAckFnSendSignalThreadFnJiffies64FnReadBranchRecordsFnGetNsCurrentPidTgidFnXdpOutputFnGetNetnsCookieFnGetCurrentAncestorCgroupIdFnSkAssignFnKtimeGetBootNsFnSeqPrintfFnSeqWriteFnSkCgroupIdFnSkAncestorCgroupIdFnRingbufOutputFnRingbufReserveFnRingbufSubmitFnRingbufDiscardFnRingbufQueryFnCsumLevelFnSkcToTcp6SockFnSkcToTcpSockFnSkcToTcpTimewaitSockFnSkcToTcpRequestSockFnSkcToUdp6SockFnGetTaskStackFnLoadHdrOptFnStoreHdrOptFnReserveHdrOptFnInodeStorageGetFnInodeStorageDeleteFnDPathFnCopyFromUserFnSnprintfBtfFnSeqPrintfBtfFnSkbCgroupClassidFnRedirectNeighFnPerCpuPtrFnThisCpuPtrFnRedirectPeerFnTaskStorageGetFnTaskStorageDeleteFnGetCurrentTaskBtfFnBprmOptsSetFnKtimeGetCoarseNsFnImaInodeHashFnSockFromFileFnCheckMtuFnForEachMapElemFnSnprintfFnSysBpfFnBtfFindByNameKindFnSysCloseFnTimerInitFnTimerSetCallbackFnTimerStartFnTimerCancelFnGetFuncIpFnGetAttachCookieFnTaskPtRegsFnGetBranchSnapshotFnTraceVprintkFnSkcToUnixSockFnKallsymsLookupNameFnFindVmaFnLoopFnStrncmpFnGetFuncArgFnGetFuncRetFnGetFuncArgCntFnGetRetvalFnSetRetvalFnXdpGetBuffLenFnXdpLoadBytesFnXdpStoreBytesFnCopyFromUserTaskFnSkbSetTstampFnImaFileHashFnKptrXchgFnMapLookupPercpuElemFnSkcToMptcpSockFnDynptrFromMemFnRingbufReserveDynptrFnRingbufSubmitDynptrFnRingbufDiscardDynptrFnDynptrReadFnDynptrWriteFnDynptrDataFnTcpRawGenSyncookieIpv4FnTcpRawGenSyncookieIpv6FnTcpRawCheckSyncookieIpv4FnTcpRawCheckSyncookieIpv6FnKtimeGetTaiNsFnUserRingbufDrainFnCgrpStorageGetFnCgrpStorageDeletemaxBuiltinFunc"
const _BuiltinFunc_name = "FnUnspecFnMapLookupElemFnMapUpdateElemFnMapDeleteElemFnProbeReadFnKtimeGetNsFnTracePrintkFnGetPrandomU32FnGetSmpProcessorIdFnSkbStoreBytesFnL3CsumReplaceFnL4CsumReplaceFnTailCallFnCloneRedirectFnGetCurrentPidTgidFnGetCurrentUidGidFnGetCurrentCommFnGetCgroupClassidFnSkbVlanPushFnSkbVlanPopFnSkbGetTunnelKeyFnSkbSetTunnelKeyFnPerfEventReadFnRedirectFnGetRouteRealmFnPerfEventOutputFnSkbLoadBytesFnGetStackidFnCsumDiffFnSkbGetTunnelOptFnSkbSetTunnelOptFnSkbChangeProtoFnSkbChangeTypeFnSkbUnderCgroupFnGetHashRecalcFnGetCurrentTaskFnProbeWriteUserFnCurrentTaskUnderCgroupFnSkbChangeTailFnSkbPullDataFnCsumUpdateFnSetHashInvalidFnGetNumaNodeIdFnSkbChangeHeadFnXdpAdjustHeadFnProbeReadStrFnGetSocketCookieFnGetSocketUidFnSetHashFnSetsockoptFnSkbAdjustRoomFnRedirectMapFnSkRedirectMapFnSockMapUpdateFnXdpAdjustMetaFnPerfEventReadValueFnPerfProgReadValueFnGetsockoptFnOverrideReturnFnSockOpsCbFlagsSetFnMsgRedirectMapFnMsgApplyBytesFnMsgCorkBytesFnMsgPullDataFnBindFnXdpAdjustTailFnSkbGetXfrmStateFnGetStackFnSkbLoadBytesRelativeFnFibLookupFnSockHashUpdateFnMsgRedirectHashFnSkRedirectHashFnLwtPushEncapFnLwtSeg6StoreBytesFnLwtSeg6AdjustSrhFnLwtSeg6ActionFnRcRepeatFnRcKeydownFnSkbCgroupIdFnGetCurrentCgroupIdFnGetLocalStorageFnSkSelectReuseportFnSkbAncestorCgroupIdFnSkLookupTcpFnSkLookupUdpFnSkReleaseFnMapPushElemFnMapPopElemFnMapPeekElemFnMsgPushDataFnMsgPopDataFnRcPointerRelFnSpinLockFnSpinUnlockFnSkFullsockFnTcpSockFnSkbEcnSetCeFnGetListenerSockFnSkcLookupTcpFnTcpCheckSyncookieFnSysctlGetNameFnSysctlGetCurrentValueFnSysctlGetNewValueFnSysctlSetNewValueFnStrtolFnStrtoulFnSkStorageGetFnSkStorageDeleteFnSendSignalFnTcpGenSyncookieFnSkbOutputFnProbeReadUserFnProbeReadKernelFnProbeReadUserStrFnProbeReadKernelStrFnTcpSendAckFnSendSignalThreadFnJiffies64FnReadBranchRecordsFnGetNsCurrentPidTgidFnXdpOutputFnGetNetnsCookieFnGetCurrentAncestorCgroupIdFnSkAssignFnKtimeGetBootNsFnSeqPrintfFnSeqWriteFnSkCgroupIdFnSkAncestorCgroupIdFnRingbufOutputFnRingbufReserveFnRingbufSubmitFnRingbufDiscardFnRingbufQueryFnCsumLevelFnSkcToTcp6SockFnSkcToTcpSockFnSkcToTcpTimewaitSockFnSkcToTcpRequestSockFnSkcToUdp6SockFnGetTaskStackFnLoadHdrOptFnStoreHdrOptFnReserveHdrOptFnInodeStorageGetFnInodeStorageDeleteFnDPathFnCopyFromUserFnSnprintfBtfFnSeqPrintfBtfFnSkbCgroupClassidFnRedirectNeighFnPerCpuPtrFnThisCpuPtrFnRedirectPeerFnTaskStorageGetFnTaskStorageDeleteFnGetCurrentTaskBtfFnBprmOptsSetFnKtimeGetCoarseNsFnImaInodeHashFnSockFromFileFnCheckMtuFnForEachMapElemFnSnprintfFnSysBpfFnBtfFindByNameKindFnSysCloseFnTimerInitFnTimerSetCallbackFnTimerStartFnTimerCancelFnGetFuncIpFnGetAttachCookieFnTaskPtRegsFnGetBranchSnapshotFnTraceVprintkFnSkcToUnixSockFnKallsymsLookupNameFnFindVmaFnLoopFnStrncmpFnGetFuncArgFnGetFuncRetFnGetFuncArgCntFnGetRetvalFnSetRetvalFnXdpGetBuffLenFnXdpLoadBytesFnXdpStoreBytesFnCopyFromUserTaskFnSkbSetTstampFnImaFileHashFnKptrXchgFnMapLookupPercpuElemFnSkcToMptcpSockFnDynptrFromMemFnRingbufReserveDynptrFnRingbufSubmitDynptrFnRingbufDiscardDynptrFnDynptrReadFnDynptrWriteFnDynptrDataFnTcpRawGenSyncookieIpv4FnTcpRawGenSyncookieIpv6FnTcpRawCheckSyncookieIpv4FnTcpRawCheckSyncookieIpv6FnKtimeGetTaiNsFnUserRingbufDrainFnCgrpStorageGetFnCgrpStorageDelete"
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}
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}
func (i BuiltinFunc) String() string {
if i < 0 || i >= BuiltinFunc(len(_BuiltinFunc_index)-1) {

View File

@@ -12,7 +12,6 @@ import (
"strings"
"github.com/cilium/ebpf/internal/sys"
"github.com/cilium/ebpf/internal/unix"
)
// InstructionSize is the size of a BPF instruction in bytes
@@ -804,7 +803,7 @@ func (insns Instructions) Tag(bo binary.ByteOrder) (string, error) {
return "", fmt.Errorf("instruction %d: %w", i, err)
}
}
return hex.EncodeToString(h.Sum(nil)[:unix.BPF_TAG_SIZE]), nil
return hex.EncodeToString(h.Sum(nil)[:sys.BPF_TAG_SIZE]), nil
}
// encodeFunctionReferences populates the Offset (or Constant, depending on

View File

@@ -52,6 +52,7 @@ func _() {
_ = x[AttachSkReuseportSelectOrMigrate-40]
_ = x[AttachPerfEvent-41]
_ = x[AttachTraceKprobeMulti-42]
_ = x[AttachTraceKprobeSession-56]
_ = x[AttachLSMCgroup-43]
_ = x[AttachStructOps-44]
_ = x[AttachNetfilter-45]
@@ -67,9 +68,9 @@ func _() {
_ = x[AttachNetkitPeer-55]
}
const _AttachType_name = "NoneCGroupInetEgressCGroupInetSockCreateCGroupSockOpsSkSKBStreamParserSkSKBStreamVerdictCGroupDeviceSkMsgVerdictCGroupInet4BindCGroupInet6BindCGroupInet4ConnectCGroupInet6ConnectCGroupInet4PostBindCGroupInet6PostBindCGroupUDP4SendmsgCGroupUDP6SendmsgLircMode2FlowDissectorCGroupSysctlCGroupUDP4RecvmsgCGroupUDP6RecvmsgCGroupGetsockoptCGroupSetsockoptTraceRawTpTraceFEntryTraceFExitModifyReturnLSMMacTraceIterCgroupInet4GetPeernameCgroupInet6GetPeernameCgroupInet4GetSocknameCgroupInet6GetSocknameXDPDevMapCgroupInetSockReleaseXDPCPUMapSkLookupXDPSkSKBVerdictSkReuseportSelectSkReuseportSelectOrMigratePerfEventTraceKprobeMultiLSMCgroupStructOpsNetfilterTCXIngressTCXEgressTraceUprobeMultiCgroupUnixConnectCgroupUnixSendmsgCgroupUnixRecvmsgCgroupUnixGetpeernameCgroupUnixGetsocknameNetkitPrimaryNetkitPeer"
const _AttachType_name = "NoneCGroupInetEgressCGroupInetSockCreateCGroupSockOpsSkSKBStreamParserSkSKBStreamVerdictCGroupDeviceSkMsgVerdictCGroupInet4BindCGroupInet6BindCGroupInet4ConnectCGroupInet6ConnectCGroupInet4PostBindCGroupInet6PostBindCGroupUDP4SendmsgCGroupUDP6SendmsgLircMode2FlowDissectorCGroupSysctlCGroupUDP4RecvmsgCGroupUDP6RecvmsgCGroupGetsockoptCGroupSetsockoptTraceRawTpTraceFEntryTraceFExitModifyReturnLSMMacTraceIterCgroupInet4GetPeernameCgroupInet6GetPeernameCgroupInet4GetSocknameCgroupInet6GetSocknameXDPDevMapCgroupInetSockReleaseXDPCPUMapSkLookupXDPSkSKBVerdictSkReuseportSelectSkReuseportSelectOrMigratePerfEventTraceKprobeMultiLSMCgroupStructOpsNetfilterTCXIngressTCXEgressTraceUprobeMultiCgroupUnixConnectCgroupUnixSendmsgCgroupUnixRecvmsgCgroupUnixGetpeernameCgroupUnixGetsocknameNetkitPrimaryNetkitPeerTraceKprobeSession"
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}
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}
func (i AttachType) String() string {
if i >= AttachType(len(_AttachType_index)-1) {

View File

@@ -99,6 +99,10 @@ func (mt *mutableTypes) copy() *mutableTypes {
return nil
}
// Prevent concurrent modification of mt.copiedTypeIDs.
mt.mu.RLock()
defer mt.mu.RUnlock()
mtCopy := &mutableTypes{
mt.imm,
sync.RWMutex{},
@@ -106,10 +110,6 @@ func (mt *mutableTypes) copy() *mutableTypes {
make(map[Type]TypeID, len(mt.copiedTypeIDs)),
}
// Prevent concurrent modification of mt.copiedTypeIDs.
mt.mu.RLock()
defer mt.mu.RUnlock()
copiesOfCopies := make(map[Type]Type, len(mt.copies))
for orig, copy := range mt.copies {
// 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
// Some Datasecs are virtual and don't have corresponding ELF sections.
switch name {
case ".ksyms":
// .ksyms describes forward declarations of kfunc signatures.
// .ksyms describes forward declarations of kfunc signatures, as well as
// references to kernel symbols.
// Nothing to fix up, all sizes and offsets are 0.
for _, vsi := range ds.Vars {
_, ok := vsi.Type.(*Func)
if !ok {
// Only Funcs are supported in the .ksyms Datasec.
return fmt.Errorf("data section %s: expected *btf.Func, not %T: %w", name, vsi.Type, ErrNotSupported)
switch t := vsi.Type.(type) {
case *Func:
continue
case *Var:
if _, ok := t.Type.(*Void); !ok {
return fmt.Errorf("data section %s: expected %s to be *Void, not %T: %w", name, vsi.Type.TypeName(), vsi.Type, ErrNotSupported)
}
default:
return fmt.Errorf("data section %s: expected to be either *btf.Func or *btf.Var, not %T: %w", name, vsi.Type, ErrNotSupported)
}
}
@@ -695,5 +701,13 @@ func (iter *TypesIterator) Next() bool {
iter.Type, ok = iter.spec.typeByID(iter.id)
iter.id++
iter.done = !ok
if !iter.done {
// Skip declTags, during unmarshaling declTags become `Tags` fields of other types.
// We keep them in the spec to avoid holes in the ID space, but for the purposes of
// iteration, they are not useful to the user.
if _, ok := iter.Type.(*declTag); ok {
return iter.Next()
}
}
return !iter.done
}

View File

@@ -39,6 +39,7 @@ const (
kindFloat // Float
// Added 5.16
kindDeclTag // DeclTag
// Added 5.17
kindTypeTag // TypeTag
// Added 6.0
kindEnum64 // Enum64

View File

@@ -16,8 +16,8 @@ import (
// ExtInfos contains ELF section metadata.
type ExtInfos struct {
// The slices are sorted by offset in ascending order.
funcInfos map[string]FuncInfos
lineInfos map[string]LineInfos
funcInfos map[string]FuncOffsets
lineInfos map[string]LineOffsets
relocationInfos map[string]CORERelocationInfos
}
@@ -58,9 +58,9 @@ func loadExtInfos(r io.ReaderAt, bo binary.ByteOrder, spec *Spec) (*ExtInfos, er
return nil, fmt.Errorf("parsing BTF function info: %w", err)
}
funcInfos := make(map[string]FuncInfos, len(btfFuncInfos))
funcInfos := make(map[string]FuncOffsets, len(btfFuncInfos))
for section, bfis := range btfFuncInfos {
funcInfos[section], err = newFuncInfos(bfis, spec)
funcInfos[section], err = newFuncOffsets(bfis, spec)
if err != nil {
return nil, fmt.Errorf("section %s: func infos: %w", section, err)
}
@@ -72,7 +72,7 @@ func loadExtInfos(r io.ReaderAt, bo binary.ByteOrder, spec *Spec) (*ExtInfos, er
return nil, fmt.Errorf("parsing BTF line info: %w", err)
}
lineInfos := make(map[string]LineInfos, len(btfLineInfos))
lineInfos := make(map[string]LineOffsets, len(btfLineInfos))
for section, blis := range btfLineInfos {
lineInfos[section], err = newLineInfos(blis, spec.strings)
if err != nil {
@@ -102,8 +102,10 @@ func loadExtInfos(r io.ReaderAt, bo binary.ByteOrder, spec *Spec) (*ExtInfos, er
return &ExtInfos{funcInfos, lineInfos, coreRelos}, nil
}
type funcInfoMeta struct{}
type coreRelocationMeta struct{}
type (
funcInfoMeta struct{}
coreRelocationMeta struct{}
)
// Assign per-section metadata from BTF to a section's instructions.
func (ei *ExtInfos) Assign(insns asm.Instructions, section string) {
@@ -117,20 +119,20 @@ func (ei *ExtInfos) Assign(insns asm.Instructions, section string) {
// Assign per-instruction metadata to the instructions in insns.
func AssignMetadataToInstructions(
insns asm.Instructions,
funcInfos FuncInfos,
lineInfos LineInfos,
funcInfos FuncOffsets,
lineInfos LineOffsets,
reloInfos CORERelocationInfos,
) {
iter := insns.Iterate()
for iter.Next() {
if len(funcInfos.infos) > 0 && funcInfos.infos[0].offset == iter.Offset {
*iter.Ins = WithFuncMetadata(*iter.Ins, funcInfos.infos[0].fn)
funcInfos.infos = funcInfos.infos[1:]
if len(funcInfos) > 0 && funcInfos[0].Offset == iter.Offset {
*iter.Ins = WithFuncMetadata(*iter.Ins, funcInfos[0].Func)
funcInfos = funcInfos[1:]
}
if len(lineInfos.infos) > 0 && lineInfos.infos[0].offset == iter.Offset {
*iter.Ins = iter.Ins.WithSource(lineInfos.infos[0].line)
lineInfos.infos = lineInfos.infos[1:]
if len(lineInfos) > 0 && lineInfos[0].Offset == iter.Offset {
*iter.Ins = iter.Ins.WithSource(lineInfos[0].Line)
lineInfos = lineInfos[1:]
}
if len(reloInfos.infos) > 0 && reloInfos.infos[0].offset == iter.Offset {
@@ -159,9 +161,9 @@ marshal:
var fiBuf, liBuf bytes.Buffer
for {
if fn := FuncMetadata(iter.Ins); fn != nil {
fi := &funcInfo{
fn: fn,
offset: iter.Offset,
fi := &FuncOffset{
Func: fn,
Offset: iter.Offset,
}
if err := fi.marshal(&fiBuf, b); err != nil {
return nil, nil, fmt.Errorf("write func info: %w", err)
@@ -178,9 +180,9 @@ marshal:
}
}
li := &lineInfo{
line: line,
offset: iter.Offset,
li := &LineOffset{
Offset: iter.Offset,
Line: line,
}
if err := li.marshal(&liBuf, b); err != nil {
return nil, nil, fmt.Errorf("write line info: %w", err)
@@ -333,17 +335,17 @@ func parseExtInfoRecordSize(r io.Reader, bo binary.ByteOrder) (uint32, error) {
return recordSize, nil
}
// FuncInfos contains a sorted list of func infos.
type FuncInfos struct {
infos []funcInfo
}
// FuncOffsets is a sorted slice of FuncOffset.
type FuncOffsets []FuncOffset
// The size of a FuncInfo in BTF wire format.
var FuncInfoSize = uint32(binary.Size(bpfFuncInfo{}))
type funcInfo struct {
fn *Func
offset asm.RawInstructionOffset
// FuncOffset represents a [btf.Func] and its raw instruction offset within a
// BPF program.
type FuncOffset struct {
Offset asm.RawInstructionOffset
Func *Func
}
type bpfFuncInfo struct {
@@ -352,7 +354,7 @@ type bpfFuncInfo struct {
TypeID TypeID
}
func newFuncInfo(fi bpfFuncInfo, spec *Spec) (*funcInfo, error) {
func newFuncOffset(fi bpfFuncInfo, spec *Spec) (*FuncOffset, error) {
typ, err := spec.TypeByID(fi.TypeID)
if err != nil {
return nil, err
@@ -368,31 +370,32 @@ func newFuncInfo(fi bpfFuncInfo, spec *Spec) (*funcInfo, error) {
return nil, fmt.Errorf("func with type ID %d doesn't have a name", fi.TypeID)
}
return &funcInfo{
fn,
return &FuncOffset{
asm.RawInstructionOffset(fi.InsnOff),
fn,
}, nil
}
func newFuncInfos(bfis []bpfFuncInfo, spec *Spec) (FuncInfos, error) {
fis := FuncInfos{
infos: make([]funcInfo, 0, len(bfis)),
}
func newFuncOffsets(bfis []bpfFuncInfo, spec *Spec) (FuncOffsets, error) {
fos := make(FuncOffsets, 0, len(bfis))
for _, bfi := range bfis {
fi, err := newFuncInfo(bfi, spec)
fi, err := newFuncOffset(bfi, spec)
if err != nil {
return FuncInfos{}, fmt.Errorf("offset %d: %w", bfi.InsnOff, err)
return FuncOffsets{}, fmt.Errorf("offset %d: %w", bfi.InsnOff, err)
}
fis.infos = append(fis.infos, *fi)
fos = append(fos, *fi)
}
sort.Slice(fis.infos, func(i, j int) bool {
return fis.infos[i].offset <= fis.infos[j].offset
sort.Slice(fos, func(i, j int) bool {
return fos[i].Offset <= fos[j].Offset
})
return fis, nil
return fos, nil
}
// LoadFuncInfos parses BTF func info in kernel wire format.
func LoadFuncInfos(reader io.Reader, bo binary.ByteOrder, recordNum uint32, spec *Spec) (FuncInfos, error) {
// LoadFuncInfos parses BTF func info from kernel wire format into a
// [FuncOffsets], a sorted slice of [btf.Func]s of (sub)programs within a BPF
// program with their corresponding raw instruction offsets.
func LoadFuncInfos(reader io.Reader, bo binary.ByteOrder, recordNum uint32, spec *Spec) (FuncOffsets, error) {
fis, err := parseFuncInfoRecords(
reader,
bo,
@@ -401,20 +404,20 @@ func LoadFuncInfos(reader io.Reader, bo binary.ByteOrder, recordNum uint32, spec
false,
)
if err != nil {
return FuncInfos{}, fmt.Errorf("parsing BTF func info: %w", err)
return FuncOffsets{}, fmt.Errorf("parsing BTF func info: %w", err)
}
return newFuncInfos(fis, spec)
return newFuncOffsets(fis, spec)
}
// marshal into the BTF wire format.
func (fi *funcInfo) marshal(w *bytes.Buffer, b *Builder) error {
id, err := b.Add(fi.fn)
func (fi *FuncOffset) marshal(w *bytes.Buffer, b *Builder) error {
id, err := b.Add(fi.Func)
if err != nil {
return err
}
bfi := bpfFuncInfo{
InsnOff: uint32(fi.offset),
InsnOff: uint32(fi.Offset),
TypeID: id,
}
buf := make([]byte, FuncInfoSize)
@@ -515,14 +518,13 @@ func (li *Line) String() string {
return li.line
}
// LineInfos contains a sorted list of line infos.
type LineInfos struct {
infos []lineInfo
}
// LineOffsets contains a sorted list of line infos.
type LineOffsets []LineOffset
type lineInfo struct {
line *Line
offset asm.RawInstructionOffset
// LineOffset represents a line info and its raw instruction offset.
type LineOffset struct {
Offset asm.RawInstructionOffset
Line *Line
}
// Constants for the format of bpfLineInfo.LineCol.
@@ -541,7 +543,7 @@ type bpfLineInfo struct {
}
// LoadLineInfos parses BTF line info in kernel wire format.
func LoadLineInfos(reader io.Reader, bo binary.ByteOrder, recordNum uint32, spec *Spec) (LineInfos, error) {
func LoadLineInfos(reader io.Reader, bo binary.ByteOrder, recordNum uint32, spec *Spec) (LineOffsets, error) {
lis, err := parseLineInfoRecords(
reader,
bo,
@@ -550,57 +552,55 @@ func LoadLineInfos(reader io.Reader, bo binary.ByteOrder, recordNum uint32, spec
false,
)
if err != nil {
return LineInfos{}, fmt.Errorf("parsing BTF line info: %w", err)
return LineOffsets{}, fmt.Errorf("parsing BTF line info: %w", err)
}
return newLineInfos(lis, spec.strings)
}
func newLineInfo(li bpfLineInfo, strings *stringTable) (lineInfo, error) {
func newLineInfo(li bpfLineInfo, strings *stringTable) (LineOffset, error) {
line, err := strings.Lookup(li.LineOff)
if err != nil {
return lineInfo{}, fmt.Errorf("lookup of line: %w", err)
return LineOffset{}, fmt.Errorf("lookup of line: %w", err)
}
fileName, err := strings.Lookup(li.FileNameOff)
if err != nil {
return lineInfo{}, fmt.Errorf("lookup of filename: %w", err)
return LineOffset{}, fmt.Errorf("lookup of filename: %w", err)
}
lineNumber := li.LineCol >> bpfLineShift
lineColumn := li.LineCol & bpfColumnMax
return lineInfo{
return LineOffset{
asm.RawInstructionOffset(li.InsnOff),
&Line{
fileName,
line,
lineNumber,
lineColumn,
},
asm.RawInstructionOffset(li.InsnOff),
}, nil
}
func newLineInfos(blis []bpfLineInfo, strings *stringTable) (LineInfos, error) {
lis := LineInfos{
infos: make([]lineInfo, 0, len(blis)),
}
func newLineInfos(blis []bpfLineInfo, strings *stringTable) (LineOffsets, error) {
lis := make([]LineOffset, 0, len(blis))
for _, bli := range blis {
li, err := newLineInfo(bli, strings)
if err != nil {
return LineInfos{}, fmt.Errorf("offset %d: %w", bli.InsnOff, err)
return LineOffsets{}, fmt.Errorf("offset %d: %w", bli.InsnOff, err)
}
lis.infos = append(lis.infos, li)
lis = append(lis, li)
}
sort.Slice(lis.infos, func(i, j int) bool {
return lis.infos[i].offset <= lis.infos[j].offset
sort.Slice(lis, func(i, j int) bool {
return lis[i].Offset <= lis[j].Offset
})
return lis, nil
}
// marshal writes the binary representation of the LineInfo to w.
func (li *lineInfo) marshal(w *bytes.Buffer, b *Builder) error {
line := li.line
func (li *LineOffset) marshal(w *bytes.Buffer, b *Builder) error {
line := li.Line
if line.lineNumber > bpfLineMax {
return fmt.Errorf("line %d exceeds %d", line.lineNumber, bpfLineMax)
}
@@ -620,7 +620,7 @@ func (li *lineInfo) marshal(w *bytes.Buffer, b *Builder) error {
}
bli := bpfLineInfo{
uint32(li.offset),
uint32(li.Offset),
fileNameOff,
lineOff,
(line.lineNumber << bpfLineShift) | line.lineColumn,
@@ -666,20 +666,19 @@ func parseLineInfos(r io.Reader, bo binary.ByteOrder, strings *stringTable) (map
// These records appear after a btf_ext_info_sec header in the line_info
// sub-section of .BTF.ext.
func parseLineInfoRecords(r io.Reader, bo binary.ByteOrder, recordSize uint32, recordNum uint32, offsetInBytes bool) ([]bpfLineInfo, error) {
var li bpfLineInfo
if exp, got := uint32(binary.Size(li)), recordSize; exp != got {
if exp, got := uint32(binary.Size(bpfLineInfo{})), recordSize; exp != got {
// BTF blob's record size is longer than we know how to parse.
return nil, fmt.Errorf("expected LineInfo record size %d, but BTF blob contains %d", exp, got)
}
out := make([]bpfLineInfo, 0, recordNum)
for i := uint32(0); i < recordNum; i++ {
if err := binary.Read(r, bo, &li); err != nil {
return nil, fmt.Errorf("can't read line info: %v", err)
}
out := make([]bpfLineInfo, recordNum)
if err := binary.Read(r, bo, out); err != nil {
return nil, fmt.Errorf("can't read line info: %v", err)
}
if offsetInBytes {
if offsetInBytes {
for i := range out {
li := &out[i]
if li.InsnOff%asm.InstructionSize != 0 {
return nil, fmt.Errorf("offset %v is not aligned with instruction size", li.InsnOff)
}
@@ -688,8 +687,6 @@ func parseLineInfoRecords(r io.Reader, bo binary.ByteOrder, recordSize uint32, r
// Convert as early as possible.
li.InsnOff /= asm.InstructionSize
}
out = append(out, li)
}
return out, nil
@@ -799,7 +796,7 @@ func parseCORERelos(r io.Reader, bo binary.ByteOrder, strings *stringTable) (map
return nil, err
}
records, err := parseCOREReloRecords(r, bo, recordSize, infoHeader.NumInfo)
records, err := parseCOREReloRecords(r, bo, infoHeader.NumInfo)
if err != nil {
return nil, fmt.Errorf("section %v: %w", secName, err)
}
@@ -811,7 +808,7 @@ func parseCORERelos(r io.Reader, bo binary.ByteOrder, strings *stringTable) (map
// parseCOREReloRecords parses a stream of CO-RE relocation entries into a
// coreRelos. These records appear after a btf_ext_info_sec header in the
// core_relos sub-section of .BTF.ext.
func parseCOREReloRecords(r io.Reader, bo binary.ByteOrder, recordSize uint32, recordNum uint32) ([]bpfCORERelo, error) {
func parseCOREReloRecords(r io.Reader, bo binary.ByteOrder, recordNum uint32) ([]bpfCORERelo, error) {
var out []bpfCORERelo
var relo bpfCORERelo

View File

@@ -11,19 +11,19 @@ import (
// haveBTF attempts to load a BTF blob containing an Int. It should pass on any
// kernel that supports BPF_BTF_LOAD.
var haveBTF = internal.NewFeatureTest("BTF", "4.18", func() error {
var haveBTF = internal.NewFeatureTest("BTF", func() error {
// 0-length anonymous integer
err := probeBTF(&Int{})
if errors.Is(err, unix.EINVAL) || errors.Is(err, unix.EPERM) {
return internal.ErrNotSupported
}
return err
})
}, "4.18")
// haveMapBTF attempts to load a minimal BTF blob containing a Var. It is
// used as a proxy for .bss, .data and .rodata map support, which generally
// come with a Var and Datasec. These were introduced in Linux 5.2.
var haveMapBTF = internal.NewFeatureTest("Map BTF (Var/Datasec)", "5.2", func() error {
var haveMapBTF = internal.NewFeatureTest("Map BTF (Var/Datasec)", func() error {
if err := haveBTF(); err != nil {
return err
}
@@ -40,12 +40,12 @@ var haveMapBTF = internal.NewFeatureTest("Map BTF (Var/Datasec)", "5.2", func()
return internal.ErrNotSupported
}
return err
})
}, "5.2")
// haveProgBTF attempts to load a BTF blob containing a Func and FuncProto. It
// is used as a proxy for ext_info (func_info) support, which depends on
// Func(Proto) by definition.
var haveProgBTF = internal.NewFeatureTest("Program BTF (func/line_info)", "5.0", func() error {
var haveProgBTF = internal.NewFeatureTest("Program BTF (func/line_info)", func() error {
if err := haveBTF(); err != nil {
return err
}
@@ -60,9 +60,9 @@ var haveProgBTF = internal.NewFeatureTest("Program BTF (func/line_info)", "5.0",
return internal.ErrNotSupported
}
return err
})
}, "5.0")
var haveFuncLinkage = internal.NewFeatureTest("BTF func linkage", "5.6", func() error {
var haveFuncLinkage = internal.NewFeatureTest("BTF func linkage", func() error {
if err := haveProgBTF(); err != nil {
return err
}
@@ -78,9 +78,44 @@ var haveFuncLinkage = internal.NewFeatureTest("BTF func linkage", "5.6", func()
return internal.ErrNotSupported
}
return err
})
}, "5.6")
var haveEnum64 = internal.NewFeatureTest("ENUM64", "6.0", func() error {
var haveDeclTags = internal.NewFeatureTest("BTF decl tags", func() error {
if err := haveBTF(); err != nil {
return err
}
t := &Typedef{
Name: "a",
Type: &Int{},
Tags: []string{"a"},
}
err := probeBTF(t)
if errors.Is(err, unix.EINVAL) {
return internal.ErrNotSupported
}
return err
}, "5.16")
var haveTypeTags = internal.NewFeatureTest("BTF type tags", func() error {
if err := haveBTF(); err != nil {
return err
}
t := &TypeTag{
Type: &Int{},
Value: "a",
}
err := probeBTF(t)
if errors.Is(err, unix.EINVAL) {
return internal.ErrNotSupported
}
return err
}, "5.17")
var haveEnum64 = internal.NewFeatureTest("ENUM64", func() error {
if err := haveBTF(); err != nil {
return err
}
@@ -97,7 +132,7 @@ var haveEnum64 = internal.NewFeatureTest("ENUM64", "6.0", func() error {
return internal.ErrNotSupported
}
return err
})
}, "6.0")
func probeBTF(typ Type) error {
b, err := NewBuilder([]Type{typ})

View File

@@ -161,6 +161,9 @@ func (gf *GoFormatter) writeTypeLit(typ Type, depth int) error {
case *Datasec:
err = gf.writeDatasecLit(v, depth)
case *Var:
err = gf.writeTypeLit(v.Type, depth)
default:
return fmt.Errorf("type %T: %w", v, ErrNotSupported)
}

View File

@@ -8,7 +8,7 @@ import (
"sync"
"github.com/cilium/ebpf/internal"
"github.com/cilium/ebpf/internal/kallsyms"
"github.com/cilium/ebpf/internal/linux"
)
var kernelBTF = struct {
@@ -21,8 +21,6 @@ var kernelBTF = struct {
// FlushKernelSpec removes any cached kernel type information.
func FlushKernelSpec() {
kallsyms.FlushKernelModuleCache()
kernelBTF.Lock()
defer kernelBTF.Unlock()
@@ -130,7 +128,7 @@ func loadKernelModuleSpec(module string, base *Spec) (*Spec, error) {
// findVMLinux scans multiple well-known paths for vmlinux kernel images.
func findVMLinux() (*os.File, error) {
release, err := internal.KernelRelease()
release, err := linux.KernelRelease()
if err != nil {
return nil, err
}

View File

@@ -18,6 +18,10 @@ type MarshalOptions struct {
Order binary.ByteOrder
// Remove function linkage information for compatibility with <5.6 kernels.
StripFuncLinkage bool
// Replace decl tags with a placeholder for compatibility with <5.16 kernels.
ReplaceDeclTags bool
// Replace TypeTags with a placeholder for compatibility with <5.17 kernels.
ReplaceTypeTags bool
// Replace Enum64 with a placeholder for compatibility with <6.0 kernels.
ReplaceEnum64 bool
// Prevent the "No type found" error when loading BTF without any types.
@@ -29,6 +33,8 @@ func KernelMarshalOptions() *MarshalOptions {
return &MarshalOptions{
Order: internal.NativeEndian,
StripFuncLinkage: haveFuncLinkage() != nil,
ReplaceDeclTags: haveDeclTags() != nil,
ReplaceTypeTags: haveTypeTags() != nil,
ReplaceEnum64: haveEnum64() != nil,
PreventNoTypeFound: true, // All current kernels require this.
}
@@ -318,15 +324,7 @@ func (e *encoder) deflateType(typ Type) (err error) {
return errors.New("Void is implicit in BTF wire format")
case *Int:
raw.SetKind(kindInt)
raw.SetSize(v.Size)
var bi btfInt
bi.SetEncoding(v.Encoding)
// We need to set bits in addition to size, since btf_type_int_is_regular
// otherwise flags this as a bitfield.
bi.SetBits(byte(v.Size) * 8)
raw.data = bi
e.deflateInt(&raw, v)
case *Pointer:
raw.SetKind(kindPointer)
@@ -368,8 +366,7 @@ func (e *encoder) deflateType(typ Type) (err error) {
raw.SetType(e.id(v.Type))
case *Const:
raw.SetKind(kindConst)
raw.SetType(e.id(v.Type))
e.deflateConst(&raw, v)
case *Restrict:
raw.SetKind(kindRestrict)
@@ -404,15 +401,10 @@ func (e *encoder) deflateType(typ Type) (err error) {
raw.SetSize(v.Size)
case *declTag:
raw.SetKind(kindDeclTag)
raw.SetType(e.id(v.Type))
raw.data = &btfDeclTag{uint32(v.Index)}
raw.NameOff, err = e.strings.Add(v.Value)
err = e.deflateDeclTag(&raw, v)
case *typeTag:
raw.SetKind(kindTypeTag)
raw.SetType(e.id(v.Type))
raw.NameOff, err = e.strings.Add(v.Value)
case *TypeTag:
err = e.deflateTypeTag(&raw, v)
default:
return fmt.Errorf("don't know how to deflate %T", v)
@@ -425,6 +417,57 @@ func (e *encoder) deflateType(typ Type) (err error) {
return raw.Marshal(e.buf, e.Order)
}
func (e *encoder) deflateInt(raw *rawType, i *Int) {
raw.SetKind(kindInt)
raw.SetSize(i.Size)
var bi btfInt
bi.SetEncoding(i.Encoding)
// We need to set bits in addition to size, since btf_type_int_is_regular
// otherwise flags this as a bitfield.
bi.SetBits(byte(i.Size) * 8)
raw.data = bi
}
func (e *encoder) deflateDeclTag(raw *rawType, tag *declTag) (err error) {
// Replace a decl tag with an integer for compatibility with <5.16 kernels,
// following libbpf behaviour.
if e.ReplaceDeclTags {
typ := &Int{"decl_tag_placeholder", 1, Unsigned}
e.deflateInt(raw, typ)
// Add the placeholder type name to the string table. The encoder added the
// original type name before this call.
raw.NameOff, err = e.strings.Add(typ.TypeName())
return
}
raw.SetKind(kindDeclTag)
raw.SetType(e.id(tag.Type))
raw.data = &btfDeclTag{uint32(tag.Index)}
raw.NameOff, err = e.strings.Add(tag.Value)
return
}
func (e *encoder) deflateConst(raw *rawType, c *Const) {
raw.SetKind(kindConst)
raw.SetType(e.id(c.Type))
}
func (e *encoder) deflateTypeTag(raw *rawType, tag *TypeTag) (err error) {
// Replace a type tag with a const qualifier for compatibility with <5.17
// kernels, following libbpf behaviour.
if e.ReplaceTypeTags {
e.deflateConst(raw, &Const{tag.Type})
return
}
raw.SetKind(kindTypeTag)
raw.SetType(e.id(tag.Type))
raw.NameOff, err = e.strings.Add(tag.Value)
return
}
func (e *encoder) deflateUnion(raw *rawType, union *Union) (err error) {
raw.SetKind(kindUnion)
raw.SetSize(union.Size)
@@ -521,7 +564,7 @@ func (e *encoder) deflateEnum64(raw *rawType, enum *Enum) (err error) {
})
}
return e.deflateUnion(raw, &Union{enum.Name, enum.Size, members})
return e.deflateUnion(raw, &Union{enum.Name, enum.Size, members, nil})
}
raw.SetKind(kindEnum64)

View File

@@ -40,9 +40,12 @@ func children(typ Type, yield func(child *Type) bool) bool {
// Explicitly type switch on the most common types to allow the inliner to
// do its work. This avoids allocating intermediate slices from walk() on
// the heap.
var tags []string
switch v := typ.(type) {
case *Void, *Int, *Enum, *Fwd, *Float:
case *Void, *Int, *Enum, *Fwd, *Float, *declTag:
// No children to traverse.
// declTags is declared as a leaf type since it's parsed into .Tags fields of other types
// during unmarshaling.
case *Pointer:
if !yield(&v.Target) {
return false
@@ -59,17 +62,32 @@ func children(typ Type, yield func(child *Type) bool) bool {
if !yield(&v.Members[i].Type) {
return false
}
for _, t := range v.Members[i].Tags {
var tag Type = &declTag{v, t, i}
if !yield(&tag) {
return false
}
}
}
tags = v.Tags
case *Union:
for i := range v.Members {
if !yield(&v.Members[i].Type) {
return false
}
for _, t := range v.Members[i].Tags {
var tag Type = &declTag{v, t, i}
if !yield(&tag) {
return false
}
}
}
tags = v.Tags
case *Typedef:
if !yield(&v.Type) {
return false
}
tags = v.Tags
case *Volatile:
if !yield(&v.Type) {
return false
@@ -86,6 +104,20 @@ func children(typ Type, yield func(child *Type) bool) bool {
if !yield(&v.Type) {
return false
}
if fp, ok := v.Type.(*FuncProto); ok {
for i := range fp.Params {
if len(v.ParamTags) <= i {
continue
}
for _, t := range v.ParamTags[i] {
var tag Type = &declTag{v, t, i}
if !yield(&tag) {
return false
}
}
}
}
tags = v.Tags
case *FuncProto:
if !yield(&v.Return) {
return false
@@ -99,17 +131,14 @@ func children(typ Type, yield func(child *Type) bool) bool {
if !yield(&v.Type) {
return false
}
tags = v.Tags
case *Datasec:
for i := range v.Vars {
if !yield(&v.Vars[i].Type) {
return false
}
}
case *declTag:
if !yield(&v.Type) {
return false
}
case *typeTag:
case *TypeTag:
if !yield(&v.Type) {
return false
}
@@ -119,5 +148,12 @@ func children(typ Type, yield func(child *Type) bool) bool {
panic(fmt.Sprintf("don't know how to walk Type %T", v))
}
for _, t := range tags {
var tag Type = &declTag{typ, t, -1}
if !yield(&tag) {
return false
}
}
return true
}

View File

@@ -67,7 +67,7 @@ var (
_ Type = (*Datasec)(nil)
_ Type = (*Float)(nil)
_ Type = (*declTag)(nil)
_ Type = (*typeTag)(nil)
_ Type = (*TypeTag)(nil)
_ Type = (*cycle)(nil)
)
@@ -169,6 +169,7 @@ type Struct struct {
// The size of the struct including padding, in bytes
Size uint32
Members []Member
Tags []string
}
func (s *Struct) Format(fs fmt.State, verb rune) {
@@ -182,6 +183,7 @@ func (s *Struct) size() uint32 { return s.Size }
func (s *Struct) copy() Type {
cpy := *s
cpy.Members = copyMembers(s.Members)
cpy.Tags = copyTags(cpy.Tags)
return &cpy
}
@@ -195,6 +197,7 @@ type Union struct {
// The size of the union including padding, in bytes.
Size uint32
Members []Member
Tags []string
}
func (u *Union) Format(fs fmt.State, verb rune) {
@@ -208,6 +211,7 @@ func (u *Union) size() uint32 { return u.Size }
func (u *Union) copy() Type {
cpy := *u
cpy.Members = copyMembers(u.Members)
cpy.Tags = copyTags(cpy.Tags)
return &cpy
}
@@ -218,6 +222,18 @@ func (u *Union) members() []Member {
func copyMembers(orig []Member) []Member {
cpy := make([]Member, len(orig))
copy(cpy, orig)
for i, member := range cpy {
cpy[i].Tags = copyTags(member.Tags)
}
return cpy
}
func copyTags(orig []string) []string {
if orig == nil { // preserve nil vs zero-len slice distinction
return nil
}
cpy := make([]string, len(orig))
copy(cpy, orig)
return cpy
}
@@ -247,6 +263,7 @@ type Member struct {
Type Type
Offset Bits
BitfieldSize Bits
Tags []string
}
// Enum lists possible values.
@@ -334,6 +351,7 @@ func (f *Fwd) matches(typ Type) bool {
type Typedef struct {
Name string
Type Type
Tags []string
}
func (td *Typedef) Format(fs fmt.State, verb rune) {
@@ -344,6 +362,7 @@ func (td *Typedef) TypeName() string { return td.Name }
func (td *Typedef) copy() Type {
cpy := *td
cpy.Tags = copyTags(td.Tags)
return &cpy
}
@@ -403,6 +422,12 @@ type Func struct {
Name string
Type Type
Linkage FuncLinkage
Tags []string
// ParamTags holds a list of tags for each parameter of the FuncProto to which `Type` points.
// If no tags are present for any param, the outer slice will be nil/len(ParamTags)==0.
// If at least 1 param has a tag, the outer slice will have the same length as the number of params.
// The inner slice contains the tags and may be nil/len(ParamTags[i])==0 if no tags are present for that param.
ParamTags [][]string
}
func FuncMetadata(ins *asm.Instruction) *Func {
@@ -424,6 +449,14 @@ func (f *Func) TypeName() string { return f.Name }
func (f *Func) copy() Type {
cpy := *f
cpy.Tags = copyTags(f.Tags)
if f.ParamTags != nil { // preserve nil vs zero-len slice distinction
ptCopy := make([][]string, len(f.ParamTags))
for i, tags := range f.ParamTags {
ptCopy[i] = copyTags(tags)
}
cpy.ParamTags = ptCopy
}
return &cpy
}
@@ -456,6 +489,7 @@ type Var struct {
Name string
Type Type
Linkage VarLinkage
Tags []string
}
func (v *Var) Format(fs fmt.State, verb rune) {
@@ -466,6 +500,7 @@ func (v *Var) TypeName() string { return v.Name }
func (v *Var) copy() Type {
cpy := *v
cpy.Tags = copyTags(v.Tags)
return &cpy
}
@@ -540,19 +575,25 @@ func (dt *declTag) copy() Type {
return &cpy
}
// typeTag associates metadata with a type.
type typeTag struct {
// TypeTag associates metadata with a pointer type. Tag types act as a custom
// modifier(const, restrict, volatile) for the target type. Unlike declTags,
// TypeTags are ordered so the order in which they are added matters.
//
// One of their uses is to mark pointers as `__kptr` meaning a pointer points
// to kernel memory. Adding a `__kptr` to pointers in map values allows you
// to store pointers to kernel memory in maps.
type TypeTag struct {
Type Type
Value string
}
func (tt *typeTag) Format(fs fmt.State, verb rune) {
func (tt *TypeTag) Format(fs fmt.State, verb rune) {
formatType(fs, verb, tt, "type=", tt.Type, "value=", tt.Value)
}
func (tt *typeTag) TypeName() string { return "" }
func (tt *typeTag) qualify() Type { return tt.Type }
func (tt *typeTag) copy() Type {
func (tt *TypeTag) TypeName() string { return "" }
func (tt *TypeTag) qualify() Type { return tt.Type }
func (tt *TypeTag) copy() Type {
cpy := *tt
return &cpy
}
@@ -591,7 +632,7 @@ var (
_ qualifier = (*Const)(nil)
_ qualifier = (*Restrict)(nil)
_ qualifier = (*Volatile)(nil)
_ qualifier = (*typeTag)(nil)
_ qualifier = (*TypeTag)(nil)
)
var errUnsizedType = errors.New("type is unsized")
@@ -918,7 +959,7 @@ func readAndInflateTypes(r io.Reader, bo binary.ByteOrder, typeLen uint32, rawSt
if err != nil {
return nil, fmt.Errorf("struct %s (id %d): %w", name, id, err)
}
typ = &Struct{name, header.Size(), members}
typ = &Struct{name, header.Size(), members, nil}
case kindUnion:
vlen := header.Vlen()
@@ -935,7 +976,7 @@ func readAndInflateTypes(r io.Reader, bo binary.ByteOrder, typeLen uint32, rawSt
if err != nil {
return nil, fmt.Errorf("union %s (id %d): %w", name, id, err)
}
typ = &Union{name, header.Size(), members}
typ = &Union{name, header.Size(), members, nil}
case kindEnum:
vlen := header.Vlen()
@@ -968,7 +1009,7 @@ func readAndInflateTypes(r io.Reader, bo binary.ByteOrder, typeLen uint32, rawSt
typ = &Fwd{name, header.FwdKind()}
case kindTypedef:
typedef := &Typedef{name, nil}
typedef := &Typedef{name, nil, nil}
fixup(header.Type(), &typedef.Type)
typ = typedef
@@ -988,7 +1029,7 @@ func readAndInflateTypes(r io.Reader, bo binary.ByteOrder, typeLen uint32, rawSt
typ = restrict
case kindFunc:
fn := &Func{name, nil, header.Linkage()}
fn := &Func{name, nil, header.Linkage(), nil, nil}
fixup(header.Type(), &fn.Type)
typ = fn
@@ -1030,7 +1071,7 @@ func readAndInflateTypes(r io.Reader, bo binary.ByteOrder, typeLen uint32, rawSt
return nil, fmt.Errorf("can't read btfVariable, id: %d: %w", id, err)
}
v := &Var{name, nil, VarLinkage(bVariable.Linkage)}
v := &Var{name, nil, VarLinkage(bVariable.Linkage), nil}
fixup(header.Type(), &v.Type)
typ = v
@@ -1081,7 +1122,7 @@ func readAndInflateTypes(r io.Reader, bo binary.ByteOrder, typeLen uint32, rawSt
declTags = append(declTags, dt)
case kindTypeTag:
tt := &typeTag{nil, name}
tt := &TypeTag{nil, name}
fixup(header.Type(), &tt.Type)
typ = tt
@@ -1142,26 +1183,69 @@ func readAndInflateTypes(r io.Reader, bo binary.ByteOrder, typeLen uint32, rawSt
for _, dt := range declTags {
switch t := dt.Type.(type) {
case *Var, *Typedef:
case *Var:
if dt.Index != -1 {
return nil, fmt.Errorf("type %s: index %d is not -1", dt, dt.Index)
return nil, fmt.Errorf("type %s: component idx %d is not -1", dt, dt.Index)
}
t.Tags = append(t.Tags, dt.Value)
case *Typedef:
if dt.Index != -1 {
return nil, fmt.Errorf("type %s: component idx %d is not -1", dt, dt.Index)
}
t.Tags = append(t.Tags, dt.Value)
case composite:
if dt.Index >= len(t.members()) {
return nil, fmt.Errorf("type %s: index %d exceeds members of %s", dt, dt.Index, t)
if dt.Index >= 0 {
members := t.members()
if dt.Index >= len(members) {
return nil, fmt.Errorf("type %s: component idx %d exceeds members of %s", dt, dt.Index, t)
}
members[dt.Index].Tags = append(members[dt.Index].Tags, dt.Value)
continue
}
if dt.Index == -1 {
switch t2 := t.(type) {
case *Struct:
t2.Tags = append(t2.Tags, dt.Value)
case *Union:
t2.Tags = append(t2.Tags, dt.Value)
}
continue
}
return nil, fmt.Errorf("type %s: decl tag for type %s has invalid component idx", dt, t)
case *Func:
fp, ok := t.Type.(*FuncProto)
if !ok {
return nil, fmt.Errorf("type %s: %s is not a FuncProto", dt, t.Type)
}
if dt.Index >= len(fp.Params) {
return nil, fmt.Errorf("type %s: index %d exceeds params of %s", dt, dt.Index, t)
// Ensure the number of argument tag lists equals the number of arguments
if len(t.ParamTags) == 0 {
t.ParamTags = make([][]string, len(fp.Params))
}
if dt.Index >= 0 {
if dt.Index >= len(fp.Params) {
return nil, fmt.Errorf("type %s: component idx %d exceeds params of %s", dt, dt.Index, t)
}
t.ParamTags[dt.Index] = append(t.ParamTags[dt.Index], dt.Value)
continue
}
if dt.Index == -1 {
t.Tags = append(t.Tags, dt.Value)
continue
}
return nil, fmt.Errorf("type %s: decl tag for type %s has invalid component idx", dt, t)
default:
return nil, fmt.Errorf("type %s: decl tag for type %s is not supported", dt, t)
}
@@ -1207,6 +1291,20 @@ func UnderlyingType(typ Type) Type {
return &cycle{typ}
}
// QualifiedType returns the type with all qualifiers removed.
func QualifiedType(typ Type) Type {
result := typ
for depth := 0; depth <= maxResolveDepth; depth++ {
switch v := (result).(type) {
case qualifier:
result = v.qualify()
default:
return result
}
}
return &cycle{typ}
}
// As returns typ if is of type T. Otherwise it peels qualifiers and Typedefs
// until it finds a T.
//

View File

@@ -12,7 +12,7 @@ func datasecResolveWorkaround(b *Builder, ds *Datasec) error {
}
switch v.Type.(type) {
case *Typedef, *Volatile, *Const, *Restrict, *typeTag:
case *Typedef, *Volatile, *Const, *Restrict, *TypeTag:
// NB: We must never call Add on a Datasec, otherwise we risk
// infinite recursion.
_, err := b.Add(v.Type)

View File

@@ -10,8 +10,10 @@ import (
"github.com/cilium/ebpf/asm"
"github.com/cilium/ebpf/btf"
"github.com/cilium/ebpf/internal"
"github.com/cilium/ebpf/internal/kallsyms"
"github.com/cilium/ebpf/internal/kconfig"
"github.com/cilium/ebpf/internal/sysenc"
"github.com/cilium/ebpf/internal/linux"
"github.com/cilium/ebpf/internal/sys"
)
// CollectionOptions control loading a collection into the kernel.
@@ -38,6 +40,11 @@ type CollectionSpec struct {
Maps map[string]*MapSpec
Programs map[string]*ProgramSpec
// Variables refer to global variables declared in the ELF. They can be read
// and modified freely before loading the Collection. Modifying them after
// loading has no effect on a running eBPF program.
Variables map[string]*VariableSpec
// Types holds type information about Maps and Programs.
// Modifications to Types are currently undefined behaviour.
Types *btf.Spec
@@ -56,6 +63,7 @@ func (cs *CollectionSpec) Copy() *CollectionSpec {
cpy := CollectionSpec{
Maps: make(map[string]*MapSpec, len(cs.Maps)),
Programs: make(map[string]*ProgramSpec, len(cs.Programs)),
Variables: make(map[string]*VariableSpec, len(cs.Variables)),
ByteOrder: cs.ByteOrder,
Types: cs.Types.Copy(),
}
@@ -68,6 +76,10 @@ func (cs *CollectionSpec) Copy() *CollectionSpec {
cpy.Programs[name] = spec.Copy()
}
for name, spec := range cs.Variables {
cpy.Variables[name] = spec.copy(&cpy)
}
return &cpy
}
@@ -134,65 +146,24 @@ func (m *MissingConstantsError) Error() string {
// From Linux 5.5 the verifier will use constants to eliminate dead code.
//
// Returns an error wrapping [MissingConstantsError] if a constant doesn't exist.
//
// Deprecated: Use [CollectionSpec.Variables] to interact with constants instead.
// RewriteConstants is now a wrapper around the VariableSpec API.
func (cs *CollectionSpec) RewriteConstants(consts map[string]interface{}) error {
replaced := make(map[string]bool)
for name, spec := range cs.Maps {
if !strings.HasPrefix(name, ".rodata") {
continue
}
b, ds, err := spec.dataSection()
if errors.Is(err, errMapNoBTFValue) {
// Data sections without a BTF Datasec are valid, but don't support
// constant replacements.
continue
}
if err != nil {
return fmt.Errorf("map %s: %w", name, err)
}
// MapSpec.Copy() performs a shallow copy. Fully copy the byte slice
// to avoid any changes affecting other copies of the MapSpec.
cpy := make([]byte, len(b))
copy(cpy, b)
for _, v := range ds.Vars {
vname := v.Type.TypeName()
replacement, ok := consts[vname]
if !ok {
continue
}
if _, ok := v.Type.(*btf.Var); !ok {
return fmt.Errorf("section %s: unexpected type %T for variable %s", name, v.Type, vname)
}
if replaced[vname] {
return fmt.Errorf("section %s: duplicate variable %s", name, vname)
}
if int(v.Offset+v.Size) > len(cpy) {
return fmt.Errorf("section %s: offset %d(+%d) for variable %s is out of bounds", name, v.Offset, v.Size, vname)
}
b, err := sysenc.Marshal(replacement, int(v.Size))
if err != nil {
return fmt.Errorf("marshaling constant replacement %s: %w", vname, err)
}
b.CopyTo(cpy[v.Offset : v.Offset+v.Size])
replaced[vname] = true
}
spec.Contents[0] = MapKV{Key: uint32(0), Value: cpy}
}
var missing []string
for c := range consts {
if !replaced[c] {
missing = append(missing, c)
for n, c := range consts {
v, ok := cs.Variables[n]
if !ok {
missing = append(missing, n)
continue
}
if !v.Constant() {
return fmt.Errorf("variable %s is not a constant", n)
}
if err := v.Set(c); err != nil {
return fmt.Errorf("rewriting constant %s: %w", n, err)
}
}
@@ -210,25 +181,23 @@ func (cs *CollectionSpec) RewriteConstants(consts map[string]interface{}) error
// if this sounds useful.
//
// 'to' must be a pointer to a struct. A field of the
// struct is updated with values from Programs or Maps if it
// has an `ebpf` tag and its type is *ProgramSpec or *MapSpec.
// struct is updated with values from Programs, Maps or Variables if it
// has an `ebpf` tag and its type is *ProgramSpec, *MapSpec or *VariableSpec.
// The tag's value specifies the name of the program or map as
// found in the CollectionSpec.
//
// struct {
// Foo *ebpf.ProgramSpec `ebpf:"xdp_foo"`
// Bar *ebpf.MapSpec `ebpf:"bar_map"`
// Foo *ebpf.ProgramSpec `ebpf:"xdp_foo"`
// Bar *ebpf.MapSpec `ebpf:"bar_map"`
// Var *ebpf.VariableSpec `ebpf:"some_var"`
// Ignored int
// }
//
// Returns an error if any of the eBPF objects can't be found, or
// if the same MapSpec or ProgramSpec is assigned multiple times.
// if the same Spec is assigned multiple times.
func (cs *CollectionSpec) Assign(to interface{}) error {
// Assign() only supports assigning ProgramSpecs and MapSpecs,
// so doesn't load any resources into the kernel.
getValue := func(typ reflect.Type, name string) (interface{}, error) {
switch typ {
case reflect.TypeOf((*ProgramSpec)(nil)):
if p := cs.Programs[name]; p != nil {
return p, nil
@@ -241,6 +210,12 @@ func (cs *CollectionSpec) Assign(to interface{}) error {
}
return nil, fmt.Errorf("missing map %q", name)
case reflect.TypeOf((*VariableSpec)(nil)):
if v := cs.Variables[name]; v != nil {
return v, nil
}
return nil, fmt.Errorf("missing variable %q", name)
default:
return nil, fmt.Errorf("unsupported type %s", typ)
}
@@ -286,6 +261,7 @@ func (cs *CollectionSpec) LoadAndAssign(to interface{}, opts *CollectionOptions)
// Support assigning Programs and Maps, lazy-loading the required objects.
assignedMaps := make(map[string]bool)
assignedProgs := make(map[string]bool)
assignedVars := make(map[string]bool)
getValue := func(typ reflect.Type, name string) (interface{}, error) {
switch typ {
@@ -298,6 +274,10 @@ func (cs *CollectionSpec) LoadAndAssign(to interface{}, opts *CollectionOptions)
assignedMaps[name] = true
return loader.loadMap(name)
case reflect.TypeOf((*Variable)(nil)):
assignedVars[name] = true
return loader.loadVariable(name)
default:
return nil, fmt.Errorf("unsupported type %s", typ)
}
@@ -338,15 +318,22 @@ func (cs *CollectionSpec) LoadAndAssign(to interface{}, opts *CollectionOptions)
for p := range assignedProgs {
delete(loader.programs, p)
}
for p := range assignedVars {
delete(loader.vars, p)
}
return nil
}
// Collection is a collection of Programs and Maps associated
// with their symbols
// Collection is a collection of live BPF resources present in the kernel.
type Collection struct {
Programs map[string]*Program
Maps map[string]*Map
// Variables contains global variables used by the Collection's program(s). On
// kernels older than 5.5, most interactions with Variables return
// [ErrNotSupported].
Variables map[string]*Variable
}
// NewCollection creates a Collection from the given spec, creating and
@@ -387,19 +374,26 @@ func NewCollectionWithOptions(spec *CollectionSpec, opts CollectionOptions) (*Co
}
}
for varName := range spec.Variables {
if _, err := loader.loadVariable(varName); err != nil {
return nil, err
}
}
// Maps can contain Program and Map stubs, so populate them after
// all Maps and Programs have been successfully loaded.
if err := loader.populateDeferredMaps(); err != nil {
return nil, err
}
// Prevent loader.cleanup from closing maps and programs.
maps, progs := loader.maps, loader.programs
loader.maps, loader.programs = nil, nil
// Prevent loader.cleanup from closing maps, programs and vars.
maps, progs, vars := loader.maps, loader.programs, loader.vars
loader.maps, loader.programs, loader.vars = nil, nil, nil
return &Collection{
progs,
maps,
vars,
}, nil
}
@@ -408,6 +402,7 @@ type collectionLoader struct {
opts *CollectionOptions
maps map[string]*Map
programs map[string]*Program
vars map[string]*Variable
}
func newCollectionLoader(coll *CollectionSpec, opts *CollectionOptions) (*collectionLoader, error) {
@@ -416,15 +411,14 @@ func newCollectionLoader(coll *CollectionSpec, opts *CollectionOptions) (*collec
}
// Check for existing MapSpecs in the CollectionSpec for all provided replacement maps.
for name, m := range opts.MapReplacements {
spec, ok := coll.Maps[name]
if !ok {
for name := range opts.MapReplacements {
if _, ok := coll.Maps[name]; !ok {
return nil, fmt.Errorf("replacement map %s not found in CollectionSpec", name)
}
}
if err := spec.Compatible(m); err != nil {
return nil, fmt.Errorf("using replacement map %s: %w", spec.Name, err)
}
if err := populateKallsyms(coll.Programs); err != nil {
return nil, fmt.Errorf("populating kallsyms caches: %w", err)
}
return &collectionLoader{
@@ -432,9 +426,49 @@ func newCollectionLoader(coll *CollectionSpec, opts *CollectionOptions) (*collec
opts,
make(map[string]*Map),
make(map[string]*Program),
make(map[string]*Variable),
}, nil
}
// populateKallsyms populates kallsyms caches, making lookups cheaper later on
// during individual program loading. Since we have less context available
// at those stages, we batch the lookups here instead to avoid redundant work.
func populateKallsyms(progs map[string]*ProgramSpec) error {
// Look up associated kernel modules for all symbols referenced by
// ProgramSpec.AttachTo for program types that support attaching to kmods.
mods := make(map[string]string)
for _, p := range progs {
if p.AttachTo != "" && p.targetsKernelModule() {
mods[p.AttachTo] = ""
}
}
if len(mods) != 0 {
if err := kallsyms.AssignModules(mods); err != nil {
return fmt.Errorf("getting modules from kallsyms: %w", err)
}
}
// Look up addresses of all kernel symbols referenced by all programs.
addrs := make(map[string]uint64)
for _, p := range progs {
iter := p.Instructions.Iterate()
for iter.Next() {
ins := iter.Ins
meta, _ := ins.Metadata.Get(ksymMetaKey{}).(*ksymMeta)
if meta != nil {
addrs[meta.Name] = 0
}
}
}
if len(addrs) != 0 {
if err := kallsyms.AssignAddresses(addrs); err != nil {
return fmt.Errorf("getting addresses from kallsyms: %w", err)
}
}
return nil
}
// close all resources left over in the collectionLoader.
func (cl *collectionLoader) close() {
for _, m := range cl.maps {
@@ -455,7 +489,22 @@ func (cl *collectionLoader) loadMap(mapName string) (*Map, error) {
return nil, fmt.Errorf("missing map %s", mapName)
}
mapSpec = mapSpec.Copy()
// Defer setting the mmapable flag on maps until load time. This avoids the
// MapSpec having different flags on some kernel versions. Also avoid running
// syscalls during ELF loading, so platforms like wasm can also parse an ELF.
if isDataSection(mapSpec.Name) && haveMmapableMaps() == nil {
mapSpec.Flags |= sys.BPF_F_MMAPABLE
}
if replaceMap, ok := cl.opts.MapReplacements[mapName]; ok {
// Check compatibility with the replacement map after setting
// feature-dependent map flags.
if err := mapSpec.Compatible(replaceMap); err != nil {
return nil, fmt.Errorf("using replacement map %s: %w", mapSpec.Name, err)
}
// Clone the map to avoid closing user's map later on.
m, err := replaceMap.Clone()
if err != nil {
@@ -537,6 +586,58 @@ func (cl *collectionLoader) loadProgram(progName string) (*Program, error) {
return prog, nil
}
func (cl *collectionLoader) loadVariable(varName string) (*Variable, error) {
if v := cl.vars[varName]; v != nil {
return v, nil
}
varSpec := cl.coll.Variables[varName]
if varSpec == nil {
return nil, fmt.Errorf("unknown variable %s", varName)
}
// Get the key of the VariableSpec's MapSpec in the CollectionSpec.
var mapName string
for n, ms := range cl.coll.Maps {
if ms == varSpec.m {
mapName = n
break
}
}
if mapName == "" {
return nil, fmt.Errorf("variable %s: underlying MapSpec %s was removed from CollectionSpec", varName, varSpec.m.Name)
}
m, err := cl.loadMap(mapName)
if err != nil {
return nil, fmt.Errorf("variable %s: %w", varName, err)
}
// If the kernel is too old or the underlying map was created without
// BPF_F_MMAPABLE, [Map.Memory] will return ErrNotSupported. In this case,
// emit a Variable with a nil Memory. This keeps Collection{Spec}.Variables
// consistent across systems with different feature sets without breaking
// LoadAndAssign.
mm, err := m.Memory()
if err != nil && !errors.Is(err, ErrNotSupported) {
return nil, fmt.Errorf("variable %s: getting memory for map %s: %w", varName, mapName, err)
}
v, err := newVariable(
varSpec.name,
varSpec.offset,
varSpec.size,
varSpec.t,
mm,
)
if err != nil {
return nil, fmt.Errorf("variable %s: %w", varName, err)
}
cl.vars[varName] = v
return v, nil
}
// populateDeferredMaps iterates maps holding programs or other maps and loads
// any dependencies. Populates all maps in cl and freezes them if specified.
func (cl *collectionLoader) populateDeferredMaps() error {
@@ -603,6 +704,7 @@ func resolveKconfig(m *MapSpec) error {
type configInfo struct {
offset uint32
size uint32
typ btf.Type
}
@@ -619,7 +721,7 @@ func resolveKconfig(m *MapSpec) error {
return fmt.Errorf("variable %s must be a 32 bits integer, got %s", n, v.Type)
}
kv, err := internal.KernelVersion()
kv, err := linux.KernelVersion()
if err != nil {
return fmt.Errorf("getting kernel version: %w", err)
}
@@ -644,6 +746,7 @@ func resolveKconfig(m *MapSpec) error {
default: // Catch CONFIG_*.
configs[n] = configInfo{
offset: vsi.Offset,
size: vsi.Size,
typ: v.Type,
}
}
@@ -651,7 +754,7 @@ func resolveKconfig(m *MapSpec) error {
// We only parse kconfig file if a CONFIG_* variable was found.
if len(configs) > 0 {
f, err := kconfig.Find()
f, err := linux.FindKConfig()
if err != nil {
return fmt.Errorf("cannot find a kconfig file: %w", err)
}
@@ -670,10 +773,10 @@ func resolveKconfig(m *MapSpec) error {
for n, info := range configs {
value, ok := kernelConfig[n]
if !ok {
return fmt.Errorf("config option %q does not exists for this kernel", n)
return fmt.Errorf("config option %q does not exist on this kernel", n)
}
err := kconfig.PutValue(data[info.offset:], info.typ, value)
err := kconfig.PutValue(data[info.offset:info.offset+info.size], info.typ, value)
if err != nil {
return fmt.Errorf("problem adding value for %s: %w", n, err)
}
@@ -723,6 +826,7 @@ func LoadCollection(file string) (*Collection, error) {
func (coll *Collection) Assign(to interface{}) error {
assignedMaps := make(map[string]bool)
assignedProgs := make(map[string]bool)
assignedVars := make(map[string]bool)
// Assign() only transfers already-loaded Maps and Programs. No extra
// loading is done.
@@ -743,6 +847,13 @@ func (coll *Collection) Assign(to interface{}) error {
}
return nil, fmt.Errorf("missing map %q", name)
case reflect.TypeOf((*Variable)(nil)):
if v := coll.Variables[name]; v != nil {
assignedVars[name] = true
return v, nil
}
return nil, fmt.Errorf("missing variable %q", name)
default:
return nil, fmt.Errorf("unsupported type %s", typ)
}
@@ -759,6 +870,9 @@ func (coll *Collection) Assign(to interface{}) error {
for m := range assignedMaps {
delete(coll.Maps, m)
}
for s := range assignedVars {
delete(coll.Variables, s)
}
return nil
}

View File

@@ -16,7 +16,6 @@ import (
"github.com/cilium/ebpf/btf"
"github.com/cilium/ebpf/internal"
"github.com/cilium/ebpf/internal/sys"
"github.com/cilium/ebpf/internal/unix"
)
type kconfigMetaKey struct{}
@@ -33,6 +32,13 @@ type kfuncMeta struct {
Func *btf.Func
}
type ksymMetaKey struct{}
type ksymMeta struct {
Binding elf.SymBind
Name string
}
// elfCode is a convenience to reduce the amount of arguments that have to
// be passed around explicitly. You should treat its contents as immutable.
type elfCode struct {
@@ -43,7 +49,9 @@ type elfCode struct {
btf *btf.Spec
extInfo *btf.ExtInfos
maps map[string]*MapSpec
vars map[string]*VariableSpec
kfuncs map[string]*btf.Func
ksyms map[string]struct{}
kconfig *MapSpec
}
@@ -71,7 +79,7 @@ func LoadCollectionSpecFromReader(rd io.ReaderAt) (*CollectionSpec, error) {
// Checks if the ELF file is for BPF data.
// Old LLVM versions set e_machine to EM_NONE.
if f.File.Machine != unix.EM_NONE && f.File.Machine != elf.EM_BPF {
if f.File.Machine != elf.EM_NONE && f.File.Machine != elf.EM_BPF {
return nil, fmt.Errorf("unexpected machine type for BPF ELF: %s", f.File.Machine)
}
@@ -101,7 +109,7 @@ func LoadCollectionSpecFromReader(rd io.ReaderAt) (*CollectionSpec, error) {
sections[idx] = newElfSection(sec, mapSection)
case sec.Name == ".maps":
sections[idx] = newElfSection(sec, btfMapSection)
case sec.Name == ".bss" || sec.Name == ".data" || strings.HasPrefix(sec.Name, ".rodata"):
case isDataSection(sec.Name):
sections[idx] = newElfSection(sec, dataSection)
case sec.Type == elf.SHT_REL:
// Store relocations under the section index of the target
@@ -134,7 +142,9 @@ func LoadCollectionSpecFromReader(rd io.ReaderAt) (*CollectionSpec, error) {
btf: btfSpec,
extInfo: btfExtInfo,
maps: make(map[string]*MapSpec),
vars: make(map[string]*VariableSpec),
kfuncs: make(map[string]*btf.Func),
ksyms: make(map[string]struct{}),
}
symbols, err := f.Symbols()
@@ -174,7 +184,7 @@ func LoadCollectionSpecFromReader(rd io.ReaderAt) (*CollectionSpec, error) {
return nil, fmt.Errorf("load programs: %w", err)
}
return &CollectionSpec{ec.maps, progs, btfSpec, ec.ByteOrder}, nil
return &CollectionSpec{ec.maps, progs, ec.vars, btfSpec, ec.ByteOrder}, nil
}
func loadLicense(sec *elf.Section) (string, error) {
@@ -201,6 +211,18 @@ func loadVersion(sec *elf.Section, bo binary.ByteOrder) (uint32, error) {
return version, nil
}
func isDataSection(name string) bool {
return name == ".bss" || strings.HasPrefix(name, ".data") || strings.HasPrefix(name, ".rodata")
}
func isConstantDataSection(name string) bool {
return strings.HasPrefix(name, ".rodata")
}
func isKconfigSection(name string) bool {
return name == ".kconfig"
}
type elfSectionKind int
const (
@@ -506,7 +528,7 @@ func (ec *elfCode) relocateInstruction(ins *asm.Instruction, rel elf.Symbol) err
case elf.STT_OBJECT:
// LLVM 9 emits OBJECT-LOCAL symbols for anonymous constants.
if bind != elf.STB_GLOBAL && bind != elf.STB_LOCAL {
if bind != elf.STB_GLOBAL && bind != elf.STB_LOCAL && bind != elf.STB_WEAK {
return fmt.Errorf("direct load: %s: %w: %s", name, errUnsupportedBinding, bind)
}
@@ -614,6 +636,8 @@ func (ec *elfCode) relocateInstruction(ins *asm.Instruction, rel elf.Symbol) err
}
kf := ec.kfuncs[name]
_, ks := ec.ksyms[name]
switch {
// If a Call / DWordLoad instruction is found and the datasec has a btf.Func with a Name
// 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
ins.Constant = 0
case ks && ins.OpCode.IsDWordLoad():
if bind != elf.STB_GLOBAL && bind != elf.STB_WEAK {
return fmt.Errorf("asm relocation: %s: %w: %s", name, errUnsupportedBinding, bind)
}
ins.Metadata.Set(ksymMetaKey{}, &ksymMeta{
Binding: bind,
Name: name,
})
// If no kconfig map is found, this must be a symbol reference from inline
// asm (see testdata/loader.c:asm_relocation()) or a call to a forward
// 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
}
}
// Some maps don't support value sizes, but annotating their map definitions
// with __type macros can still be useful, especially to let bpf2go generate
// type definitions for them.
if value != nil && !mapType.canHaveValueSize() {
valueSize = 0
}
return &MapSpec{
Name: SanitizeName(name, -1),
Type: MapType(mapType),
@@ -1092,12 +1132,21 @@ func (ec *elfCode) loadDataSections() error {
continue
}
if sec.references == 0 {
// Prune data sections which are not referenced by any
// instructions.
// If a section has no references, it will be freed as soon as the
// Collection closes, so creating and populating it is wasteful. If it has
// no symbols, it is likely an ephemeral section used during compilation
// that wasn't sanitized by the bpf linker. (like .rodata.str1.1)
//
// No symbols means no VariableSpecs can be generated from it, making it
// pointless to emit a data section for.
if sec.references == 0 && len(sec.symbols) == 0 {
continue
}
if sec.Size > math.MaxUint32 {
return fmt.Errorf("data section %s: contents exceed maximum size", sec.Name)
}
mapSpec := &MapSpec{
Name: SanitizeName(sec.Name, -1),
Type: Array,
@@ -1106,6 +1155,10 @@ func (ec *elfCode) loadDataSections() error {
MaxEntries: 1,
}
if isConstantDataSection(sec.Name) {
mapSpec.Flags = sys.BPF_F_RDONLY_PROG
}
switch sec.Type {
// Only open the section if we know there's actual data to be read.
case elf.SHT_PROGBITS:
@@ -1113,20 +1166,56 @@ func (ec *elfCode) loadDataSections() error {
if err != nil {
return fmt.Errorf("data section %s: can't get contents: %w", sec.Name, err)
}
if uint64(len(data)) > math.MaxUint32 {
return fmt.Errorf("data section %s: contents exceed maximum size", sec.Name)
}
mapSpec.Contents = []MapKV{{uint32(0), data}}
case elf.SHT_NOBITS:
// NOBITS sections like .bss contain only zeroes, and since data sections
// are Arrays, the kernel already preallocates them. Skip reading zeroes
// from the ELF.
// NOBITS sections like .bss contain only zeroes and are not allocated in
// the ELF. Since data sections are Arrays, the kernel can preallocate
// them. Don't attempt reading zeroes from the ELF, instead allocate the
// zeroed memory to support getting and setting VariableSpecs for sections
// like .bss.
mapSpec.Contents = []MapKV{{uint32(0), make([]byte, sec.Size)}}
default:
return fmt.Errorf("data section %s: unknown section type %s", sec.Name, sec.Type)
}
for off, sym := range sec.symbols {
// Skip symbols marked with the 'hidden' attribute.
if elf.ST_VISIBILITY(sym.Other) == elf.STV_HIDDEN ||
elf.ST_VISIBILITY(sym.Other) == elf.STV_INTERNAL {
continue
}
// Only accept symbols with global or weak bindings. The common
// alternative is STB_LOCAL, which are either function-scoped or declared
// 'static'.
if elf.ST_BIND(sym.Info) != elf.STB_GLOBAL &&
elf.ST_BIND(sym.Info) != elf.STB_WEAK {
continue
}
if ec.vars[sym.Name] != nil {
return fmt.Errorf("data section %s: duplicate variable %s", sec.Name, sym.Name)
}
// Skip symbols starting with a dot, they are compiler-internal symbols
// emitted by clang 11 and earlier and are not cleaned up by the bpf
// compiler backend (e.g. symbols named .Lconstinit.1 in sections like
// .rodata.cst32). Variables in C cannot start with a dot, so filter these
// out.
if strings.HasPrefix(sym.Name, ".") {
continue
}
ec.vars[sym.Name] = &VariableSpec{
name: sym.Name,
offset: off,
size: sym.Size,
m: mapSpec,
}
}
// It is possible for a data section to exist without a corresponding BTF Datasec
// if it only contains anonymous values like macro-defined arrays.
if ec.btf != nil {
@@ -1135,12 +1224,38 @@ func (ec *elfCode) loadDataSections() error {
// Assign the spec's key and BTF only if the Datasec lookup was successful.
mapSpec.Key = &btf.Void{}
mapSpec.Value = ds
}
}
if strings.HasPrefix(sec.Name, ".rodata") {
mapSpec.Flags = unix.BPF_F_RDONLY_PROG
mapSpec.Freeze = true
// Populate VariableSpecs with type information, if available.
for _, v := range ds.Vars {
name := v.Type.TypeName()
if name == "" {
return fmt.Errorf("data section %s: anonymous variable %v", sec.Name, v)
}
vt, ok := v.Type.(*btf.Var)
if !ok {
return fmt.Errorf("data section %s: unexpected type %T for variable %s", sec.Name, v.Type, name)
}
ev := ec.vars[name]
if ev == nil {
// Hidden symbols appear in the BTF Datasec but don't receive a VariableSpec.
continue
}
if uint64(v.Offset) != ev.offset {
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)
}
if uint64(v.Size) != ev.size {
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)
}
// Decouple the Var in the VariableSpec from the underlying DataSec in
// the MapSpec to avoid modifications from affecting map loads later on.
ev.t = btf.Copy(vt).(*btf.Var)
}
}
}
ec.maps[sec.Name] = mapSpec
@@ -1175,8 +1290,7 @@ func (ec *elfCode) loadKconfigSection() error {
KeySize: uint32(4),
ValueSize: ds.Size,
MaxEntries: 1,
Flags: unix.BPF_F_RDONLY_PROG,
Freeze: true,
Flags: sys.BPF_F_RDONLY_PROG,
Key: &btf.Int{Size: 4},
Value: ds,
}
@@ -1201,8 +1315,14 @@ func (ec *elfCode) loadKsymsSection() error {
}
for _, v := range ds.Vars {
// we have already checked the .ksyms Datasec to only contain Func Vars.
ec.kfuncs[v.Type.TypeName()] = v.Type.(*btf.Func)
switch t := v.Type.(type) {
case *btf.Func:
ec.kfuncs[t.TypeName()] = t
case *btf.Var:
ec.ksyms[t.TypeName()] = struct{}{}
default:
return fmt.Errorf("unexpected variable type in .ksyms: %T", v)
}
}
return nil
@@ -1266,10 +1386,10 @@ func getProgType(sectionName string) (ProgramType, AttachType, uint32, string) {
var flags uint32
if t.flags&_SEC_SLEEPABLE > 0 {
flags |= unix.BPF_F_SLEEPABLE
flags |= sys.BPF_F_SLEEPABLE
}
if t.flags&_SEC_XDP_FRAGS > 0 {
flags |= unix.BPF_F_XDP_HAS_FRAGS
flags |= sys.BPF_F_XDP_HAS_FRAGS
}
if t.flags&_SEC_EXP_ATTACH_OPT > 0 {
if programType == XDP {

View File

@@ -18,6 +18,7 @@ var elfSectionDefs = []libbpfElfSectionDef{
{"uretprobe.s+", sys.BPF_PROG_TYPE_KPROBE, 0, _SEC_SLEEPABLE},
{"kprobe.multi+", sys.BPF_PROG_TYPE_KPROBE, sys.BPF_TRACE_KPROBE_MULTI, _SEC_NONE},
{"kretprobe.multi+", sys.BPF_PROG_TYPE_KPROBE, sys.BPF_TRACE_KPROBE_MULTI, _SEC_NONE},
{"kprobe.session+", sys.BPF_PROG_TYPE_KPROBE, sys.BPF_TRACE_KPROBE_SESSION, _SEC_NONE},
{"uprobe.multi+", sys.BPF_PROG_TYPE_KPROBE, sys.BPF_TRACE_UPROBE_MULTI, _SEC_NONE},
{"uretprobe.multi+", sys.BPF_PROG_TYPE_KPROBE, sys.BPF_TRACE_UPROBE_MULTI, _SEC_NONE},
{"uprobe.multi.s+", sys.BPF_PROG_TYPE_KPROBE, sys.BPF_TRACE_UPROBE_MULTI, _SEC_SLEEPABLE},
@@ -69,6 +70,7 @@ var elfSectionDefs = []libbpfElfSectionDef{
{"sockops", sys.BPF_PROG_TYPE_SOCK_OPS, sys.BPF_CGROUP_SOCK_OPS, _SEC_ATTACHABLE_OPT},
{"sk_skb/stream_parser", sys.BPF_PROG_TYPE_SK_SKB, sys.BPF_SK_SKB_STREAM_PARSER, _SEC_ATTACHABLE_OPT},
{"sk_skb/stream_verdict", sys.BPF_PROG_TYPE_SK_SKB, sys.BPF_SK_SKB_STREAM_VERDICT, _SEC_ATTACHABLE_OPT},
{"sk_skb/verdict", sys.BPF_PROG_TYPE_SK_SKB, sys.BPF_SK_SKB_VERDICT, _SEC_ATTACHABLE_OPT},
{"sk_skb", sys.BPF_PROG_TYPE_SK_SKB, 0, _SEC_NONE},
{"sk_msg", sys.BPF_PROG_TYPE_SK_MSG, sys.BPF_SK_MSG_VERDICT, _SEC_ATTACHABLE_OPT},
{"lirc_mode2", sys.BPF_PROG_TYPE_LIRC_MODE2, sys.BPF_LIRC_MODE2, _SEC_ATTACHABLE_OPT},

387
vendor/github.com/cilium/ebpf/info.go generated vendored
View File

@@ -8,10 +8,10 @@ import (
"fmt"
"io"
"os"
"reflect"
"strings"
"syscall"
"time"
"unsafe"
"github.com/cilium/ebpf/asm"
"github.com/cilium/ebpf/btf"
@@ -39,53 +39,83 @@ import (
// MapInfo describes a map.
type MapInfo struct {
Type MapType
id MapID
KeySize uint32
ValueSize uint32
// Type of the map.
Type MapType
// KeySize is the size of the map key in bytes.
KeySize uint32
// ValueSize is the size of the map value in bytes.
ValueSize uint32
// MaxEntries is the maximum number of entries the map can hold. Its meaning
// is map-specific.
MaxEntries uint32
Flags uint32
// Flags used during map creation.
Flags uint32
// Name as supplied by user space at load time. Available from 4.15.
Name string
btf btf.ID
id MapID
btf btf.ID
mapExtra uint64
memlock uint64
frozen bool
}
// newMapInfoFromFd queries map information about the given fd. [sys.ObjInfo] is
// attempted first, supplementing any missing values with information from
// /proc/self/fdinfo. Ignores EINVAL from ObjInfo as well as ErrNotSupported
// from reading fdinfo (indicating the file exists, but no fields of interest
// were found). If both fail, an error is always returned.
func newMapInfoFromFd(fd *sys.FD) (*MapInfo, error) {
var info sys.MapInfo
err := sys.ObjInfo(fd, &info)
if errors.Is(err, syscall.EINVAL) {
return newMapInfoFromProc(fd)
}
if err != nil {
return nil, err
err1 := sys.ObjInfo(fd, &info)
// EINVAL means the kernel doesn't support BPF_OBJ_GET_INFO_BY_FD. Continue
// with fdinfo if that's the case.
if err1 != nil && !errors.Is(err1, unix.EINVAL) {
return nil, fmt.Errorf("getting object info: %w", err1)
}
return &MapInfo{
mi := &MapInfo{
MapType(info.Type),
MapID(info.Id),
info.KeySize,
info.ValueSize,
info.MaxEntries,
uint32(info.MapFlags),
unix.ByteSliceToString(info.Name[:]),
MapID(info.Id),
btf.ID(info.BtfId),
}, nil
info.MapExtra,
0,
false,
}
// Supplement OBJ_INFO with data from /proc/self/fdinfo. It contains fields
// like memlock and frozen that are not present in OBJ_INFO.
err2 := readMapInfoFromProc(fd, mi)
if err2 != nil && !errors.Is(err2, ErrNotSupported) {
return nil, fmt.Errorf("getting map info from fdinfo: %w", err2)
}
if err1 != nil && err2 != nil {
return nil, fmt.Errorf("ObjInfo and fdinfo both failed: objinfo: %w, fdinfo: %w", err1, err2)
}
return mi, nil
}
func newMapInfoFromProc(fd *sys.FD) (*MapInfo, error) {
var mi MapInfo
err := scanFdInfo(fd, map[string]interface{}{
// readMapInfoFromProc queries map information about the given fd from
// /proc/self/fdinfo. It only writes data into fields that have a zero value.
func readMapInfoFromProc(fd *sys.FD, mi *MapInfo) error {
return scanFdInfo(fd, map[string]interface{}{
"map_type": &mi.Type,
"map_id": &mi.id,
"key_size": &mi.KeySize,
"value_size": &mi.ValueSize,
"max_entries": &mi.MaxEntries,
"map_flags": &mi.Flags,
"map_extra": &mi.mapExtra,
"memlock": &mi.memlock,
"frozen": &mi.frozen,
})
if err != nil {
return nil, err
}
return &mi, nil
}
// ID returns the map ID.
@@ -109,6 +139,35 @@ func (mi *MapInfo) BTFID() (btf.ID, bool) {
return mi.btf, mi.btf > 0
}
// MapExtra returns an opaque field whose meaning is map-specific.
//
// Available from 5.16.
//
// The bool return value indicates whether this optional field is available and
// populated, if it was specified during Map creation.
func (mi *MapInfo) MapExtra() (uint64, bool) {
return mi.mapExtra, mi.mapExtra > 0
}
// Memlock returns an approximate number of bytes allocated to this map.
//
// Available from 4.10.
//
// The bool return value indicates whether this optional field is available.
func (mi *MapInfo) Memlock() (uint64, bool) {
return mi.memlock, mi.memlock > 0
}
// Frozen indicates whether [Map.Freeze] was called on this map. If true,
// modifications from user space are not allowed.
//
// Available from 5.2. Requires access to procfs.
//
// If the kernel doesn't support map freezing, this field will always be false.
func (mi *MapInfo) Frozen() bool {
return mi.frozen
}
// programStats holds statistics of a program.
type programStats struct {
// Total accumulated runtime of the program ins ns.
@@ -120,6 +179,40 @@ type programStats struct {
recursionMisses uint64
}
// programJitedInfo holds information about JITed info of a program.
type programJitedInfo struct {
// ksyms holds the ksym addresses of the BPF program, including those of its
// subprograms.
//
// Available from 4.18.
ksyms []uint64
numKsyms uint32
// insns holds the JITed machine native instructions of the program,
// including those of its subprograms.
//
// Available from 4.13.
insns []byte
numInsns uint32
// lineInfos holds the JITed line infos, which are kernel addresses.
//
// Available from 5.0.
lineInfos []uint64
numLineInfos uint32
// lineInfoRecSize is the size of a single line info record.
//
// Available from 5.0.
lineInfoRecSize uint32
// funcLens holds the insns length of each function.
//
// Available from 4.18.
funcLens []uint32
numFuncLens uint32
}
// ProgramInfo describes a program.
type ProgramInfo struct {
Type ProgramType
@@ -133,9 +226,14 @@ type ProgramInfo struct {
haveCreatedByUID bool
btf btf.ID
stats *programStats
loadTime time.Duration
maps []MapID
insns []byte
maps []MapID
insns []byte
jitedSize uint32
verifiedInstructions uint32
jitedInfo programJitedInfo
lineInfos []byte
numLineInfos uint32
@@ -164,6 +262,9 @@ func newProgramInfoFromFd(fd *sys.FD) (*ProgramInfo, error) {
runCount: info.RunCnt,
recursionMisses: info.RecursionMisses,
},
jitedSize: info.JitedProgLen,
loadTime: time.Duration(info.LoadTime),
verifiedInstructions: info.VerifiedInsns,
}
// 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) {
if info.NrMapIds > 0 {
pi.maps = make([]MapID, info.NrMapIds)
info2.NrMapIds = info.NrMapIds
info2.MapIds = sys.NewPointer(unsafe.Pointer(&pi.maps[0]))
info2.MapIds = sys.NewSlicePointer(pi.maps)
makeSecondCall = true
} else if haveProgramInfoMapIDs() == nil {
// This program really has no associated maps.
@@ -215,6 +316,40 @@ func newProgramInfoFromFd(fd *sys.FD) (*ProgramInfo, error) {
makeSecondCall = true
}
pi.jitedInfo.lineInfoRecSize = info.JitedLineInfoRecSize
if info.JitedProgLen > 0 {
pi.jitedInfo.numInsns = info.JitedProgLen
pi.jitedInfo.insns = make([]byte, info.JitedProgLen)
info2.JitedProgLen = info.JitedProgLen
info2.JitedProgInsns = sys.NewSlicePointer(pi.jitedInfo.insns)
makeSecondCall = true
}
if info.NrJitedFuncLens > 0 {
pi.jitedInfo.numFuncLens = info.NrJitedFuncLens
pi.jitedInfo.funcLens = make([]uint32, info.NrJitedFuncLens)
info2.NrJitedFuncLens = info.NrJitedFuncLens
info2.JitedFuncLens = sys.NewSlicePointer(pi.jitedInfo.funcLens)
makeSecondCall = true
}
if info.NrJitedLineInfo > 0 {
pi.jitedInfo.numLineInfos = info.NrJitedLineInfo
pi.jitedInfo.lineInfos = make([]uint64, info.NrJitedLineInfo)
info2.NrJitedLineInfo = info.NrJitedLineInfo
info2.JitedLineInfo = sys.NewSlicePointer(pi.jitedInfo.lineInfos)
info2.JitedLineInfoRecSize = info.JitedLineInfoRecSize
makeSecondCall = true
}
if info.NrJitedKsyms > 0 {
pi.jitedInfo.numKsyms = info.NrJitedKsyms
pi.jitedInfo.ksyms = make([]uint64, info.NrJitedKsyms)
info2.JitedKsyms = sys.NewSlicePointer(pi.jitedInfo.ksyms)
info2.NrJitedKsyms = info.NrJitedKsyms
makeSecondCall = true
}
if makeSecondCall {
if err := sys.ObjInfo(fd, &info2); err != nil {
return nil, err
@@ -230,7 +365,7 @@ func newProgramInfoFromProc(fd *sys.FD) (*ProgramInfo, error) {
"prog_type": &info.Type,
"prog_tag": &info.Tag,
})
if errors.Is(err, errMissingFields) {
if errors.Is(err, ErrNotSupported) {
return nil, &internal.UnsupportedFeatureError{
Name: "reading program info from /proc/self/fdinfo",
MinimumVersion: internal.Version{4, 10, 0},
@@ -305,6 +440,52 @@ func (pi *ProgramInfo) RecursionMisses() (uint64, bool) {
return 0, false
}
// btfSpec returns the BTF spec associated with the program.
func (pi *ProgramInfo) btfSpec() (*btf.Spec, error) {
id, ok := pi.BTFID()
if !ok {
return nil, fmt.Errorf("program created without BTF or unsupported kernel: %w", ErrNotSupported)
}
h, err := btf.NewHandleFromID(id)
if err != nil {
return nil, fmt.Errorf("get BTF handle: %w", err)
}
defer h.Close()
spec, err := h.Spec(nil)
if err != nil {
return nil, fmt.Errorf("get BTF spec: %w", err)
}
return spec, nil
}
// LineInfos returns the BTF line information of the program.
//
// Available from 5.0.
//
// Requires CAP_SYS_ADMIN or equivalent for reading BTF information. Returns
// ErrNotSupported if the program was created without BTF or if the kernel
// doesn't support the field.
func (pi *ProgramInfo) LineInfos() (btf.LineOffsets, error) {
if len(pi.lineInfos) == 0 {
return nil, fmt.Errorf("insufficient permissions or unsupported kernel: %w", ErrNotSupported)
}
spec, err := pi.btfSpec()
if err != nil {
return nil, err
}
return btf.LoadLineInfos(
bytes.NewReader(pi.lineInfos),
internal.NativeEndian,
pi.numLineInfos,
spec,
)
}
// Instructions returns the 'xlated' instruction stream of the program
// after it has been verified and rewritten by the kernel. These instructions
// 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) {
return insns, nil
}
// JitedSize returns the size of the program's JIT-compiled machine code in bytes, which is the
// actual code executed on the host's CPU. This field requires the BPF JIT compiler to be enabled.
//
// Available from 4.13. Reading this metadata requires CAP_BPF or equivalent.
func (pi *ProgramInfo) JitedSize() (uint32, error) {
if pi.jitedSize == 0 {
return 0, fmt.Errorf("insufficient permissions, unsupported kernel, or JIT compiler disabled: %w", ErrNotSupported)
}
return pi.jitedSize, nil
}
// TranslatedSize returns the size of the program's translated instructions in bytes, after it has
// been verified and rewritten by the kernel.
//
// Available from 4.13. Reading this metadata requires CAP_BPF or equivalent.
func (pi *ProgramInfo) TranslatedSize() (int, error) {
insns := len(pi.insns)
if insns == 0 {
return 0, fmt.Errorf("insufficient permissions or unsupported kernel: %w", ErrNotSupported)
}
return insns, nil
}
// MapIDs returns the maps related to the program.
//
// Available from 4.15.
@@ -400,6 +604,106 @@ func (pi *ProgramInfo) MapIDs() ([]MapID, bool) {
return pi.maps, pi.maps != nil
}
// LoadTime returns when the program was loaded since boot time.
//
// Available from 4.15.
//
// The bool return value indicates whether this optional field is available.
func (pi *ProgramInfo) LoadTime() (time.Duration, bool) {
// loadTime and NrMapIds were introduced in the same kernel version.
return pi.loadTime, pi.loadTime > 0
}
// VerifiedInstructions returns the number verified instructions in the program.
//
// Available from 5.16.
//
// The bool return value indicates whether this optional field is available.
func (pi *ProgramInfo) VerifiedInstructions() (uint32, bool) {
return pi.verifiedInstructions, pi.verifiedInstructions > 0
}
// JitedKsymAddrs returns the ksym addresses of the BPF program, including its
// subprograms. The addresses correspond to their symbols in /proc/kallsyms.
//
// Available from 4.18. Note that before 5.x, this field can be empty for
// programs without subprograms (bpf2bpf calls).
//
// The bool return value indicates whether this optional field is available.
//
// When a kernel address can't fit into uintptr (which is usually the case when
// running 32 bit program on a 64 bit kernel), this returns an empty slice and
// a false.
func (pi *ProgramInfo) JitedKsymAddrs() ([]uintptr, bool) {
ksyms := make([]uintptr, 0, len(pi.jitedInfo.ksyms))
if cap(ksyms) == 0 {
return ksyms, false
}
// Check if a kernel address fits into uintptr (it might not when
// using a 32 bit binary on a 64 bit kernel). This check should work
// with any kernel address, since they have 1s at the highest bits.
if a := pi.jitedInfo.ksyms[0]; uint64(uintptr(a)) != a {
return nil, false
}
for _, ksym := range pi.jitedInfo.ksyms {
ksyms = append(ksyms, uintptr(ksym))
}
return ksyms, true
}
// JitedInsns returns the JITed machine native instructions of the program.
//
// Available from 4.13.
//
// The bool return value indicates whether this optional field is available.
func (pi *ProgramInfo) JitedInsns() ([]byte, bool) {
return pi.jitedInfo.insns, len(pi.jitedInfo.insns) > 0
}
// JitedLineInfos returns the JITed line infos of the program.
//
// Available from 5.0.
//
// The bool return value indicates whether this optional field is available.
func (pi *ProgramInfo) JitedLineInfos() ([]uint64, bool) {
return pi.jitedInfo.lineInfos, len(pi.jitedInfo.lineInfos) > 0
}
// JitedFuncLens returns the insns length of each function in the JITed program.
//
// Available from 4.18.
//
// The bool return value indicates whether this optional field is available.
func (pi *ProgramInfo) JitedFuncLens() ([]uint32, bool) {
return pi.jitedInfo.funcLens, len(pi.jitedInfo.funcLens) > 0
}
// FuncInfos returns the offset and function information of all (sub)programs in
// a BPF program.
//
// Available from 5.0.
//
// Requires CAP_SYS_ADMIN or equivalent for reading BTF information. Returns
// ErrNotSupported if the program was created without BTF or if the kernel
// doesn't support the field.
func (pi *ProgramInfo) FuncInfos() (btf.FuncOffsets, error) {
if len(pi.funcInfos) == 0 {
return nil, fmt.Errorf("insufficient permissions or unsupported kernel: %w", ErrNotSupported)
}
spec, err := pi.btfSpec()
if err != nil {
return nil, err
}
return btf.LoadFuncInfos(
bytes.NewReader(pi.funcInfos),
internal.NativeEndian,
pi.numFuncInfos,
spec,
)
}
func scanFdInfo(fd *sys.FD, fields map[string]interface{}) error {
fh, err := os.Open(fmt.Sprintf("/proc/self/fdinfo/%d", fd.Int()))
if err != nil {
@@ -413,8 +717,6 @@ func scanFdInfo(fd *sys.FD, fields map[string]interface{}) error {
return nil
}
var errMissingFields = errors.New("missing fields")
func scanFdInfoReader(r io.Reader, fields map[string]interface{}) error {
var (
scanner = bufio.NewScanner(r)
@@ -433,26 +735,37 @@ func scanFdInfoReader(r io.Reader, fields map[string]interface{}) error {
continue
}
if n, err := fmt.Sscanln(parts[1], field); err != nil || n != 1 {
return fmt.Errorf("can't parse field %s: %v", name, err)
// If field already contains a non-zero value, don't overwrite it with fdinfo.
if zero(field) {
if n, err := fmt.Sscanln(parts[1], field); err != nil || n != 1 {
return fmt.Errorf("can't parse field %s: %v", name, err)
}
}
scanned++
}
if err := scanner.Err(); err != nil {
return err
return fmt.Errorf("scanning fdinfo: %w", err)
}
if len(fields) > 0 && scanned == 0 {
return ErrNotSupported
}
if scanned != len(fields) {
return errMissingFields
return nil
}
func zero(arg any) bool {
v := reflect.ValueOf(arg)
// Unwrap pointers and interfaces.
for v.Kind() == reflect.Pointer ||
v.Kind() == reflect.Interface {
v = v.Elem()
}
return nil
return v.IsZero()
}
// EnableStats starts the measuring of the runtime
@@ -471,7 +784,7 @@ func EnableStats(which uint32) (io.Closer, error) {
return fd, nil
}
var haveProgramInfoMapIDs = internal.NewFeatureTest("map IDs in program info", "4.15", func() error {
var haveProgramInfoMapIDs = internal.NewFeatureTest("map IDs in program info", func() error {
prog, err := progLoad(asm.Instructions{
asm.LoadImm(asm.R0, 0, asm.DWord),
asm.Return(),
@@ -496,4 +809,4 @@ var haveProgramInfoMapIDs = internal.NewFeatureTest("map IDs in program info", "
}
return err
})
}, "4.15")

View File

@@ -23,7 +23,7 @@ func ErrorWithLog(source string, err error, log []byte) *VerifierError {
log = bytes.Trim(log, whitespace)
if len(log) == 0 {
return &VerifierError{source, err, nil, false}
return &VerifierError{source, err, nil}
}
logLines := bytes.Split(log, []byte{'\n'})
@@ -34,7 +34,7 @@ func ErrorWithLog(source string, err error, log []byte) *VerifierError {
lines = append(lines, string(bytes.TrimRight(line, whitespace)))
}
return &VerifierError{source, err, lines, false}
return &VerifierError{source, err, lines}
}
// VerifierError includes information from the eBPF verifier.
@@ -46,8 +46,6 @@ type VerifierError struct {
Cause error
// The verifier output split into lines.
Log []string
// Deprecated: the log is never truncated anymore.
Truncated bool
}
func (le *VerifierError) Unwrap() error {

View File

@@ -3,15 +3,25 @@ package internal
import (
"errors"
"fmt"
"runtime"
"strings"
"sync"
)
// ErrNotSupported indicates that a feature is not supported by the current kernel.
// ErrNotSupported indicates that a feature is not supported.
var ErrNotSupported = errors.New("not supported")
// ErrNotSupportedOnOS indicates that a feature is not supported on the current
// operating system.
var ErrNotSupportedOnOS = fmt.Errorf("%w on %s", ErrNotSupported, runtime.GOOS)
// UnsupportedFeatureError is returned by FeatureTest() functions.
type UnsupportedFeatureError struct {
// The minimum Linux mainline version required for this feature.
// The minimum version required for this feature.
//
// On Linux this refers to the mainline kernel version, on other platforms
// to the version of the runtime.
//
// Used for the error string, and for sanity checking during testing.
MinimumVersion Version
@@ -58,11 +68,44 @@ type FeatureTest struct {
type FeatureTestFn func() error
// NewFeatureTest is a convenient way to create a single [FeatureTest].
func NewFeatureTest(name, version string, fn FeatureTestFn) func() error {
//
// versions specifies in which version of a BPF runtime a feature appeared.
// The format is "GOOS:Major.Minor[.Patch]". GOOS may be omitted when targeting
// Linux. Returns [ErrNotSupportedOnOS] if there is no version specified for the
// current OS.
func NewFeatureTest(name string, fn FeatureTestFn, versions ...string) func() error {
const nativePrefix = runtime.GOOS + ":"
if len(versions) == 0 {
return func() error {
return fmt.Errorf("feature test %q: no versions specified", name)
}
}
ft := &FeatureTest{
Name: name,
Version: version,
Fn: fn,
Name: name,
Fn: fn,
}
for _, version := range versions {
if strings.HasPrefix(version, nativePrefix) {
ft.Version = strings.TrimPrefix(version, nativePrefix)
break
}
if OnLinux && !strings.ContainsRune(version, ':') {
// Allow version numbers without a GOOS prefix on Linux.
ft.Version = version
break
}
}
if ft.Version == "" {
return func() error {
// We don't return an UnsupportedFeatureError here, since that will
// trigger version checks which don't make sense.
return fmt.Errorf("%s: %w", name, ErrNotSupportedOnOS)
}
}
return ft.execute

7
vendor/github.com/cilium/ebpf/internal/goos.go generated vendored Normal file
View File

@@ -0,0 +1,7 @@
package internal
import "runtime"
const (
OnLinux = runtime.GOOS == "linux"
)

View File

@@ -0,0 +1,20 @@
package kallsyms
import "sync"
type cache[K, V comparable] struct {
m sync.Map
}
func (c *cache[K, V]) Load(key K) (value V, _ bool) {
v, ok := c.m.Load(key)
if !ok {
return value, false
}
value = v.(V)
return value, true
}
func (c *cache[K, V]) Store(key K, value V) {
c.m.Store(key, value)
}

View File

@@ -1,74 +1,289 @@
package kallsyms
import (
"bufio"
"bytes"
"errors"
"fmt"
"io"
"os"
"sync"
"slices"
"strconv"
"strings"
"github.com/cilium/ebpf/internal"
)
var kernelModules struct {
sync.RWMutex
// function to kernel module mapping
kmods map[string]string
}
var errAmbiguousKsym = errors.New("multiple kernel symbols with the same name")
// KernelModule returns the kernel module, if any, a probe-able function is contained in.
func KernelModule(fn string) (string, error) {
kernelModules.RLock()
kmods := kernelModules.kmods
kernelModules.RUnlock()
var symAddrs cache[string, uint64]
var symModules cache[string, string]
if kmods == nil {
kernelModules.Lock()
defer kernelModules.Unlock()
kmods = kernelModules.kmods
// Module returns the kernel module providing the given symbol in the kernel, if
// any. Returns an empty string and no error if the symbol is not present in the
// kernel. Only function symbols are considered. Returns an error if multiple
// symbols with the same name were found.
//
// Consider [AssignModules] if you need to resolve multiple symbols, as it will
// only perform one iteration over /proc/kallsyms.
func Module(name string) (string, error) {
if name == "" {
return "", nil
}
if kmods != nil {
return kmods[fn], nil
if mod, ok := symModules.Load(name); ok {
return mod, nil
}
request := map[string]string{name: ""}
if err := AssignModules(request); err != nil {
return "", err
}
return request[name], nil
}
// AssignModules looks up the kernel module providing each given symbol, if any,
// and assigns them to their corresponding values in the symbols map. Only
// function symbols are considered. Results of all lookups are cached,
// successful or otherwise.
//
// Any symbols missing in the kernel are ignored. Returns an error if multiple
// symbols with a given name were found.
func AssignModules(symbols map[string]string) error {
if !internal.OnLinux {
return fmt.Errorf("read /proc/kallsyms: %w", internal.ErrNotSupportedOnOS)
}
if len(symbols) == 0 {
return nil
}
// Attempt to fetch symbols from cache.
request := make(map[string]string)
for name := range symbols {
if mod, ok := symModules.Load(name); ok {
symbols[name] = mod
continue
}
// Mark the symbol to be read from /proc/kallsyms.
request[name] = ""
}
if len(request) == 0 {
// All symbols satisfied from cache.
return nil
}
f, err := os.Open("/proc/kallsyms")
if err != nil {
return "", err
return err
}
defer f.Close()
kmods, err = loadKernelModuleMapping(f)
if err := assignModules(f, request); err != nil {
return fmt.Errorf("assigning symbol modules: %w", err)
}
// Update the cache with the new symbols. Cache all requested symbols, even if
// they're missing or don't belong to a module.
for name, mod := range request {
symModules.Store(name, mod)
symbols[name] = mod
}
return nil
}
// assignModules assigns kernel symbol modules read from f to values requested
// by symbols. Always scans the whole input to make sure the user didn't request
// an ambiguous symbol.
func assignModules(f io.Reader, symbols map[string]string) error {
if len(symbols) == 0 {
return nil
}
found := make(map[string]struct{})
r := newReader(f)
for r.Line() {
// Only look for function symbols in the kernel's text section (tT).
s, err, skip := parseSymbol(r, []rune{'t', 'T'})
if err != nil {
return fmt.Errorf("parsing kallsyms line: %w", err)
}
if skip {
continue
}
if _, requested := symbols[s.name]; !requested {
continue
}
if _, ok := found[s.name]; ok {
// We've already seen this symbol. Return an error to avoid silently
// attaching to a symbol in the wrong module. libbpf also rejects
// referring to ambiguous symbols.
//
// We can't simply check if we already have a value for the given symbol,
// since many won't have an associated kernel module.
return fmt.Errorf("symbol %s: duplicate found at address 0x%x (module %q): %w",
s.name, s.addr, s.mod, errAmbiguousKsym)
}
symbols[s.name] = s.mod
found[s.name] = struct{}{}
}
if err := r.Err(); err != nil {
return fmt.Errorf("reading kallsyms: %w", err)
}
return nil
}
// Address returns the address of the given symbol in the kernel. Returns 0 and
// no error if the symbol is not present. Returns an error if multiple addresses
// were found for a symbol.
//
// Consider [AssignAddresses] if you need to resolve multiple symbols, as it
// will only perform one iteration over /proc/kallsyms.
func Address(symbol string) (uint64, error) {
if symbol == "" {
return 0, nil
}
if addr, ok := symAddrs.Load(symbol); ok {
return addr, nil
}
request := map[string]uint64{symbol: 0}
if err := AssignAddresses(request); err != nil {
return 0, err
}
return request[symbol], nil
}
// AssignAddresses looks up the addresses of the requested symbols in the kernel
// and assigns them to their corresponding values in the symbols map. Results
// of all lookups are cached, successful or otherwise.
//
// Any symbols missing in the kernel are ignored. Returns an error if multiple
// addresses were found for a symbol.
func AssignAddresses(symbols map[string]uint64) error {
if !internal.OnLinux {
return fmt.Errorf("read /proc/kallsyms: %w", internal.ErrNotSupportedOnOS)
}
if len(symbols) == 0 {
return nil
}
// Attempt to fetch symbols from cache.
request := make(map[string]uint64)
for name := range symbols {
if addr, ok := symAddrs.Load(name); ok {
symbols[name] = addr
continue
}
// Mark the symbol to be read from /proc/kallsyms.
request[name] = 0
}
if len(request) == 0 {
// All symbols satisfied from cache.
return nil
}
f, err := os.Open("/proc/kallsyms")
if err != nil {
return "", err
return err
}
defer f.Close()
if err := assignAddresses(f, request); err != nil {
return fmt.Errorf("loading symbol addresses: %w", err)
}
kernelModules.kmods = kmods
return kmods[fn], nil
// Update the cache with the new symbols. Cache all requested symbols even if
// they weren't found, to avoid repeated lookups.
for name, addr := range request {
symAddrs.Store(name, addr)
symbols[name] = addr
}
return nil
}
// FlushKernelModuleCache removes any cached information about function to kernel module mapping.
func FlushKernelModuleCache() {
kernelModules.Lock()
defer kernelModules.Unlock()
kernelModules.kmods = nil
}
func loadKernelModuleMapping(f io.Reader) (map[string]string, error) {
mods := make(map[string]string)
scanner := bufio.NewScanner(f)
for scanner.Scan() {
fields := bytes.Fields(scanner.Bytes())
if len(fields) < 4 {
// assignAddresses assigns kernel symbol addresses read from f to values
// requested by symbols. Always scans the whole input to make sure the user
// didn't request an ambiguous symbol.
func assignAddresses(f io.Reader, symbols map[string]uint64) error {
if len(symbols) == 0 {
return nil
}
r := newReader(f)
for r.Line() {
s, err, skip := parseSymbol(r, nil)
if err != nil {
return fmt.Errorf("parsing kallsyms line: %w", err)
}
if skip {
continue
}
switch string(fields[1]) {
case "t", "T":
mods[string(fields[2])] = string(bytes.Trim(fields[3], "[]"))
existing, requested := symbols[s.name]
if existing != 0 {
// Multiple addresses for a symbol have been found. Return a friendly
// error to avoid silently attaching to the wrong symbol. libbpf also
// rejects referring to ambiguous symbols.
return fmt.Errorf("symbol %s(0x%x): duplicate found at address 0x%x: %w", s.name, existing, s.addr, errAmbiguousKsym)
}
if requested {
symbols[s.name] = s.addr
}
}
if err := r.Err(); err != nil {
return fmt.Errorf("reading kallsyms: %w", err)
}
return nil
}
type ksym struct {
addr uint64
name string
mod string
}
// parseSymbol parses a line from /proc/kallsyms into an address, type, name and
// module. Skip will be true if the symbol doesn't match any of the given symbol
// types. See `man 1 nm` for all available types.
//
// Example line: `ffffffffc1682010 T nf_nat_init [nf_nat]`
func parseSymbol(r *reader, types []rune) (s ksym, err error, skip bool) {
for i := 0; r.Word(); i++ {
switch i {
// Address of the symbol.
case 0:
s.addr, err = strconv.ParseUint(r.Text(), 16, 64)
if err != nil {
return s, fmt.Errorf("parsing address: %w", err), false
}
// Type of the symbol. Assume the character is ASCII-encoded by converting
// it directly to a rune, since it's a fixed field controlled by the kernel.
case 1:
if len(types) > 0 && !slices.Contains(types, rune(r.Bytes()[0])) {
return s, nil, true
}
// Name of the symbol.
case 2:
s.name = r.Text()
// Kernel module the symbol is provided by.
case 3:
s.mod = strings.Trim(r.Text(), "[]")
// Ignore any future fields.
default:
continue
break
}
}
if scanner.Err() != nil {
return nil, scanner.Err()
}
return mods, nil
return
}

View File

@@ -0,0 +1,118 @@
package kallsyms
import (
"bufio"
"io"
"unicode"
"unicode/utf8"
)
// reader is a line and word-oriented reader built for reading /proc/kallsyms.
// It takes an io.Reader and iterates its contents line by line, then word by
// word.
//
// It's designed to allow partial reading of lines without paying the cost of
// allocating objects that will never be accessed, resulting in less work for
// the garbage collector.
type reader struct {
s *bufio.Scanner
line []byte
word []byte
err error
}
func newReader(r io.Reader) *reader {
return &reader{
s: bufio.NewScanner(r),
}
}
// Bytes returns the current word as a byte slice.
func (r *reader) Bytes() []byte {
return r.word
}
// Text returns the output of Bytes as a string.
func (r *reader) Text() string {
return string(r.Bytes())
}
// Line advances the reader to the next line in the input. Calling Line resets
// the current word, making [reader.Bytes] and [reader.Text] return empty
// values. Follow this up with a call to [reader.Word].
//
// Like [bufio.Scanner], [reader.Err] needs to be checked after Line returns
// false to determine if an error occurred during reading.
//
// Returns true if Line can be called again. Returns false if all lines in the
// input have been read.
func (r *reader) Line() bool {
for r.s.Scan() {
line := r.s.Bytes()
if len(line) == 0 {
continue
}
r.line = line
r.word = nil
return true
}
if err := r.s.Err(); err != nil {
r.err = err
}
return false
}
// Word advances the reader to the next word in the current line.
//
// Returns true if a word is found and Word should be called again. Returns
// false when all words on the line have been read.
func (r *reader) Word() bool {
if len(r.line) == 0 {
return false
}
// Find next word start, skipping leading spaces.
start := 0
for width := 0; start < len(r.line); start += width {
var c rune
c, width = utf8.DecodeRune(r.line[start:])
if !unicode.IsSpace(c) {
break
}
}
// Whitespace scanning reached the end of the line due to trailing whitespace,
// meaning there are no more words to read
if start == len(r.line) {
return false
}
// Find next word end.
for width, i := 0, start; i < len(r.line); i += width {
var c rune
c, width = utf8.DecodeRune(r.line[i:])
if unicode.IsSpace(c) {
r.word = r.line[start:i]
r.line = r.line[i:]
return true
}
}
// The line contains data, but no end-of-word boundary was found. This is the
// last, unterminated word in the line.
if len(r.line) > start {
r.word = r.line[start:]
r.line = nil
return true
}
return false
}
func (r *reader) Err() error {
return r.err
}

View File

@@ -1,3 +1,4 @@
// Package kconfig implements a parser for the format of Linux's .config file.
package kconfig
import (
@@ -7,7 +8,6 @@ import (
"fmt"
"io"
"math"
"os"
"strconv"
"strings"
@@ -15,30 +15,6 @@ import (
"github.com/cilium/ebpf/internal"
)
// Find find a kconfig file on the host.
// It first reads from /boot/config- of the current running kernel and tries
// /proc/config.gz if nothing was found in /boot.
// If none of the file provide a kconfig, it returns an error.
func Find() (*os.File, error) {
kernelRelease, err := internal.KernelRelease()
if err != nil {
return nil, fmt.Errorf("cannot get kernel release: %w", err)
}
path := "/boot/config-" + kernelRelease
f, err := os.Open(path)
if err == nil {
return f, nil
}
f, err = os.Open("/proc/config.gz")
if err == nil {
return f, nil
}
return nil, fmt.Errorf("neither %s nor /proc/config.gz provide a kconfig", path)
}
// Parse parses the kconfig file for which a reader is given.
// All the CONFIG_* which are in filter and which are set set will be
// 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 {
switch value {
case "y", "n", "m":
return putValueTri(data, typ, value)
default:
if strings.HasPrefix(value, `"`) {
return putValueString(data, typ, value)
}
return putValueNumber(data, typ, value)
}
if strings.HasPrefix(value, `"`) {
return putValueString(data, typ, value)
}
return putValueNumber(data, typ, value)
}
// Golang translation of libbpf_tristate enum:
@@ -169,6 +146,10 @@ func putValueTri(data []byte, typ btf.Type, value string) error {
return fmt.Errorf("cannot use enum %q, only libbpf_tristate is supported", v.Name)
}
if len(data) != 4 {
return fmt.Errorf("expected enum value to occupy 4 bytes in datasec, got: %d", len(data))
}
var tri triState
switch value {
case "y":
@@ -178,10 +159,10 @@ func putValueTri(data []byte, typ btf.Type, value string) error {
case "n":
tri = TriNo
default:
return fmt.Errorf("value %q is not support for libbpf_tristate", value)
return fmt.Errorf("value %q is not supported for libbpf_tristate", value)
}
internal.NativeEndian.PutUint64(data, uint64(tri))
internal.NativeEndian.PutUint32(data, uint32(tri))
default:
return fmt.Errorf("cannot add number value, expected btf.Int or btf.Enum, got: %T", v)
}

View File

@@ -1,9 +1,11 @@
package internal
package linux
import (
"errors"
"fmt"
"io"
_ "unsafe"
"github.com/cilium/ebpf/internal"
"github.com/cilium/ebpf/internal/unix"
)
type auxvPairReader interface {
@@ -17,11 +19,8 @@ const (
_AT_SYSINFO_EHDR = 33 // Offset to vDSO blob in process image
)
//go:linkname runtime_getAuxv runtime.getAuxv
func runtime_getAuxv() []uintptr
type auxvRuntimeReader struct {
data []uintptr
data [][2]uintptr
index int
}
@@ -37,20 +36,23 @@ func (r *auxvRuntimeReader) ReadAuxvPair() (uint64, uint64, error) {
// we manually add the (_AT_NULL, _AT_NULL) pair at the end
// that is not provided by the go runtime
var tag, value uintptr
if r.index+1 < len(r.data) {
tag, value = r.data[r.index], r.data[r.index+1]
if r.index < len(r.data) {
tag, value = r.data[r.index][0], r.data[r.index][1]
} else {
tag, value = _AT_NULL, _AT_NULL
}
r.index += 2
r.index += 1
return uint64(tag), uint64(value), nil
}
func newAuxvRuntimeReader() (auxvPairReader, error) {
data := runtime_getAuxv()
if !internal.OnLinux {
return nil, fmt.Errorf("read auxv from runtime: %w", internal.ErrNotSupportedOnOS)
}
if len(data)%2 != 0 {
return nil, errors.New("malformed auxv passed from runtime")
data, err := unix.Auxv()
if err != nil {
return nil, fmt.Errorf("read auxv from runtime: %w", err)
}
return &auxvRuntimeReader{

2
vendor/github.com/cilium/ebpf/internal/linux/doc.go generated vendored Normal file
View File

@@ -0,0 +1,2 @@
// Package linux contains OS specific wrappers around package unix.
package linux

View File

@@ -0,0 +1,31 @@
package linux
import (
"fmt"
"os"
)
// FindKConfig searches for a kconfig file on the host.
//
// It first reads from /boot/config- of the current running kernel and tries
// /proc/config.gz if nothing was found in /boot.
// If none of the file provide a kconfig, it returns an error.
func FindKConfig() (*os.File, error) {
kernelRelease, err := KernelRelease()
if err != nil {
return nil, fmt.Errorf("cannot get kernel release: %w", err)
}
path := "/boot/config-" + kernelRelease
f, err := os.Open(path)
if err == nil {
return f, nil
}
f, err = os.Open("/proc/config.gz")
if err == nil {
return f, nil
}
return nil, fmt.Errorf("neither %s nor /proc/config.gz provide a kconfig", path)
}

View File

@@ -1,4 +1,4 @@
package internal
package linux
import (
"runtime"

View File

@@ -1,4 +1,4 @@
package internal
package linux
import (
"unsafe"

View File

@@ -1,4 +1,4 @@
package internal
package linux
import (
"debug/elf"
@@ -9,6 +9,7 @@ import (
"math"
"os"
"github.com/cilium/ebpf/internal"
"github.com/cilium/ebpf/internal/unix"
)
@@ -82,7 +83,7 @@ type elfNoteHeader struct {
// vdsoLinuxVersionCode returns the LINUX_VERSION_CODE embedded in
// the ELF notes section of the binary provided by the reader.
func vdsoLinuxVersionCode(r io.ReaderAt) (uint32, error) {
hdr, err := NewSafeELFFile(r)
hdr, err := internal.NewSafeELFFile(r)
if err != nil {
return 0, fmt.Errorf("reading vDSO ELF: %w", err)
}
@@ -110,7 +111,7 @@ func vdsoLinuxVersionCode(r io.ReaderAt) (uint32, error) {
var name string
if n.NameSize > 0 {
// Read the note name, aligned to 4 bytes.
buf := make([]byte, Align(n.NameSize, 4))
buf := make([]byte, internal.Align(n.NameSize, 4))
if err := binary.Read(sr, hdr.ByteOrder, &buf); err != nil {
return 0, fmt.Errorf("reading note name: %w", err)
}
@@ -132,7 +133,7 @@ func vdsoLinuxVersionCode(r io.ReaderAt) (uint32, error) {
}
// Discard the note descriptor if it exists but we're not interested in it.
if _, err := io.CopyN(io.Discard, sr, int64(Align(n.DescSize, 4))); err != nil {
if _, err := io.CopyN(io.Discard, sr, int64(internal.Align(n.DescSize, 4))); err != nil {
return 0, err
}
}

View File

@@ -0,0 +1,34 @@
package linux
import (
"fmt"
"sync"
"github.com/cilium/ebpf/internal"
"github.com/cilium/ebpf/internal/unix"
)
// KernelVersion returns the version of the currently running kernel.
var KernelVersion = sync.OnceValues(detectKernelVersion)
// detectKernelVersion returns the version of the running kernel.
func detectKernelVersion() (internal.Version, error) {
vc, err := vdsoVersion()
if err != nil {
return internal.Version{}, err
}
return internal.NewVersionFromCode(vc), nil
}
// KernelRelease returns the release string of the running kernel.
// Its format depends on the Linux distribution and corresponds to directory
// names in /lib/modules by convention. Some examples are 5.15.17-1-lts and
// 4.19.0-16-amd64.
func KernelRelease() (string, error) {
var uname unix.Utsname
if err := unix.Uname(&uname); err != nil {
return "", fmt.Errorf("uname failed: %w", err)
}
return unix.ByteSliceToString(uname.Release[:]), nil
}

View File

@@ -1,13 +1,33 @@
package internal
import "golang.org/x/exp/constraints"
// Align returns 'n' updated to 'alignment' boundary.
func Align[I constraints.Integer](n, alignment I) I {
func Align[I Integer](n, alignment I) I {
return (n + alignment - 1) / alignment * alignment
}
// IsPow returns true if n is a power of two.
func IsPow[I constraints.Integer](n I) bool {
func IsPow[I Integer](n I) bool {
return n != 0 && (n&(n-1)) == 0
}
// Between returns the value clamped between a and b.
func Between[I Integer](val, a, b I) I {
lower, upper := a, b
if lower > upper {
upper, lower = a, b
}
val = min(val, upper)
return max(val, lower)
}
// Integer represents all possible integer types.
// Remove when x/exp/constraints is moved to the standard library.
type Integer interface {
~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr
}
// List of integer types known by the Go compiler. Used by TestIntegerConstraint
// to warn if a new integer type is introduced. Remove when x/exp/constraints
// is moved to the standard library.
var integers = []string{"int", "int8", "int16", "int32", "int64", "uint", "uint8", "uint16", "uint32", "uint64", "uintptr"}

View File

@@ -4,9 +4,12 @@ import (
"fmt"
"math"
"os"
"path/filepath"
"runtime"
"strconv"
"strings"
"github.com/cilium/ebpf/internal/testutils/fdtrace"
"github.com/cilium/ebpf/internal/unix"
)
@@ -17,15 +20,7 @@ type FD struct {
}
func newFD(value int) *FD {
if onLeakFD != nil {
// Attempt to store the caller's stack for the given fd value.
// Panic if fds contains an existing stack for the fd.
old, exist := fds.LoadOrStore(value, callersFrames())
if exist {
f := old.(*runtime.Frames)
panic(fmt.Sprintf("found existing stack for fd %d:\n%s", value, FormatFrames(f)))
}
}
fdtrace.TraceFD(value, 1)
fd := &FD{value}
runtime.SetFinalizer(fd, (*FD).finalize)
@@ -39,13 +34,7 @@ func (fd *FD) finalize() {
return
}
// Invoke the fd leak callback. Calls LoadAndDelete to guarantee the callback
// is invoked at most once for one sys.FD allocation, runtime.Frames can only
// be unwound once.
f, ok := fds.LoadAndDelete(fd.Int())
if ok && onLeakFD != nil {
onLeakFD(f.(*runtime.Frames))
}
fdtrace.LeakFD(fd.raw)
_ = fd.Close()
}
@@ -92,12 +81,15 @@ func (fd *FD) Close() error {
return nil
}
return unix.Close(fd.disown())
return unix.Close(fd.Disown())
}
func (fd *FD) disown() int {
value := int(fd.raw)
fds.Delete(int(value))
// Disown destroys the FD and returns its raw file descriptor without closing
// it. After this call, the underlying fd is no longer tied to the FD's
// lifecycle.
func (fd *FD) Disown() int {
value := fd.raw
fdtrace.ForgetFD(value)
fd.raw = -1
runtime.SetFinalizer(fd, nil)
@@ -129,5 +121,45 @@ func (fd *FD) File(name string) *os.File {
return nil
}
return os.NewFile(uintptr(fd.disown()), name)
return os.NewFile(uintptr(fd.Disown()), name)
}
// ObjGetTyped wraps [ObjGet] with a readlink call to extract the type of the
// underlying bpf object.
func ObjGetTyped(attr *ObjGetAttr) (*FD, ObjType, error) {
fd, err := ObjGet(attr)
if err != nil {
return nil, 0, err
}
typ, err := readType(fd)
if err != nil {
_ = fd.Close()
return nil, 0, fmt.Errorf("reading fd type: %w", err)
}
return fd, typ, nil
}
// readType returns the bpf object type of the file descriptor by calling
// readlink(3). Returns an error if the file descriptor does not represent a bpf
// object.
func readType(fd *FD) (ObjType, error) {
s, err := os.Readlink(filepath.Join("/proc/self/fd/", fd.String()))
if err != nil {
return 0, fmt.Errorf("readlink fd %d: %w", fd.Int(), err)
}
s = strings.TrimPrefix(s, "anon_inode:")
switch s {
case "bpf-map":
return BPF_TYPE_MAP, nil
case "bpf-prog":
return BPF_TYPE_PROG, nil
case "bpf-link":
return BPF_TYPE_LINK, nil
}
return 0, fmt.Errorf("unknown type %s of fd %d", s, fd.Int())
}

View File

@@ -1,93 +0,0 @@
package sys
import (
"bytes"
"fmt"
"runtime"
"sync"
)
// OnLeakFD controls tracing [FD] lifetime to detect resources that are not
// closed by Close().
//
// If fn is not nil, tracing is enabled for all FDs created going forward. fn is
// invoked for all FDs that are closed by the garbage collector instead of an
// explicit Close() by a caller. Calling OnLeakFD twice with a non-nil fn
// (without disabling tracing in the meantime) will cause a panic.
//
// If fn is nil, tracing will be disabled. Any FDs that have not been closed are
// considered to be leaked, fn will be invoked for them, and the process will be
// terminated.
//
// fn will be invoked at most once for every unique sys.FD allocation since a
// runtime.Frames can only be unwound once.
func OnLeakFD(fn func(*runtime.Frames)) {
// Enable leak tracing if new fn is provided.
if fn != nil {
if onLeakFD != nil {
panic("OnLeakFD called twice with non-nil fn")
}
onLeakFD = fn
return
}
// fn is nil past this point.
if onLeakFD == nil {
return
}
// Call onLeakFD for all open fds.
if fs := flushFrames(); len(fs) != 0 {
for _, f := range fs {
onLeakFD(f)
}
}
onLeakFD = nil
}
var onLeakFD func(*runtime.Frames)
// fds is a registry of all file descriptors wrapped into sys.fds that were
// created while an fd tracer was active.
var fds sync.Map // map[int]*runtime.Frames
// flushFrames removes all elements from fds and returns them as a slice. This
// deals with the fact that a runtime.Frames can only be unwound once using
// Next().
func flushFrames() []*runtime.Frames {
var frames []*runtime.Frames
fds.Range(func(key, value any) bool {
frames = append(frames, value.(*runtime.Frames))
fds.Delete(key)
return true
})
return frames
}
func callersFrames() *runtime.Frames {
c := make([]uintptr, 32)
// Skip runtime.Callers and this function.
i := runtime.Callers(2, c)
if i == 0 {
return nil
}
return runtime.CallersFrames(c)
}
// FormatFrames formats a runtime.Frames as a human-readable string.
func FormatFrames(fs *runtime.Frames) string {
var b bytes.Buffer
for {
f, more := fs.Next()
b.WriteString(fmt.Sprintf("\t%s+%#x\n\t\t%s:%d\n", f.Function, f.PC-f.Entry, f.File, f.Line))
if !more {
break
}
}
return b.String()
}

View File

@@ -1,53 +0,0 @@
// Code generated by "stringer -type MapFlags"; DO NOT EDIT.
package sys
import "strconv"
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[BPF_F_NO_PREALLOC-1]
_ = x[BPF_F_NO_COMMON_LRU-2]
_ = x[BPF_F_NUMA_NODE-4]
_ = x[BPF_F_RDONLY-8]
_ = x[BPF_F_WRONLY-16]
_ = x[BPF_F_STACK_BUILD_ID-32]
_ = x[BPF_F_ZERO_SEED-64]
_ = x[BPF_F_RDONLY_PROG-128]
_ = x[BPF_F_WRONLY_PROG-256]
_ = x[BPF_F_CLONE-512]
_ = x[BPF_F_MMAPABLE-1024]
_ = x[BPF_F_PRESERVE_ELEMS-2048]
_ = x[BPF_F_INNER_MAP-4096]
_ = x[BPF_F_LINK-8192]
_ = x[BPF_F_PATH_FD-16384]
}
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"
var _MapFlags_map = map[MapFlags]string{
1: _MapFlags_name[0:17],
2: _MapFlags_name[17:36],
4: _MapFlags_name[36:51],
8: _MapFlags_name[51:63],
16: _MapFlags_name[63:75],
32: _MapFlags_name[75:95],
64: _MapFlags_name[95:110],
128: _MapFlags_name[110:127],
256: _MapFlags_name[127:144],
512: _MapFlags_name[144:155],
1024: _MapFlags_name[155:169],
2048: _MapFlags_name[169:189],
4096: _MapFlags_name[189:204],
8192: _MapFlags_name[204:214],
16384: _MapFlags_name[214:227],
}
func (i MapFlags) String() string {
if str, ok := _MapFlags_map[i]; ok {
return str
}
return "MapFlags(" + strconv.FormatInt(int64(i), 10) + ")"
}

View File

@@ -1,4 +1,4 @@
package internal
package sys
import (
"errors"
@@ -7,11 +7,11 @@ import (
"path/filepath"
"runtime"
"github.com/cilium/ebpf/internal/sys"
"github.com/cilium/ebpf/internal/linux"
"github.com/cilium/ebpf/internal/unix"
)
func Pin(currentPath, newPath string, fd *sys.FD) error {
func Pin(currentPath, newPath string, fd *FD) error {
if newPath == "" {
return errors.New("given pinning path cannot be empty")
}
@@ -19,7 +19,7 @@ func Pin(currentPath, newPath string, fd *sys.FD) error {
return nil
}
fsType, err := FSType(filepath.Dir(newPath))
fsType, err := linux.FSType(filepath.Dir(newPath))
if err != nil {
return err
}
@@ -30,8 +30,8 @@ func Pin(currentPath, newPath string, fd *sys.FD) error {
defer runtime.KeepAlive(fd)
if currentPath == "" {
return sys.ObjPin(&sys.ObjPinAttr{
Pathname: sys.NewStringPointer(newPath),
return ObjPin(&ObjPinAttr{
Pathname: NewStringPointer(newPath),
BpfFd: fd.Uint(),
})
}
@@ -47,8 +47,8 @@ func Pin(currentPath, newPath string, fd *sys.FD) error {
return fmt.Errorf("unable to move pinned object to new path %v: %w", newPath, err)
}
// Internal state not in sync with the file system so let's fix it.
return sys.ObjPin(&sys.ObjPinAttr{
Pathname: sys.NewStringPointer(newPath),
return ObjPin(&ObjPinAttr{
Pathname: NewStringPointer(newPath),
BpfFd: fd.Uint(),
})
}

View File

@@ -11,13 +11,13 @@ func NewPointer(ptr unsafe.Pointer) Pointer {
return Pointer{ptr: ptr}
}
// NewSlicePointer creates a 64-bit pointer from a byte slice.
func NewSlicePointer(buf []byte) Pointer {
// NewSlicePointer creates a 64-bit pointer from a slice.
func NewSlicePointer[T comparable](buf []T) Pointer {
if len(buf) == 0 {
return Pointer{}
}
return Pointer{ptr: unsafe.Pointer(&buf[0])}
return Pointer{ptr: unsafe.Pointer(unsafe.SliceData(buf))}
}
// NewSlicePointerLen creates a 64-bit pointer from a byte slice.

View File

@@ -2,7 +2,6 @@ package sys
import (
"runtime"
"syscall"
"unsafe"
"github.com/cilium/ebpf/internal/unix"
@@ -11,7 +10,7 @@ import (
// ENOTSUPP is a Linux internal error code that has leaked into UAPI.
//
// It is not the same as ENOTSUP or EOPNOTSUPP.
const ENOTSUPP = syscall.Errno(524)
const ENOTSUPP = unix.Errno(524)
// BPF wraps SYS_BPF.
//
@@ -133,12 +132,12 @@ func ObjInfo(fd *FD, info Info) error {
// BPFObjName is a null-terminated string made up of
// 'A-Za-z0-9_' characters.
type ObjName [unix.BPF_OBJ_NAME_LEN]byte
type ObjName [BPF_OBJ_NAME_LEN]byte
// NewObjName truncates the result if it is too long.
func NewObjName(name string) ObjName {
var result ObjName
copy(result[:unix.BPF_OBJ_NAME_LEN-1], name)
copy(result[:BPF_OBJ_NAME_LEN-1], name)
return result
}
@@ -160,29 +159,6 @@ type BTFID uint32
// TypeID identifies a type in a BTF blob.
type TypeID uint32
// MapFlags control map behaviour.
type MapFlags uint32
//go:generate go run golang.org/x/tools/cmd/stringer@latest -type MapFlags
const (
BPF_F_NO_PREALLOC MapFlags = 1 << iota
BPF_F_NO_COMMON_LRU
BPF_F_NUMA_NODE
BPF_F_RDONLY
BPF_F_WRONLY
BPF_F_STACK_BUILD_ID
BPF_F_ZERO_SEED
BPF_F_RDONLY_PROG
BPF_F_WRONLY_PROG
BPF_F_CLONE
BPF_F_MMAPABLE
BPF_F_PRESERVE_ELEMS
BPF_F_INNER_MAP
BPF_F_LINK
BPF_F_PATH_FD
)
// Flags used by bpf_mprog.
const (
BPF_F_REPLACE = 1 << (iota + 2)
@@ -192,12 +168,22 @@ const (
BPF_F_LINK_MPROG = 1 << 13 // aka BPF_F_LINK
)
// wrappedErrno wraps syscall.Errno to prevent direct comparisons with
// Flags used by BPF_PROG_LOAD.
const (
BPF_F_SLEEPABLE = 1 << 4
BPF_F_XDP_HAS_FRAGS = 1 << 5
BPF_F_XDP_DEV_BOUND_ONLY = 1 << 6
)
const BPF_TAG_SIZE = 8
const BPF_OBJ_NAME_LEN = 16
// wrappedErrno wraps [unix.Errno] to prevent direct comparisons with
// syscall.E* or unix.E* constants.
//
// You should never export an error of this type.
type wrappedErrno struct {
syscall.Errno
unix.Errno
}
func (we wrappedErrno) Unwrap() error {
@@ -213,10 +199,10 @@ func (we wrappedErrno) Error() string {
type syscallError struct {
error
errno syscall.Errno
errno unix.Errno
}
func Error(err error, errno syscall.Errno) error {
func Error(err error, errno unix.Errno) error {
return &syscallError{err, errno}
}

View File

@@ -6,6 +6,176 @@ import (
"unsafe"
)
const (
BPF_ADJ_ROOM_ENCAP_L2_MASK = 255
BPF_ADJ_ROOM_ENCAP_L2_SHIFT = 56
BPF_ANY = 0
BPF_CSUM_LEVEL_DEC = 2
BPF_CSUM_LEVEL_INC = 1
BPF_CSUM_LEVEL_QUERY = 0
BPF_CSUM_LEVEL_RESET = 3
BPF_EXIST = 2
BPF_FIB_LKUP_RET_BLACKHOLE = 1
BPF_FIB_LKUP_RET_FRAG_NEEDED = 8
BPF_FIB_LKUP_RET_FWD_DISABLED = 5
BPF_FIB_LKUP_RET_NOT_FWDED = 4
BPF_FIB_LKUP_RET_NO_NEIGH = 7
BPF_FIB_LKUP_RET_NO_SRC_ADDR = 9
BPF_FIB_LKUP_RET_PROHIBIT = 3
BPF_FIB_LKUP_RET_SUCCESS = 0
BPF_FIB_LKUP_RET_UNREACHABLE = 2
BPF_FIB_LKUP_RET_UNSUPP_LWT = 6
BPF_FIB_LOOKUP_DIRECT = 1
BPF_FIB_LOOKUP_MARK = 32
BPF_FIB_LOOKUP_OUTPUT = 2
BPF_FIB_LOOKUP_SKIP_NEIGH = 4
BPF_FIB_LOOKUP_SRC = 16
BPF_FIB_LOOKUP_TBID = 8
BPF_FLOW_DISSECTOR_F_PARSE_1ST_FRAG = 1
BPF_FLOW_DISSECTOR_F_STOP_AT_ENCAP = 4
BPF_FLOW_DISSECTOR_F_STOP_AT_FLOW_LABEL = 2
BPF_F_ADJ_ROOM_DECAP_L3_IPV4 = 128
BPF_F_ADJ_ROOM_DECAP_L3_IPV6 = 256
BPF_F_ADJ_ROOM_ENCAP_L2_ETH = 64
BPF_F_ADJ_ROOM_ENCAP_L3_IPV4 = 2
BPF_F_ADJ_ROOM_ENCAP_L3_IPV6 = 4
BPF_F_ADJ_ROOM_ENCAP_L4_GRE = 8
BPF_F_ADJ_ROOM_ENCAP_L4_UDP = 16
BPF_F_ADJ_ROOM_FIXED_GSO = 1
BPF_F_ADJ_ROOM_NO_CSUM_RESET = 32
BPF_F_BPRM_SECUREEXEC = 1
BPF_F_BROADCAST = 8
BPF_F_CLONE = 512
BPF_F_CTXLEN_MASK = 4503595332403200
BPF_F_CURRENT_CPU = 4294967295
BPF_F_CURRENT_NETNS = 18446744073709551615
BPF_F_DONT_FRAGMENT = 4
BPF_F_EXCLUDE_INGRESS = 16
BPF_F_FAST_STACK_CMP = 512
BPF_F_GET_BRANCH_RECORDS_SIZE = 1
BPF_F_HDR_FIELD_MASK = 15
BPF_F_INDEX_MASK = 4294967295
BPF_F_INGRESS = 1
BPF_F_INNER_MAP = 4096
BPF_F_INVALIDATE_HASH = 2
BPF_F_KPROBE_MULTI_RETURN = 1
BPF_F_LINK = 8192
BPF_F_LOCK = 4
BPF_F_MARK_ENFORCE = 64
BPF_F_MARK_MANGLED_0 = 32
BPF_F_MMAPABLE = 1024
BPF_F_NEIGH = 2
BPF_F_NEXTHOP = 8
BPF_F_NO_COMMON_LRU = 2
BPF_F_NO_PREALLOC = 1
BPF_F_NO_TUNNEL_KEY = 16
BPF_F_NO_USER_CONV = 262144
BPF_F_NUMA_NODE = 4
BPF_F_PATH_FD = 16384
BPF_F_PEER = 4
BPF_F_PRESERVE_ELEMS = 2048
BPF_F_PSEUDO_HDR = 16
BPF_F_RDONLY = 8
BPF_F_RDONLY_PROG = 128
BPF_F_RECOMPUTE_CSUM = 1
BPF_F_REUSE_STACKID = 1024
BPF_F_SEGV_ON_FAULT = 131072
BPF_F_SEQ_NUMBER = 8
BPF_F_SKIP_FIELD_MASK = 255
BPF_F_STACK_BUILD_ID = 32
BPF_F_SYSCTL_BASE_NAME = 1
BPF_F_TIMER_ABS = 1
BPF_F_TIMER_CPU_PIN = 2
BPF_F_TOKEN_FD = 65536
BPF_F_TUNINFO_FLAGS = 16
BPF_F_TUNINFO_IPV6 = 1
BPF_F_UPROBE_MULTI_RETURN = 1
BPF_F_USER_BUILD_ID = 2048
BPF_F_USER_STACK = 256
BPF_F_VTYPE_BTF_OBJ_FD = 32768
BPF_F_WRONLY = 16
BPF_F_WRONLY_PROG = 256
BPF_F_ZERO_CSUM_TX = 2
BPF_F_ZERO_SEED = 64
BPF_LOAD_HDR_OPT_TCP_SYN = 1
BPF_LOCAL_STORAGE_GET_F_CREATE = 1
BPF_MAX_LOOPS = 8388608
BPF_MAX_TRAMP_LINKS = 38
BPF_NOEXIST = 1
BPF_RB_AVAIL_DATA = 0
BPF_RB_CONS_POS = 2
BPF_RB_FORCE_WAKEUP = 2
BPF_RB_NO_WAKEUP = 1
BPF_RB_PROD_POS = 3
BPF_RB_RING_SIZE = 1
BPF_REG_0 = 0
BPF_REG_1 = 1
BPF_REG_10 = 10
BPF_REG_2 = 2
BPF_REG_3 = 3
BPF_REG_4 = 4
BPF_REG_5 = 5
BPF_REG_6 = 6
BPF_REG_7 = 7
BPF_REG_8 = 8
BPF_REG_9 = 9
BPF_RINGBUF_BUSY_BIT = 2147483648
BPF_RINGBUF_DISCARD_BIT = 1073741824
BPF_RINGBUF_HDR_SZ = 8
BPF_SKB_CLOCK_MONOTONIC = 1
BPF_SKB_CLOCK_REALTIME = 0
BPF_SKB_CLOCK_TAI = 2
BPF_SKB_TSTAMP_DELIVERY_MONO = 1
BPF_SKB_TSTAMP_UNSPEC = 0
BPF_SK_LOOKUP_F_NO_REUSEPORT = 2
BPF_SK_LOOKUP_F_REPLACE = 1
BPF_SK_STORAGE_GET_F_CREATE = 1
BPF_SOCK_OPS_ACTIVE_ESTABLISHED_CB = 4
BPF_SOCK_OPS_ALL_CB_FLAGS = 127
BPF_SOCK_OPS_BASE_RTT = 7
BPF_SOCK_OPS_HDR_OPT_LEN_CB = 14
BPF_SOCK_OPS_NEEDS_ECN = 6
BPF_SOCK_OPS_PARSE_ALL_HDR_OPT_CB_FLAG = 16
BPF_SOCK_OPS_PARSE_HDR_OPT_CB = 13
BPF_SOCK_OPS_PARSE_UNKNOWN_HDR_OPT_CB_FLAG = 32
BPF_SOCK_OPS_PASSIVE_ESTABLISHED_CB = 5
BPF_SOCK_OPS_RETRANS_CB = 9
BPF_SOCK_OPS_RETRANS_CB_FLAG = 2
BPF_SOCK_OPS_RTO_CB = 8
BPF_SOCK_OPS_RTO_CB_FLAG = 1
BPF_SOCK_OPS_RTT_CB = 12
BPF_SOCK_OPS_RTT_CB_FLAG = 8
BPF_SOCK_OPS_RWND_INIT = 2
BPF_SOCK_OPS_STATE_CB = 10
BPF_SOCK_OPS_STATE_CB_FLAG = 4
BPF_SOCK_OPS_TCP_CONNECT_CB = 3
BPF_SOCK_OPS_TCP_LISTEN_CB = 11
BPF_SOCK_OPS_TIMEOUT_INIT = 1
BPF_SOCK_OPS_VOID = 0
BPF_SOCK_OPS_WRITE_HDR_OPT_CB = 15
BPF_SOCK_OPS_WRITE_HDR_OPT_CB_FLAG = 64
BPF_TASK_ITER_ALL_PROCS = 0
BPF_TASK_ITER_ALL_THREADS = 1
BPF_TASK_ITER_PROC_THREADS = 2
BPF_TCP_BOUND_INACTIVE = 13
BPF_TCP_CLOSE = 7
BPF_TCP_CLOSE_WAIT = 8
BPF_TCP_CLOSING = 11
BPF_TCP_ESTABLISHED = 1
BPF_TCP_FIN_WAIT1 = 4
BPF_TCP_FIN_WAIT2 = 5
BPF_TCP_LAST_ACK = 9
BPF_TCP_LISTEN = 10
BPF_TCP_MAX_STATES = 14
BPF_TCP_NEW_SYN_RECV = 12
BPF_TCP_SYN_RECV = 3
BPF_TCP_SYN_SENT = 2
BPF_TCP_TIME_WAIT = 6
BPF_WRITE_HDR_TCP_CURRENT_MSS = 1
BPF_WRITE_HDR_TCP_SYNACK_COOKIE = 2
BPF_XFRM_STATE_OPTS_SZ = 36
)
type AdjRoomMode uint32
const (
@@ -72,7 +242,8 @@ const (
BPF_CGROUP_UNIX_GETSOCKNAME AttachType = 53
BPF_NETKIT_PRIMARY AttachType = 54
BPF_NETKIT_PEER AttachType = 55
__MAX_BPF_ATTACH_TYPE AttachType = 56
BPF_TRACE_KPROBE_SESSION AttachType = 56
__MAX_BPF_ATTACH_TYPE AttachType = 57
)
type Cmd uint32
@@ -115,6 +286,8 @@ const (
BPF_ITER_CREATE Cmd = 33
BPF_LINK_DETACH Cmd = 34
BPF_PROG_BIND_MAP Cmd = 35
BPF_TOKEN_CREATE Cmd = 36
__MAX_BPF_CMD Cmd = 37
)
type FunctionId uint32
@@ -359,7 +532,8 @@ const (
BPF_LINK_TYPE_TCX LinkType = 11
BPF_LINK_TYPE_UPROBE_MULTI LinkType = 12
BPF_LINK_TYPE_NETKIT LinkType = 13
__MAX_BPF_LINK_TYPE LinkType = 14
BPF_LINK_TYPE_SOCKMAP LinkType = 14
__MAX_BPF_LINK_TYPE LinkType = 15
)
type MapType uint32
@@ -400,6 +574,17 @@ const (
BPF_MAP_TYPE_BLOOM_FILTER MapType = 30
BPF_MAP_TYPE_USER_RINGBUF MapType = 31
BPF_MAP_TYPE_CGRP_STORAGE MapType = 32
BPF_MAP_TYPE_ARENA MapType = 33
__MAX_BPF_MAP_TYPE MapType = 34
)
type ObjType uint32
const (
BPF_TYPE_UNSPEC ObjType = 0
BPF_TYPE_PROG ObjType = 1
BPF_TYPE_MAP ObjType = 2
BPF_TYPE_LINK ObjType = 3
)
type PerfEventType uint32
@@ -450,6 +635,7 @@ const (
BPF_PROG_TYPE_SK_LOOKUP ProgType = 30
BPF_PROG_TYPE_SYSCALL ProgType = 31
BPF_PROG_TYPE_NETFILTER ProgType = 32
__MAX_BPF_PROG_TYPE ProgType = 33
)
type RetCode uint32
@@ -537,7 +723,7 @@ type MapInfo struct {
KeySize uint32
ValueSize uint32
MaxEntries uint32
MapFlags MapFlags
MapFlags uint32
Name ObjName
Ifindex uint32
BtfVmlinuxValueTypeId TypeID
@@ -546,7 +732,7 @@ type MapInfo struct {
BtfId uint32
BtfKeyTypeId TypeID
BtfValueTypeId TypeID
_ [4]byte
BtfVmlinuxId uint32
MapExtra uint64
}
@@ -556,7 +742,7 @@ type ProgInfo struct {
Tag [8]uint8
JitedProgLen uint32
XlatedProgLen uint32
JitedProgInsns uint64
JitedProgInsns Pointer
XlatedProgInsns Pointer
LoadTime uint64
CreatedByUid uint32
@@ -569,15 +755,15 @@ type ProgInfo struct {
NetnsIno uint64
NrJitedKsyms uint32
NrJitedFuncLens uint32
JitedKsyms uint64
JitedFuncLens uint64
JitedKsyms Pointer
JitedFuncLens Pointer
BtfId BTFID
FuncInfoRecSize uint32
FuncInfo Pointer
NrFuncInfo uint32
NrLineInfo uint32
LineInfo Pointer
JitedLineInfo uint64
JitedLineInfo Pointer
NrJitedLineInfo uint32
LineInfoRecSize uint32
JitedLineInfoRecSize uint32
@@ -643,6 +829,8 @@ type BtfLoadAttr struct {
BtfLogSize uint32
BtfLogLevel uint32
BtfLogTrueSize uint32
BtfFlags uint32
BtfTokenFd int32
}
func BtfLoad(attr *BtfLoadAttr) (*FD, error) {
@@ -886,7 +1074,7 @@ type MapCreateAttr struct {
KeySize uint32
ValueSize uint32
MaxEntries uint32
MapFlags MapFlags
MapFlags uint32
InnerMapFd uint32
NumaNode uint32
MapName ObjName
@@ -896,6 +1084,8 @@ type MapCreateAttr struct {
BtfValueTypeId TypeID
BtfVmlinuxValueTypeId TypeID
MapExtra uint64
ValueTypeBtfObjFd int32
MapTokenFd int32
}
func MapCreate(attr *MapCreateAttr) (*FD, error) {
@@ -1189,6 +1379,8 @@ type ProgLoadAttr struct {
CoreRelos Pointer
CoreReloRecSize uint32
LogTrueSize uint32
ProgTokenFd int32
_ [4]byte
}
func ProgLoad(attr *ProgLoadAttr) (*FD, error) {
@@ -1246,6 +1438,7 @@ type RawTracepointOpenAttr struct {
Name Pointer
ProgFd uint32
_ [4]byte
Cookie uint64
}
func RawTracepointOpen(attr *RawTracepointOpenAttr) (*FD, error) {
@@ -1287,19 +1480,20 @@ type KprobeLinkInfo struct {
Offset uint32
Addr uint64
Missed uint64
_ [8]byte
Cookie uint64
}
type KprobeMultiLinkInfo struct {
Type LinkType
Id LinkID
ProgId uint32
_ [4]byte
Addrs Pointer
Count uint32
Flags uint32
Missed uint64
_ [24]byte
Type LinkType
Id LinkID
ProgId uint32
_ [4]byte
Addrs Pointer
Count uint32
Flags uint32
Missed uint64
Cookies uint64
_ [16]byte
}
type NetNsLinkInfo struct {

View File

@@ -51,12 +51,12 @@ func SyscallOutput(dst any, size int) Buffer {
//
// Returns the number of copied bytes.
func (b Buffer) CopyTo(dst []byte) int {
return copy(dst, b.unsafeBytes())
return copy(dst, b.Bytes())
}
// AppendTo appends the buffer onto dst.
func (b Buffer) AppendTo(dst []byte) []byte {
return append(dst, b.unsafeBytes()...)
return append(dst, b.Bytes()...)
}
// Pointer returns the location where a syscall should write.
@@ -72,10 +72,12 @@ func (b Buffer) Unmarshal(data any) error {
return nil
}
return Unmarshal(data, b.unsafeBytes())
return Unmarshal(data, b.Bytes())
}
func (b Buffer) unsafeBytes() []byte {
// Bytes returns the buffer as a byte slice. Returns nil if the Buffer was
// created using UnsafeBuffer or by zero-copy unmarshaling.
func (b Buffer) Bytes() []byte {
if b.size == syscallPointerOnly {
return nil
}

View File

@@ -0,0 +1,103 @@
package fdtrace
import (
"bytes"
"fmt"
"os"
"runtime"
"sync"
"sync/atomic"
)
// foundLeak is atomic since the GC may collect objects in parallel.
var foundLeak atomic.Bool
func onLeakFD(fs *runtime.Frames) {
foundLeak.Store(true)
fmt.Fprintln(os.Stderr, "leaked fd created at:")
fmt.Fprintln(os.Stderr, formatFrames(fs))
}
// fds is a registry of all file descriptors wrapped into sys.fds that were
// created while an fd tracer was active.
var fds *sync.Map // map[int]*runtime.Frames
// TraceFD associates raw with the current execution stack.
//
// skip controls how many entries of the stack the function should skip.
func TraceFD(raw int, skip int) {
if fds == nil {
return
}
// Attempt to store the caller's stack for the given fd value.
// Panic if fds contains an existing stack for the fd.
old, exist := fds.LoadOrStore(raw, callersFrames(skip))
if exist {
f := old.(*runtime.Frames)
panic(fmt.Sprintf("found existing stack for fd %d:\n%s", raw, formatFrames(f)))
}
}
// ForgetFD removes any existing association for raw.
func ForgetFD(raw int) {
if fds != nil {
fds.Delete(raw)
}
}
// LeakFD indicates that raw was leaked.
//
// Calling the function with a value that was not passed to [TraceFD] before
// is undefined.
func LeakFD(raw int) {
if fds == nil {
return
}
// Invoke the fd leak callback. Calls LoadAndDelete to guarantee the callback
// is invoked at most once for one sys.FD allocation, runtime.Frames can only
// be unwound once.
f, ok := fds.LoadAndDelete(raw)
if ok {
onLeakFD(f.(*runtime.Frames))
}
}
// flushFrames removes all elements from fds and returns them as a slice. This
// deals with the fact that a runtime.Frames can only be unwound once using
// Next().
func flushFrames() []*runtime.Frames {
var frames []*runtime.Frames
fds.Range(func(key, value any) bool {
frames = append(frames, value.(*runtime.Frames))
fds.Delete(key)
return true
})
return frames
}
func callersFrames(skip int) *runtime.Frames {
c := make([]uintptr, 32)
// Skip runtime.Callers and this function.
i := runtime.Callers(skip+2, c)
if i == 0 {
return nil
}
return runtime.CallersFrames(c)
}
// formatFrames formats a runtime.Frames as a human-readable string.
func formatFrames(fs *runtime.Frames) string {
var b bytes.Buffer
for {
f, more := fs.Next()
b.WriteString(fmt.Sprintf("\t%s+%#x\n\t\t%s:%d\n", f.Function, f.PC-f.Entry, f.File, f.Line))
if !more {
break
}
}
return b.String()
}

View File

@@ -0,0 +1,31 @@
package fdtrace
import (
"os"
"sync"
)
type testingM interface {
Run() int
}
// TestMain runs m with fd tracing enabled.
//
// The function calls [os.Exit] and does not return.
func TestMain(m testingM) {
fds = new(sync.Map)
ret := m.Run()
if fs := flushFrames(); len(fs) != 0 {
for _, f := range fs {
onLeakFD(f)
}
}
if foundLeak.Load() {
ret = 99
}
os.Exit(ret)
}

View File

@@ -12,6 +12,7 @@ import (
"syscall"
"github.com/cilium/ebpf/internal"
"github.com/cilium/ebpf/internal/linux"
"github.com/cilium/ebpf/internal/unix"
)
@@ -112,6 +113,10 @@ func sanitizeTracefsPath(path ...string) (string, error) {
// but may be also be available at /sys/kernel/debug/tracing if debugfs is mounted.
// The available tracefs paths will depends on distribution choices.
var getTracefsPath = sync.OnceValues(func() (string, error) {
if !internal.OnLinux {
return "", fmt.Errorf("tracefs: %w", internal.ErrNotSupportedOnOS)
}
for _, p := range []struct {
path string
fsType int64
@@ -121,7 +126,7 @@ var getTracefsPath = sync.OnceValues(func() (string, error) {
// RHEL/CentOS
{"/sys/kernel/debug/tracing", unix.DEBUGFS_MAGIC},
} {
if fsType, err := internal.FSType(p.path); err == nil && fsType == p.fsType {
if fsType, err := linux.FSType(p.path); err == nil && fsType == p.fsType {
return p.path, nil
}
}
@@ -213,7 +218,10 @@ func NewEvent(args ProbeArgs) (*Event, error) {
if err == nil {
return nil, fmt.Errorf("trace event %s/%s: %w", args.Group, eventName, os.ErrExist)
}
if err != nil && !errors.Is(err, os.ErrNotExist) {
if errors.Is(err, unix.EINVAL) {
return nil, fmt.Errorf("trace event %s/%s: %w (unknown symbol?)", args.Group, eventName, err)
}
if !errors.Is(err, os.ErrNotExist) {
return nil, fmt.Errorf("checking trace event %s/%s: %w", args.Group, eventName, err)
}

View File

@@ -0,0 +1,29 @@
package unix
import (
"syscall"
linux "golang.org/x/sys/unix"
)
type Errno = syscall.Errno
const (
E2BIG = linux.E2BIG
EACCES = linux.EACCES
EAGAIN = linux.EAGAIN
EBADF = linux.EBADF
EEXIST = linux.EEXIST
EFAULT = linux.EFAULT
EILSEQ = linux.EILSEQ
EINTR = linux.EINTR
EINVAL = linux.EINVAL
ENODEV = linux.ENODEV
ENOENT = linux.ENOENT
ENOSPC = linux.ENOSPC
EOPNOTSUPP = linux.EOPNOTSUPP
EPERM = linux.EPERM
EPOLLIN = linux.EPOLLIN
ESRCH = linux.ESRCH
ESTALE = linux.ESTALE
)

View File

@@ -0,0 +1,29 @@
//go:build !linux && !windows
package unix
import "syscall"
type Errno = syscall.Errno
// Errnos are distinct and non-zero.
const (
E2BIG Errno = iota + 1
EACCES
EAGAIN
EBADF
EEXIST
EFAULT
EILSEQ
EINTR
EINVAL
ENODEV
ENOENT
ENOSPC
ENOTSUP
ENOTSUPP
EOPNOTSUPP
EPERM
ESRCH
ESTALE
)

View File

@@ -0,0 +1,59 @@
// Code generated by "stringer -type=Errno -tags=windows -output=errno_string_windows.go"; DO NOT EDIT.
package unix
import "strconv"
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[EPERM-1]
_ = x[ENOENT-2]
_ = x[ESRCH-3]
_ = x[EINTR-4]
_ = x[E2BIG-7]
_ = x[EBADF-9]
_ = x[EAGAIN-11]
_ = x[EACCES-13]
_ = x[EFAULT-14]
_ = x[EEXIST-17]
_ = x[ENODEV-19]
_ = x[EINVAL-22]
_ = x[ENOSPC-28]
_ = x[EILSEQ-42]
_ = x[ENOTSUP-129]
_ = x[EOPNOTSUPP-130]
_ = x[ENOTSUPP-536870912]
_ = x[ESTALE-536870913]
}
const _Errno_name = "EPERMENOENTESRCHEINTRE2BIGEBADFEAGAINEACCESEFAULTEEXISTENODEVEINVALENOSPCEILSEQENOTSUPEOPNOTSUPPENOTSUPPESTALE"
var _Errno_map = map[Errno]string{
1: _Errno_name[0:5],
2: _Errno_name[5:11],
3: _Errno_name[11:16],
4: _Errno_name[16:21],
7: _Errno_name[21:26],
9: _Errno_name[26:31],
11: _Errno_name[31:37],
13: _Errno_name[37:43],
14: _Errno_name[43:49],
17: _Errno_name[49:55],
19: _Errno_name[55:61],
22: _Errno_name[61:67],
28: _Errno_name[67:73],
42: _Errno_name[73:79],
129: _Errno_name[79:86],
130: _Errno_name[86:96],
536870912: _Errno_name[96:104],
536870913: _Errno_name[104:110],
}
func (i Errno) String() string {
if str, ok := _Errno_map[i]; ok {
return str
}
return "Errno(" + strconv.FormatInt(int64(i), 10) + ")"
}

View File

@@ -0,0 +1,78 @@
package unix
// The code in this file is derived from syscall_unix.go in the Go source code,
// licensed under the MIT license.
import (
"errors"
"os"
"syscall"
)
//go:generate go run golang.org/x/tools/cmd/stringer@latest -type=Errno -tags=windows -output=errno_string_windows.go
// Windows specific constants for Unix errnos.
//
// The values do not always match Linux, for example EILSEQ and EOPNOTSUPP.
//
// See https://learn.microsoft.com/en-us/cpp/c-runtime-library/errno-constants?view=msvc-170
const (
EPERM Errno = 1
ENOENT Errno = 2
ESRCH Errno = 3
EINTR Errno = 4
E2BIG Errno = 7
EBADF Errno = 9
EAGAIN Errno = 11
EACCES Errno = 13
EFAULT Errno = 14
EEXIST Errno = 17
ENODEV Errno = 19
EINVAL Errno = 22
ENFILE Errno = 23
EMFILE Errno = 24
ENOSPC Errno = 28
ENOSYS Errno = 40
ENOTEMPTY Errno = 41
EILSEQ Errno = 42
ENOTSUP Errno = 129
EOPNOTSUPP Errno = 130
ETIMEDOUT Errno = 138
EWOULDBLOCK Errno = 140
)
// These constants do not exist on Windows and therefore have a non-zero
// dummy value.
const (
ENOTSUPP Errno = Errno(syscall.APPLICATION_ERROR) + iota
ESTALE
)
// Errno is a Windows compatibility shim for Unix errnos.
type Errno uintptr
func (e Errno) Error() string {
return e.String()
}
func (e Errno) Is(target error) bool {
switch target {
case os.ErrPermission:
return e == EACCES || e == EPERM
case os.ErrExist:
return e == EEXIST || e == ENOTEMPTY
case os.ErrNotExist:
return e == ENOENT
case errors.ErrUnsupported:
return e == ENOSYS || e == ENOTSUP || e == EOPNOTSUPP
}
return false
}
func (e Errno) Temporary() bool {
return e == EINTR || e == EMFILE || e == ENFILE || e.Timeout()
}
func (e Errno) Timeout() bool {
return e == EAGAIN || e == EWOULDBLOCK || e == ETIMEDOUT
}

23
vendor/github.com/cilium/ebpf/internal/unix/error.go generated vendored Normal file
View File

@@ -0,0 +1,23 @@
package unix
import (
"fmt"
"runtime"
"strings"
"github.com/cilium/ebpf/internal"
)
// errNonLinux returns an error which wraps [internal.ErrNotSupportedOnOS] and
// includes the name of the calling function.
func errNonLinux() error {
name := "unknown"
pc, _, _, ok := runtime.Caller(1)
if ok {
name = runtime.FuncForPC(pc).Name()
if pos := strings.LastIndexByte(name, '.'); pos != -1 {
name = name[pos+1:]
}
}
return fmt.Errorf("unix: %s: %w", name, internal.ErrNotSupportedOnOS)
}

View File

@@ -0,0 +1,11 @@
//go:build !linux && !windows
package unix
func BytePtrFromString(s string) (*byte, error) {
return nil, errNonLinux()
}
func ByteSliceToString(s []byte) string {
return ""
}

View File

@@ -0,0 +1,19 @@
package unix
import (
"syscall"
"golang.org/x/sys/windows"
)
func BytePtrFromString(s string) (*byte, error) {
p, err := windows.BytePtrFromString(s)
if err == syscall.EINVAL {
err = EINVAL
}
return p, err
}
func ByteSliceToString(s []byte) string {
return windows.ByteSliceToString(s)
}

View File

@@ -8,26 +8,6 @@ import (
linux "golang.org/x/sys/unix"
)
const (
ENOENT = linux.ENOENT
EEXIST = linux.EEXIST
EAGAIN = linux.EAGAIN
ENOSPC = linux.ENOSPC
EINVAL = linux.EINVAL
EPOLLIN = linux.EPOLLIN
EINTR = linux.EINTR
EPERM = linux.EPERM
ESRCH = linux.ESRCH
ENODEV = linux.ENODEV
EBADF = linux.EBADF
E2BIG = linux.E2BIG
EFAULT = linux.EFAULT
EACCES = linux.EACCES
EILSEQ = linux.EILSEQ
EOPNOTSUPP = linux.EOPNOTSUPP
ESTALE = linux.ESTALE
)
const (
BPF_F_NO_PREALLOC = linux.BPF_F_NO_PREALLOC
BPF_F_NUMA_NODE = linux.BPF_F_NUMA_NODE
@@ -81,15 +61,16 @@ const (
SO_DETACH_BPF = linux.SO_DETACH_BPF
SOL_SOCKET = linux.SOL_SOCKET
SIGPROF = linux.SIGPROF
SIGUSR1 = linux.SIGUSR1
SIG_BLOCK = linux.SIG_BLOCK
SIG_UNBLOCK = linux.SIG_UNBLOCK
EM_NONE = linux.EM_NONE
EM_BPF = linux.EM_BPF
BPF_FS_MAGIC = linux.BPF_FS_MAGIC
TRACEFS_MAGIC = linux.TRACEFS_MAGIC
DEBUGFS_MAGIC = linux.DEBUGFS_MAGIC
BPF_RB_NO_WAKEUP = linux.BPF_RB_NO_WAKEUP
BPF_RB_FORCE_WAKEUP = linux.BPF_RB_FORCE_WAKEUP
AF_UNSPEC = linux.AF_UNSPEC
IFF_UP = linux.IFF_UP
)
type Statfs_t = linux.Statfs_t
@@ -214,3 +195,7 @@ func SchedSetaffinity(pid int, set *CPUSet) error {
func SchedGetaffinity(pid int, set *CPUSet) error {
return linux.SchedGetaffinity(pid, set)
}
func Auxv() ([][2]uintptr, error) {
return linux.Auxv()
}

View File

@@ -3,33 +3,9 @@
package unix
import (
"fmt"
"runtime"
"syscall"
)
var errNonLinux = fmt.Errorf("unsupported platform %s/%s", runtime.GOOS, runtime.GOARCH)
// Errnos are distinct and non-zero.
const (
ENOENT syscall.Errno = iota + 1
EEXIST
EAGAIN
ENOSPC
EINVAL
EINTR
EPERM
ESRCH
ENODEV
EBADF
E2BIG
EFAULT
EACCES
EILSEQ
EOPNOTSUPP
ESTALE
)
// Constants are distinct to avoid breaking switch statements.
const (
BPF_F_NO_PREALLOC = iota
@@ -84,16 +60,17 @@ const (
SO_DETACH_BPF
SOL_SOCKET
SIGPROF
SIGUSR1
SIG_BLOCK
SIG_UNBLOCK
EM_NONE
EM_BPF
BPF_FS_MAGIC
TRACEFS_MAGIC
DEBUGFS_MAGIC
BPF_RB_NO_WAKEUP
BPF_RB_FORCE_WAKEUP
BPF_F_LOCK
AF_UNSPEC
IFF_UP
)
type Statfs_t struct {
@@ -136,28 +113,28 @@ type Sigset_t struct {
Val [4]uint64
}
func Syscall(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err syscall.Errno) {
return 0, 0, syscall.ENOTSUP
func Syscall(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err Errno) {
return 0, 0, ENOTSUP
}
func PthreadSigmask(how int, set, oldset *Sigset_t) error {
return errNonLinux
return errNonLinux()
}
func FcntlInt(fd uintptr, cmd, arg int) (int, error) {
return -1, errNonLinux
return -1, errNonLinux()
}
func IoctlSetInt(fd int, req uint, value int) error {
return errNonLinux
return errNonLinux()
}
func Statfs(path string, buf *Statfs_t) error {
return errNonLinux
return errNonLinux()
}
func Close(fd int) (err error) {
return errNonLinux
return errNonLinux()
}
type EpollEvent struct {
@@ -167,23 +144,23 @@ type EpollEvent struct {
}
func EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error) {
return 0, errNonLinux
return 0, errNonLinux()
}
func EpollCtl(epfd int, op int, fd int, event *EpollEvent) (err error) {
return errNonLinux
return errNonLinux()
}
func Eventfd(initval uint, flags int) (fd int, err error) {
return 0, errNonLinux
return 0, errNonLinux()
}
func Write(fd int, p []byte) (n int, err error) {
return 0, errNonLinux
return 0, errNonLinux()
}
func EpollCreate1(flag int) (fd int, err error) {
return 0, errNonLinux
return 0, errNonLinux()
}
type PerfEventMmapPage struct {
@@ -213,15 +190,15 @@ type PerfEventMmapPage struct {
}
func SetNonblock(fd int, nonblocking bool) (err error) {
return errNonLinux
return errNonLinux()
}
func Mmap(fd int, offset int64, length int, prot int, flags int) (data []byte, err error) {
return []byte{}, errNonLinux
return []byte{}, errNonLinux()
}
func Munmap(b []byte) (err error) {
return errNonLinux
return errNonLinux()
}
type PerfEventAttr struct {
@@ -246,7 +223,7 @@ type PerfEventAttr struct {
}
func PerfEventOpen(attr *PerfEventAttr, pid int, cpu int, groupFd int, flags int) (fd int, err error) {
return 0, errNonLinux
return 0, errNonLinux()
}
type Utsname struct {
@@ -255,7 +232,7 @@ type Utsname struct {
}
func Uname(buf *Utsname) (err error) {
return errNonLinux
return errNonLinux()
}
func Getpid() int {
@@ -267,35 +244,27 @@ func Gettid() int {
}
func Tgkill(tgid int, tid int, sig syscall.Signal) (err error) {
return errNonLinux
}
func BytePtrFromString(s string) (*byte, error) {
return nil, errNonLinux
}
func ByteSliceToString(s []byte) string {
return ""
return errNonLinux()
}
func Renameat2(olddirfd int, oldpath string, newdirfd int, newpath string, flags uint) error {
return errNonLinux
return errNonLinux()
}
func Prlimit(pid, resource int, new, old *Rlimit) error {
return errNonLinux
return errNonLinux()
}
func Open(path string, mode int, perm uint32) (int, error) {
return -1, errNonLinux
return -1, errNonLinux()
}
func Fstat(fd int, stat *Stat_t) error {
return errNonLinux
return errNonLinux()
}
func SetsockoptInt(fd, level, opt, value int) error {
return errNonLinux
return errNonLinux()
}
type CPUSet struct{}
@@ -303,9 +272,13 @@ type CPUSet struct{}
func (*CPUSet) Set(int) {}
func SchedSetaffinity(pid int, set *CPUSet) error {
return errNonLinux
return errNonLinux()
}
func SchedGetaffinity(pid int, set *CPUSet) error {
return errNonLinux
return errNonLinux()
}
func Auxv() ([][2]uintptr, error) {
return nil, errNonLinux()
}

View File

@@ -2,9 +2,6 @@ package internal
import (
"fmt"
"sync"
"github.com/cilium/ebpf/internal/unix"
)
const (
@@ -78,30 +75,3 @@ func (v Version) Kernel() uint32 {
// each other when overflowing 8 bits.
return uint32(uint8(v[0]))<<16 | uint32(uint8(v[1]))<<8 | uint32(uint8(s))
}
// KernelVersion returns the version of the currently running kernel.
var KernelVersion = sync.OnceValues(func() (Version, error) {
return detectKernelVersion()
})
// detectKernelVersion returns the version of the running kernel.
func detectKernelVersion() (Version, error) {
vc, err := vdsoVersion()
if err != nil {
return Version{}, err
}
return NewVersionFromCode(vc), nil
}
// KernelRelease returns the release string of the running kernel.
// Its format depends on the Linux distribution and corresponds to directory
// names in /lib/modules by convention. Some examples are 5.15.17-1-lts and
// 4.19.0-16-amd64.
func KernelRelease() (string, error) {
var uname unix.Utsname
if err := unix.Uname(&uname); err != nil {
return "", fmt.Errorf("uname failed: %w", err)
}
return unix.ByteSliceToString(uname.Release[:]), nil
}

View File

@@ -10,6 +10,7 @@ import (
"github.com/cilium/ebpf"
"github.com/cilium/ebpf/internal"
"github.com/cilium/ebpf/internal/linux"
"github.com/cilium/ebpf/internal/sys"
"github.com/cilium/ebpf/internal/tracefs"
"github.com/cilium/ebpf/internal/unix"
@@ -60,6 +61,9 @@ func (ko *KprobeOptions) cookie() uint64 {
// platform's syscall prefix (e.g. __x64_) to support attaching to syscalls
// in a portable fashion.
//
// On kernels 6.11 and later, setting a kprobe on a nonexistent symbol using
// tracefs incorrectly returns [unix.EINVAL] instead of [os.ErrNotExist].
//
// The returned Link may implement [PerfEvent].
func Kprobe(symbol string, prog *ebpf.Program, opts *KprobeOptions) (Link, error) {
k, err := kprobe(symbol, prog, opts, false)
@@ -91,7 +95,7 @@ func Kprobe(symbol string, prog *ebpf.Program, opts *KprobeOptions) (Link, error
// in a portable fashion.
//
// On kernels 5.10 and earlier, setting a kretprobe on a nonexistent symbol
// incorrectly returns unix.EINVAL instead of os.ErrNotExist.
// incorrectly returns [unix.EINVAL] instead of [os.ErrNotExist].
//
// The returned Link may implement [PerfEvent].
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) (*
// Use kprobe PMU if the kernel has it available.
tp, err := pmuProbe(args)
if errors.Is(err, os.ErrNotExist) || errors.Is(err, unix.EINVAL) {
if prefix := internal.PlatformPrefix(); prefix != "" {
if prefix := linux.PlatformPrefix(); prefix != "" {
args.Symbol = prefix + symbol
tp, err = pmuProbe(args)
}
@@ -177,7 +181,7 @@ func kprobe(symbol string, prog *ebpf.Program, opts *KprobeOptions, ret bool) (*
if err == nil {
return tp, nil
}
if err != nil && !errors.Is(err, ErrNotSupported) {
if !errors.Is(err, ErrNotSupported) {
return nil, fmt.Errorf("creating perf_kprobe PMU (arch-specific fallback for %q): %w", symbol, err)
}
@@ -185,7 +189,7 @@ func kprobe(symbol string, prog *ebpf.Program, opts *KprobeOptions, ret bool) (*
args.Symbol = symbol
tp, err = tracefsProbe(args)
if errors.Is(err, os.ErrNotExist) || errors.Is(err, unix.EINVAL) {
if prefix := internal.PlatformPrefix(); prefix != "" {
if prefix := linux.PlatformPrefix(); prefix != "" {
args.Symbol = prefix + symbol
tp, err = tracefsProbe(args)
}

View File

@@ -37,6 +37,14 @@ type KprobeMultiOptions struct {
// Each Cookie is assigned to the Symbol or Address specified at the
// corresponding slice index.
Cookies []uint64
// Session must be true when attaching Programs with the
// [ebpf.AttachTraceKprobeSession] attach type.
//
// This makes a Kprobe execute on both function entry and return. The entry
// program can share a cookie value with the return program and can decide
// whether the return program gets executed.
Session bool
}
// 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) {
//
// Requires at least Linux 5.18.
func KretprobeMulti(prog *ebpf.Program, opts KprobeMultiOptions) (Link, error) {
return kprobeMulti(prog, opts, unix.BPF_F_KPROBE_MULTI_RETURN)
return kprobeMulti(prog, opts, sys.BPF_F_KPROBE_MULTI_RETURN)
}
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
return nil, fmt.Errorf("Cookies must be exactly Symbols or Addresses in length: %w", errInvalidInput)
}
attachType := sys.BPF_TRACE_KPROBE_MULTI
if opts.Session {
attachType = sys.BPF_TRACE_KPROBE_SESSION
}
attr := &sys.LinkCreateKprobeMultiAttr{
ProgFd: uint32(prog.FD()),
AttachType: sys.BPF_TRACE_KPROBE_MULTI,
AttachType: attachType,
KprobeMultiFlags: flags,
}
@@ -103,21 +116,31 @@ func kprobeMulti(prog *ebpf.Program, opts KprobeMultiOptions, flags uint32) (Lin
}
fd, err := sys.LinkCreateKprobeMulti(attr)
if err == nil {
return &kprobeMultiLink{RawLink{fd, ""}}, nil
}
if errors.Is(err, unix.ESRCH) {
return nil, fmt.Errorf("couldn't find one or more symbols: %w", os.ErrNotExist)
}
if errors.Is(err, unix.EINVAL) {
return nil, fmt.Errorf("%w (missing kernel symbol or prog's AttachType not AttachTraceKprobeMulti?)", err)
}
if err != nil {
if opts.Session {
if haveFeatErr := haveBPFLinkKprobeSession(); haveFeatErr != nil {
return nil, haveFeatErr
}
} else {
if haveFeatErr := haveBPFLinkKprobeMulti(); haveFeatErr != nil {
return nil, haveFeatErr
}
return nil, err
}
return &kprobeMultiLink{RawLink{fd, ""}}, nil
// Check EINVAL after running feature probes, since it's also returned when
// the kernel doesn't support the multi/session attach types.
if errors.Is(err, unix.EINVAL) {
return nil, fmt.Errorf("%w (missing kernel symbol or prog's AttachType not %s?)", err, ebpf.AttachType(attachType))
}
return nil, err
}
type kprobeMultiLink struct {
@@ -126,7 +149,7 @@ type kprobeMultiLink struct {
var _ Link = (*kprobeMultiLink)(nil)
func (kml *kprobeMultiLink) Update(prog *ebpf.Program) error {
func (kml *kprobeMultiLink) Update(_ *ebpf.Program) error {
return fmt.Errorf("update kprobe_multi: %w", ErrNotSupported)
}
@@ -149,7 +172,7 @@ func (kml *kprobeMultiLink) Info() (*Info, error) {
}, nil
}
var haveBPFLinkKprobeMulti = internal.NewFeatureTest("bpf_link_kprobe_multi", "5.18", func() error {
var haveBPFLinkKprobeMulti = internal.NewFeatureTest("bpf_link_kprobe_multi", func() error {
prog, err := ebpf.NewProgram(&ebpf.ProgramSpec{
Name: "probe_kpm_link",
Type: ebpf.Kprobe,
@@ -188,4 +211,45 @@ var haveBPFLinkKprobeMulti = internal.NewFeatureTest("bpf_link_kprobe_multi", "5
fd.Close()
return nil
})
}, "5.18")
var haveBPFLinkKprobeSession = internal.NewFeatureTest("bpf_link_kprobe_session", func() error {
prog, err := ebpf.NewProgram(&ebpf.ProgramSpec{
Name: "probe_kps_link",
Type: ebpf.Kprobe,
Instructions: asm.Instructions{
asm.Mov.Imm(asm.R0, 0),
asm.Return(),
},
AttachType: ebpf.AttachTraceKprobeSession,
License: "MIT",
})
if errors.Is(err, unix.E2BIG) {
// Kernel doesn't support AttachType field.
return internal.ErrNotSupported
}
if err != nil {
return err
}
defer prog.Close()
fd, err := sys.LinkCreateKprobeMulti(&sys.LinkCreateKprobeMultiAttr{
ProgFd: uint32(prog.FD()),
AttachType: sys.BPF_TRACE_KPROBE_SESSION,
Count: 1,
Syms: sys.NewStringSlicePointer([]string{"vprintk"}),
})
switch {
case errors.Is(err, unix.EINVAL):
return internal.ErrNotSupported
// If CONFIG_FPROBE isn't set.
case errors.Is(err, unix.EOPNOTSUPP):
return internal.ErrNotSupported
case err != nil:
return err
}
fd.Close()
return nil
}, "6.10")

View File

@@ -78,7 +78,9 @@ func NewFromID(id ID) (Link, error) {
return wrapRawLink(&RawLink{fd, ""})
}
// LoadPinnedLink loads a link that was persisted into a bpffs.
// LoadPinnedLink loads a Link from a pin (file) on the BPF virtual filesystem.
//
// Requires at least Linux 5.7.
func LoadPinnedLink(fileName string, opts *ebpf.LoadPinOptions) (Link, error) {
raw, err := loadPinnedRawLink(fileName, opts)
if err != nil {
@@ -350,7 +352,7 @@ func AttachRawLink(opts RawLinkOptions) (*RawLink, error) {
}
func loadPinnedRawLink(fileName string, opts *ebpf.LoadPinOptions) (*RawLink, error) {
fd, err := sys.ObjGet(&sys.ObjGetAttr{
fd, typ, err := sys.ObjGetTyped(&sys.ObjGetAttr{
Pathname: sys.NewStringPointer(fileName),
FileFlags: opts.Marshal(),
})
@@ -358,6 +360,11 @@ func loadPinnedRawLink(fileName string, opts *ebpf.LoadPinOptions) (*RawLink, er
return nil, fmt.Errorf("load pinned link: %w", err)
}
if typ != sys.BPF_TYPE_LINK {
_ = fd.Close()
return nil, fmt.Errorf("%s is not a Link", fileName)
}
return &RawLink{fd, fileName}, nil
}
@@ -380,7 +387,7 @@ func (l *RawLink) Close() error {
// Calling Close on a pinned Link will not break the link
// until the pin is removed.
func (l *RawLink) Pin(fileName string) error {
if err := internal.Pin(l.pinnedPath, fileName, l.fd); err != nil {
if err := sys.Pin(l.pinnedPath, fileName, l.fd); err != nil {
return err
}
l.pinnedPath = fileName
@@ -389,7 +396,7 @@ func (l *RawLink) Pin(fileName string) error {
// Unpin implements the Link interface.
func (l *RawLink) Unpin() error {
if err := internal.Unpin(l.pinnedPath); err != nil {
if err := sys.Unpin(l.pinnedPath); err != nil {
return err
}
l.pinnedPath = ""

View File

@@ -63,7 +63,7 @@ func AttachNetfilter(opts NetfilterOptions) (Link, error) {
return &netfilterLink{RawLink{fd, ""}}, nil
}
func (*netfilterLink) Update(new *ebpf.Program) error {
func (*netfilterLink) Update(_ *ebpf.Program) error {
return fmt.Errorf("netfilter update: %w", ErrNotSupported)
}

View File

@@ -115,7 +115,7 @@ func (pl *perfEventLink) Close() error {
return nil
}
func (pl *perfEventLink) Update(prog *ebpf.Program) error {
func (pl *perfEventLink) Update(_ *ebpf.Program) error {
return fmt.Errorf("perf event link update: %w", ErrNotSupported)
}
@@ -185,7 +185,7 @@ func (pi *perfEventIoctl) isLink() {}
//
// Detaching a program from a perf event is currently not possible, so a
// program replacement mechanism cannot be implemented for perf events.
func (pi *perfEventIoctl) Update(prog *ebpf.Program) error {
func (pi *perfEventIoctl) Update(_ *ebpf.Program) error {
return fmt.Errorf("perf event ioctl update: %w", ErrNotSupported)
}
@@ -303,7 +303,7 @@ func openTracepointPerfEvent(tid uint64, pid int) (*sys.FD, error) {
//
// https://elixir.bootlin.com/linux/v5.16.8/source/kernel/bpf/syscall.c#L4307
// https://github.com/torvalds/linux/commit/b89fbfbb854c9afc3047e8273cc3a694650b802e
var haveBPFLinkPerfEvent = internal.NewFeatureTest("bpf_link_perf_event", "5.15", func() error {
var haveBPFLinkPerfEvent = internal.NewFeatureTest("bpf_link_perf_event", func() error {
prog, err := ebpf.NewProgram(&ebpf.ProgramSpec{
Name: "probe_bpf_perf_link",
Type: ebpf.Kprobe,
@@ -329,4 +329,4 @@ var haveBPFLinkPerfEvent = internal.NewFeatureTest("bpf_link_perf_event", "5.15"
return nil
}
return err
})
}, "5.15")

View File

@@ -30,7 +30,7 @@ const (
NetkitType = sys.BPF_LINK_TYPE_NETKIT
)
var haveProgAttach = internal.NewFeatureTest("BPF_PROG_ATTACH", "4.10", func() error {
var haveProgAttach = internal.NewFeatureTest("BPF_PROG_ATTACH", func() error {
prog, err := ebpf.NewProgram(&ebpf.ProgramSpec{
Type: ebpf.CGroupSKB,
License: "MIT",
@@ -48,9 +48,9 @@ var haveProgAttach = internal.NewFeatureTest("BPF_PROG_ATTACH", "4.10", func() e
// have the syscall.
prog.Close()
return nil
})
}, "4.10")
var haveProgAttachReplace = internal.NewFeatureTest("BPF_PROG_ATTACH atomic replacement of MULTI progs", "5.5", func() error {
var haveProgAttachReplace = internal.NewFeatureTest("BPF_PROG_ATTACH atomic replacement of MULTI progs", func() error {
if err := haveProgAttach(); err != nil {
return err
}
@@ -90,9 +90,9 @@ var haveProgAttachReplace = internal.NewFeatureTest("BPF_PROG_ATTACH atomic repl
return nil
}
return err
})
}, "5.5")
var haveBPFLink = internal.NewFeatureTest("bpf_link", "5.7", func() error {
var haveBPFLink = internal.NewFeatureTest("bpf_link", func() error {
attr := sys.LinkCreateAttr{
// This is a hopefully invalid file descriptor, which triggers EBADF.
TargetFd: ^uint32(0),
@@ -107,9 +107,9 @@ var haveBPFLink = internal.NewFeatureTest("bpf_link", "5.7", func() error {
return nil
}
return err
})
}, "5.7")
var haveProgQuery = internal.NewFeatureTest("BPF_PROG_QUERY", "4.15", func() error {
var haveProgQuery = internal.NewFeatureTest("BPF_PROG_QUERY", func() error {
attr := sys.ProgQueryAttr{
// We rely on this being checked during the syscall.
// With an otherwise correct payload we expect EBADF here
@@ -127,9 +127,9 @@ var haveProgQuery = internal.NewFeatureTest("BPF_PROG_QUERY", "4.15", func() err
return ErrNotSupported
}
return errors.New("syscall succeeded unexpectedly")
})
}, "4.15")
var haveTCX = internal.NewFeatureTest("tcx", "6.6", func() error {
var haveTCX = internal.NewFeatureTest("tcx", func() error {
prog, err := ebpf.NewProgram(&ebpf.ProgramSpec{
Type: ebpf.SchedCLS,
License: "MIT",
@@ -162,9 +162,9 @@ var haveTCX = internal.NewFeatureTest("tcx", "6.6", func() error {
return ErrNotSupported
}
return errors.New("syscall succeeded unexpectedly")
})
}, "6.6")
var haveNetkit = internal.NewFeatureTest("netkit", "6.7", func() error {
var haveNetkit = internal.NewFeatureTest("netkit", func() error {
prog, err := ebpf.NewProgram(&ebpf.ProgramSpec{
Type: ebpf.SchedCLS,
License: "MIT",
@@ -197,4 +197,4 @@ var haveNetkit = internal.NewFeatureTest("netkit", "6.7", func() error {
return ErrNotSupported
}
return errors.New("syscall succeeded unexpectedly")
})
}, "6.7")

View File

@@ -14,7 +14,7 @@ type tracing struct {
RawLink
}
func (f *tracing) Update(new *ebpf.Program) error {
func (f *tracing) Update(_ *ebpf.Program) error {
return fmt.Errorf("tracing update: %w", ErrNotSupported)
}

View File

@@ -16,7 +16,7 @@ var (
uprobeRefCtrOffsetPMUPath = "/sys/bus/event_source/devices/uprobe/format/ref_ctr_offset"
// elixir.bootlin.com/linux/v5.15-rc7/source/kernel/events/core.c#L9799
uprobeRefCtrOffsetShift = 32
haveRefCtrOffsetPMU = internal.NewFeatureTest("RefCtrOffsetPMU", "4.20", func() error {
haveRefCtrOffsetPMU = internal.NewFeatureTest("RefCtrOffsetPMU", func() error {
_, err := os.Stat(uprobeRefCtrOffsetPMUPath)
if errors.Is(err, os.ErrNotExist) {
return internal.ErrNotSupported
@@ -25,7 +25,7 @@ var (
return err
}
return nil
})
}, "4.20")
// ErrNoSymbol indicates that the given symbol was not found
// in the ELF symbols table.
@@ -321,7 +321,7 @@ func (ex *Executable) uprobe(symbol string, prog *ebpf.Program, opts *UprobeOpti
if err == nil {
return tp, nil
}
if err != nil && !errors.Is(err, ErrNotSupported) {
if !errors.Is(err, ErrNotSupported) {
return nil, fmt.Errorf("creating perf_uprobe PMU: %w", err)
}

View File

@@ -47,7 +47,7 @@ func (ex *Executable) UretprobeMulti(symbols []string, prog *ebpf.Program, opts
// The return probe is not limited for symbols entry, so there's no special
// setup for return uprobes (other than the extra flag). The symbols, opts.Offsets
// and opts.Addresses arrays follow the same logic as for entry uprobes.
return ex.uprobeMulti(symbols, prog, opts, unix.BPF_F_UPROBE_MULTI_RETURN)
return ex.uprobeMulti(symbols, prog, opts, sys.BPF_F_UPROBE_MULTI_RETURN)
}
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
if errors.Is(err, unix.ESRCH) {
return nil, fmt.Errorf("%w (specified pid not found?)", os.ErrNotExist)
}
// Since Linux commit 46ba0e49b642 ("bpf: fix multi-uprobe PID filtering
// logic"), if the provided pid overflows MaxInt32 (turning it negative), the
// kernel will return EINVAL instead of ESRCH.
if errors.Is(err, unix.EINVAL) {
return nil, fmt.Errorf("%w (missing symbol or prog's AttachType not AttachTraceUprobeMulti?)", err)
return nil, fmt.Errorf("%w (invalid pid, missing symbol or prog's AttachType not AttachTraceUprobeMulti?)", err)
}
if err != nil {
@@ -168,11 +171,11 @@ type uprobeMultiLink struct {
var _ Link = (*uprobeMultiLink)(nil)
func (kml *uprobeMultiLink) Update(prog *ebpf.Program) error {
func (kml *uprobeMultiLink) Update(_ *ebpf.Program) error {
return fmt.Errorf("update uprobe_multi: %w", ErrNotSupported)
}
var haveBPFLinkUprobeMulti = internal.NewFeatureTest("bpf_link_uprobe_multi", "6.6", func() error {
var haveBPFLinkUprobeMulti = internal.NewFeatureTest("bpf_link_uprobe_multi", func() error {
prog, err := ebpf.NewProgram(&ebpf.ProgramSpec{
Name: "probe_upm_link",
Type: ebpf.Kprobe,
@@ -213,4 +216,4 @@ var haveBPFLinkUprobeMulti = internal.NewFeatureTest("bpf_link_uprobe_multi", "6
// should not happen
fd.Close()
return errors.New("successfully attached uprobe_multi to /, kernel bug?")
})
}, "6.6")

View File

@@ -9,10 +9,12 @@ import (
"io/fs"
"math"
"slices"
"strings"
"github.com/cilium/ebpf/asm"
"github.com/cilium/ebpf/btf"
"github.com/cilium/ebpf/internal"
"github.com/cilium/ebpf/internal/kallsyms"
)
// handles stores handle objects to avoid gc cleanup
@@ -205,13 +207,19 @@ func flattenPrograms(progs map[string]*ProgramSpec, names []string) {
// dependencies of each program.
func flattenInstructions(name string, progs map[string]*ProgramSpec, refs map[*ProgramSpec][]string) asm.Instructions {
prog := progs[name]
progRefs := refs[prog]
if len(progRefs) == 0 {
// No references, nothing to do.
return prog.Instructions
}
insns := make(asm.Instructions, len(prog.Instructions))
copy(insns, prog.Instructions)
// Add all direct references of prog to the list of to be linked programs.
pending := make([]string, len(refs[prog]))
copy(pending, refs[prog])
pending := make([]string, len(progRefs))
copy(pending, progRefs)
// All references for which we've appended instructions.
linked := make(map[string]bool)
@@ -457,3 +465,42 @@ func resolveKconfigReferences(insns asm.Instructions) (_ *Map, err error) {
return kconfig, nil
}
func resolveKsymReferences(insns asm.Instructions) error {
var missing []string
iter := insns.Iterate()
for iter.Next() {
ins := iter.Ins
meta, _ := ins.Metadata.Get(ksymMetaKey{}).(*ksymMeta)
if meta == nil {
continue
}
addr, err := kallsyms.Address(meta.Name)
if err != nil {
return fmt.Errorf("resolve ksym %s: %w", meta.Name, err)
}
if addr != 0 {
ins.Constant = int64(addr)
continue
}
if meta.Binding == elf.STB_WEAK {
// A weak ksym variable in eBPF C means its resolution is optional.
// Set a zero constant explicitly for clarity.
ins.Constant = 0
continue
}
if !slices.Contains(missing, meta.Name) {
missing = append(missing, meta.Name)
}
}
if len(missing) > 0 {
return fmt.Errorf("kernel is missing symbol: %s", strings.Join(missing, ","))
}
return nil
}

127
vendor/github.com/cilium/ebpf/map.go generated vendored
View File

@@ -66,16 +66,13 @@ type MapSpec struct {
Pinning PinType
// Specify numa node during map creation
// (effective only if unix.BPF_F_NUMA_NODE flag is set,
// (effective only if sys.BPF_F_NUMA_NODE flag is set,
// which can be imported from golang.org/x/sys/unix)
NumaNode uint32
// The initial contents of the map. May be nil.
Contents []MapKV
// Whether to freeze a map after setting its initial contents.
Freeze bool
// InnerMap is used as a template for ArrayOfMaps and HashOfMaps
InnerMap *MapSpec
@@ -161,6 +158,17 @@ func (spec *MapSpec) fixupMagicFields() (*MapSpec, error) {
// behaviour in the past.
spec.MaxEntries = n
}
case CPUMap:
n, err := PossibleCPU()
if err != nil {
return nil, fmt.Errorf("fixup cpu map: %w", err)
}
if n := uint32(n); spec.MaxEntries == 0 || spec.MaxEntries > n {
// Perform clamping similar to PerfEventArray.
spec.MaxEntries = n
}
}
return spec, nil
@@ -190,6 +198,14 @@ func (ms *MapSpec) dataSection() ([]byte, *btf.Datasec, error) {
return value, ds, nil
}
func (ms *MapSpec) readOnly() bool {
return (ms.Flags & sys.BPF_F_RDONLY_PROG) > 0
}
func (ms *MapSpec) writeOnly() bool {
return (ms.Flags & sys.BPF_F_WRONLY_PROG) > 0
}
// MapKV is used to initialize the contents of a Map.
type MapKV struct {
Key interface{}
@@ -222,7 +238,7 @@ func (ms *MapSpec) Compatible(m *Map) error {
// BPF_F_RDONLY_PROG is set unconditionally for devmaps. Explicitly allow this
// mismatch.
if !((ms.Type == DevMap || ms.Type == DevMapHash) && m.flags^ms.Flags == unix.BPF_F_RDONLY_PROG) &&
if !((ms.Type == DevMap || ms.Type == DevMapHash) && m.flags^ms.Flags == sys.BPF_F_RDONLY_PROG) &&
m.flags != ms.Flags {
diffs = append(diffs, fmt.Sprintf("Flags: %d changed to %d", m.flags, ms.Flags))
}
@@ -254,6 +270,8 @@ type Map struct {
pinnedPath string
// Per CPU maps return values larger than the size in the spec
fullValueSize int
memory *Memory
}
// NewMapFromFD creates a map from a raw fd.
@@ -359,7 +377,7 @@ func newMapWithOptions(spec *MapSpec, opts MapOptions) (_ *Map, err error) {
return nil, errors.New("inner maps cannot be pinned")
}
template, err := spec.InnerMap.createMap(nil, opts)
template, err := spec.InnerMap.createMap(nil)
if err != nil {
return nil, fmt.Errorf("inner map: %w", err)
}
@@ -371,7 +389,7 @@ func newMapWithOptions(spec *MapSpec, opts MapOptions) (_ *Map, err error) {
innerFd = template.fd
}
m, err := spec.createMap(innerFd, opts)
m, err := spec.createMap(innerFd)
if err != nil {
return nil, err
}
@@ -387,9 +405,54 @@ func newMapWithOptions(spec *MapSpec, opts MapOptions) (_ *Map, err error) {
return m, nil
}
// Memory returns a memory-mapped region for the Map. The Map must have been
// created with the BPF_F_MMAPABLE flag. Repeated calls to Memory return the
// same mapping. Callers are responsible for coordinating access to Memory.
func (m *Map) Memory() (*Memory, error) {
if m.memory != nil {
return m.memory, nil
}
if m.flags&sys.BPF_F_MMAPABLE == 0 {
return nil, fmt.Errorf("Map was not created with the BPF_F_MMAPABLE flag: %w", ErrNotSupported)
}
size, err := m.memorySize()
if err != nil {
return nil, err
}
mm, err := newMemory(m.FD(), size)
if err != nil {
return nil, fmt.Errorf("creating new Memory: %w", err)
}
m.memory = mm
return mm, nil
}
func (m *Map) memorySize() (int, error) {
switch m.Type() {
case Array:
// In Arrays, values are always laid out on 8-byte boundaries regardless of
// architecture. Multiply by MaxEntries and align the result to the host's
// page size.
size := int(internal.Align(m.ValueSize(), 8) * m.MaxEntries())
size = internal.Align(size, os.Getpagesize())
return size, nil
case Arena:
// For Arenas, MaxEntries denotes the maximum number of pages available to
// the arena.
return int(m.MaxEntries()) * os.Getpagesize(), nil
}
return 0, fmt.Errorf("determine memory size of map type %s: %w", m.Type(), ErrNotSupported)
}
// createMap validates the spec's properties and creates the map in the kernel
// using the given opts. It does not populate or freeze the map.
func (spec *MapSpec) createMap(inner *sys.FD, opts MapOptions) (_ *Map, err error) {
func (spec *MapSpec) createMap(inner *sys.FD) (_ *Map, err error) {
closeOnError := func(closer io.Closer) {
if err != nil {
closer.Close()
@@ -416,7 +479,7 @@ func (spec *MapSpec) createMap(inner *sys.FD, opts MapOptions) (_ *Map, err erro
KeySize: spec.KeySize,
ValueSize: spec.ValueSize,
MaxEntries: spec.MaxEntries,
MapFlags: sys.MapFlags(spec.Flags),
MapFlags: spec.Flags,
NumaNode: spec.NumaNode,
}
@@ -474,32 +537,32 @@ func handleMapCreateError(attr sys.MapCreateAttr, spec *MapSpec, err error) erro
if errors.Is(err, unix.EINVAL) && spec.Type == UnspecifiedMap {
return fmt.Errorf("map create: cannot use type %s", UnspecifiedMap)
}
if errors.Is(err, unix.EINVAL) && spec.Flags&unix.BPF_F_NO_PREALLOC > 0 {
if errors.Is(err, unix.EINVAL) && spec.Flags&sys.BPF_F_NO_PREALLOC > 0 {
return fmt.Errorf("map create: %w (noPrealloc flag may be incompatible with map type %s)", err, spec.Type)
}
switch spec.Type {
case ArrayOfMaps, HashOfMaps:
if spec.Type.canStoreMap() {
if haveFeatErr := haveNestedMaps(); haveFeatErr != nil {
return fmt.Errorf("map create: %w", haveFeatErr)
}
}
if spec.Flags&(unix.BPF_F_RDONLY_PROG|unix.BPF_F_WRONLY_PROG) > 0 || spec.Freeze {
if spec.readOnly() || spec.writeOnly() {
if haveFeatErr := haveMapMutabilityModifiers(); haveFeatErr != nil {
return fmt.Errorf("map create: %w", haveFeatErr)
}
}
if spec.Flags&unix.BPF_F_MMAPABLE > 0 {
if spec.Flags&sys.BPF_F_MMAPABLE > 0 {
if haveFeatErr := haveMmapableMaps(); haveFeatErr != nil {
return fmt.Errorf("map create: %w", haveFeatErr)
}
}
if spec.Flags&unix.BPF_F_INNER_MAP > 0 {
if spec.Flags&sys.BPF_F_INNER_MAP > 0 {
if haveFeatErr := haveInnerMaps(); haveFeatErr != nil {
return fmt.Errorf("map create: %w", haveFeatErr)
}
}
if spec.Flags&unix.BPF_F_NO_PREALLOC > 0 {
if spec.Flags&sys.BPF_F_NO_PREALLOC > 0 {
if haveFeatErr := haveNoPreallocMaps(); haveFeatErr != nil {
return fmt.Errorf("map create: %w", haveFeatErr)
}
@@ -530,6 +593,7 @@ func newMap(fd *sys.FD, name string, typ MapType, keySize, valueSize, maxEntries
flags,
"",
int(valueSize),
nil,
}
if !typ.hasPerCPUValue() {
@@ -577,7 +641,12 @@ func (m *Map) Flags() uint32 {
return m.flags
}
// Info returns metadata about the map.
// Info returns metadata about the map. This was first introduced in Linux 4.5,
// but newer kernels support more MapInfo fields with the introduction of more
// features. See [MapInfo] and its methods for more details.
//
// Returns an error wrapping ErrNotSupported if the kernel supports neither
// BPF_OBJ_GET_INFO_BY_FD nor reading map information from /proc/self/fdinfo.
func (m *Map) Info() (*MapInfo, error) {
return newMapInfoFromFd(m.fd)
}
@@ -604,7 +673,7 @@ func (m *Map) Handle() (*btf.Handle, error) {
type MapLookupFlags uint64
// LookupLock look up the value of a spin-locked map.
const LookupLock MapLookupFlags = unix.BPF_F_LOCK
const LookupLock MapLookupFlags = sys.BPF_F_LOCK
// Lookup retrieves a value from a Map.
//
@@ -1336,6 +1405,7 @@ func (m *Map) Clone() (*Map, error) {
m.flags,
"",
m.fullValueSize,
nil,
}, nil
}
@@ -1349,7 +1419,7 @@ func (m *Map) Clone() (*Map, error) {
// This requires bpffs to be mounted above fileName.
// See https://docs.cilium.io/en/stable/network/kubernetes/configuration/#mounting-bpffs-with-systemd
func (m *Map) Pin(fileName string) error {
if err := internal.Pin(m.pinnedPath, fileName, m.fd); err != nil {
if err := sys.Pin(m.pinnedPath, fileName, m.fd); err != nil {
return err
}
m.pinnedPath = fileName
@@ -1362,7 +1432,7 @@ func (m *Map) Pin(fileName string) error {
//
// Unpinning an unpinned Map returns nil.
func (m *Map) Unpin() error {
if err := internal.Unpin(m.pinnedPath); err != nil {
if err := sys.Unpin(m.pinnedPath); err != nil {
return err
}
m.pinnedPath = ""
@@ -1400,7 +1470,7 @@ func (m *Map) finalize(spec *MapSpec) error {
}
}
if spec.Freeze {
if isConstantDataSection(spec.Name) || isKconfigSection(spec.Name) {
if err := m.Freeze(); err != nil {
return fmt.Errorf("freezing map: %w", err)
}
@@ -1501,9 +1571,11 @@ func (m *Map) unmarshalValue(value any, buf sysenc.Buffer) error {
return buf.Unmarshal(value)
}
// LoadPinnedMap loads a Map from a BPF file.
// LoadPinnedMap opens a Map from a pin (file) on the BPF virtual filesystem.
//
// Requires at least Linux 4.5.
func LoadPinnedMap(fileName string, opts *LoadPinOptions) (*Map, error) {
fd, err := sys.ObjGet(&sys.ObjGetAttr{
fd, typ, err := sys.ObjGetTyped(&sys.ObjGetAttr{
Pathname: sys.NewStringPointer(fileName),
FileFlags: opts.Marshal(),
})
@@ -1511,6 +1583,11 @@ func LoadPinnedMap(fileName string, opts *LoadPinOptions) (*Map, error) {
return nil, err
}
if typ != sys.BPF_TYPE_MAP {
_ = fd.Close()
return nil, fmt.Errorf("%s is not a Map", fileName)
}
m, err := newMapFromFD(fd)
if err == nil {
m.pinnedPath = fileName
@@ -1530,6 +1607,10 @@ func unmarshalMap(buf sysenc.Buffer) (*Map, error) {
// marshalMap marshals the fd of a map into a buffer in host endianness.
func marshalMap(m *Map, length int) ([]byte, error) {
if m == nil {
return nil, errors.New("can't marshal a nil Map")
}
if length != 4 {
return nil, fmt.Errorf("can't marshal map to %d bytes", length)
}

145
vendor/github.com/cilium/ebpf/memory.go generated vendored Normal file
View File

@@ -0,0 +1,145 @@
package ebpf
import (
"errors"
"fmt"
"io"
"runtime"
"github.com/cilium/ebpf/internal/unix"
)
// Memory is the building block for accessing the memory of specific bpf map
// types (Array and Arena at the time of writing) without going through the bpf
// syscall interface.
//
// Given the fd of a bpf map created with the BPF_F_MMAPABLE flag, a shared
// 'file'-based memory-mapped region can be allocated in the process' address
// space, exposing the bpf map's memory by simply accessing a memory location.
var ErrReadOnly = errors.New("resource is read-only")
// Memory implements accessing a Map's memory without making any syscalls.
// Pay attention to the difference between Go and C struct alignment rules. Use
// [structs.HostLayout] on supported Go versions to help with alignment.
//
// Note on memory coherence: avoid using packed structs in memory shared between
// user space and eBPF C programs. This drops a struct's memory alignment to 1,
// forcing the compiler to use single-byte loads and stores for field accesses.
// This may lead to partially-written data to be observed from user space.
//
// On most architectures, the memmove implementation used by Go's copy() will
// access data in word-sized chunks. If paired with a matching access pattern on
// the eBPF C side (and if using default memory alignment), accessing shared
// memory without atomics or other synchronization primitives should be sound
// for individual values. For accesses beyond a single value, the usual
// concurrent programming rules apply.
type Memory struct {
b []byte
ro bool
}
func newMemory(fd, size int) (*Memory, error) {
// Typically, maps created with BPF_F_RDONLY_PROG remain writable from user
// space until frozen. As a security precaution, the kernel doesn't allow
// mapping bpf map memory as read-write into user space if the bpf map was
// frozen, or if it was created using the RDONLY_PROG flag.
//
// The user would be able to write to the map after freezing (since the kernel
// can't change the protection mode of an already-mapped page), while the
// verifier assumes the contents to be immutable.
b, err := unix.Mmap(fd, 0, size, unix.PROT_READ|unix.PROT_WRITE, unix.MAP_SHARED)
// If the map is frozen when an rw mapping is requested, expect EPERM. If the
// map was created with BPF_F_RDONLY_PROG, expect EACCES.
var ro bool
if errors.Is(err, unix.EPERM) || errors.Is(err, unix.EACCES) {
ro = true
b, err = unix.Mmap(fd, 0, size, unix.PROT_READ, unix.MAP_SHARED)
}
if err != nil {
return nil, fmt.Errorf("setting up memory-mapped region: %w", err)
}
mm := &Memory{
b,
ro,
}
runtime.SetFinalizer(mm, (*Memory).close)
return mm, nil
}
func (mm *Memory) close() {
if err := unix.Munmap(mm.b); err != nil {
panic(fmt.Errorf("unmapping memory: %w", err))
}
mm.b = nil
}
// Size returns the size of the memory-mapped region in bytes.
func (mm *Memory) Size() int {
return len(mm.b)
}
// ReadOnly returns true if the memory-mapped region is read-only.
func (mm *Memory) ReadOnly() bool {
return mm.ro
}
// bounds returns true if an access at off of the given size is within bounds.
func (mm *Memory) bounds(off uint64, size uint64) bool {
return off+size < uint64(len(mm.b))
}
// ReadAt implements [io.ReaderAt]. Useful for creating a new [io.OffsetWriter].
//
// See [Memory] for details around memory coherence.
func (mm *Memory) ReadAt(p []byte, off int64) (int, error) {
if mm.b == nil {
return 0, fmt.Errorf("memory-mapped region closed")
}
if p == nil {
return 0, fmt.Errorf("input buffer p is nil")
}
if off < 0 || off >= int64(len(mm.b)) {
return 0, fmt.Errorf("read offset out of range")
}
n := copy(p, mm.b[off:])
if n < len(p) {
return n, io.EOF
}
return n, nil
}
// WriteAt implements [io.WriterAt]. Useful for creating a new
// [io.SectionReader].
//
// See [Memory] for details around memory coherence.
func (mm *Memory) WriteAt(p []byte, off int64) (int, error) {
if mm.b == nil {
return 0, fmt.Errorf("memory-mapped region closed")
}
if mm.ro {
return 0, fmt.Errorf("memory-mapped region not writable: %w", ErrReadOnly)
}
if p == nil {
return 0, fmt.Errorf("output buffer p is nil")
}
if off < 0 || off >= int64(len(mm.b)) {
return 0, fmt.Errorf("write offset out of range")
}
n := copy(mm.b[off:], p)
if n < len(p) {
return n, io.EOF
}
return n, nil
}

View File

@@ -2,3 +2,4 @@
base = "docs/"
publish = "site/"
command = "mkdocs build"
environment = { PYTHON_VERSION = "3.13" }

102
vendor/github.com/cilium/ebpf/prog.go generated vendored
View File

@@ -16,6 +16,7 @@ import (
"github.com/cilium/ebpf/btf"
"github.com/cilium/ebpf/internal"
"github.com/cilium/ebpf/internal/kallsyms"
"github.com/cilium/ebpf/internal/linux"
"github.com/cilium/ebpf/internal/sys"
"github.com/cilium/ebpf/internal/sysenc"
"github.com/cilium/ebpf/internal/unix"
@@ -46,14 +47,15 @@ const (
outputPad = 256 + 2
)
// Deprecated: the correct log size is now detected automatically and this
// constant is unused.
const DefaultVerifierLogSize = 64 * 1024
// minVerifierLogSize is the default number of bytes allocated for the
// verifier log.
const minVerifierLogSize = 64 * 1024
// maxVerifierLogSize is the maximum size of verifier log buffer the kernel
// will accept before returning EINVAL. May be increased to MaxUint32 in the
// future, but avoid the unnecessary EINVAL for now.
const maxVerifierLogSize = math.MaxUint32 >> 2
// ProgramOptions control loading a program into the kernel.
type ProgramOptions struct {
// Bitmap controlling the detail emitted by the kernel's eBPF verifier log.
@@ -73,9 +75,10 @@ type ProgramOptions struct {
// attempt at loading the program.
LogLevel LogLevel
// Deprecated: the correct log buffer size is determined automatically
// and this field is ignored.
LogSize int
// Starting size of the verifier log buffer. If the verifier log is larger
// than this size, the buffer will be grown to fit the entire log. Leave at
// its default value unless troubleshooting.
LogSizeStart uint32
// Disables the verifier log completely, regardless of other options.
LogDisabled bool
@@ -162,26 +165,35 @@ func (ps *ProgramSpec) Tag() (string, error) {
return ps.Instructions.Tag(internal.NativeEndian)
}
// KernelModule returns the kernel module, if any, the AttachTo function is contained in.
func (ps *ProgramSpec) KernelModule() (string, error) {
// kernelModule returns the kernel module providing the symbol in
// ProgramSpec.AttachTo, if any. Returns an empty string if the symbol is not
// present or not part of a kernel module.
func (ps *ProgramSpec) kernelModule() (string, error) {
if ps.targetsKernelModule() {
return kallsyms.Module(ps.AttachTo)
}
return "", nil
}
// targetsKernelModule returns true if the program supports being attached to a
// symbol provided by a kernel module.
func (ps *ProgramSpec) targetsKernelModule() bool {
if ps.AttachTo == "" {
return "", nil
return false
}
switch ps.Type {
default:
return "", nil
case Tracing:
switch ps.AttachType {
default:
return "", nil
case AttachTraceFEntry:
case AttachTraceFExit:
case AttachTraceFEntry, AttachTraceFExit:
return true
}
fallthrough
case Kprobe:
return kallsyms.KernelModule(ps.AttachTo)
return true
}
return false
}
// VerifierError is returned by [NewProgram] and [NewProgramWithOptions] if a
@@ -261,7 +273,7 @@ func newProgramWithOptions(spec *ProgramSpec, opts ProgramOptions) (*Program, er
// Overwrite Kprobe program version if set to zero or the magic version constant.
kv := spec.KernelVersion
if spec.Type == Kprobe && (kv == 0 || kv == internal.MagicKernelVersion) {
v, err := internal.KernelVersion()
v, err := linux.KernelVersion()
if err != nil {
return nil, fmt.Errorf("detecting kernel version: %w", err)
}
@@ -283,7 +295,7 @@ func newProgramWithOptions(spec *ProgramSpec, opts ProgramOptions) (*Program, er
insns := make(asm.Instructions, len(spec.Instructions))
copy(insns, spec.Instructions)
kmodName, err := spec.KernelModule()
kmodName, err := spec.kernelModule()
if err != nil {
return nil, fmt.Errorf("kernel module search: %w", err)
}
@@ -344,6 +356,10 @@ func newProgramWithOptions(spec *ProgramSpec, opts ProgramOptions) (*Program, er
}
defer kconfig.Close()
if err := resolveKsymReferences(insns); err != nil {
return nil, fmt.Errorf("resolve .ksyms: %w", err)
}
if err := fixupAndValidate(insns); err != nil {
return nil, err
}
@@ -395,9 +411,10 @@ func newProgramWithOptions(spec *ProgramSpec, opts ProgramOptions) (*Program, er
// The caller requested a specific verifier log level. Set up the log buffer
// so that there is a chance of loading the program in a single shot.
logSize := internal.Between(opts.LogSizeStart, minVerifierLogSize, maxVerifierLogSize)
var logBuf []byte
if !opts.LogDisabled && opts.LogLevel != 0 {
logBuf = make([]byte, minVerifierLogSize)
logBuf = make([]byte, logSize)
attr.LogLevel = opts.LogLevel
attr.LogSize = uint32(len(logBuf))
attr.LogBuf = sys.NewSlicePointer(logBuf)
@@ -431,12 +448,11 @@ func newProgramWithOptions(spec *ProgramSpec, opts ProgramOptions) (*Program, er
attr.LogLevel = LogLevelBranch
}
// Make an educated guess how large the buffer should be. Start
// at minVerifierLogSize and then double the size.
logSize := uint32(max(len(logBuf)*2, minVerifierLogSize))
if int(logSize) < len(logBuf) {
return nil, errors.New("overflow while probing log buffer size")
}
// Make an educated guess how large the buffer should be by multiplying.
// Ensure the size doesn't overflow.
const factor = 2
logSize = internal.Between(logSize, minVerifierLogSize, maxVerifierLogSize/factor)
logSize *= factor
if attr.LogTrueSize != 0 {
// 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
return nil, fmt.Errorf("load program: %w (MEMLOCK may be too low, consider rlimit.RemoveMemlock)", err)
}
case errors.Is(err, unix.EFAULT):
// EFAULT is returned when the kernel hits a verifier bug, and always
// overrides ENOSPC, defeating the buffer growth strategy. Warn the user
// that they may need to increase the buffer size manually.
return nil, fmt.Errorf("load program: %w (hit verifier bug, increase LogSizeStart to fit the log and check dmesg)", err)
case errors.Is(err, unix.EINVAL):
if bytes.Contains(tail, coreBadCall) {
err = errBadRelocation
@@ -598,7 +620,7 @@ func (p *Program) Clone() (*Program, error) {
// This requires bpffs to be mounted above fileName.
// See https://docs.cilium.io/en/stable/network/kubernetes/configuration/#mounting-bpffs-with-systemd
func (p *Program) Pin(fileName string) error {
if err := internal.Pin(p.pinnedPath, fileName, p.fd); err != nil {
if err := sys.Pin(p.pinnedPath, fileName, p.fd); err != nil {
return err
}
p.pinnedPath = fileName
@@ -611,7 +633,7 @@ func (p *Program) Pin(fileName string) error {
//
// Unpinning an unpinned Program returns nil.
func (p *Program) Unpin() error {
if err := internal.Unpin(p.pinnedPath); err != nil {
if err := sys.Unpin(p.pinnedPath); err != nil {
return err
}
p.pinnedPath = ""
@@ -699,6 +721,10 @@ func (p *Program) Test(in []byte) (uint32, []byte, error) {
//
// Note: the same restrictions from Test apply.
func (p *Program) Run(opts *RunOptions) (uint32, error) {
if opts == nil {
opts = &RunOptions{}
}
ret, _, err := p.run(opts)
if err != nil {
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
return ret, total, nil
}
var haveProgRun = internal.NewFeatureTest("BPF_PROG_RUN", "4.12", func() error {
var haveProgRun = internal.NewFeatureTest("BPF_PROG_RUN", func() error {
prog, err := NewProgram(&ProgramSpec{
// SocketFilter does not require privileges on newer kernels.
Type: SocketFilter,
@@ -774,7 +800,7 @@ var haveProgRun = internal.NewFeatureTest("BPF_PROG_RUN", "4.12", func() error {
}
return err
})
}, "4.12")
func (p *Program) run(opts *RunOptions) (uint32, time.Duration, error) {
if uint(len(opts.Data)) > math.MaxUint32 {
@@ -883,6 +909,10 @@ func unmarshalProgram(buf sysenc.Buffer) (*Program, error) {
}
func marshalProgram(p *Program, length int) ([]byte, error) {
if p == nil {
return nil, errors.New("can't marshal a nil Program")
}
if length != 4 {
return nil, fmt.Errorf("can't marshal program to %d bytes", length)
}
@@ -892,11 +922,12 @@ func marshalProgram(p *Program, length int) ([]byte, error) {
return buf, nil
}
// LoadPinnedProgram loads a Program from a BPF file.
// LoadPinnedProgram loads a Program from a pin (file) on the BPF virtual
// filesystem.
//
// Requires at least Linux 4.11.
func LoadPinnedProgram(fileName string, opts *LoadPinOptions) (*Program, error) {
fd, err := sys.ObjGet(&sys.ObjGetAttr{
fd, typ, err := sys.ObjGetTyped(&sys.ObjGetAttr{
Pathname: sys.NewStringPointer(fileName),
FileFlags: opts.Marshal(),
})
@@ -904,6 +935,11 @@ func LoadPinnedProgram(fileName string, opts *LoadPinOptions) (*Program, error)
return nil, err
}
if typ != sys.BPF_TYPE_PROG {
_ = fd.Close()
return nil, fmt.Errorf("%s is not a Program", fileName)
}
info, err := newProgramInfoFromFd(fd)
if err != nil {
_ = fd.Close()

View File

@@ -10,6 +10,7 @@ import (
"github.com/cilium/ebpf/asm"
"github.com/cilium/ebpf/internal"
"github.com/cilium/ebpf/internal/linux"
"github.com/cilium/ebpf/internal/sys"
"github.com/cilium/ebpf/internal/tracefs"
"github.com/cilium/ebpf/internal/unix"
@@ -60,7 +61,7 @@ func progLoad(insns asm.Instructions, typ ProgramType, license string) (*sys.FD,
})
}
var haveNestedMaps = internal.NewFeatureTest("nested maps", "4.12", func() error {
var haveNestedMaps = internal.NewFeatureTest("nested maps", func() error {
_, err := sys.MapCreate(&sys.MapCreateAttr{
MapType: sys.MapType(ArrayOfMaps),
KeySize: 4,
@@ -76,9 +77,9 @@ var haveNestedMaps = internal.NewFeatureTest("nested maps", "4.12", func() error
return nil
}
return err
})
}, "4.12")
var haveMapMutabilityModifiers = internal.NewFeatureTest("read- and write-only maps", "5.2", func() error {
var haveMapMutabilityModifiers = internal.NewFeatureTest("read- and write-only maps", func() error {
// This checks BPF_F_RDONLY_PROG and BPF_F_WRONLY_PROG. Since
// BPF_MAP_FREEZE appeared in 5.2 as well we don't do a separate check.
m, err := sys.MapCreate(&sys.MapCreateAttr{
@@ -86,39 +87,39 @@ var haveMapMutabilityModifiers = internal.NewFeatureTest("read- and write-only m
KeySize: 4,
ValueSize: 4,
MaxEntries: 1,
MapFlags: unix.BPF_F_RDONLY_PROG,
MapFlags: sys.BPF_F_RDONLY_PROG,
})
if err != nil {
return internal.ErrNotSupported
}
_ = m.Close()
return nil
})
}, "5.2")
var haveMmapableMaps = internal.NewFeatureTest("mmapable maps", "5.5", func() error {
var haveMmapableMaps = internal.NewFeatureTest("mmapable maps", func() error {
// This checks BPF_F_MMAPABLE, which appeared in 5.5 for array maps.
m, err := sys.MapCreate(&sys.MapCreateAttr{
MapType: sys.MapType(Array),
KeySize: 4,
ValueSize: 4,
MaxEntries: 1,
MapFlags: unix.BPF_F_MMAPABLE,
MapFlags: sys.BPF_F_MMAPABLE,
})
if err != nil {
return internal.ErrNotSupported
}
_ = m.Close()
return nil
})
}, "5.5")
var haveInnerMaps = internal.NewFeatureTest("inner maps", "5.10", func() error {
var haveInnerMaps = internal.NewFeatureTest("inner maps", func() error {
// This checks BPF_F_INNER_MAP, which appeared in 5.10.
m, err := sys.MapCreate(&sys.MapCreateAttr{
MapType: sys.MapType(Array),
KeySize: 4,
ValueSize: 4,
MaxEntries: 1,
MapFlags: unix.BPF_F_INNER_MAP,
MapFlags: sys.BPF_F_INNER_MAP,
})
if err != nil {
@@ -126,16 +127,16 @@ var haveInnerMaps = internal.NewFeatureTest("inner maps", "5.10", func() error {
}
_ = m.Close()
return nil
})
}, "5.10")
var haveNoPreallocMaps = internal.NewFeatureTest("prealloc maps", "4.6", func() error {
var haveNoPreallocMaps = internal.NewFeatureTest("prealloc maps", func() error {
// This checks BPF_F_NO_PREALLOC, which appeared in 4.6.
m, err := sys.MapCreate(&sys.MapCreateAttr{
MapType: sys.MapType(Hash),
KeySize: 4,
ValueSize: 4,
MaxEntries: 1,
MapFlags: unix.BPF_F_NO_PREALLOC,
MapFlags: sys.BPF_F_NO_PREALLOC,
})
if err != nil {
@@ -143,7 +144,7 @@ var haveNoPreallocMaps = internal.NewFeatureTest("prealloc maps", "4.6", func()
}
_ = m.Close()
return nil
})
}, "4.6")
func wrapMapError(err error) error {
if err == nil {
@@ -169,7 +170,7 @@ func wrapMapError(err error) error {
return err
}
var haveObjName = internal.NewFeatureTest("object names", "4.15", func() error {
var haveObjName = internal.NewFeatureTest("object names", func() error {
attr := sys.MapCreateAttr{
MapType: sys.MapType(Array),
KeySize: 4,
@@ -178,16 +179,24 @@ var haveObjName = internal.NewFeatureTest("object names", "4.15", func() error {
MapName: sys.NewObjName("feature_test"),
}
// Tolerate EPERM as this runs during ELF loading which is potentially
// unprivileged. Only EINVAL is conclusive, thrown from CHECK_ATTR.
fd, err := sys.MapCreate(&attr)
if err != nil {
if errors.Is(err, unix.EPERM) {
return nil
}
if errors.Is(err, unix.EINVAL) {
return internal.ErrNotSupported
}
if err != nil {
return err
}
_ = fd.Close()
return nil
})
}, "4.15")
var objNameAllowsDot = internal.NewFeatureTest("dot in object names", "5.2", func() error {
var objNameAllowsDot = internal.NewFeatureTest("dot in object names", func() error {
if err := haveObjName(); err != nil {
return err
}
@@ -200,16 +209,25 @@ var objNameAllowsDot = internal.NewFeatureTest("dot in object names", "5.2", fun
MapName: sys.NewObjName(".test"),
}
// Tolerate EPERM, otherwise MapSpec.Name has its dots removed when run by
// unprivileged tools. (bpf2go, other code gen). Only EINVAL is conclusive,
// thrown from bpf_obj_name_cpy().
fd, err := sys.MapCreate(&attr)
if err != nil {
if errors.Is(err, unix.EPERM) {
return nil
}
if errors.Is(err, unix.EINVAL) {
return internal.ErrNotSupported
}
if err != nil {
return err
}
_ = fd.Close()
return nil
})
}, "5.2")
var haveBatchAPI = internal.NewFeatureTest("map batch api", "5.6", func() error {
var haveBatchAPI = internal.NewFeatureTest("map batch api", func() error {
var maxEntries uint32 = 2
attr := sys.MapCreateAttr{
MapType: sys.MapType(Hash),
@@ -239,9 +257,9 @@ var haveBatchAPI = internal.NewFeatureTest("map batch api", "5.6", func() error
return internal.ErrNotSupported
}
return nil
})
}, "5.6")
var haveProbeReadKernel = internal.NewFeatureTest("bpf_probe_read_kernel", "5.5", func() error {
var haveProbeReadKernel = internal.NewFeatureTest("bpf_probe_read_kernel", func() error {
insns := asm.Instructions{
asm.Mov.Reg(asm.R1, asm.R10),
asm.Add.Imm(asm.R1, -8),
@@ -257,9 +275,9 @@ var haveProbeReadKernel = internal.NewFeatureTest("bpf_probe_read_kernel", "5.5"
}
_ = fd.Close()
return nil
})
}, "5.5")
var haveBPFToBPFCalls = internal.NewFeatureTest("bpf2bpf calls", "4.16", func() error {
var haveBPFToBPFCalls = internal.NewFeatureTest("bpf2bpf calls", func() error {
insns := asm.Instructions{
asm.Call.Label("prog2").WithSymbol("prog1"),
asm.Return(),
@@ -273,10 +291,10 @@ var haveBPFToBPFCalls = internal.NewFeatureTest("bpf2bpf calls", "4.16", func()
}
_ = fd.Close()
return nil
})
}, "4.16")
var haveSyscallWrapper = internal.NewFeatureTest("syscall wrapper", "4.17", func() error {
prefix := internal.PlatformPrefix()
var haveSyscallWrapper = internal.NewFeatureTest("syscall wrapper", func() error {
prefix := linux.PlatformPrefix()
if prefix == "" {
return fmt.Errorf("unable to find the platform prefix for (%s)", runtime.GOARCH)
}
@@ -302,9 +320,9 @@ var haveSyscallWrapper = internal.NewFeatureTest("syscall wrapper", "4.17", func
}
return evt.Close()
})
}, "4.17")
var haveProgramExtInfos = internal.NewFeatureTest("program ext_infos", "5.0", func() error {
var haveProgramExtInfos = internal.NewFeatureTest("program ext_infos", func() error {
insns := asm.Instructions{
asm.Mov.Imm(asm.R0, 0),
asm.Return(),
@@ -334,4 +352,4 @@ var haveProgramExtInfos = internal.NewFeatureTest("program ext_infos", "5.0", fu
}
return err
})
}, "5.0")

View File

@@ -2,7 +2,6 @@ package ebpf
import (
"github.com/cilium/ebpf/internal/sys"
"github.com/cilium/ebpf/internal/unix"
)
//go:generate go run golang.org/x/tools/cmd/stringer@latest -output types_string.go -type=MapType,ProgramType,PinType
@@ -95,6 +94,14 @@ const (
InodeStorage
// TaskStorage - Specialized local storage map for task_struct.
TaskStorage
// BloomFilter - Space-efficient data structure to quickly test whether an element exists in a set.
BloomFilter
// UserRingbuf - The reverse of RingBuf, used to send messages from user space to BPF programs.
UserRingbuf
// CgroupStorage - Store data keyed on a cgroup. If the cgroup disappears, the key is automatically removed.
CgroupStorage
// Arena - Sparse shared memory region between a BPF program and user space.
Arena
)
// hasPerCPUValue returns true if the Map stores a value per CPU.
@@ -120,6 +127,21 @@ func (mt MapType) canStoreProgram() bool {
return mt == ProgramArray
}
// canHaveValueSize returns true if the map type supports setting a value size.
func (mt MapType) canHaveValueSize() bool {
switch mt {
case RingBuf, Arena:
return false
// Special-case perf events since they require a value size of either 0 or 4
// for historical reasons. Let the library fix this up later.
case PerfEventArray:
return false
}
return true
}
// ProgramType of the eBPF program
type ProgramType uint32
@@ -214,6 +236,7 @@ const (
AttachSkReuseportSelectOrMigrate = AttachType(sys.BPF_SK_REUSEPORT_SELECT_OR_MIGRATE)
AttachPerfEvent = AttachType(sys.BPF_PERF_EVENT)
AttachTraceKprobeMulti = AttachType(sys.BPF_TRACE_KPROBE_MULTI)
AttachTraceKprobeSession = AttachType(sys.BPF_TRACE_KPROBE_SESSION)
AttachLSMCgroup = AttachType(sys.BPF_LSM_CGROUP)
AttachStructOps = AttachType(sys.BPF_STRUCT_OPS)
AttachNetfilter = AttachType(sys.BPF_NETFILTER)
@@ -263,10 +286,10 @@ func (lpo *LoadPinOptions) Marshal() uint32 {
flags := lpo.Flags
if lpo.ReadOnly {
flags |= unix.BPF_F_RDONLY
flags |= sys.BPF_F_RDONLY
}
if lpo.WriteOnly {
flags |= unix.BPF_F_WRONLY
flags |= sys.BPF_F_WRONLY
}
return flags
}

View File

@@ -38,11 +38,15 @@ func _() {
_ = x[RingBuf-27]
_ = x[InodeStorage-28]
_ = x[TaskStorage-29]
_ = x[BloomFilter-30]
_ = x[UserRingbuf-31]
_ = x[CgroupStorage-32]
_ = x[Arena-33]
}
const _MapType_name = "UnspecifiedMapHashArrayProgramArrayPerfEventArrayPerCPUHashPerCPUArrayStackTraceCGroupArrayLRUHashLRUCPUHashLPMTrieArrayOfMapsHashOfMapsDevMapSockMapCPUMapXSKMapSockHashCGroupStorageReusePortSockArrayPerCPUCGroupStorageQueueStackSkStorageDevMapHashStructOpsMapRingBufInodeStorageTaskStorage"
const _MapType_name = "UnspecifiedMapHashArrayProgramArrayPerfEventArrayPerCPUHashPerCPUArrayStackTraceCGroupArrayLRUHashLRUCPUHashLPMTrieArrayOfMapsHashOfMapsDevMapSockMapCPUMapXSKMapSockHashCGroupStorageReusePortSockArrayPerCPUCGroupStorageQueueStackSkStorageDevMapHashStructOpsMapRingBufInodeStorageTaskStorageBloomFilterUserRingbufCgroupStorageArena"
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}
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}
func (i MapType) String() string {
if i >= MapType(len(_MapType_index)-1) {

230
vendor/github.com/cilium/ebpf/variable.go generated vendored Normal file
View File

@@ -0,0 +1,230 @@
package ebpf
import (
"fmt"
"io"
"github.com/cilium/ebpf/btf"
"github.com/cilium/ebpf/internal/sysenc"
)
// VariableSpec is a convenience wrapper for modifying global variables of a
// CollectionSpec before loading it into the kernel.
//
// All operations on a VariableSpec's underlying MapSpec are performed in the
// host's native endianness.
type VariableSpec struct {
name string
offset uint64
size uint64
m *MapSpec
t *btf.Var
}
// Set sets the value of the VariableSpec to the provided input using the host's
// native endianness.
func (s *VariableSpec) Set(in any) error {
buf, err := sysenc.Marshal(in, int(s.size))
if err != nil {
return fmt.Errorf("marshaling value %s: %w", s.name, err)
}
b, _, err := s.m.dataSection()
if err != nil {
return fmt.Errorf("getting data section of map %s: %w", s.m.Name, err)
}
if int(s.offset+s.size) > len(b) {
return fmt.Errorf("offset %d(+%d) for variable %s is out of bounds", s.offset, s.size, s.name)
}
// MapSpec.Copy() performs a shallow copy. Fully copy the byte slice
// to avoid any changes affecting other copies of the MapSpec.
cpy := make([]byte, len(b))
copy(cpy, b)
buf.CopyTo(cpy[s.offset : s.offset+s.size])
s.m.Contents[0] = MapKV{Key: uint32(0), Value: cpy}
return nil
}
// Get writes the value of the VariableSpec to the provided output using the
// host's native endianness.
func (s *VariableSpec) Get(out any) error {
b, _, err := s.m.dataSection()
if err != nil {
return fmt.Errorf("getting data section of map %s: %w", s.m.Name, err)
}
if int(s.offset+s.size) > len(b) {
return fmt.Errorf("offset %d(+%d) for variable %s is out of bounds", s.offset, s.size, s.name)
}
if err := sysenc.Unmarshal(out, b[s.offset:s.offset+s.size]); err != nil {
return fmt.Errorf("unmarshaling value: %w", err)
}
return nil
}
// Size returns the size of the variable in bytes.
func (s *VariableSpec) Size() uint64 {
return s.size
}
// MapName returns the name of the underlying MapSpec.
func (s *VariableSpec) MapName() string {
return s.m.Name
}
// Offset returns the offset of the variable in the underlying MapSpec.
func (s *VariableSpec) Offset() uint64 {
return s.offset
}
// Constant returns true if the VariableSpec represents a variable that is
// read-only from the perspective of the BPF program.
func (s *VariableSpec) Constant() bool {
return s.m.readOnly()
}
// Type returns the [btf.Var] representing the variable in its data section.
// This is useful for inspecting the variable's decl tags and the type
// information of the inner type.
//
// Returns nil if the original ELF object did not contain BTF information.
func (s *VariableSpec) Type() *btf.Var {
return s.t
}
func (s *VariableSpec) String() string {
return fmt.Sprintf("%s (type=%v, map=%s, offset=%d, size=%d)", s.name, s.t, s.m.Name, s.offset, s.size)
}
// copy returns a new VariableSpec with the same values as the original,
// but with a different underlying MapSpec. This is useful when copying a
// CollectionSpec. Returns nil if a MapSpec with the same name is not found.
func (s *VariableSpec) copy(cpy *CollectionSpec) *VariableSpec {
out := &VariableSpec{
name: s.name,
offset: s.offset,
size: s.size,
}
if s.t != nil {
out.t = btf.Copy(s.t).(*btf.Var)
}
// Attempt to find a MapSpec with the same name in the copied CollectionSpec.
for _, m := range cpy.Maps {
if m.Name == s.m.Name {
out.m = m
return out
}
}
return nil
}
// Variable is a convenience wrapper for modifying global variables of a
// Collection after loading it into the kernel. Operations on a Variable are
// performed using direct memory access, bypassing the BPF map syscall API.
//
// On kernels older than 5.5, most interactions with Variable return
// [ErrNotSupported].
type Variable struct {
name string
offset uint64
size uint64
t *btf.Var
mm *Memory
}
func newVariable(name string, offset, size uint64, t *btf.Var, mm *Memory) (*Variable, error) {
if mm != nil {
if int(offset+size) > mm.Size() {
return nil, fmt.Errorf("offset %d(+%d) is out of bounds", offset, size)
}
}
return &Variable{
name: name,
offset: offset,
size: size,
t: t,
mm: mm,
}, nil
}
// Size returns the size of the variable.
func (v *Variable) Size() uint64 {
return v.size
}
// ReadOnly returns true if the Variable represents a variable that is read-only
// after loading the Collection into the kernel.
//
// On systems without BPF_F_MMAPABLE support, ReadOnly always returns true.
func (v *Variable) ReadOnly() bool {
if v.mm == nil {
return true
}
return v.mm.ReadOnly()
}
// Type returns the [btf.Var] representing the variable in its data section.
// This is useful for inspecting the variable's decl tags and the type
// information of the inner type.
//
// Returns nil if the original ELF object did not contain BTF information.
func (v *Variable) Type() *btf.Var {
return v.t
}
func (v *Variable) String() string {
return fmt.Sprintf("%s (type=%v)", v.name, v.t)
}
// Set the value of the Variable to the provided input. The input must marshal
// to the same length as the size of the Variable.
func (v *Variable) Set(in any) error {
if v.mm == nil {
return fmt.Errorf("variable %s: direct access requires Linux 5.5 or later: %w", v.name, ErrNotSupported)
}
if v.ReadOnly() {
return fmt.Errorf("variable %s: %w", v.name, ErrReadOnly)
}
buf, err := sysenc.Marshal(in, int(v.size))
if err != nil {
return fmt.Errorf("marshaling value %s: %w", v.name, err)
}
if _, err := v.mm.WriteAt(buf.Bytes(), int64(v.offset)); err != nil {
return fmt.Errorf("writing value to %s: %w", v.name, err)
}
return nil
}
// Get writes the value of the Variable to the provided output. The output must
// be a pointer to a value whose size matches the Variable.
func (v *Variable) Get(out any) error {
if v.mm == nil {
return fmt.Errorf("variable %s: direct access requires Linux 5.5 or later: %w", v.name, ErrNotSupported)
}
if !v.mm.bounds(v.offset, v.size) {
return fmt.Errorf("variable %s: access out of bounds: %w", v.name, io.EOF)
}
if err := sysenc.Unmarshal(out, v.mm.b[v.offset:v.offset+v.size]); err != nil {
return fmt.Errorf("unmarshaling value %s: %w", v.name, err)
}
return nil
}

27
vendor/golang.org/x/exp/LICENSE generated vendored
View File

@@ -1,27 +0,0 @@
Copyright (c) 2009 The Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

22
vendor/golang.org/x/exp/PATENTS generated vendored
View File

@@ -1,22 +0,0 @@
Additional IP Rights Grant (Patents)
"This implementation" means the copyrightable works distributed by
Google as part of the Go project.
Google hereby grants to You a perpetual, worldwide, non-exclusive,
no-charge, royalty-free, irrevocable (except as stated in this section)
patent license to make, have made, use, offer to sell, sell, import,
transfer and otherwise run, modify and propagate the contents of this
implementation of Go, where such license applies only to those patent
claims, both currently owned or controlled by Google and acquired in
the future, licensable by Google that are necessarily infringed by this
implementation of Go. This grant does not include claims that would be
infringed only as a consequence of further modification of this
implementation. If you or your agent or exclusive licensee institute or
order or agree to the institution of patent litigation against any
entity (including a cross-claim or counterclaim in a lawsuit) alleging
that this implementation of Go or any code incorporated within this
implementation of Go constitutes direct or contributory patent
infringement, or inducement of patent infringement, then any patent
rights granted to you under this License for this implementation of Go
shall terminate as of the date such litigation is filed.

View File

@@ -1,50 +0,0 @@
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package constraints defines a set of useful constraints to be used
// with type parameters.
package constraints
// Signed is a constraint that permits any signed integer type.
// If future releases of Go add new predeclared signed integer types,
// this constraint will be modified to include them.
type Signed interface {
~int | ~int8 | ~int16 | ~int32 | ~int64
}
// Unsigned is a constraint that permits any unsigned integer type.
// If future releases of Go add new predeclared unsigned integer types,
// this constraint will be modified to include them.
type Unsigned interface {
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr
}
// Integer is a constraint that permits any integer type.
// If future releases of Go add new predeclared integer types,
// this constraint will be modified to include them.
type Integer interface {
Signed | Unsigned
}
// Float is a constraint that permits any floating-point type.
// If future releases of Go add new predeclared floating-point types,
// this constraint will be modified to include them.
type Float interface {
~float32 | ~float64
}
// Complex is a constraint that permits any complex numeric type.
// If future releases of Go add new predeclared complex numeric types,
// this constraint will be modified to include them.
type Complex interface {
~complex64 | ~complex128
}
// Ordered is a constraint that permits any ordered type: any type
// that supports the operators < <= >= >.
// If future releases of Go add new ordered types,
// this constraint will be modified to include them.
type Ordered interface {
Integer | Float | ~string
}

9
vendor/modules.txt vendored
View File

@@ -2,16 +2,18 @@
## explicit; go 1.16
github.com/checkpoint-restore/go-criu/v6
github.com/checkpoint-restore/go-criu/v6/rpc
# github.com/cilium/ebpf v0.16.0
## explicit; go 1.21
# github.com/cilium/ebpf v0.17.3
## explicit; go 1.22
github.com/cilium/ebpf
github.com/cilium/ebpf/asm
github.com/cilium/ebpf/btf
github.com/cilium/ebpf/internal
github.com/cilium/ebpf/internal/kallsyms
github.com/cilium/ebpf/internal/kconfig
github.com/cilium/ebpf/internal/linux
github.com/cilium/ebpf/internal/sys
github.com/cilium/ebpf/internal/sysenc
github.com/cilium/ebpf/internal/testutils/fdtrace
github.com/cilium/ebpf/internal/tracefs
github.com/cilium/ebpf/internal/unix
github.com/cilium/ebpf/link
@@ -78,9 +80,6 @@ github.com/vishvananda/netlink/nl
# github.com/vishvananda/netns v0.0.4
## explicit; go 1.17
github.com/vishvananda/netns
# golang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2
## explicit; go 1.18
golang.org/x/exp/constraints
# golang.org/x/net v0.35.0
## explicit; go 1.18
golang.org/x/net/bpf