build(deps): bump github.com/cilium/ebpf from 0.7.0 to 0.8.0

Bumps [github.com/cilium/ebpf](https://github.com/cilium/ebpf) from 0.7.0 to 0.8.0.
- [Release notes](https://github.com/cilium/ebpf/releases)
- [Commits](https://github.com/cilium/ebpf/compare/v0.7.0...v0.8.0)

---
updated-dependencies:
- dependency-name: github.com/cilium/ebpf
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
This commit is contained in:
dependabot[bot]
2022-01-24 04:15:07 +00:00
committed by GitHub
parent 2a62093dbb
commit cb36410868
68 changed files with 4771 additions and 2459 deletions
+1 -1
View File
@@ -4,7 +4,7 @@ go 1.16
require (
github.com/checkpoint-restore/go-criu/v5 v5.3.0
github.com/cilium/ebpf v0.7.0
github.com/cilium/ebpf v0.8.0
github.com/containerd/console v1.0.3
github.com/coreos/go-systemd/v22 v22.3.2
github.com/cyphar/filepath-securejoin v0.2.3
+16 -9
View File
@@ -1,34 +1,37 @@
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/checkpoint-restore/go-criu/v5 v5.3.0 h1:wpFFOoomK3389ue2lAb0Boag6XPht5QYpipxmSNL4d8=
github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E=
github.com/cilium/ebpf v0.7.0 h1:1k/q3ATgxSXRdrmPfH8d7YK0GfqVsEKZAX9dQZvs56k=
github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA=
github.com/cilium/ebpf v0.8.0 h1:2V6KSg3FRADVU2BMIRemZ0hV+9OM+aAHhZDjQyjJTAs=
github.com/cilium/ebpf v0.8.0/go.mod h1:f5zLIM0FSNuAkSyLAN7X+Hy6yznlF1mNiWUMfxMtrgk=
github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw=
github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U=
github.com/coreos/go-systemd/v22 v22.3.2 h1:D9/bQk5vlXQFZ6Kwuu6zaiXJ9oTPe68++AzAJc1DzSI=
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/cyphar/filepath-securejoin v0.2.3 h1:YX6ebbZCZP7VkM3scTTokDgBL2TY741X51MTk3ycuNI=
github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/frankban/quicktest v1.11.3 h1:8sXhOn0uLys67V8EsXLc6eszDs8VXWxL3iRvebPhedY=
github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
github.com/frankban/quicktest v1.14.0 h1:+cqqvzZV87b4adx/5ayVOaYZ2CrvM4ejQvUdBzPPUss=
github.com/frankban/quicktest v1.14.0/go.mod h1:NeW+ay9A/U67EYXNFA1nPE8e/tnQv/09mUdL/ijj8og=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/godbus/dbus/v5 v5.0.6 h1:mkgN1ofwASrYnJ5W6U/BxG15eXXXjirgZc7CLqkcaro=
github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/moby/sys/mountinfo v0.5.0 h1:2Ks8/r6lopsxWi9m58nlwjaeSzUX9iiL1vj5qB/9ObI=
github.com/moby/sys/mountinfo v0.5.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU=
github.com/mrunalp/fileutils v0.5.0 h1:NKzVxiH7eSk+OQ4M+ZYW1K6h27RUV3MI6NUTsHhU6Z4=
@@ -39,6 +42,8 @@ github.com/opencontainers/selinux v1.10.0 h1:rAiKF8hTcgLI3w0DHm6i0ylVVcOrlgR1kK9
github.com/opencontainers/selinux v1.10.0/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/seccomp/libseccomp-golang v0.9.2-0.20210429002308-3879420cc921 h1:58EBmR2dMNL2n/FnbQewK3D14nXr0V9CObDSvMJLq+Y=
@@ -77,4 +82,6 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0
google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+6
View File
@@ -78,3 +78,9 @@ tend to use bpf_link to do so. Older hooks unfortunately use a combination of
syscalls, netlink messages, etc. Adding support for a new link type should not
pull in large dependencies like netlink, so XDP programs or tracepoints are
out of scope.
Each bpf_link_type has one corresponding Go type, e.g. `link.tracing` corresponds
to BPF_LINK_TRACING. In general, these types should be unexported as long as they
don't export methods outside of the Link interface. Each Go type may have multiple
exported constructors. For example `AttachTracing` and `AttachLSM` create a
tracing link, but are distinct functions since they may require different arguments.
+37 -16
View File
@@ -1,14 +1,20 @@
# The development version of clang is distributed as the 'clang' binary,
# while stable/released versions have a version number attached.
# Pin the default clang to a stable version.
CLANG ?= clang-12
CFLAGS := -target bpf -O2 -g -Wall -Werror $(CFLAGS)
CLANG ?= clang-13
STRIP ?= llvm-strip-13
CFLAGS := -O2 -g -Wall -Werror $(CFLAGS)
# Obtain an absolute path to the directory of the Makefile.
# Assume the Makefile is in the root of the repository.
REPODIR := $(shell dirname $(realpath $(firstword $(MAKEFILE_LIST))))
UIDGID := $(shell stat -c '%u:%g' ${REPODIR})
# Prefer podman if installed, otherwise use docker.
# Note: Setting the var at runtime will always override.
CONTAINER_ENGINE ?= $(if $(shell command -v podman), podman, docker)
CONTAINER_RUN_ARGS ?= $(if $(filter ${CONTAINER_ENGINE}, podman),, --user "${UIDGID}")
IMAGE := $(shell cat ${REPODIR}/testdata/docker/IMAGE)
VERSION := $(shell cat ${REPODIR}/testdata/docker/VERSION)
@@ -26,23 +32,27 @@ TARGETS := \
testdata/strings \
testdata/freplace \
testdata/iproute2_map_compat \
testdata/map_spin_lock \
testdata/subprog_reloc \
testdata/fwd_decl \
internal/btf/testdata/relocs
.PHONY: all clean docker-all docker-shell
.PHONY: all clean container-all container-shell generate
.DEFAULT_TARGET = docker-all
.DEFAULT_TARGET = container-all
# Build all ELF binaries using a Dockerized LLVM toolchain.
docker-all:
docker run --rm --user "${UIDGID}" \
# Build all ELF binaries using a containerized LLVM toolchain.
container-all:
${CONTAINER_ENGINE} run --rm ${CONTAINER_RUN_ARGS} \
-v "${REPODIR}":/ebpf -w /ebpf --env MAKEFLAGS \
--env CFLAGS="-fdebug-prefix-map=/ebpf=." \
--env HOME="/tmp" \
"${IMAGE}:${VERSION}" \
make all
$(MAKE) all
# (debug) Drop the user into a shell inside the Docker container as root.
docker-shell:
docker run --rm -ti \
# (debug) Drop the user into a shell inside the container as root.
container-shell:
${CONTAINER_ENGINE} run --rm -ti \
-v "${REPODIR}":/ebpf -w /ebpf \
"${IMAGE}:${VERSION}"
@@ -50,21 +60,32 @@ clean:
-$(RM) testdata/*.elf
-$(RM) internal/btf/testdata/*.elf
all: $(addsuffix -el.elf,$(TARGETS)) $(addsuffix -eb.elf,$(TARGETS))
all: $(addsuffix -el.elf,$(TARGETS)) $(addsuffix -eb.elf,$(TARGETS)) generate
ln -srf testdata/loader-$(CLANG)-el.elf testdata/loader-el.elf
ln -srf testdata/loader-$(CLANG)-eb.elf testdata/loader-eb.elf
# $BPF_CLANG is used in go:generate invocations.
generate: export BPF_CLANG := $(CLANG)
generate: export BPF_CFLAGS := $(CFLAGS)
generate:
go generate ./cmd/bpf2go/test
cd examples/ && go generate ./...
testdata/loader-%-el.elf: testdata/loader.c
$* $(CFLAGS) -mlittle-endian -c $< -o $@
$* $(CFLAGS) -target bpfel -c $< -o $@
$(STRIP) -g $@
testdata/loader-%-eb.elf: testdata/loader.c
$* $(CFLAGS) -mbig-endian -c $< -o $@
$* $(CFLAGS) -target bpfeb -c $< -o $@
$(STRIP) -g $@
%-el.elf: %.c
$(CLANG) $(CFLAGS) -mlittle-endian -c $< -o $@
$(CLANG) $(CFLAGS) -target bpfel -c $< -o $@
$(STRIP) -g $@
%-eb.elf : %.c
$(CLANG) $(CFLAGS) -mbig-endian -c $< -o $@
$(CLANG) $(CFLAGS) -target bpfeb -c $< -o $@
$(STRIP) -g $@
# Usage: make VMLINUX=/path/to/vmlinux vmlinux-btf
.PHONY: vmlinux-btf
+8 -2
View File
@@ -45,13 +45,16 @@ This library includes the following packages:
`PERF_EVENT_ARRAY`
* [ringbuf](https://pkg.go.dev/github.com/cilium/ebpf/ringbuf) allows reading from a
`BPF_MAP_TYPE_RINGBUF` map
* [features](https://pkg.go.dev/github.com/cilium/ebpf/features) implements the equivalent
of `bpftool feature probe` for discovering BPF-related kernel features using native Go.
* [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.
## Requirements
* A version of Go that is [supported by
upstream](https://golang.org/doc/devel/release.html#policy)
* Linux >= 4.9. CI is run against LTS releases.
* Linux >= 4.4. CI is run against LTS releases.
## Regenerating Testdata
@@ -59,6 +62,9 @@ Run `make` in the root of this repository to rebuild testdata in all
subpackages. This requires Docker, as it relies on a standardized build
environment to keep the build output stable.
It is possible to regenerate data using Podman by overriding the `CONTAINER_*`
variables: `CONTAINER_ENGINE=podman CONTAINER_RUN_ARGS= make`.
The toolchain image build files are kept in [testdata/docker/](testdata/docker/).
## License
+7
View File
@@ -190,6 +190,13 @@ const (
FnSysBpf
FnBtfFindByNameKind
FnSysClose
FnTimerInit
FnTimerSetCallback
FnTimerStart
FnTimerCancel
FnGetFuncIp
FnGetAttachCookie
FnTaskPtRegs
)
// Call emits a function call.
+9 -2
View File
@@ -177,11 +177,18 @@ func _() {
_ = x[FnSysBpf-166]
_ = x[FnBtfFindByNameKind-167]
_ = x[FnSysClose-168]
_ = x[FnTimerInit-169]
_ = x[FnTimerSetCallback-170]
_ = x[FnTimerStart-171]
_ = x[FnTimerCancel-172]
_ = x[FnGetFuncIp-173]
_ = x[FnGetAttachCookie-174]
_ = x[FnTaskPtRegs-175]
}
const _BuiltinFunc_name = "FnUnspecFnMapLookupElemFnMapUpdateElemFnMapDeleteElemFnProbeReadFnKtimeGetNsFnTracePrintkFnGetPrandomU32FnGetSmpProcessorIdFnSkbStoreBytesFnL3CsumReplaceFnL4CsumReplaceFnTailCallFnCloneRedirectFnGetCurrentPidTgidFnGetCurrentUidGidFnGetCurrentCommFnGetCgroupClassidFnSkbVlanPushFnSkbVlanPopFnSkbGetTunnelKeyFnSkbSetTunnelKeyFnPerfEventReadFnRedirectFnGetRouteRealmFnPerfEventOutputFnSkbLoadBytesFnGetStackidFnCsumDiffFnSkbGetTunnelOptFnSkbSetTunnelOptFnSkbChangeProtoFnSkbChangeTypeFnSkbUnderCgroupFnGetHashRecalcFnGetCurrentTaskFnProbeWriteUserFnCurrentTaskUnderCgroupFnSkbChangeTailFnSkbPullDataFnCsumUpdateFnSetHashInvalidFnGetNumaNodeIdFnSkbChangeHeadFnXdpAdjustHeadFnProbeReadStrFnGetSocketCookieFnGetSocketUidFnSetHashFnSetsockoptFnSkbAdjustRoomFnRedirectMapFnSkRedirectMapFnSockMapUpdateFnXdpAdjustMetaFnPerfEventReadValueFnPerfProgReadValueFnGetsockoptFnOverrideReturnFnSockOpsCbFlagsSetFnMsgRedirectMapFnMsgApplyBytesFnMsgCorkBytesFnMsgPullDataFnBindFnXdpAdjustTailFnSkbGetXfrmStateFnGetStackFnSkbLoadBytesRelativeFnFibLookupFnSockHashUpdateFnMsgRedirectHashFnSkRedirectHashFnLwtPushEncapFnLwtSeg6StoreBytesFnLwtSeg6AdjustSrhFnLwtSeg6ActionFnRcRepeatFnRcKeydownFnSkbCgroupIdFnGetCurrentCgroupIdFnGetLocalStorageFnSkSelectReuseportFnSkbAncestorCgroupIdFnSkLookupTcpFnSkLookupUdpFnSkReleaseFnMapPushElemFnMapPopElemFnMapPeekElemFnMsgPushDataFnMsgPopDataFnRcPointerRelFnSpinLockFnSpinUnlockFnSkFullsockFnTcpSockFnSkbEcnSetCeFnGetListenerSockFnSkcLookupTcpFnTcpCheckSyncookieFnSysctlGetNameFnSysctlGetCurrentValueFnSysctlGetNewValueFnSysctlSetNewValueFnStrtolFnStrtoulFnSkStorageGetFnSkStorageDeleteFnSendSignalFnTcpGenSyncookieFnSkbOutputFnProbeReadUserFnProbeReadKernelFnProbeReadUserStrFnProbeReadKernelStrFnTcpSendAckFnSendSignalThreadFnJiffies64FnReadBranchRecordsFnGetNsCurrentPidTgidFnXdpOutputFnGetNetnsCookieFnGetCurrentAncestorCgroupIdFnSkAssignFnKtimeGetBootNsFnSeqPrintfFnSeqWriteFnSkCgroupIdFnSkAncestorCgroupIdFnRingbufOutputFnRingbufReserveFnRingbufSubmitFnRingbufDiscardFnRingbufQueryFnCsumLevelFnSkcToTcp6SockFnSkcToTcpSockFnSkcToTcpTimewaitSockFnSkcToTcpRequestSockFnSkcToUdp6SockFnGetTaskStackFnLoadHdrOptFnStoreHdrOptFnReserveHdrOptFnInodeStorageGetFnInodeStorageDeleteFnDPathFnCopyFromUserFnSnprintfBtfFnSeqPrintfBtfFnSkbCgroupClassidFnRedirectNeighFnPerCpuPtrFnThisCpuPtrFnRedirectPeerFnTaskStorageGetFnTaskStorageDeleteFnGetCurrentTaskBtfFnBprmOptsSetFnKtimeGetCoarseNsFnImaInodeHashFnSockFromFileFnCheckMtuFnForEachMapElemFnSnprintfFnSysBpfFnBtfFindByNameKindFnSysClose"
const _BuiltinFunc_name = "FnUnspecFnMapLookupElemFnMapUpdateElemFnMapDeleteElemFnProbeReadFnKtimeGetNsFnTracePrintkFnGetPrandomU32FnGetSmpProcessorIdFnSkbStoreBytesFnL3CsumReplaceFnL4CsumReplaceFnTailCallFnCloneRedirectFnGetCurrentPidTgidFnGetCurrentUidGidFnGetCurrentCommFnGetCgroupClassidFnSkbVlanPushFnSkbVlanPopFnSkbGetTunnelKeyFnSkbSetTunnelKeyFnPerfEventReadFnRedirectFnGetRouteRealmFnPerfEventOutputFnSkbLoadBytesFnGetStackidFnCsumDiffFnSkbGetTunnelOptFnSkbSetTunnelOptFnSkbChangeProtoFnSkbChangeTypeFnSkbUnderCgroupFnGetHashRecalcFnGetCurrentTaskFnProbeWriteUserFnCurrentTaskUnderCgroupFnSkbChangeTailFnSkbPullDataFnCsumUpdateFnSetHashInvalidFnGetNumaNodeIdFnSkbChangeHeadFnXdpAdjustHeadFnProbeReadStrFnGetSocketCookieFnGetSocketUidFnSetHashFnSetsockoptFnSkbAdjustRoomFnRedirectMapFnSkRedirectMapFnSockMapUpdateFnXdpAdjustMetaFnPerfEventReadValueFnPerfProgReadValueFnGetsockoptFnOverrideReturnFnSockOpsCbFlagsSetFnMsgRedirectMapFnMsgApplyBytesFnMsgCorkBytesFnMsgPullDataFnBindFnXdpAdjustTailFnSkbGetXfrmStateFnGetStackFnSkbLoadBytesRelativeFnFibLookupFnSockHashUpdateFnMsgRedirectHashFnSkRedirectHashFnLwtPushEncapFnLwtSeg6StoreBytesFnLwtSeg6AdjustSrhFnLwtSeg6ActionFnRcRepeatFnRcKeydownFnSkbCgroupIdFnGetCurrentCgroupIdFnGetLocalStorageFnSkSelectReuseportFnSkbAncestorCgroupIdFnSkLookupTcpFnSkLookupUdpFnSkReleaseFnMapPushElemFnMapPopElemFnMapPeekElemFnMsgPushDataFnMsgPopDataFnRcPointerRelFnSpinLockFnSpinUnlockFnSkFullsockFnTcpSockFnSkbEcnSetCeFnGetListenerSockFnSkcLookupTcpFnTcpCheckSyncookieFnSysctlGetNameFnSysctlGetCurrentValueFnSysctlGetNewValueFnSysctlSetNewValueFnStrtolFnStrtoulFnSkStorageGetFnSkStorageDeleteFnSendSignalFnTcpGenSyncookieFnSkbOutputFnProbeReadUserFnProbeReadKernelFnProbeReadUserStrFnProbeReadKernelStrFnTcpSendAckFnSendSignalThreadFnJiffies64FnReadBranchRecordsFnGetNsCurrentPidTgidFnXdpOutputFnGetNetnsCookieFnGetCurrentAncestorCgroupIdFnSkAssignFnKtimeGetBootNsFnSeqPrintfFnSeqWriteFnSkCgroupIdFnSkAncestorCgroupIdFnRingbufOutputFnRingbufReserveFnRingbufSubmitFnRingbufDiscardFnRingbufQueryFnCsumLevelFnSkcToTcp6SockFnSkcToTcpSockFnSkcToTcpTimewaitSockFnSkcToTcpRequestSockFnSkcToUdp6SockFnGetTaskStackFnLoadHdrOptFnStoreHdrOptFnReserveHdrOptFnInodeStorageGetFnInodeStorageDeleteFnDPathFnCopyFromUserFnSnprintfBtfFnSeqPrintfBtfFnSkbCgroupClassidFnRedirectNeighFnPerCpuPtrFnThisCpuPtrFnRedirectPeerFnTaskStorageGetFnTaskStorageDeleteFnGetCurrentTaskBtfFnBprmOptsSetFnKtimeGetCoarseNsFnImaInodeHashFnSockFromFileFnCheckMtuFnForEachMapElemFnSnprintfFnSysBpfFnBtfFindByNameKindFnSysCloseFnTimerInitFnTimerSetCallbackFnTimerStartFnTimerCancelFnGetFuncIpFnGetAttachCookieFnTaskPtRegs"
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}
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}
func (i BuiltinFunc) String() string {
if i < 0 || i >= BuiltinFunc(len(_BuiltinFunc_index)-1) {
+135 -55
View File
@@ -26,13 +26,17 @@ func (rio RawInstructionOffset) Bytes() uint64 {
// Instruction is a single eBPF instruction.
type Instruction struct {
OpCode OpCode
Dst Register
Src Register
Offset int16
Constant int64
OpCode OpCode
Dst Register
Src Register
Offset int16
Constant int64
// Reference denotes a reference (e.g. a jump) to another symbol.
Reference string
Symbol string
// Symbol denotes an instruction at the start of a function body.
Symbol string
}
// Sym creates a symbol.
@@ -43,33 +47,45 @@ func (ins Instruction) Sym(name string) Instruction {
// Unmarshal decodes a BPF instruction.
func (ins *Instruction) Unmarshal(r io.Reader, bo binary.ByteOrder) (uint64, error) {
var bi bpfInstruction
err := binary.Read(r, bo, &bi)
if err != nil {
data := make([]byte, InstructionSize)
if _, err := io.ReadFull(r, data); err != nil {
return 0, err
}
ins.OpCode = bi.OpCode
ins.Offset = bi.Offset
ins.Constant = int64(bi.Constant)
ins.Dst, ins.Src, err = bi.Registers.Unmarshal(bo)
if err != nil {
return 0, fmt.Errorf("can't unmarshal registers: %s", err)
ins.OpCode = OpCode(data[0])
regs := data[1]
switch bo {
case binary.LittleEndian:
ins.Dst, ins.Src = Register(regs&0xF), Register(regs>>4)
case binary.BigEndian:
ins.Dst, ins.Src = Register(regs>>4), Register(regs&0xf)
}
if !bi.OpCode.IsDWordLoad() {
ins.Offset = int16(bo.Uint16(data[2:4]))
// Convert to int32 before widening to int64
// to ensure the signed bit is carried over.
ins.Constant = int64(int32(bo.Uint32(data[4:8])))
if !ins.OpCode.IsDWordLoad() {
return InstructionSize, nil
}
var bi2 bpfInstruction
if err := binary.Read(r, bo, &bi2); err != nil {
// Pull another instruction from the stream to retrieve the second
// half of the 64-bit immediate value.
if _, err := io.ReadFull(r, data); err != nil {
// No Wrap, to avoid io.EOF clash
return 0, errors.New("64bit immediate is missing second half")
}
if bi2.OpCode != 0 || bi2.Offset != 0 || bi2.Registers != 0 {
// Require that all fields other than the value are zero.
if bo.Uint32(data[0:4]) != 0 {
return 0, errors.New("64bit immediate has non-zero fields")
}
ins.Constant = int64(uint64(uint32(bi2.Constant))<<32 | uint64(uint32(bi.Constant)))
cons1 := uint32(ins.Constant)
cons2 := int32(bo.Uint32(data[4:8]))
ins.Constant = int64(cons2)<<32 | int64(cons1)
return 2 * InstructionSize, nil
}
@@ -93,14 +109,12 @@ func (ins Instruction) Marshal(w io.Writer, bo binary.ByteOrder) (uint64, error)
return 0, fmt.Errorf("can't marshal registers: %s", err)
}
bpfi := bpfInstruction{
ins.OpCode,
regs,
ins.Offset,
cons,
}
if err := binary.Write(w, bo, &bpfi); err != nil {
data := make([]byte, InstructionSize)
data[0] = byte(ins.OpCode)
data[1] = byte(regs)
bo.PutUint16(data[2:4], uint16(ins.Offset))
bo.PutUint32(data[4:8], uint32(cons))
if _, err := w.Write(data); err != nil {
return 0, err
}
@@ -108,11 +122,11 @@ func (ins Instruction) Marshal(w io.Writer, bo binary.ByteOrder) (uint64, error)
return InstructionSize, nil
}
bpfi = bpfInstruction{
Constant: int32(ins.Constant >> 32),
}
if err := binary.Write(w, bo, &bpfi); err != nil {
// The first half of the second part of a double-wide instruction
// must be zero. The second half carries the value.
bo.PutUint32(data[0:4], 0)
bo.PutUint32(data[4:8], uint32(ins.Constant>>32))
if _, err := w.Write(data); err != nil {
return 0, err
}
@@ -181,6 +195,18 @@ func (ins *Instruction) IsFunctionCall() bool {
return ins.OpCode.JumpOp() == Call && ins.Src == PseudoCall
}
// IsLoadOfFunctionPointer returns true if the instruction loads a function pointer.
func (ins *Instruction) IsLoadOfFunctionPointer() bool {
return ins.OpCode.IsDWordLoad() && ins.Src == PseudoFunc
}
// IsFunctionReference returns true if the instruction references another BPF
// function, either by invoking a Call jump operation or by loading a function
// pointer.
func (ins *Instruction) IsFunctionReference() bool {
return ins.IsFunctionCall() || ins.IsLoadOfFunctionPointer()
}
// IsBuiltinCall returns true if the instruction is a built-in call, i.e. BPF helper call.
func (ins *Instruction) IsBuiltinCall() bool {
return ins.OpCode.JumpOp() == Call && ins.Src == R0 && ins.Dst == R0
@@ -226,8 +252,8 @@ func (ins Instruction) Format(f fmt.State, c rune) {
}
fmt.Fprintf(f, "%v ", op)
switch cls := op.Class(); cls {
case LdClass, LdXClass, StClass, StXClass:
switch cls := op.Class(); {
case cls.isLoadOrStore():
switch op.Mode() {
case ImmMode:
fmt.Fprintf(f, "dst: %s imm: %d", ins.Dst, ins.Constant)
@@ -241,7 +267,7 @@ func (ins Instruction) Format(f fmt.State, c rune) {
fmt.Fprintf(f, "dst: %s src: %s", ins.Dst, ins.Src)
}
case ALU64Class, ALUClass:
case cls.IsALU():
fmt.Fprintf(f, "dst: %s ", ins.Dst)
if op.ALUOp() == Swap || op.Source() == ImmSource {
fmt.Fprintf(f, "imm: %d", ins.Constant)
@@ -249,7 +275,7 @@ func (ins Instruction) Format(f fmt.State, c rune) {
fmt.Fprintf(f, "src: %s", ins.Src)
}
case JumpClass:
case cls.IsJump():
switch jop := op.JumpOp(); jop {
case Call:
if ins.Src == PseudoCall {
@@ -275,13 +301,60 @@ ref:
}
}
// Size returns the amount of bytes ins would occupy in binary form.
func (ins Instruction) Size() uint64 {
return uint64(InstructionSize * ins.OpCode.rawInstructions())
}
// Instructions is an eBPF program.
type Instructions []Instruction
// Unmarshal unmarshals an Instructions from a binary instruction stream.
// All instructions in insns are replaced by instructions decoded from r.
func (insns *Instructions) Unmarshal(r io.Reader, bo binary.ByteOrder) error {
if len(*insns) > 0 {
*insns = nil
}
var offset uint64
for {
var ins Instruction
n, err := ins.Unmarshal(r, bo)
if errors.Is(err, io.EOF) {
break
}
if err != nil {
return fmt.Errorf("offset %d: %w", offset, err)
}
*insns = append(*insns, ins)
offset += n
}
return nil
}
// Name returns the name of the function insns belongs to, if any.
func (insns Instructions) Name() string {
if len(insns) == 0 {
return ""
}
return insns[0].Symbol
}
func (insns Instructions) String() string {
return fmt.Sprint(insns)
}
// Size returns the amount of bytes insns would occupy in binary form.
func (insns Instructions) Size() uint64 {
var sum uint64
for _, ins := range insns {
sum += ins.Size()
}
return sum
}
// RewriteMapPtr rewrites all loads of a specific map pointer to a new fd.
//
// Returns an error if the symbol isn't used, see IsUnreferencedSymbol.
@@ -331,6 +404,31 @@ func (insns Instructions) SymbolOffsets() (map[string]int, error) {
return offsets, nil
}
// FunctionReferences returns a set of symbol names these Instructions make
// bpf-to-bpf calls to.
func (insns Instructions) FunctionReferences() map[string]bool {
calls := make(map[string]bool)
for _, ins := range insns {
if ins.Constant != -1 {
// BPF-to-BPF calls have -1 constants.
continue
}
if ins.Reference == "" {
continue
}
if !ins.IsFunctionReference() {
continue
}
calls[ins.Reference] = true
}
return calls
}
// ReferenceOffsets returns the set of references and their offset in
// the instructions.
func (insns Instructions) ReferenceOffsets() map[string][]int {
@@ -464,13 +562,6 @@ func (iter *InstructionIterator) Next() bool {
return true
}
type bpfInstruction struct {
OpCode OpCode
Registers bpfRegisters
Offset int16
Constant int32
}
type bpfRegisters uint8
func newBPFRegisters(dst, src Register, bo binary.ByteOrder) (bpfRegisters, error) {
@@ -484,17 +575,6 @@ func newBPFRegisters(dst, src Register, bo binary.ByteOrder) (bpfRegisters, erro
}
}
func (r bpfRegisters) Unmarshal(bo binary.ByteOrder) (dst, src Register, err error) {
switch bo {
case binary.LittleEndian:
return Register(r & 0xF), Register(r >> 4), nil
case binary.BigEndian:
return Register(r >> 4), Register(r & 0xf), nil
default:
return 0, 0, fmt.Errorf("unrecognized ByteOrder %T", bo)
}
}
type unreferencedSymbolError struct {
symbol string
}
+37 -13
View File
@@ -60,14 +60,10 @@ func (op JumpOp) Op(source Source) OpCode {
return OpCode(JumpClass).SetJumpOp(op).SetSource(source)
}
// Imm compares dst to value, and adjusts PC by offset if the condition is fulfilled.
// Imm compares 64 bit dst to 64 bit value (sign extended), and adjusts PC by offset if the condition is fulfilled.
func (op JumpOp) Imm(dst Register, value int32, label string) Instruction {
if op == Exit || op == Call || op == Ja {
return Instruction{OpCode: InvalidOpCode}
}
return Instruction{
OpCode: OpCode(JumpClass).SetJumpOp(op).SetSource(ImmSource),
OpCode: op.opCode(JumpClass, ImmSource),
Dst: dst,
Offset: -1,
Constant: int64(value),
@@ -75,14 +71,22 @@ func (op JumpOp) Imm(dst Register, value int32, label string) Instruction {
}
}
// Reg compares dst to src, and adjusts PC by offset if the condition is fulfilled.
func (op JumpOp) Reg(dst, src Register, label string) Instruction {
if op == Exit || op == Call || op == Ja {
return Instruction{OpCode: InvalidOpCode}
}
// Imm32 compares 32 bit dst to 32 bit value, and adjusts PC by offset if the condition is fulfilled.
// Requires kernel 5.1.
func (op JumpOp) Imm32(dst Register, value int32, label string) Instruction {
return Instruction{
OpCode: OpCode(JumpClass).SetJumpOp(op).SetSource(RegSource),
OpCode: op.opCode(Jump32Class, ImmSource),
Dst: dst,
Offset: -1,
Constant: int64(value),
Reference: label,
}
}
// Reg compares 64 bit dst to 64 bit src, and adjusts PC by offset if the condition is fulfilled.
func (op JumpOp) Reg(dst, src Register, label string) Instruction {
return Instruction{
OpCode: op.opCode(JumpClass, RegSource),
Dst: dst,
Src: src,
Offset: -1,
@@ -90,6 +94,26 @@ func (op JumpOp) Reg(dst, src Register, label string) Instruction {
}
}
// Reg32 compares 32 bit dst to 32 bit src, and adjusts PC by offset if the condition is fulfilled.
// Requires kernel 5.1.
func (op JumpOp) Reg32(dst, src Register, label string) Instruction {
return Instruction{
OpCode: op.opCode(Jump32Class, RegSource),
Dst: dst,
Src: src,
Offset: -1,
Reference: label,
}
}
func (op JumpOp) opCode(class Class, source Source) OpCode {
if op == Exit || op == Call || op == Ja {
return InvalidOpCode
}
return OpCode(class).SetJumpOp(op).SetSource(source)
}
// Label adjusts PC to the address of the label.
func (op JumpOp) Label(label string) Instruction {
if op == Call {
+67 -34
View File
@@ -7,14 +7,6 @@ import (
//go:generate stringer -output opcode_string.go -type=Class
type encoding int
const (
unknownEncoding encoding = iota
loadOrStore
jumpOrALU
)
// Class of operations
//
// msb lsb
@@ -38,19 +30,39 @@ const (
ALUClass Class = 0x04
// JumpClass jump operators
JumpClass Class = 0x05
// Jump32Class jump operators with 32 bit comparaisons
// Requires kernel 5.1
Jump32Class Class = 0x06
// ALU64Class arithmetic in 64 bit mode
ALU64Class Class = 0x07
)
func (cls Class) encoding() encoding {
switch cls {
case LdClass, LdXClass, StClass, StXClass:
return loadOrStore
case ALU64Class, ALUClass, JumpClass:
return jumpOrALU
default:
return unknownEncoding
}
// IsLoad checks if this is either LdClass or LdXClass.
func (cls Class) IsLoad() bool {
return cls == LdClass || cls == LdXClass
}
// IsStore checks if this is either StClass or StXClass.
func (cls Class) IsStore() bool {
return cls == StClass || cls == StXClass
}
func (cls Class) isLoadOrStore() bool {
return cls.IsLoad() || cls.IsStore()
}
// IsALU checks if this is either ALUClass or ALU64Class.
func (cls Class) IsALU() bool {
return cls == ALUClass || cls == ALU64Class
}
// IsJump checks if this is either JumpClass or Jump32Class.
func (cls Class) IsJump() bool {
return cls == JumpClass || cls == Jump32Class
}
func (cls Class) isJumpOrALU() bool {
return cls.IsJump() || cls.IsALU()
}
// OpCode is a packed eBPF opcode.
@@ -86,7 +98,7 @@ func (op OpCode) Class() Class {
// Mode returns the mode for load and store operations.
func (op OpCode) Mode() Mode {
if op.Class().encoding() != loadOrStore {
if !op.Class().isLoadOrStore() {
return InvalidMode
}
return Mode(op & modeMask)
@@ -94,7 +106,7 @@ func (op OpCode) Mode() Mode {
// Size returns the size for load and store operations.
func (op OpCode) Size() Size {
if op.Class().encoding() != loadOrStore {
if !op.Class().isLoadOrStore() {
return InvalidSize
}
return Size(op & sizeMask)
@@ -102,7 +114,7 @@ func (op OpCode) Size() Size {
// Source returns the source for branch and ALU operations.
func (op OpCode) Source() Source {
if op.Class().encoding() != jumpOrALU || op.ALUOp() == Swap {
if !op.Class().isJumpOrALU() || op.ALUOp() == Swap {
return InvalidSource
}
return Source(op & sourceMask)
@@ -110,7 +122,7 @@ func (op OpCode) Source() Source {
// ALUOp returns the ALUOp.
func (op OpCode) ALUOp() ALUOp {
if op.Class().encoding() != jumpOrALU {
if !op.Class().IsALU() {
return InvalidALUOp
}
return ALUOp(op & aluMask)
@@ -125,18 +137,27 @@ func (op OpCode) Endianness() Endianness {
}
// JumpOp returns the JumpOp.
// Returns InvalidJumpOp if it doesn't encode a jump.
func (op OpCode) JumpOp() JumpOp {
if op.Class().encoding() != jumpOrALU {
if !op.Class().IsJump() {
return InvalidJumpOp
}
return JumpOp(op & jumpMask)
jumpOp := JumpOp(op & jumpMask)
// Some JumpOps are only supported by JumpClass, not Jump32Class.
if op.Class() == Jump32Class && (jumpOp == Exit || jumpOp == Call || jumpOp == Ja) {
return InvalidJumpOp
}
return jumpOp
}
// SetMode sets the mode on load and store operations.
//
// Returns InvalidOpCode if op is of the wrong class.
func (op OpCode) SetMode(mode Mode) OpCode {
if op.Class().encoding() != loadOrStore || !valid(OpCode(mode), modeMask) {
if !op.Class().isLoadOrStore() || !valid(OpCode(mode), modeMask) {
return InvalidOpCode
}
return (op & ^modeMask) | OpCode(mode)
@@ -146,7 +167,7 @@ func (op OpCode) SetMode(mode Mode) OpCode {
//
// Returns InvalidOpCode if op is of the wrong class.
func (op OpCode) SetSize(size Size) OpCode {
if op.Class().encoding() != loadOrStore || !valid(OpCode(size), sizeMask) {
if !op.Class().isLoadOrStore() || !valid(OpCode(size), sizeMask) {
return InvalidOpCode
}
return (op & ^sizeMask) | OpCode(size)
@@ -156,7 +177,7 @@ func (op OpCode) SetSize(size Size) OpCode {
//
// Returns InvalidOpCode if op is of the wrong class.
func (op OpCode) SetSource(source Source) OpCode {
if op.Class().encoding() != jumpOrALU || !valid(OpCode(source), sourceMask) {
if !op.Class().isJumpOrALU() || !valid(OpCode(source), sourceMask) {
return InvalidOpCode
}
return (op & ^sourceMask) | OpCode(source)
@@ -166,8 +187,7 @@ func (op OpCode) SetSource(source Source) OpCode {
//
// Returns InvalidOpCode if op is of the wrong class.
func (op OpCode) SetALUOp(alu ALUOp) OpCode {
class := op.Class()
if (class != ALUClass && class != ALU64Class) || !valid(OpCode(alu), aluMask) {
if !op.Class().IsALU() || !valid(OpCode(alu), aluMask) {
return InvalidOpCode
}
return (op & ^aluMask) | OpCode(alu)
@@ -177,17 +197,25 @@ func (op OpCode) SetALUOp(alu ALUOp) OpCode {
//
// Returns InvalidOpCode if op is of the wrong class.
func (op OpCode) SetJumpOp(jump JumpOp) OpCode {
if op.Class() != JumpClass || !valid(OpCode(jump), jumpMask) {
if !op.Class().IsJump() || !valid(OpCode(jump), jumpMask) {
return InvalidOpCode
}
return (op & ^jumpMask) | OpCode(jump)
newOp := (op & ^jumpMask) | OpCode(jump)
// Check newOp is legal.
if newOp.JumpOp() == InvalidJumpOp {
return InvalidOpCode
}
return newOp
}
func (op OpCode) String() string {
var f strings.Builder
switch class := op.Class(); class {
case LdClass, LdXClass, StClass, StXClass:
switch class := op.Class(); {
case class.isLoadOrStore():
f.WriteString(strings.TrimSuffix(class.String(), "Class"))
mode := op.Mode()
@@ -204,7 +232,7 @@ func (op OpCode) String() string {
f.WriteString("B")
}
case ALU64Class, ALUClass:
case class.IsALU():
f.WriteString(op.ALUOp().String())
if op.ALUOp() == Swap {
@@ -218,8 +246,13 @@ func (op OpCode) String() string {
f.WriteString(strings.TrimSuffix(op.Source().String(), "Source"))
}
case JumpClass:
case class.IsJump():
f.WriteString(op.JumpOp().String())
if class == Jump32Class {
f.WriteString("32")
}
if jop := op.JumpOp(); jop != Exit && jop != Call {
f.WriteString(strings.TrimSuffix(op.Source().String(), "Source"))
}
+5 -13
View File
@@ -14,25 +14,17 @@ func _() {
_ = x[StXClass-3]
_ = x[ALUClass-4]
_ = x[JumpClass-5]
_ = x[Jump32Class-6]
_ = x[ALU64Class-7]
}
const (
_Class_name_0 = "LdClassLdXClassStClassStXClassALUClassJumpClass"
_Class_name_1 = "ALU64Class"
)
const _Class_name = "LdClassLdXClassStClassStXClassALUClassJumpClassJump32ClassALU64Class"
var (
_Class_index_0 = [...]uint8{0, 7, 15, 22, 30, 38, 47}
)
var _Class_index = [...]uint8{0, 7, 15, 22, 30, 38, 47, 58, 68}
func (i Class) String() string {
switch {
case 0 <= i && i <= 5:
return _Class_name_0[_Class_index_0[i]:_Class_index_0[i+1]]
case i == 7:
return _Class_name_1
default:
if i >= Class(len(_Class_index)-1) {
return "Class(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _Class_name[_Class_index[i]:_Class_index[i+1]]
}
+1
View File
@@ -38,6 +38,7 @@ const (
PseudoMapFD = R1 // BPF_PSEUDO_MAP_FD
PseudoMapValue = R2 // BPF_PSEUDO_MAP_VALUE
PseudoCall = R1 // BPF_PSEUDO_CALL
PseudoFunc = R4 // BPF_PSEUDO_FUNC
)
func (r Register) String() string {
+23 -7
View File
@@ -10,8 +10,8 @@ import (
"strings"
"github.com/cilium/ebpf/asm"
"github.com/cilium/ebpf/internal"
"github.com/cilium/ebpf/internal/btf"
"github.com/cilium/ebpf/internal/sys"
)
// CollectionOptions control loading a collection into the kernel.
@@ -244,9 +244,14 @@ func (cs *CollectionSpec) LoadAndAssign(to interface{}, opts *CollectionOptions)
switch m.typ {
case ProgramArray:
// Require all lazy-loaded ProgramArrays to be assigned to the given object.
// Without any references, they will be closed on the first GC and all tail
// calls into them will miss.
if !assignedMaps[n] {
// The kernel empties a ProgramArray once the last user space reference
// to it closes, which leads to failed tail calls. Combined with the library
// closing map fds via GC finalizers this can lead to surprising behaviour.
// Only allow unassigned ProgramArrays when the library hasn't pre-populated
// any entries from static value declarations. At this point, we know the map
// is empty and there's no way for the caller to interact with the map going
// forward.
if !assignedMaps[n] && len(cs.Maps[n].Contents) > 0 {
return fmt.Errorf("ProgramArray %s must be assigned to prevent missed tail calls", n)
}
}
@@ -281,7 +286,11 @@ func NewCollectionWithOptions(spec *CollectionSpec, opts CollectionOptions) (*Co
}
}
for progName := range spec.Programs {
for progName, prog := range spec.Programs {
if prog.Type == UnspecifiedProgram {
continue
}
if _, err := loader.loadProgram(progName); err != nil {
return nil, err
}
@@ -419,9 +428,16 @@ func (cl *collectionLoader) loadProgram(progName string) (*Program, error) {
return nil, fmt.Errorf("unknown program %s", progName)
}
// Bail out early if we know the kernel is going to reject the program.
// This skips loading map dependencies, saving some cleanup work later.
if progSpec.Type == UnspecifiedProgram {
return nil, fmt.Errorf("cannot load program %s: program type is unspecified", progName)
}
progSpec = progSpec.Copy()
// Rewrite any reference to a valid map.
// Rewrite any reference to a valid map in the program's instructions,
// which includes all of its dependencies.
for i := range progSpec.Instructions {
ins := &progSpec.Instructions[i]
@@ -442,7 +458,7 @@ func (cl *collectionLoader) loadProgram(progName string) (*Program, error) {
fd := m.FD()
if fd < 0 {
return nil, fmt.Errorf("map %s: %w", ins.Reference, internal.ErrClosedFd)
return nil, fmt.Errorf("map %s: %w", ins.Reference, sys.ErrClosedFd)
}
if err := ins.RewriteMapPtr(m.FD()); err != nil {
return nil, fmt.Errorf("program %s: map %s: %w", progName, ins.Reference, err)
+278 -156
View File
@@ -100,37 +100,6 @@ func LoadCollectionSpecFromReader(rd io.ReaderAt) (*CollectionSpec, error) {
return nil, fmt.Errorf("load BTF: %w", err)
}
// Assign symbols to all the sections we're interested in.
symbols, err := f.Symbols()
if err != nil {
return nil, fmt.Errorf("load symbols: %v", err)
}
for _, symbol := range symbols {
idx := symbol.Section
symType := elf.ST_TYPE(symbol.Info)
section := sections[idx]
if section == nil {
continue
}
// Older versions of LLVM don't tag symbols correctly, so keep
// all NOTYPE ones.
keep := symType == elf.STT_NOTYPE
switch section.kind {
case mapSection, btfMapSection, dataSection:
keep = keep || symType == elf.STT_OBJECT
case programSection:
keep = keep || symType == elf.STT_FUNC
}
if !keep || symbol.Name == "" {
continue
}
section.symbols[symbol.Value] = symbol
}
ec := &elfCode{
SafeELFFile: f,
sections: sections,
@@ -139,6 +108,13 @@ func LoadCollectionSpecFromReader(rd io.ReaderAt) (*CollectionSpec, error) {
btf: btfSpec,
}
symbols, err := f.Symbols()
if err != nil {
return nil, fmt.Errorf("load symbols: %v", err)
}
ec.assignSymbols(symbols)
// Go through relocation sections, and parse the ones for sections we're
// interested in. Make sure that relocations point at valid sections.
for idx, relSection := range relSections {
@@ -183,7 +159,7 @@ func LoadCollectionSpecFromReader(rd io.ReaderAt) (*CollectionSpec, error) {
}
// Finally, collect programs and link them.
progs, err := ec.loadPrograms()
progs, err := ec.loadProgramSections()
if err != nil {
return nil, fmt.Errorf("load programs: %w", err)
}
@@ -247,12 +223,57 @@ func newElfSection(section *elf.Section, kind elfSectionKind) *elfSection {
}
}
func (ec *elfCode) loadPrograms() (map[string]*ProgramSpec, error) {
var (
progs []*ProgramSpec
libs []*ProgramSpec
)
// assignSymbols takes a list of symbols and assigns them to their
// respective sections, indexed by name.
func (ec *elfCode) assignSymbols(symbols []elf.Symbol) {
for _, symbol := range symbols {
symType := elf.ST_TYPE(symbol.Info)
symSection := ec.sections[symbol.Section]
if symSection == nil {
continue
}
// Anonymous symbols only occur in debug sections which we don't process
// relocations for. Anonymous symbols are not referenced from other sections.
if symbol.Name == "" {
continue
}
// Older versions of LLVM don't tag symbols correctly, so keep
// all NOTYPE ones.
switch symSection.kind {
case mapSection, btfMapSection, dataSection:
if symType != elf.STT_NOTYPE && symType != elf.STT_OBJECT {
continue
}
case programSection:
if symType != elf.STT_NOTYPE && symType != elf.STT_FUNC {
continue
}
// LLVM emits LBB_ (Local Basic Block) symbols that seem to be jump
// targets within sections, but BPF has no use for them.
if symType == elf.STT_NOTYPE && elf.ST_BIND(symbol.Info) == elf.STB_LOCAL &&
strings.HasPrefix(symbol.Name, "LBB") {
continue
}
// Only collect symbols that occur in program/maps/data sections.
default:
continue
}
symSection.symbols[symbol.Value] = symbol
}
}
// loadProgramSections iterates ec's sections and emits a ProgramSpec
// for each function it finds.
//
// The resulting map is indexed by function name.
func (ec *elfCode) loadProgramSections() (map[string]*ProgramSpec, error) {
progs := make(map[string]*ProgramSpec)
// Generate a ProgramSpec for each function found in each program section.
for _, sec := range ec.sections {
if sec.kind != programSection {
continue
@@ -262,86 +283,150 @@ func (ec *elfCode) loadPrograms() (map[string]*ProgramSpec, error) {
return nil, fmt.Errorf("section %v: missing symbols", sec.Name)
}
funcSym, ok := sec.symbols[0]
if !ok {
return nil, fmt.Errorf("section %v: no label at start", sec.Name)
}
insns, length, err := ec.loadInstructions(sec)
funcs, err := ec.loadFunctions(sec)
if err != nil {
return nil, fmt.Errorf("program %s: %w", funcSym.Name, err)
return nil, fmt.Errorf("section %v: %w", sec.Name, err)
}
progType, attachType, progFlags, attachTo := getProgType(sec.Name)
spec := &ProgramSpec{
Name: funcSym.Name,
Type: progType,
Flags: progFlags,
AttachType: attachType,
AttachTo: attachTo,
License: ec.license,
KernelVersion: ec.version,
Instructions: insns,
ByteOrder: ec.ByteOrder,
}
if ec.btf != nil {
spec.BTF, err = ec.btf.Program(sec.Name, length)
if err != nil && !errors.Is(err, btf.ErrNoExtendedInfo) {
return nil, fmt.Errorf("program %s: %w", funcSym.Name, err)
for name, insns := range funcs {
spec := &ProgramSpec{
Name: name,
Type: progType,
Flags: progFlags,
AttachType: attachType,
AttachTo: attachTo,
SectionName: sec.Name,
License: ec.license,
KernelVersion: ec.version,
Instructions: insns,
ByteOrder: ec.ByteOrder,
}
}
if spec.Type == UnspecifiedProgram {
// There is no single name we can use for "library" sections,
// since they may contain multiple functions. We'll decode the
// labels they contain later on, and then link sections that way.
libs = append(libs, spec)
} else {
progs = append(progs, spec)
if ec.btf != nil {
spec.BTF, err = ec.btf.Program(name)
if err != nil && !errors.Is(err, btf.ErrNoExtendedInfo) {
return nil, fmt.Errorf("program %s: %w", name, err)
}
}
// Function names must be unique within a single ELF blob.
if progs[name] != nil {
return nil, fmt.Errorf("duplicate program name %s", name)
}
progs[name] = spec
}
}
res := make(map[string]*ProgramSpec, len(progs))
for _, prog := range progs {
err := link(prog, libs)
if err != nil {
return nil, fmt.Errorf("program %s: %w", prog.Name, err)
}
res[prog.Name] = prog
// Populate each prog's references with pointers to all of its callees.
if err := populateReferences(progs); err != nil {
return nil, fmt.Errorf("populating references: %w", err)
}
return res, nil
// Hide programs (e.g. library functions) that were not explicitly emitted
// to an ELF section. These could be exposed in a separate CollectionSpec
// field later to allow them to be modified.
for n, p := range progs {
if p.SectionName == ".text" {
delete(progs, n)
}
}
return progs, nil
}
func (ec *elfCode) loadInstructions(section *elfSection) (asm.Instructions, uint64, error) {
// loadFunctions extracts instruction streams from the given program section
// starting at each symbol in the section. The section's symbols must already
// be narrowed down to STT_NOTYPE (emitted by clang <8) or STT_FUNC.
//
// The resulting map is indexed by function name.
func (ec *elfCode) loadFunctions(section *elfSection) (map[string]asm.Instructions, error) {
var (
r = bufio.NewReader(section.Open())
insns asm.Instructions
funcs = make(map[string]asm.Instructions)
offset uint64
insns asm.Instructions
)
for {
var ins asm.Instruction
ins := asm.Instruction{
// Symbols denote the first instruction of a function body.
Symbol: section.symbols[offset].Name,
}
// Pull one instruction from the instruction stream.
n, err := ins.Unmarshal(r, ec.ByteOrder)
if err == io.EOF {
return insns, offset, nil
if errors.Is(err, io.EOF) {
fn := insns.Name()
if fn == "" {
return nil, errors.New("reached EOF before finding a valid symbol")
}
// Reached the end of the section and the decoded instruction buffer
// contains at least one valid instruction belonging to a function.
// Store the result and stop processing instructions.
funcs[fn] = insns
break
}
if err != nil {
return nil, 0, fmt.Errorf("offset %d: %w", offset, err)
return nil, fmt.Errorf("offset %d: %w", offset, err)
}
ins.Symbol = section.symbols[offset].Name
// Decoded the first instruction of a function body but insns already
// holds a valid instruction stream. Store the result and flush insns.
if ins.Symbol != "" && insns.Name() != "" {
funcs[insns.Name()] = insns
insns = nil
}
if rel, ok := section.relocations[offset]; ok {
// A relocation was found for the current offset. Apply it to the insn.
if err = ec.relocateInstruction(&ins, rel); err != nil {
return nil, 0, fmt.Errorf("offset %d: relocate instruction: %w", offset, err)
return nil, fmt.Errorf("offset %d: relocate instruction: %w", offset, err)
}
} else {
// Up to LLVM 9, calls to subprograms within the same ELF section are
// sometimes encoded using relative jumps without relocation entries.
// If, after all relocations entries have been processed, there are
// still relative pseudocalls left, they must point to an existing
// symbol within the section.
// When splitting sections into subprograms, the targets of these calls
// are no longer in scope, so they must be resolved here.
if ins.IsFunctionReference() && ins.Constant != -1 {
tgt := jumpTarget(offset, ins)
sym := section.symbols[tgt].Name
if sym == "" {
return nil, fmt.Errorf("offset %d: no jump target found at offset %d", offset, tgt)
}
ins.Reference = sym
ins.Constant = -1
}
}
insns = append(insns, ins)
offset += n
}
return funcs, nil
}
// jumpTarget takes ins' offset within an instruction stream (in bytes)
// and returns its absolute jump destination (in bytes) within the
// instruction stream.
func jumpTarget(offset uint64, ins asm.Instruction) uint64 {
// A relative jump instruction describes the amount of raw BPF instructions
// to jump, convert the offset into bytes.
dest := ins.Constant * asm.InstructionSize
// The starting point of the jump is the end of the current instruction.
dest += int64(offset + asm.InstructionSize)
if dest < 0 {
return 0
}
return uint64(dest)
}
func (ec *elfCode) relocateInstruction(ins *asm.Instruction, rel elf.Symbol) error {
@@ -413,44 +498,70 @@ func (ec *elfCode) relocateInstruction(ins *asm.Instruction, rel elf.Symbol) err
}
case programSection:
if ins.OpCode.JumpOp() != asm.Call {
return fmt.Errorf("not a call instruction: %s", ins)
}
if ins.Src != asm.PseudoCall {
return fmt.Errorf("call: %s: incorrect source register", name)
}
switch typ {
case elf.STT_NOTYPE, elf.STT_FUNC:
if bind != elf.STB_GLOBAL {
return fmt.Errorf("call: %s: unsupported binding: %s", name, bind)
switch opCode := ins.OpCode; {
case opCode.JumpOp() == asm.Call:
if ins.Src != asm.PseudoCall {
return fmt.Errorf("call: %s: incorrect source register", name)
}
case elf.STT_SECTION:
if bind != elf.STB_LOCAL {
return fmt.Errorf("call: %s: unsupported binding: %s", name, bind)
switch typ {
case elf.STT_NOTYPE, elf.STT_FUNC:
if bind != elf.STB_GLOBAL {
return fmt.Errorf("call: %s: unsupported binding: %s", name, bind)
}
case elf.STT_SECTION:
if bind != elf.STB_LOCAL {
return fmt.Errorf("call: %s: unsupported binding: %s", name, bind)
}
// The function we want to call is in the indicated section,
// at the offset encoded in the instruction itself. Reverse
// the calculation to find the real function we're looking for.
// A value of -1 references the first instruction in the section.
offset := int64(int32(ins.Constant)+1) * asm.InstructionSize
sym, ok := target.symbols[uint64(offset)]
if !ok {
return fmt.Errorf("call: no symbol at offset %d", offset)
}
name = sym.Name
ins.Constant = -1
default:
return fmt.Errorf("call: %s: invalid symbol type %s", name, typ)
}
case opCode.IsDWordLoad():
switch typ {
case elf.STT_FUNC:
if bind != elf.STB_GLOBAL {
return fmt.Errorf("load: %s: unsupported binding: %s", name, bind)
}
case elf.STT_SECTION:
if bind != elf.STB_LOCAL {
return fmt.Errorf("load: %s: unsupported binding: %s", name, bind)
}
// ins.Constant already contains the offset in bytes from the
// start of the section. This is different than a call to a
// static function.
default:
return fmt.Errorf("load: %s: invalid symbol type %s", name, typ)
}
// The function we want to call is in the indicated section,
// at the offset encoded in the instruction itself. Reverse
// the calculation to find the real function we're looking for.
// A value of -1 references the first instruction in the section.
offset := int64(int32(ins.Constant)+1) * asm.InstructionSize
if offset < 0 {
return fmt.Errorf("call: %s: invalid offset %d", name, offset)
}
sym, ok := target.symbols[uint64(offset)]
sym, ok := target.symbols[uint64(ins.Constant)]
if !ok {
return fmt.Errorf("call: %s: no symbol at offset %d", name, offset)
return fmt.Errorf("load: no symbol at offset %d", ins.Constant)
}
ins.Constant = -1
name = sym.Name
ins.Constant = -1
ins.Src = asm.PseudoFunc
default:
return fmt.Errorf("call: %s: invalid symbol type %s", name, typ)
return fmt.Errorf("neither a call nor a load instruction: %v", ins)
}
case undefSection:
@@ -525,7 +636,7 @@ func (ec *elfCode) loadMaps(maps map[string]*MapSpec) error {
return fmt.Errorf("map %s: reading map tail: %w", mapName, err)
}
if len(extra) > 0 {
spec.Extra = *bytes.NewReader(extra)
spec.Extra = bytes.NewReader(extra)
}
if err := spec.clampPerfEventArraySize(); err != nil {
@@ -554,7 +665,7 @@ func (ec *elfCode) loadBTFMaps(maps map[string]*MapSpec) error {
// Each section must appear as a DataSec in the ELF's BTF blob.
var ds *btf.Datasec
if err := ec.btf.FindType(sec.Name, &ds); err != nil {
if err := ec.btf.TypeByName(sec.Name, &ds); err != nil {
return fmt.Errorf("cannot find section '%s' in BTF: %w", sec.Name, err)
}
@@ -926,7 +1037,7 @@ func (ec *elfCode) loadDataSections(maps map[string]*MapSpec) error {
}
var datasec *btf.Datasec
if err := ec.btf.FindType(sec.Name, &datasec); err != nil {
if err := ec.btf.TypeByName(sec.Name, &datasec); err != nil {
return fmt.Errorf("data section %s: can't get BTF: %w", sec.Name, err)
}
@@ -969,68 +1080,79 @@ func getProgType(sectionName string) (ProgramType, AttachType, uint32, string) {
attachType AttachType
progFlags uint32
}{
// From https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/tools/lib/bpf/libbpf.c
// Please update the types from libbpf.c and follow the order of it.
// https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/tools/lib/bpf/libbpf.c
"socket": {SocketFilter, AttachNone, 0},
"sk_reuseport/migrate": {SkReuseport, AttachSkReuseportSelectOrMigrate, 0},
"sk_reuseport": {SkReuseport, AttachSkReuseportSelect, 0},
"seccomp": {SocketFilter, AttachNone, 0},
"kprobe/": {Kprobe, AttachNone, 0},
"uprobe/": {Kprobe, AttachNone, 0},
"kretprobe/": {Kprobe, AttachNone, 0},
"uretprobe/": {Kprobe, AttachNone, 0},
"tc": {SchedCLS, AttachNone, 0},
"classifier": {SchedCLS, AttachNone, 0},
"action": {SchedACT, AttachNone, 0},
"tracepoint/": {TracePoint, AttachNone, 0},
"tp/": {TracePoint, AttachNone, 0},
"raw_tracepoint/": {RawTracepoint, AttachNone, 0},
"raw_tp/": {RawTracepoint, AttachNone, 0},
"raw_tracepoint.w/": {RawTracepointWritable, AttachNone, 0},
"raw_tp.w/": {RawTracepointWritable, AttachNone, 0},
"tp_btf/": {Tracing, AttachTraceRawTp, 0},
"xdp": {XDP, AttachNone, 0},
"perf_event": {PerfEvent, AttachNone, 0},
"lwt_in": {LWTIn, AttachNone, 0},
"lwt_out": {LWTOut, AttachNone, 0},
"lwt_xmit": {LWTXmit, AttachNone, 0},
"lwt_seg6local": {LWTSeg6Local, AttachNone, 0},
"sockops": {SockOps, AttachCGroupSockOps, 0},
"sk_skb/stream_parser": {SkSKB, AttachSkSKBStreamParser, 0},
"sk_skb/stream_verdict": {SkSKB, AttachSkSKBStreamParser, 0},
"sk_msg": {SkMsg, AttachSkSKBStreamVerdict, 0},
"lirc_mode2": {LircMode2, AttachLircMode2, 0},
"flow_dissector": {FlowDissector, AttachFlowDissector, 0},
"iter/": {Tracing, AttachTraceIter, 0},
"fentry/": {Tracing, AttachTraceFEntry, 0},
"fmod_ret/": {Tracing, AttachModifyReturn, 0},
"fexit/": {Tracing, AttachTraceFExit, 0},
"fentry.s/": {Tracing, AttachTraceFEntry, unix.BPF_F_SLEEPABLE},
"fmod_ret.s/": {Tracing, AttachModifyReturn, unix.BPF_F_SLEEPABLE},
"fexit.s/": {Tracing, AttachTraceFExit, unix.BPF_F_SLEEPABLE},
"sk_lookup/": {SkLookup, AttachSkLookup, 0},
"freplace/": {Extension, AttachNone, 0},
"lsm/": {LSM, AttachLSMMac, 0},
"lsm.s/": {LSM, AttachLSMMac, unix.BPF_F_SLEEPABLE},
"iter/": {Tracing, AttachTraceIter, 0},
"syscall": {Syscall, AttachNone, 0},
"xdp_devmap/": {XDP, AttachXDPDevMap, 0},
"xdp_cpumap/": {XDP, AttachXDPCPUMap, 0},
"xdp": {XDP, AttachNone, 0},
"perf_event": {PerfEvent, AttachNone, 0},
"lwt_in": {LWTIn, AttachNone, 0},
"lwt_out": {LWTOut, AttachNone, 0},
"lwt_xmit": {LWTXmit, AttachNone, 0},
"lwt_seg6local": {LWTSeg6Local, AttachNone, 0},
"cgroup_skb/ingress": {CGroupSKB, AttachCGroupInetIngress, 0},
"cgroup_skb/egress": {CGroupSKB, AttachCGroupInetEgress, 0},
"cgroup/skb": {CGroupSKB, AttachNone, 0},
"cgroup/sock_create": {CGroupSKB, AttachCGroupInetSockCreate, 0},
"cgroup/sock_release": {CGroupSKB, AttachCgroupInetSockRelease, 0},
"cgroup/sock": {CGroupSock, AttachCGroupInetSockCreate, 0},
"cgroup/post_bind4": {CGroupSock, AttachCGroupInet4PostBind, 0},
"cgroup/post_bind6": {CGroupSock, AttachCGroupInet6PostBind, 0},
"cgroup/dev": {CGroupDevice, AttachCGroupDevice, 0},
"sockops": {SockOps, AttachCGroupSockOps, 0},
"sk_skb/stream_parser": {SkSKB, AttachSkSKBStreamParser, 0},
"sk_skb/stream_verdict": {SkSKB, AttachSkSKBStreamVerdict, 0},
"sk_skb": {SkSKB, AttachNone, 0},
"sk_msg": {SkMsg, AttachSkMsgVerdict, 0},
"lirc_mode2": {LircMode2, AttachLircMode2, 0},
"flow_dissector": {FlowDissector, AttachFlowDissector, 0},
"cgroup/bind4": {CGroupSockAddr, AttachCGroupInet4Bind, 0},
"cgroup/bind6": {CGroupSockAddr, AttachCGroupInet6Bind, 0},
"cgroup/connect4": {CGroupSockAddr, AttachCGroupInet4Connect, 0},
"cgroup/connect6": {CGroupSockAddr, AttachCGroupInet6Connect, 0},
"cgroup/sendmsg4": {CGroupSockAddr, AttachCGroupUDP4Sendmsg, 0},
"cgroup/sendmsg6": {CGroupSockAddr, AttachCGroupUDP6Sendmsg, 0},
"cgroup/recvmsg4": {CGroupSockAddr, AttachCGroupUDP4Recvmsg, 0},
"cgroup/recvmsg6": {CGroupSockAddr, AttachCGroupUDP6Recvmsg, 0},
"cgroup/getpeername4": {CGroupSockAddr, AttachCgroupInet4GetPeername, 0},
"cgroup/getpeername6": {CGroupSockAddr, AttachCgroupInet6GetPeername, 0},
"cgroup/getsockname4": {CGroupSockAddr, AttachCgroupInet4GetSockname, 0},
"cgroup/getsockname6": {CGroupSockAddr, AttachCgroupInet6GetSockname, 0},
"cgroup/sysctl": {CGroupSysctl, AttachCGroupSysctl, 0},
"cgroup/getsockopt": {CGroupSockopt, AttachCGroupGetsockopt, 0},
"cgroup/setsockopt": {CGroupSockopt, AttachCGroupSetsockopt, 0},
"struct_ops+": {StructOps, AttachNone, 0},
"sk_lookup/": {SkLookup, AttachSkLookup, 0},
"cgroup_skb/ingress": {CGroupSKB, AttachCGroupInetIngress, 0},
"cgroup_skb/egress": {CGroupSKB, AttachCGroupInetEgress, 0},
"cgroup/dev": {CGroupDevice, AttachCGroupDevice, 0},
"cgroup/skb": {CGroupSKB, AttachNone, 0},
"cgroup/sock": {CGroupSock, AttachCGroupInetSockCreate, 0},
"cgroup/post_bind4": {CGroupSock, AttachCGroupInet4PostBind, 0},
"cgroup/post_bind6": {CGroupSock, AttachCGroupInet6PostBind, 0},
"cgroup/bind4": {CGroupSockAddr, AttachCGroupInet4Bind, 0},
"cgroup/bind6": {CGroupSockAddr, AttachCGroupInet6Bind, 0},
"cgroup/connect4": {CGroupSockAddr, AttachCGroupInet4Connect, 0},
"cgroup/connect6": {CGroupSockAddr, AttachCGroupInet6Connect, 0},
"cgroup/sendmsg4": {CGroupSockAddr, AttachCGroupUDP4Sendmsg, 0},
"cgroup/sendmsg6": {CGroupSockAddr, AttachCGroupUDP6Sendmsg, 0},
"cgroup/recvmsg4": {CGroupSockAddr, AttachCGroupUDP4Recvmsg, 0},
"cgroup/recvmsg6": {CGroupSockAddr, AttachCGroupUDP6Recvmsg, 0},
"cgroup/sysctl": {CGroupSysctl, AttachCGroupSysctl, 0},
"cgroup/getsockopt": {CGroupSockopt, AttachCGroupGetsockopt, 0},
"cgroup/setsockopt": {CGroupSockopt, AttachCGroupSetsockopt, 0},
"classifier": {SchedCLS, AttachNone, 0},
"action": {SchedACT, AttachNone, 0},
"cgroup/getsockname4": {CGroupSockAddr, AttachCgroupInet4GetSockname, 0},
"cgroup/getsockname6": {CGroupSockAddr, AttachCgroupInet6GetSockname, 0},
"cgroup/getpeername4": {CGroupSockAddr, AttachCgroupInet4GetPeername, 0},
"cgroup/getpeername6": {CGroupSockAddr, AttachCgroupInet6GetPeername, 0},
"seccomp": {SocketFilter, AttachNone, 0},
}
for prefix, t := range types {
-22
View File
@@ -1,22 +0,0 @@
//go:build gofuzz
// +build gofuzz
// Use with https://github.com/dvyukov/go-fuzz
package ebpf
import "bytes"
func FuzzLoadCollectionSpec(data []byte) int {
spec, err := LoadCollectionSpecFromReader(bytes.NewReader(data))
if err != nil {
if spec != nil {
panic("spec is not nil")
}
return 0
}
if spec == nil {
panic("spec is nil")
}
return 1
}
+2 -2
View File
@@ -3,7 +3,7 @@ module github.com/cilium/ebpf
go 1.16
require (
github.com/frankban/quicktest v1.11.3
github.com/google/go-cmp v0.5.4
github.com/frankban/quicktest v1.14.0
github.com/google/go-cmp v0.5.6
golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34
)
+14 -7
View File
@@ -1,13 +1,20 @@
github.com/frankban/quicktest v1.11.3 h1:8sXhOn0uLys67V8EsXLc6eszDs8VXWxL3iRvebPhedY=
github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/frankban/quicktest v1.14.0 h1:+cqqvzZV87b4adx/5ayVOaYZ2CrvM4ejQvUdBzPPUss=
github.com/frankban/quicktest v1.14.0/go.mod h1:NeW+ay9A/U67EYXNFA1nPE8e/tnQv/09mUdL/ijj8og=
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34 h1:GkvMjFtXUmahfDtashnc1mnrCtuBVcwse5QV2lUk/tI=
golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
+99 -53
View File
@@ -2,6 +2,7 @@ package ebpf
import (
"bufio"
"bytes"
"encoding/hex"
"errors"
"fmt"
@@ -10,9 +11,13 @@ import (
"strings"
"syscall"
"time"
"unsafe"
"github.com/cilium/ebpf/asm"
"github.com/cilium/ebpf/internal"
"github.com/cilium/ebpf/internal/btf"
"github.com/cilium/ebpf/internal/sys"
"github.com/cilium/ebpf/internal/unix"
)
// MapInfo describes a map.
@@ -23,12 +28,13 @@ type MapInfo struct {
ValueSize uint32
MaxEntries uint32
Flags uint32
// Name as supplied by user space at load time.
// Name as supplied by user space at load time. Available from 4.15.
Name string
}
func newMapInfoFromFd(fd *internal.FD) (*MapInfo, error) {
info, err := bpfGetMapInfoByFD(fd)
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)
}
@@ -37,18 +43,17 @@ func newMapInfoFromFd(fd *internal.FD) (*MapInfo, error) {
}
return &MapInfo{
MapType(info.map_type),
MapID(info.id),
info.key_size,
info.value_size,
info.max_entries,
info.map_flags,
// name is available from 4.15.
internal.CString(info.name[:]),
MapType(info.Type),
MapID(info.Id),
info.KeySize,
info.ValueSize,
info.MaxEntries,
info.MapFlags,
unix.ByteSliceToString(info.Name[:]),
}, nil
}
func newMapInfoFromProc(fd *internal.FD) (*MapInfo, error) {
func newMapInfoFromProc(fd *sys.FD) (*MapInfo, error) {
var mi MapInfo
err := scanFdInfo(fd, map[string]interface{}{
"map_type": &mi.Type,
@@ -84,20 +89,21 @@ type programStats struct {
type ProgramInfo struct {
Type ProgramType
id ProgramID
// Truncated hash of the BPF bytecode.
// Truncated hash of the BPF bytecode. Available from 4.13.
Tag string
// Name as supplied by user space at load time.
// Name as supplied by user space at load time. Available from 4.15.
Name string
// BTF for the program.
btf btf.ID
// IDS map ids related to program.
ids []MapID
btf btf.ID
stats *programStats
maps []MapID
insns []byte
}
func newProgramInfoFromFd(fd *internal.FD) (*ProgramInfo, error) {
info, err := bpfGetProgInfoByFD(fd, nil)
func newProgramInfoFromFd(fd *sys.FD) (*ProgramInfo, error) {
var info sys.ProgInfo
err := sys.ObjInfo(fd, &info)
if errors.Is(err, syscall.EINVAL) {
return newProgramInfoFromProc(fd)
}
@@ -105,32 +111,43 @@ func newProgramInfoFromFd(fd *internal.FD) (*ProgramInfo, error) {
return nil, err
}
var mapIDs []MapID
if info.nr_map_ids > 0 {
mapIDs = make([]MapID, info.nr_map_ids)
info, err = bpfGetProgInfoByFD(fd, mapIDs)
if err != nil {
pi := ProgramInfo{
Type: ProgramType(info.Type),
id: ProgramID(info.Id),
Tag: hex.EncodeToString(info.Tag[:]),
Name: unix.ByteSliceToString(info.Name[:]),
btf: btf.ID(info.BtfId),
stats: &programStats{
runtime: time.Duration(info.RunTimeNs),
runCount: info.RunCnt,
},
}
// Start with a clean struct for the second call, otherwise we may get EFAULT.
var info2 sys.ProgInfo
if info.NrMapIds > 0 {
pi.maps = make([]MapID, info.NrMapIds)
info2.NrMapIds = info.NrMapIds
info2.MapIds = sys.NewPointer(unsafe.Pointer(&pi.maps[0]))
}
if info.XlatedProgLen > 0 {
pi.insns = make([]byte, info.XlatedProgLen)
info2.XlatedProgLen = info.XlatedProgLen
info2.XlatedProgInsns = sys.NewSlicePointer(pi.insns)
}
if info.NrMapIds > 0 || info.XlatedProgLen > 0 {
if err := sys.ObjInfo(fd, &info2); err != nil {
return nil, err
}
}
return &ProgramInfo{
Type: ProgramType(info.prog_type),
id: ProgramID(info.id),
// tag is available if the kernel supports BPF_PROG_GET_INFO_BY_FD.
Tag: hex.EncodeToString(info.tag[:]),
// name is available from 4.15.
Name: internal.CString(info.name[:]),
btf: btf.ID(info.btf_id),
ids: mapIDs,
stats: &programStats{
runtime: time.Duration(info.run_time_ns),
runCount: info.run_cnt,
},
}, nil
return &pi, nil
}
func newProgramInfoFromProc(fd *internal.FD) (*ProgramInfo, error) {
func newProgramInfoFromProc(fd *sys.FD) (*ProgramInfo, error) {
var info ProgramInfo
err := scanFdInfo(fd, map[string]interface{}{
"prog_type": &info.Type,
@@ -191,20 +208,47 @@ func (pi *ProgramInfo) Runtime() (time.Duration, bool) {
return time.Duration(0), false
}
// 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
// inspecting loaded programs for troubleshooting, dumping, etc.
//
// For example, map accesses are made to reference their kernel map IDs,
// not the FDs they had when the program was inserted.
//
// The first instruction is marked as a symbol using the Program's name.
//
// Available from 4.13. Requires CAP_BPF or equivalent.
func (pi *ProgramInfo) Instructions() (asm.Instructions, error) {
// If the calling process is not BPF-capable or if the kernel doesn't
// support getting xlated instructions, the field will be zero.
if len(pi.insns) == 0 {
return nil, fmt.Errorf("insufficient permissions or unsupported kernel: %w", ErrNotSupported)
}
r := bytes.NewReader(pi.insns)
var insns asm.Instructions
if err := insns.Unmarshal(r, internal.NativeEndian); err != nil {
return nil, fmt.Errorf("unmarshaling instructions: %w", err)
}
// Tag the first instruction with the name of the program, if available.
insns[0] = insns[0].Sym(pi.Name)
return insns, nil
}
// MapIDs returns the maps related to the program.
//
// Available from 4.15.
//
// The bool return value indicates whether this optional field is available.
func (pi *ProgramInfo) MapIDs() ([]MapID, bool) {
return pi.ids, pi.ids != nil
return pi.maps, pi.maps != nil
}
func scanFdInfo(fd *internal.FD, fields map[string]interface{}) error {
raw, err := fd.Value()
if err != nil {
return err
}
fh, err := os.Open(fmt.Sprintf("/proc/self/fdinfo/%d", raw))
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 {
return err
}
@@ -247,6 +291,10 @@ func scanFdInfoReader(r io.Reader, fields map[string]interface{}) error {
return err
}
if len(fields) > 0 && scanned == 0 {
return ErrNotSupported
}
if scanned != len(fields) {
return errMissingFields
}
@@ -261,11 +309,9 @@ func scanFdInfoReader(r io.Reader, fields map[string]interface{}) error {
//
// Requires at least 5.8.
func EnableStats(which uint32) (io.Closer, error) {
attr := internal.BPFEnableStatsAttr{
StatsType: which,
}
fd, err := internal.BPFEnableStats(&attr)
fd, err := sys.EnableStats(&sys.EnableStatsAttr{
Type: which,
})
if err != nil {
return nil, err
}
+270 -177
View File
@@ -11,9 +11,9 @@ import (
"os"
"reflect"
"sync"
"unsafe"
"github.com/cilium/ebpf/internal"
"github.com/cilium/ebpf/internal/sys"
"github.com/cilium/ebpf/internal/unix"
)
@@ -31,14 +31,23 @@ type ID uint32
// Spec represents decoded BTF.
type Spec struct {
rawTypes []rawType
strings stringTable
types []Type
namedTypes map[string][]NamedType
funcInfos map[string]extInfo
lineInfos map[string]extInfo
coreRelos map[string]coreRelos
byteOrder binary.ByteOrder
// Data from .BTF.
rawTypes []rawType
strings stringTable
// Inflated Types.
types []Type
// Types indexed by essential name.
// Includes all struct flavors and types with the same name.
namedTypes map[essentialName][]Type
// Data from .BTF.ext.
funcInfos map[string]FuncInfo
lineInfos map[string]LineInfos
coreRelos map[string]CoreRelos
byteOrder binary.ByteOrder
}
type btfHeader struct {
@@ -53,12 +62,30 @@ type btfHeader struct {
StringLen uint32
}
// LoadSpecFromReader reads BTF sections from an ELF.
// typeStart returns the offset from the beginning of the .BTF section
// to the start of its type entries.
func (h *btfHeader) typeStart() int64 {
return int64(h.HdrLen + h.TypeOff)
}
// stringStart returns the offset from the beginning of the .BTF section
// to the start of its string table.
func (h *btfHeader) stringStart() int64 {
return int64(h.HdrLen + h.StringOff)
}
// LoadSpecFromReader reads from an ELF or a raw BTF blob.
//
// Returns ErrNotFound if the reader contains no BTF.
// Returns ErrNotFound if reading from an ELF which contains no BTF.
func LoadSpecFromReader(rd io.ReaderAt) (*Spec, error) {
file, err := internal.NewSafeELFFile(rd)
if err != nil {
if bo := guessRawBTFByteOrder(rd); bo != nil {
// Try to parse a naked BTF blob. This will return an error if
// we encounter a Datasec, since we can't fix it up.
return loadRawSpec(io.NewSectionReader(rd, 0, math.MaxInt64), bo, nil, nil)
}
return nil, err
}
defer file.Close()
@@ -129,22 +156,96 @@ func loadSpecFromELF(file *internal.SafeELFFile, variableOffsets map[variable]ui
return spec, nil
}
spec.funcInfos, spec.lineInfos, spec.coreRelos, err = parseExtInfos(btfExtSection.Open(), file.ByteOrder, spec.strings)
if btfExtSection.ReaderAt == nil {
return nil, fmt.Errorf("compressed ext_info is not supported")
}
extInfo, err := loadExtInfos(btfExtSection, file.ByteOrder, spec.strings)
if err != nil {
return nil, fmt.Errorf("can't read ext info: %w", err)
return nil, fmt.Errorf("can't parse ext info: %w", err)
}
if err := spec.splitExtInfos(extInfo); err != nil {
return nil, fmt.Errorf("linking funcInfos and lineInfos: %w", err)
}
return spec, nil
}
// LoadRawSpec reads a blob of BTF data that isn't wrapped in an ELF file.
//
// Prefer using LoadSpecFromReader, since this function only supports a subset
// of BTF.
func LoadRawSpec(btf io.Reader, bo binary.ByteOrder) (*Spec, error) {
// This will return an error if we encounter a Datasec, since we can't fix
// it up.
return loadRawSpec(btf, bo, nil, nil)
// splitExtInfos takes FuncInfos, LineInfos and CoreRelos indexed by section and
// transforms them to be indexed by function. Retrieves function names from
// the BTF spec.
func (spec *Spec) splitExtInfos(info *extInfo) error {
ofi := make(map[string]FuncInfo)
oli := make(map[string]LineInfos)
ocr := make(map[string]CoreRelos)
for secName, secFuncs := range info.funcInfos {
// Collect functions from each section and organize them by name.
for _, fi := range secFuncs {
name, err := fi.Name(spec)
if err != nil {
return fmt.Errorf("looking up function name: %w", err)
}
// FuncInfo offsets are scoped to the ELF section. Zero them out
// since they are meaningless outside of that context. The linker
// will determine the offset of the function within the final
// instruction stream before handing it off to the kernel.
fi.InsnOff = 0
ofi[name] = fi
}
// Attribute LineInfo records to their respective functions, if any.
if lines := info.lineInfos[secName]; lines != nil {
for _, li := range lines {
fi := secFuncs.funcForOffset(li.InsnOff)
if fi == nil {
return fmt.Errorf("section %s: error looking up FuncInfo for LineInfo %v", secName, li)
}
// Offsets are ELF section-scoped, make them function-scoped by
// subtracting the function's start offset.
li.InsnOff -= fi.InsnOff
name, err := fi.Name(spec)
if err != nil {
return fmt.Errorf("looking up function name: %w", err)
}
oli[name] = append(oli[name], li)
}
}
// Attribute CO-RE relocations to their respective functions, if any.
if relos := info.relos[secName]; relos != nil {
for _, r := range relos {
fi := secFuncs.funcForOffset(r.insnOff)
if fi == nil {
return fmt.Errorf("section %s: error looking up FuncInfo for CO-RE relocation %v", secName, r)
}
// Offsets are ELF section-scoped, make them function-scoped by
// subtracting the function's start offset.
r.insnOff -= fi.InsnOff
name, err := fi.Name(spec)
if err != nil {
return fmt.Errorf("looking up function name: %w", err)
}
ocr[name] = append(ocr[name], r)
}
}
}
spec.funcInfos = ofi
spec.lineInfos = oli
spec.coreRelos = ocr
return nil
}
func loadRawSpec(btf io.Reader, bo binary.ByteOrder, sectionSizes map[string]uint32, variableOffsets map[variable]uint32) (*Spec, error) {
@@ -195,18 +296,21 @@ func LoadKernelSpec() (*Spec, error) {
}
func loadKernelSpec() (*Spec, error) {
release, err := unix.KernelRelease()
if err != nil {
return nil, fmt.Errorf("can't read kernel release number: %w", err)
}
fh, err := os.Open("/sys/kernel/btf/vmlinux")
if err == nil {
defer fh.Close()
return LoadRawSpec(fh, internal.NativeEndian)
return loadRawSpec(fh, internal.NativeEndian, nil, nil)
}
var uname unix.Utsname
if err := unix.Uname(&uname); err != nil {
return nil, fmt.Errorf("uname failed: %w", err)
}
end := bytes.IndexByte(uname.Release[:], 0)
release := string(uname.Release[:end])
// use same list of locations as libbpf
// https://github.com/libbpf/libbpf/blob/9a3a42608dbe3731256a5682a125ac1e23bced8f/src/btf.c#L3114-L3122
locations := []string{
@@ -240,54 +344,72 @@ func loadKernelSpec() (*Spec, error) {
return nil, fmt.Errorf("no BTF for kernel version %s: %w", release, internal.ErrNotSupported)
}
// parseBTFHeader parses the header of the .BTF section.
func parseBTFHeader(r io.Reader, bo binary.ByteOrder) (*btfHeader, error) {
var header btfHeader
if err := binary.Read(r, bo, &header); err != nil {
return nil, fmt.Errorf("can't read header: %v", err)
}
if header.Magic != btfMagic {
return nil, fmt.Errorf("incorrect magic value %v", header.Magic)
}
if header.Version != 1 {
return nil, fmt.Errorf("unexpected version %v", header.Version)
}
if header.Flags != 0 {
return nil, fmt.Errorf("unsupported flags %v", header.Flags)
}
remainder := int64(header.HdrLen) - int64(binary.Size(&header))
if remainder < 0 {
return nil, errors.New("header length shorter than btfHeader size")
}
if _, err := io.CopyN(internal.DiscardZeroes{}, r, remainder); err != nil {
return nil, fmt.Errorf("header padding: %v", err)
}
return &header, nil
}
func guessRawBTFByteOrder(r io.ReaderAt) binary.ByteOrder {
for _, bo := range []binary.ByteOrder{
binary.LittleEndian,
binary.BigEndian,
} {
if _, err := parseBTFHeader(io.NewSectionReader(r, 0, math.MaxInt64), bo); err == nil {
return bo
}
}
return nil
}
// parseBTF reads a .BTF section into memory and parses it into a list of
// raw types and a string table.
func parseBTF(btf io.Reader, bo binary.ByteOrder) ([]rawType, stringTable, error) {
rawBTF, err := io.ReadAll(btf)
if err != nil {
return nil, nil, fmt.Errorf("can't read BTF: %v", err)
}
rd := bytes.NewReader(rawBTF)
var header btfHeader
if err := binary.Read(rd, bo, &header); err != nil {
return nil, nil, fmt.Errorf("can't read header: %v", err)
header, err := parseBTFHeader(rd, bo)
if err != nil {
return nil, nil, fmt.Errorf("parsing .BTF header: %v", err)
}
if header.Magic != btfMagic {
return nil, nil, fmt.Errorf("incorrect magic value %v", header.Magic)
}
if header.Version != 1 {
return nil, nil, fmt.Errorf("unexpected version %v", header.Version)
}
if header.Flags != 0 {
return nil, nil, fmt.Errorf("unsupported flags %v", header.Flags)
}
remainder := int64(header.HdrLen) - int64(binary.Size(&header))
if remainder < 0 {
return nil, nil, errors.New("header is too short")
}
if _, err := io.CopyN(internal.DiscardZeroes{}, rd, remainder); err != nil {
return nil, nil, fmt.Errorf("header padding: %v", err)
}
if _, err := rd.Seek(int64(header.HdrLen+header.StringOff), io.SeekStart); err != nil {
return nil, nil, fmt.Errorf("can't seek to start of string section: %v", err)
}
rawStrings, err := readStringTable(io.LimitReader(rd, int64(header.StringLen)))
buf := io.NewSectionReader(rd, header.stringStart(), int64(header.StringLen))
rawStrings, err := readStringTable(buf)
if err != nil {
return nil, nil, fmt.Errorf("can't read type names: %w", err)
}
if _, err := rd.Seek(int64(header.HdrLen+header.TypeOff), io.SeekStart); err != nil {
return nil, nil, fmt.Errorf("can't seek to start of type section: %v", err)
}
rawTypes, err := readTypes(io.LimitReader(rd, int64(header.TypeLen)), bo)
buf = io.NewSectionReader(rd, header.typeStart(), int64(header.TypeLen))
rawTypes, err := readTypes(buf, bo)
if err != nil {
return nil, nil, fmt.Errorf("can't read types: %w", err)
}
@@ -353,11 +475,12 @@ func fixupDatasec(rawTypes []rawType, rawStrings stringTable, sectionSizes map[s
// Copy creates a copy of Spec.
func (s *Spec) Copy() *Spec {
types, _ := copyTypes(s.types, nil)
namedTypes := make(map[string][]NamedType)
namedTypes := make(map[essentialName][]Type)
for _, typ := range types {
if named, ok := typ.(NamedType); ok {
name := essentialName(named.TypeName())
namedTypes[name] = append(namedTypes[name], named)
if name := typ.TypeName(); name != "" {
en := newEssentialName(name)
namedTypes[en] = append(namedTypes[en], typ)
}
}
@@ -438,19 +561,13 @@ func (sw sliceWriter) Write(p []byte) (int, error) {
return copy(sw, p), nil
}
// Program finds the BTF for a specific section.
//
// Length is the number of bytes in the raw BPF instruction stream.
// Program finds the BTF for a specific function.
//
// Returns an error which may wrap ErrNoExtendedInfo if the Spec doesn't
// contain extended BTF info.
func (s *Spec) Program(name string, length uint64) (*Program, error) {
if length == 0 {
return nil, errors.New("length musn't be zero")
}
func (s *Spec) Program(name string) (*Program, error) {
if s.funcInfos == nil && s.lineInfos == nil && s.coreRelos == nil {
return nil, fmt.Errorf("BTF for section %s: %w", name, ErrNoExtendedInfo)
return nil, fmt.Errorf("BTF for function %s: %w", name, ErrNoExtendedInfo)
}
funcInfos, funcOK := s.funcInfos[name]
@@ -458,20 +575,59 @@ func (s *Spec) Program(name string, length uint64) (*Program, error) {
relos, coreOK := s.coreRelos[name]
if !funcOK && !lineOK && !coreOK {
return nil, fmt.Errorf("no extended BTF info for section %s", name)
return nil, fmt.Errorf("no extended BTF info for function %s", name)
}
return &Program{s, length, funcInfos, lineInfos, relos}, nil
return &Program{s, funcInfos, lineInfos, relos}, nil
}
// FindType searches for a type with a specific name.
// TypeByID returns the BTF Type with the given type ID.
//
// Called T a type that satisfies Type, typ must be a non-nil **T.
// On success, the address of the found type will be copied in typ.
// Returns an error wrapping ErrNotFound if a Type with the given ID
// does not exist in the Spec.
func (s *Spec) TypeByID(id TypeID) (Type, error) {
if int(id) > len(s.types) {
return nil, fmt.Errorf("type ID %d: %w", id, ErrNotFound)
}
return s.types[id], nil
}
// AnyTypesByName returns a list of BTF Types with the given name.
//
// If the BTF blob describes multiple compilation units like vmlinux, multiple
// Types with the same name and kind can exist, but might not describe the same
// data structure.
//
// Returns an error wrapping ErrNotFound if no matching Type exists in the Spec.
func (s *Spec) AnyTypesByName(name string) ([]Type, error) {
types := s.namedTypes[newEssentialName(name)]
if len(types) == 0 {
return nil, fmt.Errorf("type name %s: %w", name, ErrNotFound)
}
// Return a copy to prevent changes to namedTypes.
result := make([]Type, 0, len(types))
for _, t := range types {
// Match against the full name, not just the essential one
// in case the type being looked up is a struct flavor.
if t.TypeName() == name {
result = append(result, t)
}
}
return result, nil
}
// TypeByName searches for a Type with a specific name. Since multiple
// Types with the same name can exist, the parameter typ is taken to
// narrow down the search in case of a clash.
//
// typ must be a non-nil pointer to an implementation of a Type.
// On success, the address of the found Type will be copied to typ.
//
// Returns an error wrapping ErrNotFound if no matching
// type exists in spec.
func (s *Spec) FindType(name string, typ interface{}) error {
// Type exists in the Spec. If multiple candidates are found,
// an error is returned.
func (s *Spec) TypeByName(name string, typ interface{}) error {
typValue := reflect.ValueOf(typ)
if typValue.Kind() != reflect.Ptr {
return fmt.Errorf("%T is not a pointer", typ)
@@ -487,14 +643,14 @@ func (s *Spec) FindType(name string, typ interface{}) error {
return fmt.Errorf("%T does not satisfy Type interface", typ)
}
var candidate Type
for _, typ := range s.namedTypes[essentialName(name)] {
if reflect.TypeOf(typ) != wanted {
continue
}
types, err := s.AnyTypesByName(name)
if err != nil {
return err
}
// Match against the full name, not just the essential one.
if typ.TypeName() != name {
var candidate Type
for _, typ := range types {
if reflect.TypeOf(typ) != wanted {
continue
}
@@ -517,7 +673,7 @@ func (s *Spec) FindType(name string, typ interface{}) error {
// Handle is a reference to BTF loaded into the kernel.
type Handle struct {
spec *Spec
fd *internal.FD
fd *sys.FD
}
// NewHandle loads BTF into the kernel.
@@ -544,18 +700,18 @@ func NewHandle(spec *Spec) (*Handle, error) {
return nil, errors.New("BTF exceeds the maximum size")
}
attr := &bpfLoadBTFAttr{
btf: internal.NewSlicePointer(btf),
btfSize: uint32(len(btf)),
attr := &sys.BtfLoadAttr{
Btf: sys.NewSlicePointer(btf),
BtfSize: uint32(len(btf)),
}
fd, err := bpfLoadBTF(attr)
fd, err := sys.BtfLoad(attr)
if err != nil {
logBuf := make([]byte, 64*1024)
attr.logBuf = internal.NewSlicePointer(logBuf)
attr.btfLogSize = uint32(len(logBuf))
attr.btfLogLevel = 1
_, logErr := bpfLoadBTF(attr)
attr.BtfLogBuf = sys.NewSlicePointer(logBuf)
attr.BtfLogSize = uint32(len(logBuf))
attr.BtfLogLevel = 1
_, logErr := sys.BtfLoad(attr)
return nil, internal.ErrorWithLog(err, logBuf, logErr)
}
@@ -568,7 +724,9 @@ func NewHandle(spec *Spec) (*Handle, error) {
//
// Requires CAP_SYS_ADMIN.
func NewHandleFromID(id ID) (*Handle, error) {
fd, err := internal.BPFObjGetFDByID(internal.BPF_BTF_GET_FD_BY_ID, uint32(id))
fd, err := sys.BtfGetFdById(&sys.BtfGetFdByIdAttr{
Id: uint32(id),
})
if err != nil {
return nil, fmt.Errorf("get BTF by id: %w", err)
}
@@ -596,12 +754,7 @@ func (h *Handle) Close() error {
// FD returns the file descriptor for the handle.
func (h *Handle) FD() int {
value, err := h.fd.Value()
if err != nil {
return -1
}
return int(value)
return h.fd.Int()
}
// Map is the BTF for a map.
@@ -612,10 +765,10 @@ type Map struct {
// Program is the BTF information for a stream of instructions.
type Program struct {
spec *Spec
length uint64
funcInfos, lineInfos extInfo
coreRelos coreRelos
spec *Spec
FuncInfo FuncInfo
LineInfos LineInfos
CoreRelos CoreRelos
}
// Spec returns the BTF spec of this program.
@@ -623,54 +776,11 @@ func (p *Program) Spec() *Spec {
return p.spec
}
// Append the information from other to the Program.
func (p *Program) Append(other *Program) error {
if other.spec != p.spec {
return fmt.Errorf("can't append program with different BTF specs")
}
funcInfos, err := p.funcInfos.append(other.funcInfos, p.length)
if err != nil {
return fmt.Errorf("func infos: %w", err)
}
lineInfos, err := p.lineInfos.append(other.lineInfos, p.length)
if err != nil {
return fmt.Errorf("line infos: %w", err)
}
p.funcInfos = funcInfos
p.lineInfos = lineInfos
p.coreRelos = p.coreRelos.append(other.coreRelos, p.length)
p.length += other.length
return nil
}
// FuncInfos returns the binary form of BTF function infos.
func (p *Program) FuncInfos() (recordSize uint32, bytes []byte, err error) {
bytes, err = p.funcInfos.MarshalBinary()
if err != nil {
return 0, nil, fmt.Errorf("func infos: %w", err)
}
return p.funcInfos.recordSize, bytes, nil
}
// LineInfos returns the binary form of BTF line infos.
func (p *Program) LineInfos() (recordSize uint32, bytes []byte, err error) {
bytes, err = p.lineInfos.MarshalBinary()
if err != nil {
return 0, nil, fmt.Errorf("line infos: %w", err)
}
return p.lineInfos.recordSize, bytes, nil
}
// Fixups returns the changes required to adjust the program to the target.
//
// Passing a nil target will relocate against the running kernel.
func (p *Program) Fixups(target *Spec) (COREFixups, error) {
if len(p.coreRelos) == 0 {
if len(p.CoreRelos) == 0 {
return nil, nil
}
@@ -682,24 +792,7 @@ func (p *Program) Fixups(target *Spec) (COREFixups, error) {
}
}
return coreRelocate(p.spec, target, p.coreRelos)
}
type bpfLoadBTFAttr struct {
btf internal.Pointer
logBuf internal.Pointer
btfSize uint32
btfLogSize uint32
btfLogLevel uint32
}
func bpfLoadBTF(attr *bpfLoadBTFAttr) (*internal.FD, error) {
fd, err := internal.BPF(internal.BPF_BTF_LOAD, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
if err != nil {
return nil, err
}
return internal.NewFD(uint32(fd)), nil
return coreRelocate(p.spec, target, p.CoreRelos)
}
func marshalBTF(types interface{}, strings []byte, bo binary.ByteOrder) []byte {
@@ -744,9 +837,9 @@ var haveBTF = internal.FeatureTest("BTF", "5.1", func() error {
btf := marshalBTF(&types, strings, internal.NativeEndian)
fd, err := bpfLoadBTF(&bpfLoadBTFAttr{
btf: internal.NewSlicePointer(btf),
btfSize: uint32(len(btf)),
fd, err := sys.BtfLoad(&sys.BtfLoadAttr{
Btf: sys.NewSlicePointer(btf),
BtfSize: uint32(len(btf)),
})
if errors.Is(err, unix.EINVAL) || errors.Is(err, unix.EPERM) {
// Treat both EINVAL and EPERM as not supported: loading the program
@@ -782,9 +875,9 @@ var haveFuncLinkage = internal.FeatureTest("BTF func linkage", "5.6", func() err
btf := marshalBTF(&types, strings, internal.NativeEndian)
fd, err := bpfLoadBTF(&bpfLoadBTFAttr{
btf: internal.NewSlicePointer(btf),
btfSize: uint32(len(btf)),
fd, err := sys.BtfLoad(&sys.BtfLoadAttr{
Btf: sys.NewSlicePointer(btf),
BtfSize: uint32(len(btf)),
})
if errors.Is(err, unix.EINVAL) {
return internal.ErrNotSupported
+27 -14
View File
@@ -97,7 +97,7 @@ func (f COREFixup) isNonExistant() bool {
type COREFixups map[uint64]COREFixup
// Apply a set of CO-RE relocations to a BPF program.
// Apply returns a copy of insns with CO-RE relocations applied.
func (fs COREFixups) Apply(insns asm.Instructions) (asm.Instructions, error) {
if len(fs) == 0 {
cpy := make(asm.Instructions, len(insns))
@@ -191,13 +191,13 @@ func (k COREKind) checksForExistence() bool {
return k == reloEnumvalExists || k == reloTypeExists || k == reloFieldExists
}
func coreRelocate(local, target *Spec, relos coreRelos) (COREFixups, error) {
func coreRelocate(local, target *Spec, relos CoreRelos) (COREFixups, error) {
if local.byteOrder != target.byteOrder {
return nil, fmt.Errorf("can't relocate %s against %s", local.byteOrder, target.byteOrder)
}
var ids []TypeID
relosByID := make(map[TypeID]coreRelos)
relosByID := make(map[TypeID]CoreRelos)
result := make(COREFixups, len(relos))
for _, relo := range relos {
if relo.kind == reloTypeIDLocal {
@@ -234,13 +234,13 @@ func coreRelocate(local, target *Spec, relos coreRelos) (COREFixups, error) {
}
localType := local.types[id]
named, ok := localType.(NamedType)
if !ok || named.TypeName() == "" {
localTypeName := localType.TypeName()
if localTypeName == "" {
return nil, fmt.Errorf("relocate unnamed or anonymous type %s: %w", localType, ErrNotSupported)
}
relos := relosByID[id]
targets := target.namedTypes[essentialName(named.TypeName())]
targets := target.namedTypes[newEssentialName(localTypeName)]
fixups, err := coreCalculateFixups(localType, targets, relos)
if err != nil {
return nil, fmt.Errorf("relocate %s: %w", localType, err)
@@ -262,9 +262,9 @@ var errImpossibleRelocation = errors.New("impossible relocation")
//
// The best target is determined by scoring: the less poisoning we have to do
// the better the target is.
func coreCalculateFixups(local Type, targets []NamedType, relos coreRelos) ([]COREFixup, error) {
func coreCalculateFixups(local Type, targets []Type, relos CoreRelos) ([]COREFixup, error) {
localID := local.ID()
local, err := copyType(local, skipQualifierAndTypedef)
local, err := copyType(local, skipQualifiersAndTypedefs)
if err != nil {
return nil, err
}
@@ -273,7 +273,7 @@ func coreCalculateFixups(local Type, targets []NamedType, relos coreRelos) ([]CO
var bestFixups []COREFixup
for i := range targets {
targetID := targets[i].ID()
target, err := copyType(targets[i], skipQualifierAndTypedef)
target, err := copyType(targets[i], skipQualifiersAndTypedefs)
if err != nil {
return nil, err
}
@@ -326,7 +326,7 @@ func coreCalculateFixups(local Type, targets []NamedType, relos coreRelos) ([]CO
// coreCalculateFixup calculates the fixup for a single local type, target type
// and relocation.
func coreCalculateFixup(local Type, localID TypeID, target Type, targetID TypeID, relo coreRelo) (COREFixup, error) {
func coreCalculateFixup(local Type, localID TypeID, target Type, targetID TypeID, relo CoreRelo) (COREFixup, error) {
fixup := func(local, target uint32) (COREFixup, error) {
return COREFixup{relo.kind, local, target, false}, nil
}
@@ -704,9 +704,9 @@ func coreFindEnumValue(local Type, localAcc coreAccessor, target Type) (localVal
return nil, nil, errImpossibleRelocation
}
localName := essentialName(localValue.Name)
localName := newEssentialName(localValue.Name)
for i, targetValue := range targetEnum.Values {
if essentialName(targetValue.Name) != localName {
if newEssentialName(targetValue.Name) != localName {
continue
}
@@ -831,7 +831,7 @@ func coreAreMembersCompatible(localType Type, targetType Type) error {
return nil
}
if essentialName(a) == essentialName(b) {
if newEssentialName(a) == newEssentialName(b) {
return nil
}
@@ -872,7 +872,7 @@ func coreAreMembersCompatible(localType Type, targetType Type) error {
}
}
func skipQualifierAndTypedef(typ Type) (Type, error) {
func skipQualifiersAndTypedefs(typ Type) (Type, error) {
result := typ
for depth := 0; depth <= maxTypeDepth; depth++ {
switch v := (result).(type) {
@@ -886,3 +886,16 @@ func skipQualifierAndTypedef(typ Type) (Type, error) {
}
return nil, errors.New("exceeded type depth")
}
func skipQualifiers(typ Type) (Type, error) {
result := typ
for depth := 0; depth <= maxTypeDepth; depth++ {
switch v := (result).(type) {
case qualifier:
result = v.qualify()
default:
return result, nil
}
}
return nil, errors.New("exceeded type depth")
}
+443 -258
View File
@@ -1,22 +1,73 @@
package btf
import (
"bufio"
"bytes"
"encoding/binary"
"errors"
"fmt"
"io"
"math"
"github.com/cilium/ebpf/asm"
"github.com/cilium/ebpf/internal"
)
// extInfo contains extended program metadata.
//
// It is indexed per section.
type extInfo struct {
funcInfos map[string]FuncInfos
lineInfos map[string]LineInfos
relos map[string]CoreRelos
}
// loadExtInfos parses the .BTF.ext section into its constituent parts.
func loadExtInfos(r io.ReaderAt, bo binary.ByteOrder, strings stringTable) (*extInfo, error) {
// Open unbuffered section reader. binary.Read() calls io.ReadFull on
// the header structs, resulting in one syscall per header.
headerRd := io.NewSectionReader(r, 0, math.MaxInt64)
extHeader, err := parseBTFExtHeader(headerRd, bo)
if err != nil {
return nil, fmt.Errorf("parsing BTF extension header: %w", err)
}
coreHeader, err := parseBTFExtCoreHeader(headerRd, bo, extHeader)
if err != nil {
return nil, fmt.Errorf("parsing BTF CO-RE header: %w", err)
}
buf := internal.NewBufferedSectionReader(r, extHeader.funcInfoStart(), int64(extHeader.FuncInfoLen))
funcInfos, err := parseFuncInfos(buf, bo, strings)
if err != nil {
return nil, fmt.Errorf("parsing BTF function info: %w", err)
}
buf = internal.NewBufferedSectionReader(r, extHeader.lineInfoStart(), int64(extHeader.LineInfoLen))
lineInfos, err := parseLineInfos(buf, bo, strings)
if err != nil {
return nil, fmt.Errorf("parsing BTF line info: %w", err)
}
relos := make(map[string]CoreRelos)
if coreHeader != nil && coreHeader.CoreReloOff > 0 && coreHeader.CoreReloLen > 0 {
buf = internal.NewBufferedSectionReader(r, extHeader.coreReloStart(coreHeader), int64(coreHeader.CoreReloLen))
relos, err = parseCoreRelos(buf, bo, strings)
if err != nil {
return nil, fmt.Errorf("parsing CO-RE relocation info: %w", err)
}
}
return &extInfo{funcInfos, lineInfos, relos}, nil
}
// btfExtHeader is found at the start of the .BTF.ext section.
type btfExtHeader struct {
Magic uint16
Version uint8
Flags uint8
HdrLen uint32
// HdrLen is larger than the size of struct btfExtHeader when it is
// immediately followed by a btfExtCoreHeader.
HdrLen uint32
FuncInfoOff uint32
FuncInfoLen uint32
@@ -24,82 +75,74 @@ type btfExtHeader struct {
LineInfoLen uint32
}
// parseBTFExtHeader parses the header of the .BTF.ext section.
func parseBTFExtHeader(r io.Reader, bo binary.ByteOrder) (*btfExtHeader, error) {
var header btfExtHeader
if err := binary.Read(r, bo, &header); err != nil {
return nil, fmt.Errorf("can't read header: %v", err)
}
if header.Magic != btfMagic {
return nil, fmt.Errorf("incorrect magic value %v", header.Magic)
}
if header.Version != 1 {
return nil, fmt.Errorf("unexpected version %v", header.Version)
}
if header.Flags != 0 {
return nil, fmt.Errorf("unsupported flags %v", header.Flags)
}
if int64(header.HdrLen) < int64(binary.Size(&header)) {
return nil, fmt.Errorf("header length shorter than btfExtHeader size")
}
return &header, nil
}
// funcInfoStart returns the offset from the beginning of the .BTF.ext section
// to the start of its func_info entries.
func (h *btfExtHeader) funcInfoStart() int64 {
return int64(h.HdrLen + h.FuncInfoOff)
}
// lineInfoStart returns the offset from the beginning of the .BTF.ext section
// to the start of its line_info entries.
func (h *btfExtHeader) lineInfoStart() int64 {
return int64(h.HdrLen + h.LineInfoOff)
}
// coreReloStart returns the offset from the beginning of the .BTF.ext section
// to the start of its CO-RE relocation entries.
func (h *btfExtHeader) coreReloStart(ch *btfExtCoreHeader) int64 {
return int64(h.HdrLen + ch.CoreReloOff)
}
// btfExtCoreHeader is found right after the btfExtHeader when its HdrLen
// field is larger than its size.
type btfExtCoreHeader struct {
CoreReloOff uint32
CoreReloLen uint32
}
func parseExtInfos(r io.ReadSeeker, bo binary.ByteOrder, strings stringTable) (funcInfo, lineInfo map[string]extInfo, relos map[string]coreRelos, err error) {
var header btfExtHeader
// parseBTFExtCoreHeader parses the tail of the .BTF.ext header. If additional
// header bytes are present, extHeader.HdrLen will be larger than the struct,
// indicating the presence of a CO-RE extension header.
func parseBTFExtCoreHeader(r io.Reader, bo binary.ByteOrder, extHeader *btfExtHeader) (*btfExtCoreHeader, error) {
extHdrSize := int64(binary.Size(&extHeader))
remainder := int64(extHeader.HdrLen) - extHdrSize
if remainder == 0 {
return nil, nil
}
var coreHeader btfExtCoreHeader
if err := binary.Read(r, bo, &header); err != nil {
return nil, nil, nil, fmt.Errorf("can't read header: %v", err)
if err := binary.Read(r, bo, &coreHeader); err != nil {
return nil, fmt.Errorf("can't read header: %v", err)
}
if header.Magic != btfMagic {
return nil, nil, nil, fmt.Errorf("incorrect magic value %v", header.Magic)
}
if header.Version != 1 {
return nil, nil, nil, fmt.Errorf("unexpected version %v", header.Version)
}
if header.Flags != 0 {
return nil, nil, nil, fmt.Errorf("unsupported flags %v", header.Flags)
}
remainder := int64(header.HdrLen) - int64(binary.Size(&header))
if remainder < 0 {
return nil, nil, nil, errors.New("header is too short")
}
coreHdrSize := int64(binary.Size(&coreHeader))
if remainder >= coreHdrSize {
if err := binary.Read(r, bo, &coreHeader); err != nil {
return nil, nil, nil, fmt.Errorf("can't read CO-RE relocation header: %v", err)
}
remainder -= coreHdrSize
}
// Of course, the .BTF.ext header has different semantics than the
// .BTF ext header. We need to ignore non-null values.
_, err = io.CopyN(io.Discard, r, remainder)
if err != nil {
return nil, nil, nil, fmt.Errorf("header padding: %v", err)
}
if _, err := r.Seek(int64(header.HdrLen+header.FuncInfoOff), io.SeekStart); err != nil {
return nil, nil, nil, fmt.Errorf("can't seek to function info section: %v", err)
}
buf := bufio.NewReader(io.LimitReader(r, int64(header.FuncInfoLen)))
funcInfo, err = parseExtInfo(buf, bo, strings)
if err != nil {
return nil, nil, nil, fmt.Errorf("function info: %w", err)
}
if _, err := r.Seek(int64(header.HdrLen+header.LineInfoOff), io.SeekStart); err != nil {
return nil, nil, nil, fmt.Errorf("can't seek to line info section: %v", err)
}
buf = bufio.NewReader(io.LimitReader(r, int64(header.LineInfoLen)))
lineInfo, err = parseExtInfo(buf, bo, strings)
if err != nil {
return nil, nil, nil, fmt.Errorf("line info: %w", err)
}
if coreHeader.CoreReloOff > 0 && coreHeader.CoreReloLen > 0 {
if _, err := r.Seek(int64(header.HdrLen+coreHeader.CoreReloOff), io.SeekStart); err != nil {
return nil, nil, nil, fmt.Errorf("can't seek to CO-RE relocation section: %v", err)
}
relos, err = parseExtInfoRelos(io.LimitReader(r, int64(coreHeader.CoreReloLen)), bo, strings)
if err != nil {
return nil, nil, nil, fmt.Errorf("CO-RE relocation info: %w", err)
}
}
return funcInfo, lineInfo, relos, nil
return &coreHeader, nil
}
type btfExtInfoSec struct {
@@ -107,193 +150,11 @@ type btfExtInfoSec struct {
NumInfo uint32
}
type extInfoRecord struct {
InsnOff uint64
Opaque []byte
}
type extInfo struct {
byteOrder binary.ByteOrder
recordSize uint32
records []extInfoRecord
}
func (ei extInfo) append(other extInfo, offset uint64) (extInfo, error) {
if other.byteOrder != ei.byteOrder {
return extInfo{}, fmt.Errorf("ext_info byte order mismatch, want %v (got %v)", ei.byteOrder, other.byteOrder)
}
if other.recordSize != ei.recordSize {
return extInfo{}, fmt.Errorf("ext_info record size mismatch, want %d (got %d)", ei.recordSize, other.recordSize)
}
records := make([]extInfoRecord, 0, len(ei.records)+len(other.records))
records = append(records, ei.records...)
for _, info := range other.records {
records = append(records, extInfoRecord{
InsnOff: info.InsnOff + offset,
Opaque: info.Opaque,
})
}
return extInfo{ei.byteOrder, ei.recordSize, records}, nil
}
func (ei extInfo) MarshalBinary() ([]byte, error) {
if ei.byteOrder != internal.NativeEndian {
return nil, fmt.Errorf("%s is not the native byte order", ei.byteOrder)
}
if len(ei.records) == 0 {
return nil, nil
}
buf := bytes.NewBuffer(make([]byte, 0, int(ei.recordSize)*len(ei.records)))
for _, info := range ei.records {
// The kernel expects offsets in number of raw bpf instructions,
// while the ELF tracks it in bytes.
insnOff := uint32(info.InsnOff / asm.InstructionSize)
if err := binary.Write(buf, internal.NativeEndian, insnOff); err != nil {
return nil, fmt.Errorf("can't write instruction offset: %v", err)
}
buf.Write(info.Opaque)
}
return buf.Bytes(), nil
}
func parseExtInfo(r io.Reader, bo binary.ByteOrder, strings stringTable) (map[string]extInfo, error) {
const maxRecordSize = 256
var recordSize uint32
if err := binary.Read(r, bo, &recordSize); err != nil {
return nil, fmt.Errorf("can't read record size: %v", err)
}
if recordSize < 4 {
// Need at least insnOff
return nil, errors.New("record size too short")
}
if recordSize > maxRecordSize {
return nil, fmt.Errorf("record size %v exceeds %v", recordSize, maxRecordSize)
}
result := make(map[string]extInfo)
for {
secName, infoHeader, err := parseExtInfoHeader(r, bo, strings)
if errors.Is(err, io.EOF) {
return result, nil
}
var records []extInfoRecord
for i := uint32(0); i < infoHeader.NumInfo; i++ {
var byteOff uint32
if err := binary.Read(r, bo, &byteOff); err != nil {
return nil, fmt.Errorf("section %v: can't read extended info offset: %v", secName, err)
}
buf := make([]byte, int(recordSize-4))
if _, err := io.ReadFull(r, buf); err != nil {
return nil, fmt.Errorf("section %v: can't read record: %v", secName, err)
}
if byteOff%asm.InstructionSize != 0 {
return nil, fmt.Errorf("section %v: offset %v is not aligned with instruction size", secName, byteOff)
}
records = append(records, extInfoRecord{uint64(byteOff), buf})
}
result[secName] = extInfo{
bo,
recordSize,
records,
}
}
}
// bpfCoreRelo matches `struct bpf_core_relo` from the kernel
type bpfCoreRelo struct {
InsnOff uint32
TypeID TypeID
AccessStrOff uint32
Kind COREKind
}
type coreRelo struct {
insnOff uint32
typeID TypeID
accessor coreAccessor
kind COREKind
}
type coreRelos []coreRelo
// append two slices of extInfoRelo to each other. The InsnOff of b are adjusted
// by offset.
func (r coreRelos) append(other coreRelos, offset uint64) coreRelos {
result := make([]coreRelo, 0, len(r)+len(other))
result = append(result, r...)
for _, relo := range other {
relo.insnOff += uint32(offset)
result = append(result, relo)
}
return result
}
var extInfoReloSize = binary.Size(bpfCoreRelo{})
func parseExtInfoRelos(r io.Reader, bo binary.ByteOrder, strings stringTable) (map[string]coreRelos, error) {
var recordSize uint32
if err := binary.Read(r, bo, &recordSize); err != nil {
return nil, fmt.Errorf("read record size: %v", err)
}
if recordSize != uint32(extInfoReloSize) {
return nil, fmt.Errorf("expected record size %d, got %d", extInfoReloSize, recordSize)
}
result := make(map[string]coreRelos)
for {
secName, infoHeader, err := parseExtInfoHeader(r, bo, strings)
if errors.Is(err, io.EOF) {
return result, nil
}
var relos coreRelos
for i := uint32(0); i < infoHeader.NumInfo; i++ {
var relo bpfCoreRelo
if err := binary.Read(r, bo, &relo); err != nil {
return nil, fmt.Errorf("section %v: read record: %v", secName, err)
}
if relo.InsnOff%asm.InstructionSize != 0 {
return nil, fmt.Errorf("section %v: offset %v is not aligned with instruction size", secName, relo.InsnOff)
}
accessorStr, err := strings.Lookup(relo.AccessStrOff)
if err != nil {
return nil, err
}
accessor, err := parseCoreAccessor(accessorStr)
if err != nil {
return nil, fmt.Errorf("accessor %q: %s", accessorStr, err)
}
relos = append(relos, coreRelo{
relo.InsnOff,
relo.TypeID,
accessor,
relo.Kind,
})
}
result[secName] = relos
}
}
func parseExtInfoHeader(r io.Reader, bo binary.ByteOrder, strings stringTable) (string, *btfExtInfoSec, error) {
// parseExtInfoSec parses a btf_ext_info_sec header within .BTF.ext,
// appearing within func_info and line_info sub-sections.
// These headers appear once for each program section in the ELF and are
// followed by one or more func/line_info records for the section.
func parseExtInfoSec(r io.Reader, bo binary.ByteOrder, strings stringTable) (string, *btfExtInfoSec, error) {
var infoHeader btfExtInfoSec
if err := binary.Read(r, bo, &infoHeader); err != nil {
return "", nil, fmt.Errorf("read ext info header: %w", err)
@@ -303,6 +164,9 @@ func parseExtInfoHeader(r io.Reader, bo binary.ByteOrder, strings stringTable) (
if err != nil {
return "", nil, fmt.Errorf("get section name: %w", err)
}
if secName == "" {
return "", nil, fmt.Errorf("extinfo header refers to empty section name")
}
if infoHeader.NumInfo == 0 {
return "", nil, fmt.Errorf("section %s has zero records", secName)
@@ -310,3 +174,324 @@ func parseExtInfoHeader(r io.Reader, bo binary.ByteOrder, strings stringTable) (
return secName, &infoHeader, nil
}
// parseExtInfoRecordSize parses the uint32 at the beginning of a func_infos
// or line_infos segment that describes the length of all extInfoRecords in
// that segment.
func parseExtInfoRecordSize(r io.Reader, bo binary.ByteOrder) (uint32, error) {
const maxRecordSize = 256
var recordSize uint32
if err := binary.Read(r, bo, &recordSize); err != nil {
return 0, fmt.Errorf("can't read record size: %v", err)
}
if recordSize < 4 {
// Need at least InsnOff worth of bytes per record.
return 0, errors.New("record size too short")
}
if recordSize > maxRecordSize {
return 0, fmt.Errorf("record size %v exceeds %v", recordSize, maxRecordSize)
}
return recordSize, nil
}
// FuncInfo represents the location and type ID of a function in a BPF ELF.
type FuncInfo struct {
// Instruction offset of the function within an ELF section.
// Always zero after parsing a funcinfo from an ELF, instruction streams
// are split on function boundaries.
InsnOff uint32
TypeID TypeID
}
// Name looks up the FuncInfo's corresponding function name in the given spec.
func (fi FuncInfo) Name(spec *Spec) (string, error) {
// Look up function name based on type ID.
typ, err := spec.TypeByID(fi.TypeID)
if err != nil {
return "", fmt.Errorf("looking up type by ID: %w", err)
}
if _, ok := typ.(*Func); !ok {
return "", fmt.Errorf("type ID %d is a %T, but expected a Func", fi.TypeID, typ)
}
// C doesn't have anonymous functions, but check just in case.
if name := typ.TypeName(); name != "" {
return name, nil
}
return "", fmt.Errorf("Func with type ID %d doesn't have a name", fi.TypeID)
}
// Marshal writes the binary representation of the FuncInfo to w.
// The function offset is converted from bytes to instructions.
func (fi FuncInfo) Marshal(w io.Writer, offset uint64) error {
fi.InsnOff += uint32(offset)
// The kernel expects offsets in number of raw bpf instructions,
// while the ELF tracks it in bytes.
fi.InsnOff /= asm.InstructionSize
return binary.Write(w, internal.NativeEndian, fi)
}
type FuncInfos []FuncInfo
// funcForOffset returns the function that the instruction at the given
// ELF section offset belongs to.
//
// For example, consider an ELF section that contains 3 functions (a, b, c)
// at offsets 0, 10 and 15 respectively. Offset 5 will return function a,
// offset 12 will return b, offset >= 15 will return c, etc.
func (infos FuncInfos) funcForOffset(offset uint32) *FuncInfo {
for n, fi := range infos {
// Iterator went past the offset the caller is looking for,
// no point in continuing the search.
if offset < fi.InsnOff {
return nil
}
// If there is no next item in the list, or if the given offset
// is smaller than the next function, the offset belongs to
// the current function.
if n+1 >= len(infos) || offset < infos[n+1].InsnOff {
return &fi
}
}
return nil
}
// parseLineInfos parses a func_info sub-section within .BTF.ext ito a map of
// func infos indexed by section name.
func parseFuncInfos(r io.Reader, bo binary.ByteOrder, strings stringTable) (map[string]FuncInfos, error) {
recordSize, err := parseExtInfoRecordSize(r, bo)
if err != nil {
return nil, err
}
result := make(map[string]FuncInfos)
for {
secName, infoHeader, err := parseExtInfoSec(r, bo, strings)
if errors.Is(err, io.EOF) {
return result, nil
}
if err != nil {
return nil, err
}
records, err := parseFuncInfoRecords(r, bo, recordSize, infoHeader.NumInfo)
if err != nil {
return nil, fmt.Errorf("section %v: %w", secName, err)
}
result[secName] = records
}
}
// parseFuncInfoRecords parses a stream of func_infos into a funcInfos.
// These records appear after a btf_ext_info_sec header in the func_info
// sub-section of .BTF.ext.
func parseFuncInfoRecords(r io.Reader, bo binary.ByteOrder, recordSize uint32, recordNum uint32) (FuncInfos, error) {
var out FuncInfos
var fi FuncInfo
if exp, got := uint32(binary.Size(fi)), recordSize; exp != got {
// BTF blob's record size is longer than we know how to parse.
return nil, fmt.Errorf("expected FuncInfo record size %d, but BTF blob contains %d", exp, got)
}
for i := uint32(0); i < recordNum; i++ {
if err := binary.Read(r, bo, &fi); err != nil {
return nil, fmt.Errorf("can't read function info: %v", err)
}
if fi.InsnOff%asm.InstructionSize != 0 {
return nil, fmt.Errorf("offset %v is not aligned with instruction size", fi.InsnOff)
}
out = append(out, fi)
}
return out, nil
}
// LineInfo represents the location and contents of a single line of source
// code a BPF ELF was compiled from.
type LineInfo struct {
// Instruction offset of the function within an ELF section.
// After parsing a LineInfo from an ELF, this offset is relative to
// the function body instead of an ELF section.
InsnOff uint32
FileNameOff uint32
LineOff uint32
LineCol uint32
}
// Marshal writes the binary representation of the LineInfo to w.
// The instruction offset is converted from bytes to instructions.
func (li LineInfo) Marshal(w io.Writer, offset uint64) error {
li.InsnOff += uint32(offset)
// The kernel expects offsets in number of raw bpf instructions,
// while the ELF tracks it in bytes.
li.InsnOff /= asm.InstructionSize
return binary.Write(w, internal.NativeEndian, li)
}
type LineInfos []LineInfo
// Marshal writes the binary representation of the LineInfos to w.
func (li LineInfos) Marshal(w io.Writer, off uint64) error {
if len(li) == 0 {
return nil
}
for _, info := range li {
if err := info.Marshal(w, off); err != nil {
return err
}
}
return nil
}
// parseLineInfos parses a line_info sub-section within .BTF.ext ito a map of
// line infos indexed by section name.
func parseLineInfos(r io.Reader, bo binary.ByteOrder, strings stringTable) (map[string]LineInfos, error) {
recordSize, err := parseExtInfoRecordSize(r, bo)
if err != nil {
return nil, err
}
result := make(map[string]LineInfos)
for {
secName, infoHeader, err := parseExtInfoSec(r, bo, strings)
if errors.Is(err, io.EOF) {
return result, nil
}
if err != nil {
return nil, err
}
records, err := parseLineInfoRecords(r, bo, recordSize, infoHeader.NumInfo)
if err != nil {
return nil, fmt.Errorf("section %v: %w", secName, err)
}
result[secName] = records
}
}
// parseLineInfoRecords parses a stream of line_infos into a lineInfos.
// 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) (LineInfos, error) {
var out LineInfos
var li LineInfo
if exp, got := uint32(binary.Size(li)), 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)
}
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)
}
if li.InsnOff%asm.InstructionSize != 0 {
return nil, fmt.Errorf("offset %v is not aligned with instruction size", li.InsnOff)
}
out = append(out, li)
}
return out, nil
}
// bpfCoreRelo matches the kernel's struct bpf_core_relo.
type bpfCoreRelo struct {
InsnOff uint32
TypeID TypeID
AccessStrOff uint32
Kind COREKind
}
type CoreRelo struct {
insnOff uint32
typeID TypeID
accessor coreAccessor
kind COREKind
}
type CoreRelos []CoreRelo
var extInfoReloSize = binary.Size(bpfCoreRelo{})
// parseCoreRelos parses a core_relos sub-section within .BTF.ext ito a map of
// CO-RE relocations indexed by section name.
func parseCoreRelos(r io.Reader, bo binary.ByteOrder, strings stringTable) (map[string]CoreRelos, error) {
recordSize, err := parseExtInfoRecordSize(r, bo)
if err != nil {
return nil, err
}
if recordSize != uint32(extInfoReloSize) {
return nil, fmt.Errorf("expected record size %d, got %d", extInfoReloSize, recordSize)
}
result := make(map[string]CoreRelos)
for {
secName, infoHeader, err := parseExtInfoSec(r, bo, strings)
if errors.Is(err, io.EOF) {
return result, nil
}
if err != nil {
return nil, err
}
records, err := parseCoreReloRecords(r, bo, recordSize, infoHeader.NumInfo, strings)
if err != nil {
return nil, fmt.Errorf("section %v: %w", secName, err)
}
result[secName] = records
}
}
// 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, strings stringTable) (CoreRelos, error) {
var out CoreRelos
var relo bpfCoreRelo
for i := uint32(0); i < recordNum; i++ {
if err := binary.Read(r, bo, &relo); err != nil {
return nil, fmt.Errorf("can't read CO-RE relocation: %v", err)
}
if relo.InsnOff%asm.InstructionSize != 0 {
return nil, fmt.Errorf("offset %v is not aligned with instruction size", relo.InsnOff)
}
accessorStr, err := strings.Lookup(relo.AccessStrOff)
if err != nil {
return nil, err
}
accessor, err := parseCoreAccessor(accessorStr)
if err != nil {
return nil, fmt.Errorf("accessor %q: %s", accessorStr, err)
}
out = append(out, CoreRelo{
relo.InsnOff,
relo.TypeID,
accessor,
relo.Kind,
})
}
return out, nil
}
+304
View File
@@ -0,0 +1,304 @@
package btf
import (
"errors"
"fmt"
"strings"
)
var errNestedTooDeep = errors.New("nested too deep")
// GoFormatter converts a Type to Go syntax.
//
// A zero GoFormatter is valid to use.
type GoFormatter struct {
w strings.Builder
// Types present in this map are referred to using the given name if they
// are encountered when outputting another type.
Names map[Type]string
// Identifier is called for each field of struct-like types. By default the
// field name is used as is.
Identifier func(string) string
// EnumIdentifier is called for each element of an enum. By default the
// name of the enum type is concatenated with Identifier(element).
EnumIdentifier func(name, element string) string
}
// TypeDeclaration generates a Go type declaration for a BTF type.
func (gf *GoFormatter) TypeDeclaration(name string, typ Type) (string, error) {
gf.w.Reset()
if err := gf.writeTypeDecl(name, typ); err != nil {
return "", err
}
return gf.w.String(), nil
}
func (gf *GoFormatter) identifier(s string) string {
if gf.Identifier != nil {
return gf.Identifier(s)
}
return s
}
func (gf *GoFormatter) enumIdentifier(name, element string) string {
if gf.EnumIdentifier != nil {
return gf.EnumIdentifier(name, element)
}
return name + gf.identifier(element)
}
// writeTypeDecl outputs a declaration of the given type.
//
// It encodes https://golang.org/ref/spec#Type_declarations:
//
// type foo struct { bar uint32; }
// type bar int32
func (gf *GoFormatter) writeTypeDecl(name string, typ Type) error {
if name == "" {
return fmt.Errorf("need a name for type %s", typ)
}
typ, err := skipQualifiers(typ)
if err != nil {
return err
}
switch v := typ.(type) {
case *Enum:
fmt.Fprintf(&gf.w, "type %s int32", name)
if len(v.Values) == 0 {
return nil
}
gf.w.WriteString("; const ( ")
for _, ev := range v.Values {
id := gf.enumIdentifier(name, ev.Name)
fmt.Fprintf(&gf.w, "%s %s = %d; ", id, name, ev.Value)
}
gf.w.WriteString(")")
return nil
}
fmt.Fprintf(&gf.w, "type %s ", name)
return gf.writeTypeLit(typ, 0)
}
// writeType outputs the name of a named type or a literal describing the type.
//
// It encodes https://golang.org/ref/spec#Types.
//
// foo (if foo is a named type)
// uint32
func (gf *GoFormatter) writeType(typ Type, depth int) error {
typ, err := skipQualifiers(typ)
if err != nil {
return err
}
name := gf.Names[typ]
if name != "" {
gf.w.WriteString(name)
return nil
}
return gf.writeTypeLit(typ, depth)
}
// writeTypeLit outputs a literal describing the type.
//
// The function ignores named types.
//
// It encodes https://golang.org/ref/spec#TypeLit.
//
// struct { bar uint32; }
// uint32
func (gf *GoFormatter) writeTypeLit(typ Type, depth int) error {
depth++
if depth > maxTypeDepth {
return errNestedTooDeep
}
typ, err := skipQualifiers(typ)
if err != nil {
return err
}
switch v := typ.(type) {
case *Int:
gf.writeIntLit(v)
case *Enum:
gf.w.WriteString("int32")
case *Typedef:
err = gf.writeType(v.Type, depth)
case *Array:
fmt.Fprintf(&gf.w, "[%d]", v.Nelems)
err = gf.writeType(v.Type, depth)
case *Struct:
err = gf.writeStructLit(v.Size, v.Members, depth)
case *Union:
// Always choose the first member to represent the union in Go.
err = gf.writeStructLit(v.Size, v.Members[:1], depth)
case *Datasec:
err = gf.writeDatasecLit(v, depth)
default:
return fmt.Errorf("type %s: %w", typ, ErrNotSupported)
}
if err != nil {
return fmt.Errorf("%s: %w", typ, err)
}
return nil
}
func (gf *GoFormatter) writeIntLit(i *Int) {
// NB: Encoding.IsChar is ignored.
if i.Encoding.IsBool() && i.Size == 1 {
gf.w.WriteString("bool")
return
}
bits := i.Size * 8
if i.Encoding.IsSigned() {
fmt.Fprintf(&gf.w, "int%d", bits)
} else {
fmt.Fprintf(&gf.w, "uint%d", bits)
}
}
func (gf *GoFormatter) writeStructLit(size uint32, members []Member, depth int) error {
gf.w.WriteString("struct { ")
prevOffset := uint32(0)
skippedBitfield := false
for i, m := range members {
if m.BitfieldSize > 0 {
skippedBitfield = true
continue
}
offset := m.OffsetBits / 8
if n := offset - prevOffset; skippedBitfield && n > 0 {
fmt.Fprintf(&gf.w, "_ [%d]byte /* unsupported bitfield */; ", n)
} else {
gf.writePadding(n)
}
size, err := Sizeof(m.Type)
if err != nil {
return fmt.Errorf("field %d: %w", i, err)
}
prevOffset = offset + uint32(size)
if err := gf.writeStructField(m, depth); err != nil {
return fmt.Errorf("field %d: %w", i, err)
}
}
gf.writePadding(size - prevOffset)
gf.w.WriteString("}")
return nil
}
func (gf *GoFormatter) writeStructField(m Member, depth int) error {
if m.BitfieldSize > 0 {
return fmt.Errorf("bitfields are not supported")
}
if m.OffsetBits%8 != 0 {
return fmt.Errorf("unsupported offset %d", m.OffsetBits)
}
if m.Name == "" {
// Special case a nested anonymous union like
// struct foo { union { int bar; int baz }; }
// by replacing the whole union with its first member.
union, ok := m.Type.(*Union)
if !ok {
return fmt.Errorf("anonymous fields are not supported")
}
if len(union.Members) == 0 {
return errors.New("empty anonymous union")
}
depth++
if depth > maxTypeDepth {
return errNestedTooDeep
}
m := union.Members[0]
size, err := Sizeof(m.Type)
if err != nil {
return err
}
if err := gf.writeStructField(m, depth); err != nil {
return err
}
gf.writePadding(union.Size - uint32(size))
return nil
}
fmt.Fprintf(&gf.w, "%s ", gf.identifier(m.Name))
if err := gf.writeType(m.Type, depth); err != nil {
return err
}
gf.w.WriteString("; ")
return nil
}
func (gf *GoFormatter) writeDatasecLit(ds *Datasec, depth int) error {
gf.w.WriteString("struct { ")
prevOffset := uint32(0)
for i, vsi := range ds.Vars {
v := vsi.Type.(*Var)
if v.Linkage != GlobalVar {
// Ignore static, extern, etc. for now.
continue
}
if v.Name == "" {
return fmt.Errorf("variable %d: empty name", i)
}
gf.writePadding(vsi.Offset - prevOffset)
prevOffset = vsi.Offset + vsi.Size
fmt.Fprintf(&gf.w, "%s ", gf.identifier(v.Name))
if err := gf.writeType(v.Type, depth); err != nil {
return fmt.Errorf("variable %d: %w", i, err)
}
gf.w.WriteString("; ")
}
gf.writePadding(ds.Size - prevOffset)
gf.w.WriteString("}")
return nil
}
func (gf *GoFormatter) writePadding(bytes uint32) {
if bytes > 0 {
fmt.Fprintf(&gf.w, "_ [%d]byte; ", bytes)
}
}
-50
View File
@@ -1,50 +0,0 @@
//go:build gofuzz
// +build gofuzz
// Use with https://github.com/dvyukov/go-fuzz
package btf
import (
"bytes"
"encoding/binary"
"github.com/cilium/ebpf/internal"
)
func FuzzSpec(data []byte) int {
if len(data) < binary.Size(btfHeader{}) {
return -1
}
spec, err := loadNakedSpec(bytes.NewReader(data), internal.NativeEndian, nil, nil)
if err != nil {
if spec != nil {
panic("spec is not nil")
}
return 0
}
if spec == nil {
panic("spec is nil")
}
return 1
}
func FuzzExtInfo(data []byte) int {
if len(data) < binary.Size(btfExtHeader{}) {
return -1
}
table := stringTable("\x00foo\x00barfoo\x00")
info, err := parseExtInfo(bytes.NewReader(data), internal.NativeEndian, table)
if err != nil {
if info != nil {
panic("info is not nil")
}
return 0
}
if info == nil {
panic("info is nil")
}
return 1
}
+13 -10
View File
@@ -4,6 +4,8 @@ import (
"bytes"
"github.com/cilium/ebpf/internal"
"github.com/cilium/ebpf/internal/sys"
"github.com/cilium/ebpf/internal/unix"
)
// info describes a BTF object.
@@ -18,19 +20,20 @@ type info struct {
KernelBTF bool
}
func newInfoFromFd(fd *internal.FD) (*info, error) {
func newInfoFromFd(fd *sys.FD) (*info, error) {
// We invoke the syscall once with a empty BTF and name buffers to get size
// information to allocate buffers. Then we invoke it a second time with
// buffers to receive the data.
bpfInfo, err := bpfGetBTFInfoByFD(fd, nil, nil)
if err != nil {
var btfInfo sys.BtfInfo
if err := sys.ObjInfo(fd, &btfInfo); err != nil {
return nil, err
}
btfBuffer := make([]byte, bpfInfo.btfSize)
nameBuffer := make([]byte, bpfInfo.nameLen)
bpfInfo, err = bpfGetBTFInfoByFD(fd, btfBuffer, nameBuffer)
if err != nil {
btfBuffer := make([]byte, btfInfo.BtfSize)
nameBuffer := make([]byte, btfInfo.NameLen)
btfInfo.Btf, btfInfo.BtfSize = sys.NewSlicePointerLen(btfBuffer)
btfInfo.Name, btfInfo.NameLen = sys.NewSlicePointerLen(nameBuffer)
if err := sys.ObjInfo(fd, &btfInfo); err != nil {
return nil, err
}
@@ -41,8 +44,8 @@ func newInfoFromFd(fd *internal.FD) (*info, error) {
return &info{
BTF: spec,
ID: ID(bpfInfo.id),
Name: internal.CString(nameBuffer),
KernelBTF: bpfInfo.kernelBTF != 0,
ID: ID(btfInfo.Id),
Name: unix.ByteSliceToString(nameBuffer),
KernelBTF: btfInfo.KernelBtf != 0,
}, nil
}
-31
View File
@@ -1,31 +0,0 @@
package btf
import (
"fmt"
"unsafe"
"github.com/cilium/ebpf/internal"
)
type bpfBTFInfo struct {
btf internal.Pointer
btfSize uint32
id uint32
name internal.Pointer
nameLen uint32
kernelBTF uint32
}
func bpfGetBTFInfoByFD(fd *internal.FD, btf, name []byte) (*bpfBTFInfo, error) {
info := bpfBTFInfo{
btf: internal.NewSlicePointer(btf),
btfSize: uint32(len(btf)),
name: internal.NewSlicePointer(name),
nameLen: uint32(len(name)),
}
if err := internal.BPFObjGetInfoByFD(fd, unsafe.Pointer(&info), unsafe.Sizeof(info)); err != nil {
return nil, fmt.Errorf("can't get program info: %w", err)
}
return &info, nil
}
+71 -36
View File
@@ -18,9 +18,12 @@ func (tid TypeID) ID() TypeID {
// Type represents a type described by BTF.
type Type interface {
// The type ID of the Type within this BTF spec.
ID() TypeID
String() string
// Name of the type, empty for anonymous types and types that cannot
// carry a name, like Void and Pointer.
TypeName() string
// Make a copy of the type, without copying Type members.
copy() Type
@@ -28,37 +31,32 @@ type Type interface {
// Enumerate all nested Types. Repeated calls must visit nested
// types in the same order.
walk(*typeDeque)
}
// NamedType is a type with a name.
type NamedType interface {
Type
// Name of the type, empty for anonymous types.
TypeName() string
String() string
}
var (
_ NamedType = (*Int)(nil)
_ NamedType = (*Struct)(nil)
_ NamedType = (*Union)(nil)
_ NamedType = (*Enum)(nil)
_ NamedType = (*Fwd)(nil)
_ NamedType = (*Func)(nil)
_ NamedType = (*Typedef)(nil)
_ NamedType = (*Var)(nil)
_ NamedType = (*Datasec)(nil)
_ NamedType = (*Float)(nil)
_ Type = (*Int)(nil)
_ Type = (*Struct)(nil)
_ Type = (*Union)(nil)
_ Type = (*Enum)(nil)
_ Type = (*Fwd)(nil)
_ Type = (*Func)(nil)
_ Type = (*Typedef)(nil)
_ Type = (*Var)(nil)
_ Type = (*Datasec)(nil)
_ Type = (*Float)(nil)
)
// Void is the unit type of BTF.
type Void struct{}
func (v *Void) ID() TypeID { return 0 }
func (v *Void) String() string { return "void#0" }
func (v *Void) size() uint32 { return 0 }
func (v *Void) copy() Type { return (*Void)(nil) }
func (v *Void) walk(*typeDeque) {}
func (v *Void) ID() TypeID { return 0 }
func (v *Void) String() string { return "void#0" }
func (v *Void) TypeName() string { return "" }
func (v *Void) size() uint32 { return 0 }
func (v *Void) copy() Type { return (*Void)(nil) }
func (v *Void) walk(*typeDeque) {}
type IntEncoding byte
@@ -68,9 +66,22 @@ const (
Bool
)
func (ie IntEncoding) IsSigned() bool {
return ie&Signed != 0
}
func (ie IntEncoding) IsChar() bool {
return ie&Char != 0
}
func (ie IntEncoding) IsBool() bool {
return ie&Bool != 0
}
// Int is an integer of a given length.
type Int struct {
TypeID
Name string
// The size of the integer in bytes.
@@ -86,12 +97,12 @@ func (i *Int) String() string {
var s strings.Builder
switch {
case i.Encoding&Char != 0:
case i.Encoding.IsChar():
s.WriteString("char")
case i.Encoding&Bool != 0:
case i.Encoding.IsBool():
s.WriteString("bool")
default:
if i.Encoding&Signed == 0 {
if !i.Encoding.IsSigned() {
s.WriteRune('u')
}
s.WriteString("int")
@@ -129,6 +140,7 @@ func (p *Pointer) String() string {
return fmt.Sprintf("pointer#%d[target=#%d]", p.TypeID, p.Target.ID())
}
func (p *Pointer) TypeName() string { return "" }
func (p *Pointer) size() uint32 { return 8 }
func (p *Pointer) walk(tdq *typeDeque) { tdq.push(&p.Target) }
func (p *Pointer) copy() Type {
@@ -147,6 +159,8 @@ func (arr *Array) String() string {
return fmt.Sprintf("array#%d[type=#%d n=%d]", arr.TypeID, arr.Type.ID(), arr.Nelems)
}
func (arr *Array) TypeName() string { return "" }
func (arr *Array) walk(tdq *typeDeque) { tdq.push(&arr.Type) }
func (arr *Array) copy() Type {
cpy := *arr
@@ -343,6 +357,8 @@ func (v *Volatile) String() string {
return fmt.Sprintf("volatile#%d[#%d]", v.TypeID, v.Type.ID())
}
func (v *Volatile) TypeName() string { return "" }
func (v *Volatile) qualify() Type { return v.Type }
func (v *Volatile) walk(tdq *typeDeque) { tdq.push(&v.Type) }
func (v *Volatile) copy() Type {
@@ -360,6 +376,8 @@ func (c *Const) String() string {
return fmt.Sprintf("const#%d[#%d]", c.TypeID, c.Type.ID())
}
func (c *Const) TypeName() string { return "" }
func (c *Const) qualify() Type { return c.Type }
func (c *Const) walk(tdq *typeDeque) { tdq.push(&c.Type) }
func (c *Const) copy() Type {
@@ -377,6 +395,8 @@ func (r *Restrict) String() string {
return fmt.Sprintf("restrict#%d[#%d]", r.TypeID, r.Type.ID())
}
func (r *Restrict) TypeName() string { return "" }
func (r *Restrict) qualify() Type { return r.Type }
func (r *Restrict) walk(tdq *typeDeque) { tdq.push(&r.Type) }
func (r *Restrict) copy() Type {
@@ -421,6 +441,8 @@ func (fp *FuncProto) String() string {
return s.String()
}
func (fp *FuncProto) TypeName() string { return "" }
func (fp *FuncProto) walk(tdq *typeDeque) {
tdq.push(&fp.Return)
for i := range fp.Params {
@@ -594,6 +616,12 @@ func Sizeof(typ Type) (int, error) {
return 0, fmt.Errorf("type %s: exceeded type depth", typ)
}
// Copy a Type recursively.
func Copy(typ Type) Type {
typ, _ = copyType(typ, nil)
return typ
}
// copy a Type recursively.
//
// typ may form a cycle.
@@ -735,7 +763,7 @@ func (dq *typeDeque) all() []*Type {
// Returns a map of named types (so, where NameOff is non-zero) and a slice of types
// indexed by TypeID. Since BTF ignores compilation units, multiple types may share
// the same name. A Type may form a cyclic graph by pointing at itself.
func inflateRawTypes(rawTypes []rawType, rawStrings stringTable) (types []Type, namedTypes map[string][]NamedType, err error) {
func inflateRawTypes(rawTypes []rawType, rawStrings stringTable) (types []Type, namedTypes map[essentialName][]Type, err error) {
type fixupDef struct {
id TypeID
expectedKind btfKind
@@ -774,7 +802,7 @@ func inflateRawTypes(rawTypes []rawType, rawStrings stringTable) (types []Type,
types = make([]Type, 0, len(rawTypes))
types = append(types, (*Void)(nil))
namedTypes = make(map[string][]NamedType)
namedTypes = make(map[essentialName][]Type)
for i, raw := range rawTypes {
var (
@@ -918,10 +946,8 @@ func inflateRawTypes(rawTypes []rawType, rawStrings stringTable) (types []Type,
types = append(types, typ)
if named, ok := typ.(NamedType); ok {
if name := essentialName(named.TypeName()); name != "" {
namedTypes[name] = append(namedTypes[name], named)
}
if name := newEssentialName(typ.TypeName()); name != "" {
namedTypes[name] = append(namedTypes[name], typ)
}
}
@@ -947,11 +973,20 @@ func inflateRawTypes(rawTypes []rawType, rawStrings stringTable) (types []Type,
return types, namedTypes, nil
}
// essentialName returns name without a ___ suffix.
func essentialName(name string) string {
// essentialName represents the name of a BTF type stripped of any flavor
// suffixes after a ___ delimiter.
type essentialName string
// newEssentialName returns name without a ___ suffix.
//
// CO-RE has the concept of 'struct flavors', which are used to deal with
// changes in kernel data structures. Anything after three underscores
// in a type name is ignored for the purpose of finding a candidate type
// in the kernel's BTF.
func newEssentialName(name string) essentialName {
lastIdx := strings.LastIndex(name, "___")
if lastIdx > 0 {
return name[:lastIdx]
return essentialName(name[:lastIdx])
}
return name
return essentialName(name)
}
+1 -11
View File
@@ -1,7 +1,6 @@
package internal
import (
"bytes"
"errors"
"fmt"
"strings"
@@ -15,7 +14,7 @@ import (
// logErr should be the error returned by the syscall that generated
// the log. It is used to check for truncation of the output.
func ErrorWithLog(err error, log []byte, logErr error) error {
logStr := strings.Trim(CString(log), "\t\r\n ")
logStr := strings.Trim(unix.ByteSliceToString(log), "\t\r\n ")
if errors.Is(logErr, unix.ENOSPC) {
logStr += " (truncated...)"
}
@@ -40,12 +39,3 @@ func (le *VerifierError) Error() string {
return fmt.Sprintf("%s: %s", le.cause, le.log)
}
// CString turns a NUL / zero terminated byte buffer into a string.
func CString(in []byte) string {
inLen := bytes.IndexByte(in, 0)
if inLen == -1 {
return ""
}
return string(in[:inLen])
}
-69
View File
@@ -1,69 +0,0 @@
package internal
import (
"errors"
"fmt"
"os"
"runtime"
"strconv"
"github.com/cilium/ebpf/internal/unix"
)
var ErrClosedFd = errors.New("use of closed file descriptor")
type FD struct {
raw int64
}
func NewFD(value uint32) *FD {
fd := &FD{int64(value)}
runtime.SetFinalizer(fd, (*FD).Close)
return fd
}
func (fd *FD) String() string {
return strconv.FormatInt(fd.raw, 10)
}
func (fd *FD) Value() (uint32, error) {
if fd.raw < 0 {
return 0, ErrClosedFd
}
return uint32(fd.raw), nil
}
func (fd *FD) Close() error {
if fd.raw < 0 {
return nil
}
value := int(fd.raw)
fd.raw = -1
fd.Forget()
return unix.Close(value)
}
func (fd *FD) Forget() {
runtime.SetFinalizer(fd, nil)
}
func (fd *FD) Dup() (*FD, error) {
if fd.raw < 0 {
return nil, ErrClosedFd
}
dup, err := unix.FcntlInt(uintptr(fd.raw), unix.F_DUPFD_CLOEXEC, 0)
if err != nil {
return nil, fmt.Errorf("can't dup fd: %v", err)
}
return NewFD(uint32(dup)), nil
}
func (fd *FD) File(name string) *os.File {
fd.Forget()
return os.NewFile(uintptr(fd.raw), name)
}
+47 -1
View File
@@ -1,6 +1,35 @@
package internal
import "errors"
import (
"bufio"
"compress/gzip"
"errors"
"io"
"os"
)
// NewBufferedSectionReader wraps an io.ReaderAt in an appropriately-sized
// buffered reader. It is a convenience function for reading subsections of
// ELF sections while minimizing the amount of read() syscalls made.
//
// Syscall overhead is non-negligible in continuous integration context
// where ELFs might be accessed over virtual filesystems with poor random
// access performance. Buffering reads makes sense because (sub)sections
// end up being read completely anyway.
//
// Use instead of the r.Seek() + io.LimitReader() pattern.
func NewBufferedSectionReader(ra io.ReaderAt, off, n int64) io.Reader {
// Clamp the size of the buffer to one page to avoid slurping large parts
// of a file into memory. bufio.NewReader uses a hardcoded default buffer
// of 4096. Allow arches with larger pages to allocate more, but don't
// allocate a fixed 4k buffer if we only need to read a small segment.
buf := n
if ps := int64(os.Getpagesize()); n > ps {
buf = ps
}
return bufio.NewReaderSize(io.NewSectionReader(ra, off, n), int(buf))
}
// DiscardZeroes makes sure that all written bytes are zero
// before discarding them.
@@ -14,3 +43,20 @@ func (DiscardZeroes) Write(p []byte) (int, error) {
}
return len(p), nil
}
// ReadAllCompressed decompresses a gzipped file into memory.
func ReadAllCompressed(file string) ([]byte, error) {
fh, err := os.Open(file)
if err != nil {
return nil, err
}
defer fh.Close()
gz, err := gzip.NewReader(fh)
if err != nil {
return nil, err
}
defer gz.Close()
return io.ReadAll(gz)
}
+84
View File
@@ -0,0 +1,84 @@
package internal
import (
"bytes"
"errors"
"go/format"
"go/scanner"
"io"
"strings"
"unicode"
)
// Identifier turns a C style type or field name into an exportable Go equivalent.
func Identifier(str string) string {
prev := rune(-1)
return strings.Map(func(r rune) rune {
// See https://golang.org/ref/spec#Identifiers
switch {
case unicode.IsLetter(r):
if prev == -1 {
r = unicode.ToUpper(r)
}
case r == '_':
switch {
// The previous rune was deleted, or we are at the
// beginning of the string.
case prev == -1:
fallthrough
// The previous rune is a lower case letter or a digit.
case unicode.IsDigit(prev) || (unicode.IsLetter(prev) && unicode.IsLower(prev)):
// delete the current rune, and force the
// next character to be uppercased.
r = -1
}
case unicode.IsDigit(r):
default:
// Delete the current rune. prev is unchanged.
return -1
}
prev = r
return r
}, str)
}
// WriteFormatted outputs a formatted src into out.
//
// If formatting fails it returns an informative error message.
func WriteFormatted(src []byte, out io.Writer) error {
formatted, err := format.Source(src)
if err == nil {
_, err = out.Write(formatted)
return err
}
var el scanner.ErrorList
if !errors.As(err, &el) {
return err
}
var nel scanner.ErrorList
for _, err := range el {
if !err.Pos.IsValid() {
nel = append(nel, err)
continue
}
buf := src[err.Pos.Offset:]
nl := bytes.IndexRune(buf, '\n')
if nl == -1 {
nel = append(nel, err)
continue
}
err.Msg += ": " + string(buf[:nl])
nel = append(nel, err)
}
return nel
}
+28 -6
View File
@@ -4,24 +4,43 @@ import (
"errors"
"fmt"
"os"
"path/filepath"
"runtime"
"github.com/cilium/ebpf/internal/sys"
"github.com/cilium/ebpf/internal/unix"
)
func Pin(currentPath, newPath string, fd *FD) error {
func Pin(currentPath, newPath string, fd *sys.FD) error {
const bpfFSType = 0xcafe4a11
if newPath == "" {
return errors.New("given pinning path cannot be empty")
}
if currentPath == newPath {
return nil
}
if currentPath == "" {
return BPFObjPin(newPath, fd)
var statfs unix.Statfs_t
if err := unix.Statfs(filepath.Dir(newPath), &statfs); err != nil {
return err
} else if uint64(statfs.Type) != bpfFSType {
return fmt.Errorf("%s is not on a bpf filesystem", newPath)
}
var err error
defer runtime.KeepAlive(fd)
if currentPath == "" {
return sys.ObjPin(&sys.ObjPinAttr{
Pathname: sys.NewStringPointer(newPath),
BpfFd: fd.Uint(),
})
}
// Renameat2 is used instead of os.Rename to disallow the new path replacing
// an existing path.
if err = unix.Renameat2(unix.AT_FDCWD, currentPath, unix.AT_FDCWD, newPath, unix.RENAME_NOREPLACE); err == nil {
err := unix.Renameat2(unix.AT_FDCWD, currentPath, unix.AT_FDCWD, newPath, unix.RENAME_NOREPLACE)
if err == nil {
// Object is now moved to the new pinning path.
return nil
}
@@ -29,7 +48,10 @@ func Pin(currentPath, newPath string, fd *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 BPFObjPin(newPath, fd)
return sys.ObjPin(&sys.ObjPinAttr{
Pathname: sys.NewStringPointer(newPath),
BpfFd: fd.Uint(),
})
}
func Unpin(pinnedPath string) error {
+4
View File
@@ -0,0 +1,4 @@
// Package sys contains bindings for the BPF syscall.
package sys
//go:generate go run github.com/cilium/ebpf/internal/cmd/gentypes ../btf/testdata/vmlinux-btf.gz
+96
View File
@@ -0,0 +1,96 @@
package sys
import (
"fmt"
"math"
"os"
"runtime"
"strconv"
"github.com/cilium/ebpf/internal/unix"
)
var ErrClosedFd = unix.EBADF
type FD struct {
raw int
}
func newFD(value int) *FD {
fd := &FD{value}
runtime.SetFinalizer(fd, (*FD).Close)
return fd
}
// NewFD wraps a raw fd with a finalizer.
//
// You must not use the raw fd after calling this function, since the underlying
// file descriptor number may change. This is because the BPF UAPI assumes that
// zero is not a valid fd value.
func NewFD(value int) (*FD, error) {
if value < 0 {
return nil, fmt.Errorf("invalid fd %d", value)
}
fd := newFD(value)
if value != 0 {
return fd, nil
}
dup, err := fd.Dup()
_ = fd.Close()
return dup, err
}
func (fd *FD) String() string {
return strconv.FormatInt(int64(fd.raw), 10)
}
func (fd *FD) Int() int {
return fd.raw
}
func (fd *FD) Uint() uint32 {
if fd.raw < 0 || int64(fd.raw) > math.MaxUint32 {
// Best effort: this is the number most likely to be an invalid file
// descriptor. It is equal to -1 (on two's complement arches).
return math.MaxUint32
}
return uint32(fd.raw)
}
func (fd *FD) Close() error {
if fd.raw < 0 {
return nil
}
value := int(fd.raw)
fd.raw = -1
fd.Forget()
return unix.Close(value)
}
func (fd *FD) Forget() {
runtime.SetFinalizer(fd, nil)
}
func (fd *FD) Dup() (*FD, error) {
if fd.raw < 0 {
return nil, ErrClosedFd
}
// Always require the fd to be larger than zero: the BPF API treats the value
// as "no argument provided".
dup, err := unix.FcntlInt(uintptr(fd.raw), unix.F_DUPFD_CLOEXEC, 1)
if err != nil {
return nil, fmt.Errorf("can't dup fd: %v", err)
}
return newFD(dup), nil
}
func (fd *FD) File(name string) *os.File {
fd.Forget()
return os.NewFile(uintptr(fd.raw), name)
}
@@ -1,4 +1,4 @@
package internal
package sys
import (
"unsafe"
@@ -20,6 +20,13 @@ func NewSlicePointer(buf []byte) Pointer {
return Pointer{ptr: unsafe.Pointer(&buf[0])}
}
// NewSlicePointer creates a 64-bit pointer from a byte slice.
//
// Useful to assign both the pointer and the length in one go.
func NewSlicePointerLen(buf []byte) (Pointer, uint32) {
return NewSlicePointer(buf), uint32(len(buf))
}
// NewStringPointer creates a 64-bit pointer from a string.
func NewStringPointer(str string) Pointer {
p, err := unix.BytePtrFromString(str)
@@ -1,7 +1,7 @@
//go:build armbe || mips || mips64p32
// +build armbe mips mips64p32
package internal
package sys
import (
"unsafe"
@@ -1,7 +1,7 @@
//go:build 386 || amd64p32 || arm || mipsle || mips64p32le
// +build 386 amd64p32 arm mipsle mips64p32le
package internal
package sys
import (
"unsafe"
@@ -1,7 +1,7 @@
//go:build !386 && !amd64p32 && !arm && !mipsle && !mips64p32le && !armbe && !mips && !mips64p32
// +build !386,!amd64p32,!arm,!mipsle,!mips64p32le,!armbe,!mips,!mips64p32
package internal
package sys
import (
"unsafe"
+123
View File
@@ -0,0 +1,123 @@
package sys
import (
"runtime"
"syscall"
"unsafe"
"github.com/cilium/ebpf/internal/unix"
)
// BPF wraps SYS_BPF.
//
// Any pointers contained in attr must use the Pointer type from this package.
func BPF(cmd Cmd, attr unsafe.Pointer, size uintptr) (uintptr, error) {
for {
r1, _, errNo := unix.Syscall(unix.SYS_BPF, uintptr(cmd), uintptr(attr), size)
runtime.KeepAlive(attr)
// As of ~4.20 the verifier can be interrupted by a signal,
// and returns EAGAIN in that case.
if errNo == unix.EAGAIN && cmd == BPF_PROG_LOAD {
continue
}
var err error
if errNo != 0 {
err = wrappedErrno{errNo}
}
return r1, err
}
}
// Info is implemented by all structs that can be passed to the ObjInfo syscall.
//
// MapInfo
// ProgInfo
// LinkInfo
// BtfInfo
type Info interface {
info() (unsafe.Pointer, uint32)
}
var _ Info = (*MapInfo)(nil)
func (i *MapInfo) info() (unsafe.Pointer, uint32) {
return unsafe.Pointer(i), uint32(unsafe.Sizeof(*i))
}
var _ Info = (*ProgInfo)(nil)
func (i *ProgInfo) info() (unsafe.Pointer, uint32) {
return unsafe.Pointer(i), uint32(unsafe.Sizeof(*i))
}
var _ Info = (*LinkInfo)(nil)
func (i *LinkInfo) info() (unsafe.Pointer, uint32) {
return unsafe.Pointer(i), uint32(unsafe.Sizeof(*i))
}
var _ Info = (*BtfInfo)(nil)
func (i *BtfInfo) info() (unsafe.Pointer, uint32) {
return unsafe.Pointer(i), uint32(unsafe.Sizeof(*i))
}
// ObjInfo retrieves information about a BPF Fd.
//
// info may be one of MapInfo, ProgInfo, LinkInfo and BtfInfo.
func ObjInfo(fd *FD, info Info) error {
ptr, len := info.info()
err := ObjGetInfoByFd(&ObjGetInfoByFdAttr{
BpfFd: fd.Uint(),
InfoLen: len,
Info: NewPointer(ptr),
})
runtime.KeepAlive(fd)
return err
}
// BPFObjName is a null-terminated string made up of
// 'A-Za-z0-9_' characters.
type ObjName [unix.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)
return result
}
// LinkID uniquely identifies a bpf_link.
type LinkID uint32
// wrappedErrno wraps syscall.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
}
func (we wrappedErrno) Unwrap() error {
return we.Errno
}
type syscallError struct {
error
errno syscall.Errno
}
func Error(err error, errno syscall.Errno) error {
return &syscallError{err, errno}
}
func (se *syscallError) Is(target error) bool {
return target == se.error
}
func (se *syscallError) Unwrap() error {
return se.errno
}
+954
View File
@@ -0,0 +1,954 @@
// Code generated by internal/cmd/gentypes; DO NOT EDIT.
package sys
import (
"unsafe"
)
type AdjRoomMode int32
const (
BPF_ADJ_ROOM_NET AdjRoomMode = 0
BPF_ADJ_ROOM_MAC AdjRoomMode = 1
)
type AttachType int32
const (
BPF_CGROUP_INET_INGRESS AttachType = 0
BPF_CGROUP_INET_EGRESS AttachType = 1
BPF_CGROUP_INET_SOCK_CREATE AttachType = 2
BPF_CGROUP_SOCK_OPS AttachType = 3
BPF_SK_SKB_STREAM_PARSER AttachType = 4
BPF_SK_SKB_STREAM_VERDICT AttachType = 5
BPF_CGROUP_DEVICE AttachType = 6
BPF_SK_MSG_VERDICT AttachType = 7
BPF_CGROUP_INET4_BIND AttachType = 8
BPF_CGROUP_INET6_BIND AttachType = 9
BPF_CGROUP_INET4_CONNECT AttachType = 10
BPF_CGROUP_INET6_CONNECT AttachType = 11
BPF_CGROUP_INET4_POST_BIND AttachType = 12
BPF_CGROUP_INET6_POST_BIND AttachType = 13
BPF_CGROUP_UDP4_SENDMSG AttachType = 14
BPF_CGROUP_UDP6_SENDMSG AttachType = 15
BPF_LIRC_MODE2 AttachType = 16
BPF_FLOW_DISSECTOR AttachType = 17
BPF_CGROUP_SYSCTL AttachType = 18
BPF_CGROUP_UDP4_RECVMSG AttachType = 19
BPF_CGROUP_UDP6_RECVMSG AttachType = 20
BPF_CGROUP_GETSOCKOPT AttachType = 21
BPF_CGROUP_SETSOCKOPT AttachType = 22
BPF_TRACE_RAW_TP AttachType = 23
BPF_TRACE_FENTRY AttachType = 24
BPF_TRACE_FEXIT AttachType = 25
BPF_MODIFY_RETURN AttachType = 26
BPF_LSM_MAC AttachType = 27
BPF_TRACE_ITER AttachType = 28
BPF_CGROUP_INET4_GETPEERNAME AttachType = 29
BPF_CGROUP_INET6_GETPEERNAME AttachType = 30
BPF_CGROUP_INET4_GETSOCKNAME AttachType = 31
BPF_CGROUP_INET6_GETSOCKNAME AttachType = 32
BPF_XDP_DEVMAP AttachType = 33
BPF_CGROUP_INET_SOCK_RELEASE AttachType = 34
BPF_XDP_CPUMAP AttachType = 35
BPF_SK_LOOKUP AttachType = 36
BPF_XDP AttachType = 37
BPF_SK_SKB_VERDICT AttachType = 38
__MAX_BPF_ATTACH_TYPE AttachType = 39
)
type Cmd int32
const (
BPF_MAP_CREATE Cmd = 0
BPF_MAP_LOOKUP_ELEM Cmd = 1
BPF_MAP_UPDATE_ELEM Cmd = 2
BPF_MAP_DELETE_ELEM Cmd = 3
BPF_MAP_GET_NEXT_KEY Cmd = 4
BPF_PROG_LOAD Cmd = 5
BPF_OBJ_PIN Cmd = 6
BPF_OBJ_GET Cmd = 7
BPF_PROG_ATTACH Cmd = 8
BPF_PROG_DETACH Cmd = 9
BPF_PROG_TEST_RUN Cmd = 10
BPF_PROG_GET_NEXT_ID Cmd = 11
BPF_MAP_GET_NEXT_ID Cmd = 12
BPF_PROG_GET_FD_BY_ID Cmd = 13
BPF_MAP_GET_FD_BY_ID Cmd = 14
BPF_OBJ_GET_INFO_BY_FD Cmd = 15
BPF_PROG_QUERY Cmd = 16
BPF_RAW_TRACEPOINT_OPEN Cmd = 17
BPF_BTF_LOAD Cmd = 18
BPF_BTF_GET_FD_BY_ID Cmd = 19
BPF_TASK_FD_QUERY Cmd = 20
BPF_MAP_LOOKUP_AND_DELETE_ELEM Cmd = 21
BPF_MAP_FREEZE Cmd = 22
BPF_BTF_GET_NEXT_ID Cmd = 23
BPF_MAP_LOOKUP_BATCH Cmd = 24
BPF_MAP_LOOKUP_AND_DELETE_BATCH Cmd = 25
BPF_MAP_UPDATE_BATCH Cmd = 26
BPF_MAP_DELETE_BATCH Cmd = 27
BPF_LINK_CREATE Cmd = 28
BPF_LINK_UPDATE Cmd = 29
BPF_LINK_GET_FD_BY_ID Cmd = 30
BPF_LINK_GET_NEXT_ID Cmd = 31
BPF_ENABLE_STATS Cmd = 32
BPF_ITER_CREATE Cmd = 33
BPF_LINK_DETACH Cmd = 34
BPF_PROG_BIND_MAP Cmd = 35
)
type FunctionId int32
const (
BPF_FUNC_unspec FunctionId = 0
BPF_FUNC_map_lookup_elem FunctionId = 1
BPF_FUNC_map_update_elem FunctionId = 2
BPF_FUNC_map_delete_elem FunctionId = 3
BPF_FUNC_probe_read FunctionId = 4
BPF_FUNC_ktime_get_ns FunctionId = 5
BPF_FUNC_trace_printk FunctionId = 6
BPF_FUNC_get_prandom_u32 FunctionId = 7
BPF_FUNC_get_smp_processor_id FunctionId = 8
BPF_FUNC_skb_store_bytes FunctionId = 9
BPF_FUNC_l3_csum_replace FunctionId = 10
BPF_FUNC_l4_csum_replace FunctionId = 11
BPF_FUNC_tail_call FunctionId = 12
BPF_FUNC_clone_redirect FunctionId = 13
BPF_FUNC_get_current_pid_tgid FunctionId = 14
BPF_FUNC_get_current_uid_gid FunctionId = 15
BPF_FUNC_get_current_comm FunctionId = 16
BPF_FUNC_get_cgroup_classid FunctionId = 17
BPF_FUNC_skb_vlan_push FunctionId = 18
BPF_FUNC_skb_vlan_pop FunctionId = 19
BPF_FUNC_skb_get_tunnel_key FunctionId = 20
BPF_FUNC_skb_set_tunnel_key FunctionId = 21
BPF_FUNC_perf_event_read FunctionId = 22
BPF_FUNC_redirect FunctionId = 23
BPF_FUNC_get_route_realm FunctionId = 24
BPF_FUNC_perf_event_output FunctionId = 25
BPF_FUNC_skb_load_bytes FunctionId = 26
BPF_FUNC_get_stackid FunctionId = 27
BPF_FUNC_csum_diff FunctionId = 28
BPF_FUNC_skb_get_tunnel_opt FunctionId = 29
BPF_FUNC_skb_set_tunnel_opt FunctionId = 30
BPF_FUNC_skb_change_proto FunctionId = 31
BPF_FUNC_skb_change_type FunctionId = 32
BPF_FUNC_skb_under_cgroup FunctionId = 33
BPF_FUNC_get_hash_recalc FunctionId = 34
BPF_FUNC_get_current_task FunctionId = 35
BPF_FUNC_probe_write_user FunctionId = 36
BPF_FUNC_current_task_under_cgroup FunctionId = 37
BPF_FUNC_skb_change_tail FunctionId = 38
BPF_FUNC_skb_pull_data FunctionId = 39
BPF_FUNC_csum_update FunctionId = 40
BPF_FUNC_set_hash_invalid FunctionId = 41
BPF_FUNC_get_numa_node_id FunctionId = 42
BPF_FUNC_skb_change_head FunctionId = 43
BPF_FUNC_xdp_adjust_head FunctionId = 44
BPF_FUNC_probe_read_str FunctionId = 45
BPF_FUNC_get_socket_cookie FunctionId = 46
BPF_FUNC_get_socket_uid FunctionId = 47
BPF_FUNC_set_hash FunctionId = 48
BPF_FUNC_setsockopt FunctionId = 49
BPF_FUNC_skb_adjust_room FunctionId = 50
BPF_FUNC_redirect_map FunctionId = 51
BPF_FUNC_sk_redirect_map FunctionId = 52
BPF_FUNC_sock_map_update FunctionId = 53
BPF_FUNC_xdp_adjust_meta FunctionId = 54
BPF_FUNC_perf_event_read_value FunctionId = 55
BPF_FUNC_perf_prog_read_value FunctionId = 56
BPF_FUNC_getsockopt FunctionId = 57
BPF_FUNC_override_return FunctionId = 58
BPF_FUNC_sock_ops_cb_flags_set FunctionId = 59
BPF_FUNC_msg_redirect_map FunctionId = 60
BPF_FUNC_msg_apply_bytes FunctionId = 61
BPF_FUNC_msg_cork_bytes FunctionId = 62
BPF_FUNC_msg_pull_data FunctionId = 63
BPF_FUNC_bind FunctionId = 64
BPF_FUNC_xdp_adjust_tail FunctionId = 65
BPF_FUNC_skb_get_xfrm_state FunctionId = 66
BPF_FUNC_get_stack FunctionId = 67
BPF_FUNC_skb_load_bytes_relative FunctionId = 68
BPF_FUNC_fib_lookup FunctionId = 69
BPF_FUNC_sock_hash_update FunctionId = 70
BPF_FUNC_msg_redirect_hash FunctionId = 71
BPF_FUNC_sk_redirect_hash FunctionId = 72
BPF_FUNC_lwt_push_encap FunctionId = 73
BPF_FUNC_lwt_seg6_store_bytes FunctionId = 74
BPF_FUNC_lwt_seg6_adjust_srh FunctionId = 75
BPF_FUNC_lwt_seg6_action FunctionId = 76
BPF_FUNC_rc_repeat FunctionId = 77
BPF_FUNC_rc_keydown FunctionId = 78
BPF_FUNC_skb_cgroup_id FunctionId = 79
BPF_FUNC_get_current_cgroup_id FunctionId = 80
BPF_FUNC_get_local_storage FunctionId = 81
BPF_FUNC_sk_select_reuseport FunctionId = 82
BPF_FUNC_skb_ancestor_cgroup_id FunctionId = 83
BPF_FUNC_sk_lookup_tcp FunctionId = 84
BPF_FUNC_sk_lookup_udp FunctionId = 85
BPF_FUNC_sk_release FunctionId = 86
BPF_FUNC_map_push_elem FunctionId = 87
BPF_FUNC_map_pop_elem FunctionId = 88
BPF_FUNC_map_peek_elem FunctionId = 89
BPF_FUNC_msg_push_data FunctionId = 90
BPF_FUNC_msg_pop_data FunctionId = 91
BPF_FUNC_rc_pointer_rel FunctionId = 92
BPF_FUNC_spin_lock FunctionId = 93
BPF_FUNC_spin_unlock FunctionId = 94
BPF_FUNC_sk_fullsock FunctionId = 95
BPF_FUNC_tcp_sock FunctionId = 96
BPF_FUNC_skb_ecn_set_ce FunctionId = 97
BPF_FUNC_get_listener_sock FunctionId = 98
BPF_FUNC_skc_lookup_tcp FunctionId = 99
BPF_FUNC_tcp_check_syncookie FunctionId = 100
BPF_FUNC_sysctl_get_name FunctionId = 101
BPF_FUNC_sysctl_get_current_value FunctionId = 102
BPF_FUNC_sysctl_get_new_value FunctionId = 103
BPF_FUNC_sysctl_set_new_value FunctionId = 104
BPF_FUNC_strtol FunctionId = 105
BPF_FUNC_strtoul FunctionId = 106
BPF_FUNC_sk_storage_get FunctionId = 107
BPF_FUNC_sk_storage_delete FunctionId = 108
BPF_FUNC_send_signal FunctionId = 109
BPF_FUNC_tcp_gen_syncookie FunctionId = 110
BPF_FUNC_skb_output FunctionId = 111
BPF_FUNC_probe_read_user FunctionId = 112
BPF_FUNC_probe_read_kernel FunctionId = 113
BPF_FUNC_probe_read_user_str FunctionId = 114
BPF_FUNC_probe_read_kernel_str FunctionId = 115
BPF_FUNC_tcp_send_ack FunctionId = 116
BPF_FUNC_send_signal_thread FunctionId = 117
BPF_FUNC_jiffies64 FunctionId = 118
BPF_FUNC_read_branch_records FunctionId = 119
BPF_FUNC_get_ns_current_pid_tgid FunctionId = 120
BPF_FUNC_xdp_output FunctionId = 121
BPF_FUNC_get_netns_cookie FunctionId = 122
BPF_FUNC_get_current_ancestor_cgroup_id FunctionId = 123
BPF_FUNC_sk_assign FunctionId = 124
BPF_FUNC_ktime_get_boot_ns FunctionId = 125
BPF_FUNC_seq_printf FunctionId = 126
BPF_FUNC_seq_write FunctionId = 127
BPF_FUNC_sk_cgroup_id FunctionId = 128
BPF_FUNC_sk_ancestor_cgroup_id FunctionId = 129
BPF_FUNC_ringbuf_output FunctionId = 130
BPF_FUNC_ringbuf_reserve FunctionId = 131
BPF_FUNC_ringbuf_submit FunctionId = 132
BPF_FUNC_ringbuf_discard FunctionId = 133
BPF_FUNC_ringbuf_query FunctionId = 134
BPF_FUNC_csum_level FunctionId = 135
BPF_FUNC_skc_to_tcp6_sock FunctionId = 136
BPF_FUNC_skc_to_tcp_sock FunctionId = 137
BPF_FUNC_skc_to_tcp_timewait_sock FunctionId = 138
BPF_FUNC_skc_to_tcp_request_sock FunctionId = 139
BPF_FUNC_skc_to_udp6_sock FunctionId = 140
BPF_FUNC_get_task_stack FunctionId = 141
BPF_FUNC_load_hdr_opt FunctionId = 142
BPF_FUNC_store_hdr_opt FunctionId = 143
BPF_FUNC_reserve_hdr_opt FunctionId = 144
BPF_FUNC_inode_storage_get FunctionId = 145
BPF_FUNC_inode_storage_delete FunctionId = 146
BPF_FUNC_d_path FunctionId = 147
BPF_FUNC_copy_from_user FunctionId = 148
BPF_FUNC_snprintf_btf FunctionId = 149
BPF_FUNC_seq_printf_btf FunctionId = 150
BPF_FUNC_skb_cgroup_classid FunctionId = 151
BPF_FUNC_redirect_neigh FunctionId = 152
BPF_FUNC_per_cpu_ptr FunctionId = 153
BPF_FUNC_this_cpu_ptr FunctionId = 154
BPF_FUNC_redirect_peer FunctionId = 155
BPF_FUNC_task_storage_get FunctionId = 156
BPF_FUNC_task_storage_delete FunctionId = 157
BPF_FUNC_get_current_task_btf FunctionId = 158
BPF_FUNC_bprm_opts_set FunctionId = 159
BPF_FUNC_ktime_get_coarse_ns FunctionId = 160
BPF_FUNC_ima_inode_hash FunctionId = 161
BPF_FUNC_sock_from_file FunctionId = 162
BPF_FUNC_check_mtu FunctionId = 163
BPF_FUNC_for_each_map_elem FunctionId = 164
BPF_FUNC_snprintf FunctionId = 165
__BPF_FUNC_MAX_ID FunctionId = 166
)
type HdrStartOff int32
const (
BPF_HDR_START_MAC HdrStartOff = 0
BPF_HDR_START_NET HdrStartOff = 1
)
type LinkType int32
const (
BPF_LINK_TYPE_UNSPEC LinkType = 0
BPF_LINK_TYPE_RAW_TRACEPOINT LinkType = 1
BPF_LINK_TYPE_TRACING LinkType = 2
BPF_LINK_TYPE_CGROUP LinkType = 3
BPF_LINK_TYPE_ITER LinkType = 4
BPF_LINK_TYPE_NETNS LinkType = 5
BPF_LINK_TYPE_XDP LinkType = 6
MAX_BPF_LINK_TYPE LinkType = 7
)
type MapType int32
const (
BPF_MAP_TYPE_UNSPEC MapType = 0
BPF_MAP_TYPE_HASH MapType = 1
BPF_MAP_TYPE_ARRAY MapType = 2
BPF_MAP_TYPE_PROG_ARRAY MapType = 3
BPF_MAP_TYPE_PERF_EVENT_ARRAY MapType = 4
BPF_MAP_TYPE_PERCPU_HASH MapType = 5
BPF_MAP_TYPE_PERCPU_ARRAY MapType = 6
BPF_MAP_TYPE_STACK_TRACE MapType = 7
BPF_MAP_TYPE_CGROUP_ARRAY MapType = 8
BPF_MAP_TYPE_LRU_HASH MapType = 9
BPF_MAP_TYPE_LRU_PERCPU_HASH MapType = 10
BPF_MAP_TYPE_LPM_TRIE MapType = 11
BPF_MAP_TYPE_ARRAY_OF_MAPS MapType = 12
BPF_MAP_TYPE_HASH_OF_MAPS MapType = 13
BPF_MAP_TYPE_DEVMAP MapType = 14
BPF_MAP_TYPE_SOCKMAP MapType = 15
BPF_MAP_TYPE_CPUMAP MapType = 16
BPF_MAP_TYPE_XSKMAP MapType = 17
BPF_MAP_TYPE_SOCKHASH MapType = 18
BPF_MAP_TYPE_CGROUP_STORAGE MapType = 19
BPF_MAP_TYPE_REUSEPORT_SOCKARRAY MapType = 20
BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE MapType = 21
BPF_MAP_TYPE_QUEUE MapType = 22
BPF_MAP_TYPE_STACK MapType = 23
BPF_MAP_TYPE_SK_STORAGE MapType = 24
BPF_MAP_TYPE_DEVMAP_HASH MapType = 25
BPF_MAP_TYPE_STRUCT_OPS MapType = 26
BPF_MAP_TYPE_RINGBUF MapType = 27
BPF_MAP_TYPE_INODE_STORAGE MapType = 28
BPF_MAP_TYPE_TASK_STORAGE MapType = 29
)
type ProgType int32
const (
BPF_PROG_TYPE_UNSPEC ProgType = 0
BPF_PROG_TYPE_SOCKET_FILTER ProgType = 1
BPF_PROG_TYPE_KPROBE ProgType = 2
BPF_PROG_TYPE_SCHED_CLS ProgType = 3
BPF_PROG_TYPE_SCHED_ACT ProgType = 4
BPF_PROG_TYPE_TRACEPOINT ProgType = 5
BPF_PROG_TYPE_XDP ProgType = 6
BPF_PROG_TYPE_PERF_EVENT ProgType = 7
BPF_PROG_TYPE_CGROUP_SKB ProgType = 8
BPF_PROG_TYPE_CGROUP_SOCK ProgType = 9
BPF_PROG_TYPE_LWT_IN ProgType = 10
BPF_PROG_TYPE_LWT_OUT ProgType = 11
BPF_PROG_TYPE_LWT_XMIT ProgType = 12
BPF_PROG_TYPE_SOCK_OPS ProgType = 13
BPF_PROG_TYPE_SK_SKB ProgType = 14
BPF_PROG_TYPE_CGROUP_DEVICE ProgType = 15
BPF_PROG_TYPE_SK_MSG ProgType = 16
BPF_PROG_TYPE_RAW_TRACEPOINT ProgType = 17
BPF_PROG_TYPE_CGROUP_SOCK_ADDR ProgType = 18
BPF_PROG_TYPE_LWT_SEG6LOCAL ProgType = 19
BPF_PROG_TYPE_LIRC_MODE2 ProgType = 20
BPF_PROG_TYPE_SK_REUSEPORT ProgType = 21
BPF_PROG_TYPE_FLOW_DISSECTOR ProgType = 22
BPF_PROG_TYPE_CGROUP_SYSCTL ProgType = 23
BPF_PROG_TYPE_RAW_TRACEPOINT_WRITABLE ProgType = 24
BPF_PROG_TYPE_CGROUP_SOCKOPT ProgType = 25
BPF_PROG_TYPE_TRACING ProgType = 26
BPF_PROG_TYPE_STRUCT_OPS ProgType = 27
BPF_PROG_TYPE_EXT ProgType = 28
BPF_PROG_TYPE_LSM ProgType = 29
BPF_PROG_TYPE_SK_LOOKUP ProgType = 30
)
type RetCode int32
const (
BPF_OK RetCode = 0
BPF_DROP RetCode = 2
BPF_REDIRECT RetCode = 7
BPF_LWT_REROUTE RetCode = 128
)
type SkAction int32
const (
SK_DROP SkAction = 0
SK_PASS SkAction = 1
)
type StackBuildIdStatus int32
const (
BPF_STACK_BUILD_ID_EMPTY StackBuildIdStatus = 0
BPF_STACK_BUILD_ID_VALID StackBuildIdStatus = 1
BPF_STACK_BUILD_ID_IP StackBuildIdStatus = 2
)
type StatsType int32
const (
BPF_STATS_RUN_TIME StatsType = 0
)
type XdpAction int32
const (
XDP_ABORTED XdpAction = 0
XDP_DROP XdpAction = 1
XDP_PASS XdpAction = 2
XDP_TX XdpAction = 3
XDP_REDIRECT XdpAction = 4
)
type BtfInfo struct {
Btf Pointer
BtfSize uint32
Id uint32
Name Pointer
NameLen uint32
KernelBtf uint32
}
type FuncInfo struct {
InsnOff uint32
TypeId uint32
}
type LineInfo struct {
InsnOff uint32
FileNameOff uint32
LineOff uint32
LineCol uint32
}
type LinkInfo struct {
Type LinkType
Id LinkID
ProgId uint32
_ [4]byte
Extra [16]uint8
}
type MapInfo struct {
Type uint32
Id uint32
KeySize uint32
ValueSize uint32
MaxEntries uint32
MapFlags uint32
Name ObjName
Ifindex uint32
BtfVmlinuxValueTypeId uint32
NetnsDev uint64
NetnsIno uint64
BtfId uint32
BtfKeyTypeId uint32
BtfValueTypeId uint32
_ [4]byte
}
type ProgInfo struct {
Type uint32
Id uint32
Tag [8]uint8
JitedProgLen uint32
XlatedProgLen uint32
JitedProgInsns uint64
XlatedProgInsns Pointer
LoadTime uint64
CreatedByUid uint32
NrMapIds uint32
MapIds Pointer
Name ObjName
Ifindex uint32
_ [4]byte /* unsupported bitfield */
NetnsDev uint64
NetnsIno uint64
NrJitedKsyms uint32
NrJitedFuncLens uint32
JitedKsyms uint64
JitedFuncLens uint64
BtfId uint32
FuncInfoRecSize uint32
FuncInfo uint64
NrFuncInfo uint32
NrLineInfo uint32
LineInfo uint64
JitedLineInfo uint64
NrJitedLineInfo uint32
LineInfoRecSize uint32
JitedLineInfoRecSize uint32
NrProgTags uint32
ProgTags uint64
RunTimeNs uint64
RunCnt uint64
RecursionMisses uint64
}
type BtfGetFdByIdAttr struct{ Id uint32 }
func BtfGetFdById(attr *BtfGetFdByIdAttr) (*FD, error) {
fd, err := BPF(BPF_BTF_GET_FD_BY_ID, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
if err != nil {
return nil, err
}
return NewFD(int(fd))
}
type BtfLoadAttr struct {
Btf Pointer
BtfLogBuf Pointer
BtfSize uint32
BtfLogSize uint32
BtfLogLevel uint32
_ [4]byte
}
func BtfLoad(attr *BtfLoadAttr) (*FD, error) {
fd, err := BPF(BPF_BTF_LOAD, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
if err != nil {
return nil, err
}
return NewFD(int(fd))
}
type EnableStatsAttr struct{ Type uint32 }
func EnableStats(attr *EnableStatsAttr) (*FD, error) {
fd, err := BPF(BPF_ENABLE_STATS, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
if err != nil {
return nil, err
}
return NewFD(int(fd))
}
type IterCreateAttr struct {
LinkFd uint32
Flags uint32
}
func IterCreate(attr *IterCreateAttr) (*FD, error) {
fd, err := BPF(BPF_ITER_CREATE, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
if err != nil {
return nil, err
}
return NewFD(int(fd))
}
type LinkCreateAttr struct {
ProgFd uint32
TargetFd uint32
AttachType AttachType
Flags uint32
TargetBtfId uint32
_ [12]byte
}
func LinkCreate(attr *LinkCreateAttr) (*FD, error) {
fd, err := BPF(BPF_LINK_CREATE, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
if err != nil {
return nil, err
}
return NewFD(int(fd))
}
type LinkCreateIterAttr struct {
ProgFd uint32
TargetFd uint32
AttachType AttachType
Flags uint32
IterInfo Pointer
IterInfoLen uint32
_ [4]byte
}
func LinkCreateIter(attr *LinkCreateIterAttr) (*FD, error) {
fd, err := BPF(BPF_LINK_CREATE, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
if err != nil {
return nil, err
}
return NewFD(int(fd))
}
type LinkUpdateAttr struct {
LinkFd uint32
NewProgFd uint32
Flags uint32
OldProgFd uint32
}
func LinkUpdate(attr *LinkUpdateAttr) error {
_, err := BPF(BPF_LINK_UPDATE, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
return err
}
type MapCreateAttr struct {
MapType MapType
KeySize uint32
ValueSize uint32
MaxEntries uint32
MapFlags uint32
InnerMapFd uint32
NumaNode uint32
MapName ObjName
MapIfindex uint32
BtfFd uint32
BtfKeyTypeId uint32
BtfValueTypeId uint32
BtfVmlinuxValueTypeId uint32
}
func MapCreate(attr *MapCreateAttr) (*FD, error) {
fd, err := BPF(BPF_MAP_CREATE, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
if err != nil {
return nil, err
}
return NewFD(int(fd))
}
type MapDeleteBatchAttr struct {
InBatch Pointer
OutBatch Pointer
Keys Pointer
Values Pointer
Count uint32
MapFd uint32
ElemFlags uint64
Flags uint64
}
func MapDeleteBatch(attr *MapDeleteBatchAttr) error {
_, err := BPF(BPF_MAP_DELETE_BATCH, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
return err
}
type MapDeleteElemAttr struct {
MapFd uint32
_ [4]byte
Key Pointer
Value Pointer
Flags uint64
}
func MapDeleteElem(attr *MapDeleteElemAttr) error {
_, err := BPF(BPF_MAP_DELETE_ELEM, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
return err
}
type MapFreezeAttr struct{ MapFd uint32 }
func MapFreeze(attr *MapFreezeAttr) error {
_, err := BPF(BPF_MAP_FREEZE, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
return err
}
type MapGetFdByIdAttr struct{ Id uint32 }
func MapGetFdById(attr *MapGetFdByIdAttr) (*FD, error) {
fd, err := BPF(BPF_MAP_GET_FD_BY_ID, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
if err != nil {
return nil, err
}
return NewFD(int(fd))
}
type MapGetNextIdAttr struct {
Id uint32
NextId uint32
}
func MapGetNextId(attr *MapGetNextIdAttr) error {
_, err := BPF(BPF_MAP_GET_NEXT_ID, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
return err
}
type MapGetNextKeyAttr struct {
MapFd uint32
_ [4]byte
Key Pointer
NextKey Pointer
}
func MapGetNextKey(attr *MapGetNextKeyAttr) error {
_, err := BPF(BPF_MAP_GET_NEXT_KEY, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
return err
}
type MapLookupAndDeleteBatchAttr struct {
InBatch Pointer
OutBatch Pointer
Keys Pointer
Values Pointer
Count uint32
MapFd uint32
ElemFlags uint64
Flags uint64
}
func MapLookupAndDeleteBatch(attr *MapLookupAndDeleteBatchAttr) error {
_, err := BPF(BPF_MAP_LOOKUP_AND_DELETE_BATCH, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
return err
}
type MapLookupAndDeleteElemAttr struct {
MapFd uint32
_ [4]byte
Key Pointer
Value Pointer
Flags uint64
}
func MapLookupAndDeleteElem(attr *MapLookupAndDeleteElemAttr) error {
_, err := BPF(BPF_MAP_LOOKUP_AND_DELETE_ELEM, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
return err
}
type MapLookupBatchAttr struct {
InBatch Pointer
OutBatch Pointer
Keys Pointer
Values Pointer
Count uint32
MapFd uint32
ElemFlags uint64
Flags uint64
}
func MapLookupBatch(attr *MapLookupBatchAttr) error {
_, err := BPF(BPF_MAP_LOOKUP_BATCH, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
return err
}
type MapLookupElemAttr struct {
MapFd uint32
_ [4]byte
Key Pointer
Value Pointer
Flags uint64
}
func MapLookupElem(attr *MapLookupElemAttr) error {
_, err := BPF(BPF_MAP_LOOKUP_ELEM, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
return err
}
type MapUpdateBatchAttr struct {
InBatch Pointer
OutBatch Pointer
Keys Pointer
Values Pointer
Count uint32
MapFd uint32
ElemFlags uint64
Flags uint64
}
func MapUpdateBatch(attr *MapUpdateBatchAttr) error {
_, err := BPF(BPF_MAP_UPDATE_BATCH, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
return err
}
type MapUpdateElemAttr struct {
MapFd uint32
_ [4]byte
Key Pointer
Value Pointer
Flags uint64
}
func MapUpdateElem(attr *MapUpdateElemAttr) error {
_, err := BPF(BPF_MAP_UPDATE_ELEM, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
return err
}
type ObjGetAttr struct {
Pathname Pointer
BpfFd uint32
FileFlags uint32
}
func ObjGet(attr *ObjGetAttr) (*FD, error) {
fd, err := BPF(BPF_OBJ_GET, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
if err != nil {
return nil, err
}
return NewFD(int(fd))
}
type ObjGetInfoByFdAttr struct {
BpfFd uint32
InfoLen uint32
Info Pointer
}
func ObjGetInfoByFd(attr *ObjGetInfoByFdAttr) error {
_, err := BPF(BPF_OBJ_GET_INFO_BY_FD, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
return err
}
type ObjPinAttr struct {
Pathname Pointer
BpfFd uint32
FileFlags uint32
}
func ObjPin(attr *ObjPinAttr) error {
_, err := BPF(BPF_OBJ_PIN, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
return err
}
type ProgAttachAttr struct {
TargetFd uint32
AttachBpfFd uint32
AttachType uint32
AttachFlags uint32
ReplaceBpfFd uint32
}
func ProgAttach(attr *ProgAttachAttr) error {
_, err := BPF(BPF_PROG_ATTACH, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
return err
}
type ProgBindMapAttr struct {
ProgFd uint32
MapFd uint32
Flags uint32
}
func ProgBindMap(attr *ProgBindMapAttr) error {
_, err := BPF(BPF_PROG_BIND_MAP, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
return err
}
type ProgDetachAttr struct {
TargetFd uint32
AttachBpfFd uint32
AttachType uint32
}
func ProgDetach(attr *ProgDetachAttr) error {
_, err := BPF(BPF_PROG_DETACH, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
return err
}
type ProgGetFdByIdAttr struct{ Id uint32 }
func ProgGetFdById(attr *ProgGetFdByIdAttr) (*FD, error) {
fd, err := BPF(BPF_PROG_GET_FD_BY_ID, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
if err != nil {
return nil, err
}
return NewFD(int(fd))
}
type ProgGetNextIdAttr struct {
Id uint32
NextId uint32
}
func ProgGetNextId(attr *ProgGetNextIdAttr) error {
_, err := BPF(BPF_PROG_GET_NEXT_ID, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
return err
}
type ProgLoadAttr struct {
ProgType ProgType
InsnCnt uint32
Insns Pointer
License Pointer
LogLevel uint32
LogSize uint32
LogBuf Pointer
KernVersion uint32
ProgFlags uint32
ProgName ObjName
ProgIfindex uint32
ExpectedAttachType AttachType
ProgBtfFd uint32
FuncInfoRecSize uint32
FuncInfo Pointer
FuncInfoCnt uint32
LineInfoRecSize uint32
LineInfo Pointer
LineInfoCnt uint32
AttachBtfId uint32
AttachProgFd uint32
_ [4]byte
}
func ProgLoad(attr *ProgLoadAttr) (*FD, error) {
fd, err := BPF(BPF_PROG_LOAD, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
if err != nil {
return nil, err
}
return NewFD(int(fd))
}
type ProgRunAttr struct {
ProgFd uint32
Retval uint32
DataSizeIn uint32
DataSizeOut uint32
DataIn Pointer
DataOut Pointer
Repeat uint32
Duration uint32
CtxSizeIn uint32
CtxSizeOut uint32
CtxIn Pointer
CtxOut Pointer
Flags uint32
Cpu uint32
}
func ProgRun(attr *ProgRunAttr) error {
_, err := BPF(BPF_PROG_TEST_RUN, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
return err
}
type RawTracepointOpenAttr struct {
Name Pointer
ProgFd uint32
_ [4]byte
}
func RawTracepointOpen(attr *RawTracepointOpenAttr) (*FD, error) {
fd, err := BPF(BPF_RAW_TRACEPOINT_OPEN, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
if err != nil {
return nil, err
}
return NewFD(int(fd))
}
type CgroupLinkInfo struct {
CgroupId uint64
AttachType AttachType
_ [4]byte
}
type IterLinkInfo struct {
TargetName Pointer
TargetNameLen uint32
}
type NetNsLinkInfo struct {
NetnsIno uint32
AttachType AttachType
}
type RawTracepointLinkInfo struct {
TpName Pointer
TpNameLen uint32
_ [4]byte
}
type TracingLinkInfo struct {
AttachType AttachType
TargetObjId uint32
TargetBtfId uint32
}
type XDPLinkInfo struct{ Ifindex uint32 }
-304
View File
@@ -1,304 +0,0 @@
package internal
import (
"errors"
"fmt"
"path/filepath"
"runtime"
"syscall"
"unsafe"
"github.com/cilium/ebpf/internal/unix"
)
//go:generate stringer -output syscall_string.go -type=BPFCmd
// BPFCmd identifies a subcommand of the bpf syscall.
type BPFCmd int
// Well known BPF commands.
const (
BPF_MAP_CREATE BPFCmd = iota
BPF_MAP_LOOKUP_ELEM
BPF_MAP_UPDATE_ELEM
BPF_MAP_DELETE_ELEM
BPF_MAP_GET_NEXT_KEY
BPF_PROG_LOAD
BPF_OBJ_PIN
BPF_OBJ_GET
BPF_PROG_ATTACH
BPF_PROG_DETACH
BPF_PROG_TEST_RUN
BPF_PROG_GET_NEXT_ID
BPF_MAP_GET_NEXT_ID
BPF_PROG_GET_FD_BY_ID
BPF_MAP_GET_FD_BY_ID
BPF_OBJ_GET_INFO_BY_FD
BPF_PROG_QUERY
BPF_RAW_TRACEPOINT_OPEN
BPF_BTF_LOAD
BPF_BTF_GET_FD_BY_ID
BPF_TASK_FD_QUERY
BPF_MAP_LOOKUP_AND_DELETE_ELEM
BPF_MAP_FREEZE
BPF_BTF_GET_NEXT_ID
BPF_MAP_LOOKUP_BATCH
BPF_MAP_LOOKUP_AND_DELETE_BATCH
BPF_MAP_UPDATE_BATCH
BPF_MAP_DELETE_BATCH
BPF_LINK_CREATE
BPF_LINK_UPDATE
BPF_LINK_GET_FD_BY_ID
BPF_LINK_GET_NEXT_ID
BPF_ENABLE_STATS
BPF_ITER_CREATE
)
// BPF wraps SYS_BPF.
//
// Any pointers contained in attr must use the Pointer type from this package.
func BPF(cmd BPFCmd, attr unsafe.Pointer, size uintptr) (uintptr, error) {
r1, _, errNo := unix.Syscall(unix.SYS_BPF, uintptr(cmd), uintptr(attr), size)
runtime.KeepAlive(attr)
var err error
if errNo != 0 {
err = wrappedErrno{errNo}
}
return r1, err
}
type BPFProgLoadAttr struct {
ProgType uint32
InsCount uint32
Instructions Pointer
License Pointer
LogLevel uint32
LogSize uint32
LogBuf Pointer
KernelVersion uint32 // since 4.1 2541517c32be
ProgFlags uint32 // since 4.11 e07b98d9bffe
ProgName BPFObjName // since 4.15 067cae47771c
ProgIfIndex uint32 // since 4.15 1f6f4cb7ba21
ExpectedAttachType uint32 // since 4.17 5e43f899b03a
ProgBTFFd uint32
FuncInfoRecSize uint32
FuncInfo Pointer
FuncInfoCnt uint32
LineInfoRecSize uint32
LineInfo Pointer
LineInfoCnt uint32
AttachBTFID uint32
AttachProgFd uint32
}
// BPFProgLoad wraps BPF_PROG_LOAD.
func BPFProgLoad(attr *BPFProgLoadAttr) (*FD, error) {
for {
fd, err := BPF(BPF_PROG_LOAD, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
// As of ~4.20 the verifier can be interrupted by a signal,
// and returns EAGAIN in that case.
if errors.Is(err, unix.EAGAIN) {
continue
}
if err != nil {
return nil, err
}
return NewFD(uint32(fd)), nil
}
}
type BPFProgAttachAttr struct {
TargetFd uint32
AttachBpfFd uint32
AttachType uint32
AttachFlags uint32
ReplaceBpfFd uint32
}
func BPFProgAttach(attr *BPFProgAttachAttr) error {
_, err := BPF(BPF_PROG_ATTACH, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
return err
}
type BPFProgDetachAttr struct {
TargetFd uint32
AttachBpfFd uint32
AttachType uint32
}
func BPFProgDetach(attr *BPFProgDetachAttr) error {
_, err := BPF(BPF_PROG_DETACH, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
return err
}
type BPFEnableStatsAttr struct {
StatsType uint32
}
func BPFEnableStats(attr *BPFEnableStatsAttr) (*FD, error) {
ptr, err := BPF(BPF_ENABLE_STATS, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
if err != nil {
return nil, fmt.Errorf("enable stats: %w", err)
}
return NewFD(uint32(ptr)), nil
}
type bpfObjAttr struct {
fileName Pointer
fd uint32
fileFlags uint32
}
const bpfFSType = 0xcafe4a11
// BPFObjPin wraps BPF_OBJ_PIN.
func BPFObjPin(fileName string, fd *FD) error {
dirName := filepath.Dir(fileName)
var statfs unix.Statfs_t
if err := unix.Statfs(dirName, &statfs); err != nil {
return err
}
if uint64(statfs.Type) != bpfFSType {
return fmt.Errorf("%s is not on a bpf filesystem", fileName)
}
value, err := fd.Value()
if err != nil {
return err
}
attr := bpfObjAttr{
fileName: NewStringPointer(fileName),
fd: value,
}
_, err = BPF(BPF_OBJ_PIN, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
if err != nil {
return fmt.Errorf("pin object %s: %w", fileName, err)
}
return nil
}
// BPFObjGet wraps BPF_OBJ_GET.
func BPFObjGet(fileName string, flags uint32) (*FD, error) {
attr := bpfObjAttr{
fileName: NewStringPointer(fileName),
fileFlags: flags,
}
ptr, err := BPF(BPF_OBJ_GET, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
if err != nil {
return nil, fmt.Errorf("get object %s: %w", fileName, err)
}
return NewFD(uint32(ptr)), nil
}
type bpfObjGetInfoByFDAttr struct {
fd uint32
infoLen uint32
info Pointer
}
// BPFObjGetInfoByFD wraps BPF_OBJ_GET_INFO_BY_FD.
//
// Available from 4.13.
func BPFObjGetInfoByFD(fd *FD, info unsafe.Pointer, size uintptr) error {
value, err := fd.Value()
if err != nil {
return err
}
attr := bpfObjGetInfoByFDAttr{
fd: value,
infoLen: uint32(size),
info: NewPointer(info),
}
_, err = BPF(BPF_OBJ_GET_INFO_BY_FD, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
if err != nil {
return fmt.Errorf("fd %v: %w", fd, err)
}
return nil
}
type bpfGetFDByIDAttr struct {
id uint32
next uint32
}
// BPFObjGetInfoByFD wraps BPF_*_GET_FD_BY_ID.
//
// Available from 4.13.
func BPFObjGetFDByID(cmd BPFCmd, id uint32) (*FD, error) {
attr := bpfGetFDByIDAttr{
id: id,
}
ptr, err := BPF(cmd, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
return NewFD(uint32(ptr)), err
}
// BPFObjName is a null-terminated string made up of
// 'A-Za-z0-9_' characters.
type BPFObjName [unix.BPF_OBJ_NAME_LEN]byte
// NewBPFObjName truncates the result if it is too long.
func NewBPFObjName(name string) BPFObjName {
var result BPFObjName
copy(result[:unix.BPF_OBJ_NAME_LEN-1], name)
return result
}
type BPFMapCreateAttr struct {
MapType uint32
KeySize uint32
ValueSize uint32
MaxEntries uint32
Flags uint32
InnerMapFd uint32 // since 4.12 56f668dfe00d
NumaNode uint32 // since 4.14 96eabe7a40aa
MapName BPFObjName // since 4.15 ad5b177bd73f
MapIfIndex uint32
BTFFd uint32
BTFKeyTypeID uint32
BTFValueTypeID uint32
}
func BPFMapCreate(attr *BPFMapCreateAttr) (*FD, error) {
fd, err := BPF(BPF_MAP_CREATE, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
if err != nil {
return nil, err
}
return NewFD(uint32(fd)), nil
}
// wrappedErrno wraps syscall.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
}
func (we wrappedErrno) Unwrap() error {
return we.Errno
}
type syscallError struct {
error
errno syscall.Errno
}
func SyscallError(err error, errno syscall.Errno) error {
return &syscallError{err, errno}
}
func (se *syscallError) Is(target error) bool {
return target == se.error
}
func (se *syscallError) Unwrap() error {
return se.errno
}
-56
View File
@@ -1,56 +0,0 @@
// Code generated by "stringer -output syscall_string.go -type=BPFCmd"; DO NOT EDIT.
package internal
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_MAP_CREATE-0]
_ = x[BPF_MAP_LOOKUP_ELEM-1]
_ = x[BPF_MAP_UPDATE_ELEM-2]
_ = x[BPF_MAP_DELETE_ELEM-3]
_ = x[BPF_MAP_GET_NEXT_KEY-4]
_ = x[BPF_PROG_LOAD-5]
_ = x[BPF_OBJ_PIN-6]
_ = x[BPF_OBJ_GET-7]
_ = x[BPF_PROG_ATTACH-8]
_ = x[BPF_PROG_DETACH-9]
_ = x[BPF_PROG_TEST_RUN-10]
_ = x[BPF_PROG_GET_NEXT_ID-11]
_ = x[BPF_MAP_GET_NEXT_ID-12]
_ = x[BPF_PROG_GET_FD_BY_ID-13]
_ = x[BPF_MAP_GET_FD_BY_ID-14]
_ = x[BPF_OBJ_GET_INFO_BY_FD-15]
_ = x[BPF_PROG_QUERY-16]
_ = x[BPF_RAW_TRACEPOINT_OPEN-17]
_ = x[BPF_BTF_LOAD-18]
_ = x[BPF_BTF_GET_FD_BY_ID-19]
_ = x[BPF_TASK_FD_QUERY-20]
_ = x[BPF_MAP_LOOKUP_AND_DELETE_ELEM-21]
_ = x[BPF_MAP_FREEZE-22]
_ = x[BPF_BTF_GET_NEXT_ID-23]
_ = x[BPF_MAP_LOOKUP_BATCH-24]
_ = x[BPF_MAP_LOOKUP_AND_DELETE_BATCH-25]
_ = x[BPF_MAP_UPDATE_BATCH-26]
_ = x[BPF_MAP_DELETE_BATCH-27]
_ = x[BPF_LINK_CREATE-28]
_ = x[BPF_LINK_UPDATE-29]
_ = x[BPF_LINK_GET_FD_BY_ID-30]
_ = x[BPF_LINK_GET_NEXT_ID-31]
_ = x[BPF_ENABLE_STATS-32]
_ = x[BPF_ITER_CREATE-33]
}
const _BPFCmd_name = "BPF_MAP_CREATEBPF_MAP_LOOKUP_ELEMBPF_MAP_UPDATE_ELEMBPF_MAP_DELETE_ELEMBPF_MAP_GET_NEXT_KEYBPF_PROG_LOADBPF_OBJ_PINBPF_OBJ_GETBPF_PROG_ATTACHBPF_PROG_DETACHBPF_PROG_TEST_RUNBPF_PROG_GET_NEXT_IDBPF_MAP_GET_NEXT_IDBPF_PROG_GET_FD_BY_IDBPF_MAP_GET_FD_BY_IDBPF_OBJ_GET_INFO_BY_FDBPF_PROG_QUERYBPF_RAW_TRACEPOINT_OPENBPF_BTF_LOADBPF_BTF_GET_FD_BY_IDBPF_TASK_FD_QUERYBPF_MAP_LOOKUP_AND_DELETE_ELEMBPF_MAP_FREEZEBPF_BTF_GET_NEXT_IDBPF_MAP_LOOKUP_BATCHBPF_MAP_LOOKUP_AND_DELETE_BATCHBPF_MAP_UPDATE_BATCHBPF_MAP_DELETE_BATCHBPF_LINK_CREATEBPF_LINK_UPDATEBPF_LINK_GET_FD_BY_IDBPF_LINK_GET_NEXT_IDBPF_ENABLE_STATSBPF_ITER_CREATE"
var _BPFCmd_index = [...]uint16{0, 14, 33, 52, 71, 91, 104, 115, 126, 141, 156, 173, 193, 212, 233, 253, 275, 289, 312, 324, 344, 361, 391, 405, 424, 444, 475, 495, 515, 530, 545, 566, 586, 602, 617}
func (i BPFCmd) String() string {
if i < 0 || i >= BPFCmd(len(_BPFCmd_index)-1) {
return "BPFCmd(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _BPFCmd_name[_BPFCmd_index[i]:_BPFCmd_index[i+1]]
}
+14 -13
View File
@@ -4,7 +4,6 @@
package unix
import (
"bytes"
"syscall"
linux "golang.org/x/sys/unix"
@@ -23,6 +22,7 @@ const (
ENODEV = linux.ENODEV
EBADF = linux.EBADF
E2BIG = linux.E2BIG
EFAULT = linux.EFAULT
// ENOTSUPP is not the same as ENOTSUP or EOPNOTSUP
ENOTSUPP = syscall.Errno(0x20c)
@@ -66,11 +66,16 @@ const (
PERF_RECORD_SAMPLE = linux.PERF_RECORD_SAMPLE
AT_FDCWD = linux.AT_FDCWD
RENAME_NOREPLACE = linux.RENAME_NOREPLACE
SO_ATTACH_BPF = linux.SO_ATTACH_BPF
SO_DETACH_BPF = linux.SO_DETACH_BPF
SOL_SOCKET = linux.SOL_SOCKET
)
// Statfs_t is a wrapper
type Statfs_t = linux.Statfs_t
type Stat_t = linux.Stat_t
// Rlimit is a wrapper
type Rlimit = linux.Rlimit
@@ -191,18 +196,14 @@ func Renameat2(olddirfd int, oldpath string, newdirfd int, newpath string, flags
return linux.Renameat2(olddirfd, oldpath, newdirfd, newpath, flags)
}
func KernelRelease() (string, error) {
var uname Utsname
err := Uname(&uname)
if err != nil {
return "", err
}
end := bytes.IndexByte(uname.Release[:], 0)
release := string(uname.Release[:end])
return release, nil
}
func Prlimit(pid, resource int, new, old *Rlimit) error {
return linux.Prlimit(pid, resource, new, old)
}
func Open(path string, mode int, perm uint32) (int, error) {
return linux.Open(path, mode, perm)
}
func Fstat(fd int, stat *Stat_t) error {
return linux.Fstat(fd, stat)
}
+14 -4
View File
@@ -23,6 +23,7 @@ const (
ENODEV = syscall.ENODEV
EBADF = syscall.Errno(0)
E2BIG = syscall.Errno(0)
EFAULT = syscall.EFAULT
// ENOTSUPP is not the same as ENOTSUP or EOPNOTSUP
ENOTSUPP = syscall.Errno(0x20c)
@@ -67,6 +68,9 @@ const (
PERF_RECORD_SAMPLE = 9
AT_FDCWD = -0x2
RENAME_NOREPLACE = 0x1
SO_ATTACH_BPF = 0x32
SO_DETACH_BPF = 0x1b
SOL_SOCKET = 0x1
)
// Statfs_t is a wrapper
@@ -85,6 +89,8 @@ type Statfs_t struct {
Spare [4]int64
}
type Stat_t struct{}
// Rlimit is a wrapper
type Rlimit struct {
Cur uint64
@@ -258,10 +264,14 @@ func Renameat2(olddirfd int, oldpath string, newdirfd int, newpath string, flags
return errNonLinux
}
func KernelRelease() (string, error) {
return "", errNonLinux
}
func Prlimit(pid, resource int, new, old *Rlimit) error {
return errNonLinux
}
func Open(path string, mode int, perm uint32) (int, error) {
return -1, errNonLinux
}
func Fstat(fd int, stat *Stat_t) error {
return errNonLinux
}
+145
View File
@@ -0,0 +1,145 @@
package internal
import (
"debug/elf"
"encoding/binary"
"errors"
"fmt"
"io"
"math"
"os"
"github.com/cilium/ebpf/internal/unix"
)
var (
errAuxvNoVDSO = errors.New("no vdso address found in auxv")
)
// vdsoVersion returns the LINUX_VERSION_CODE embedded in the vDSO library
// linked into the current process image.
func vdsoVersion() (uint32, error) {
// Read data from the auxiliary vector, which is normally passed directly
// to the process. Go does not expose that data, so we must read it from procfs.
// https://man7.org/linux/man-pages/man3/getauxval.3.html
av, err := os.Open("/proc/self/auxv")
if err != nil {
return 0, fmt.Errorf("opening auxv: %w", err)
}
defer av.Close()
vdsoAddr, err := vdsoMemoryAddress(av)
if err != nil {
return 0, fmt.Errorf("finding vDSO memory address: %w", err)
}
// Use /proc/self/mem rather than unsafe.Pointer tricks.
mem, err := os.Open("/proc/self/mem")
if err != nil {
return 0, fmt.Errorf("opening mem: %w", err)
}
defer mem.Close()
// Open ELF at provided memory address, as offset into /proc/self/mem.
c, err := vdsoLinuxVersionCode(io.NewSectionReader(mem, int64(vdsoAddr), math.MaxInt64))
if err != nil {
return 0, fmt.Errorf("reading linux version code: %w", err)
}
return c, nil
}
// vdsoMemoryAddress returns the memory address of the vDSO library
// linked into the current process image. r is an io.Reader into an auxv blob.
func vdsoMemoryAddress(r io.Reader) (uint64, error) {
const (
_AT_NULL = 0 // End of vector
_AT_SYSINFO_EHDR = 33 // Offset to vDSO blob in process image
)
// Loop through all tag/value pairs in auxv until we find `AT_SYSINFO_EHDR`,
// the address of a page containing the virtual Dynamic Shared Object (vDSO).
aux := struct{ Tag, Val uint64 }{}
for {
if err := binary.Read(r, NativeEndian, &aux); err != nil {
return 0, fmt.Errorf("reading auxv entry: %w", err)
}
switch aux.Tag {
case _AT_SYSINFO_EHDR:
if aux.Val != 0 {
return aux.Val, nil
}
return 0, fmt.Errorf("invalid vDSO address in auxv")
// _AT_NULL is always the last tag/val pair in the aux vector
// and can be treated like EOF.
case _AT_NULL:
return 0, errAuxvNoVDSO
}
}
}
// format described at https://www.man7.org/linux/man-pages/man5/elf.5.html in section 'Notes (Nhdr)'
type elfNoteHeader struct {
NameSize int32
DescSize int32
Type int32
}
// 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)
if err != nil {
return 0, fmt.Errorf("reading vDSO ELF: %w", err)
}
sec := hdr.SectionByType(elf.SHT_NOTE)
if sec == nil {
return 0, fmt.Errorf("no note section found in vDSO ELF")
}
sr := sec.Open()
var n elfNoteHeader
// Read notes until we find one named 'Linux'.
for {
if err := binary.Read(sr, hdr.ByteOrder, &n); err != nil {
if errors.Is(err, io.EOF) {
return 0, fmt.Errorf("no Linux note in ELF")
}
return 0, fmt.Errorf("reading note header: %w", err)
}
// If a note name is defined, it follows the note header.
var name string
if n.NameSize > 0 {
// Read the note name, aligned to 4 bytes.
buf := make([]byte, Align(int(n.NameSize), 4))
if err := binary.Read(sr, hdr.ByteOrder, &buf); err != nil {
return 0, fmt.Errorf("reading note name: %w", err)
}
// Read nul-terminated string.
name = unix.ByteSliceToString(buf[:n.NameSize])
}
// If a note descriptor is defined, it follows the name.
// It is possible for a note to have a descriptor but not a name.
if n.DescSize > 0 {
// LINUX_VERSION_CODE is a uint32 value.
if name == "Linux" && n.DescSize == 4 && n.Type == 0 {
var version uint32
if err := binary.Read(sr, hdr.ByteOrder, &version); err != nil {
return 0, fmt.Errorf("reading note descriptor: %w", err)
}
return version, nil
}
// Discard the note descriptor if it exists but we're not interested in it.
if _, err := io.CopyN(io.Discard, sr, int64(Align(int(n.DescSize), 4))); err != nil {
return 0, err
}
}
}
}
+12 -68
View File
@@ -2,11 +2,7 @@ package internal
import (
"fmt"
"os"
"regexp"
"sync"
"github.com/cilium/ebpf/internal/unix"
)
const (
@@ -18,12 +14,6 @@ const (
)
var (
// Match between one and three decimals separated by dots, with the last
// segment (patch level) being optional on some kernels.
// The x.y.z string must appear at the start of a string or right after
// whitespace to prevent sequences like 'x.y.z-a.b.c' from matching 'a.b.c'.
rgxKernelVersion = regexp.MustCompile(`(?:\A|\s)\d{1,3}\.\d{1,3}(?:\.\d{1,3})?`)
kernelVersion = struct {
once sync.Once
version Version
@@ -46,6 +36,15 @@ func NewVersion(ver string) (Version, error) {
return Version{major, minor, patch}, nil
}
// NewVersionFromCode creates a version from a LINUX_VERSION_CODE.
func NewVersionFromCode(code uint32) Version {
return Version{
uint16(uint8(code >> 16)),
uint16(uint8(code >> 8)),
uint16(uint8(code)),
}
}
func (v Version) String() string {
if v[2] == 0 {
return fmt.Sprintf("v%d.%d", v[0], v[1])
@@ -98,66 +97,11 @@ func KernelVersion() (Version, error) {
return kernelVersion.version, nil
}
// detectKernelVersion returns the version of the running kernel. It scans the
// following sources in order: /proc/version_signature, uname -v, uname -r.
// In each of those locations, the last-appearing x.y(.z) value is selected
// for parsing. The first location that yields a usable version number is
// returned.
// detectKernelVersion returns the version of the running kernel.
func detectKernelVersion() (Version, error) {
// Try reading /proc/version_signature for Ubuntu compatibility.
// Example format: Ubuntu 4.15.0-91.92-generic 4.15.18
// This method exists in the kernel itself, see d18acd15c
// ("perf tools: Fix kernel version error in ubuntu").
if pvs, err := os.ReadFile("/proc/version_signature"); err == nil {
// If /proc/version_signature exists, failing to parse it is an error.
// It only exists on Ubuntu, where the real patch level is not obtainable
// through any other method.
v, err := findKernelVersion(string(pvs))
if err != nil {
return Version{}, err
}
return v, nil
}
var uname unix.Utsname
if err := unix.Uname(&uname); err != nil {
return Version{}, fmt.Errorf("calling uname: %w", err)
}
// Debian puts the version including the patch level in uname.Version.
// It is not an error if there's no version number in uname.Version,
// as most distributions don't use it. Parsing can continue on uname.Release.
// Example format: #1 SMP Debian 4.19.37-5+deb10u2 (2019-08-08)
if v, err := findKernelVersion(unix.ByteSliceToString(uname.Version[:])); err == nil {
return v, nil
}
// Most other distributions have the full kernel version including patch
// level in uname.Release.
// Example format: 4.19.0-5-amd64, 5.5.10-arch1-1
v, err := findKernelVersion(unix.ByteSliceToString(uname.Release[:]))
vc, err := vdsoVersion()
if err != nil {
return Version{}, err
}
return v, nil
}
// findKernelVersion matches s against rgxKernelVersion and parses the result
// into a Version. If s contains multiple matches, the last entry is selected.
func findKernelVersion(s string) (Version, error) {
m := rgxKernelVersion.FindAllString(s, -1)
if m == nil {
return Version{}, fmt.Errorf("no kernel version in string: %s", s)
}
// Pick the last match of the string in case there are multiple.
s = m[len(m)-1]
v, err := NewVersion(s)
if err != nil {
return Version{}, fmt.Errorf("parsing version string %s: %w", s, err)
}
return v, nil
return NewVersionFromCode(vc), nil
}
+6
View File
@@ -57,6 +57,8 @@ func AttachCgroup(opts CgroupOptions) (Link, error) {
}
// LoadPinnedCgroup loads a pinned cgroup from a bpffs.
//
// Deprecated: use LoadPinnedLink instead.
func LoadPinnedCgroup(fileName string, opts *ebpf.LoadPinOptions) (Link, error) {
link, err := LoadPinnedRawLink(fileName, CgroupType, opts)
if err != nil {
@@ -151,6 +153,10 @@ func (cg *progAttachCgroup) Unpin() error {
return fmt.Errorf("can't pin cgroup: %w", ErrNotSupported)
}
func (cg *progAttachCgroup) Info() (*Info, error) {
return nil, fmt.Errorf("can't get cgroup info: %w", ErrNotSupported)
}
type linkCgroup struct {
RawLink
}
-88
View File
@@ -1,88 +0,0 @@
package link
import (
"fmt"
"github.com/cilium/ebpf"
"github.com/cilium/ebpf/internal/btf"
)
type FreplaceLink struct {
RawLink
}
// AttachFreplace attaches the given eBPF program to the function it replaces.
//
// The program and name can either be provided at link time, or can be provided
// at program load time. If they were provided at load time, they should be nil
// and empty respectively here, as they will be ignored by the kernel.
// Examples:
//
// AttachFreplace(dispatcher, "function", replacement)
// AttachFreplace(nil, "", replacement)
func AttachFreplace(targetProg *ebpf.Program, name string, prog *ebpf.Program) (*FreplaceLink, error) {
if (name == "") != (targetProg == nil) {
return nil, fmt.Errorf("must provide both or neither of name and targetProg: %w", errInvalidInput)
}
if prog == nil {
return nil, fmt.Errorf("prog cannot be nil: %w", errInvalidInput)
}
if prog.Type() != ebpf.Extension {
return nil, fmt.Errorf("eBPF program type %s is not an Extension: %w", prog.Type(), errInvalidInput)
}
var (
target int
typeID btf.TypeID
)
if targetProg != nil {
info, err := targetProg.Info()
if err != nil {
return nil, err
}
btfID, ok := info.BTFID()
if !ok {
return nil, fmt.Errorf("could not get BTF ID for program %s: %w", info.Name, errInvalidInput)
}
btfHandle, err := btf.NewHandleFromID(btfID)
if err != nil {
return nil, err
}
defer btfHandle.Close()
var function *btf.Func
if err := btfHandle.Spec().FindType(name, &function); err != nil {
return nil, err
}
target = targetProg.FD()
typeID = function.ID()
}
link, err := AttachRawLink(RawLinkOptions{
Target: target,
Program: prog,
Attach: ebpf.AttachNone,
BTF: typeID,
})
if err != nil {
return nil, err
}
return &FreplaceLink{*link}, nil
}
// Update implements the Link interface.
func (f *FreplaceLink) Update(new *ebpf.Program) error {
return fmt.Errorf("freplace update: %w", ErrNotSupported)
}
// LoadPinnedFreplace loads a pinned iterator from a bpffs.
func LoadPinnedFreplace(fileName string, opts *ebpf.LoadPinOptions) (*FreplaceLink, error) {
link, err := LoadPinnedRawLink(fileName, TracingType, opts)
if err != nil {
return nil, err
}
return &FreplaceLink{*link}, err
}
+14 -17
View File
@@ -6,7 +6,7 @@ import (
"unsafe"
"github.com/cilium/ebpf"
"github.com/cilium/ebpf/internal"
"github.com/cilium/ebpf/internal/sys"
)
type IterOptions struct {
@@ -31,26 +31,26 @@ func AttachIter(opts IterOptions) (*Iter, error) {
progFd := opts.Program.FD()
if progFd < 0 {
return nil, fmt.Errorf("invalid program: %s", internal.ErrClosedFd)
return nil, fmt.Errorf("invalid program: %s", sys.ErrClosedFd)
}
var info bpfIterLinkInfoMap
if opts.Map != nil {
mapFd := opts.Map.FD()
if mapFd < 0 {
return nil, fmt.Errorf("invalid map: %w", internal.ErrClosedFd)
return nil, fmt.Errorf("invalid map: %w", sys.ErrClosedFd)
}
info.map_fd = uint32(mapFd)
}
attr := bpfLinkCreateIterAttr{
prog_fd: uint32(progFd),
attach_type: ebpf.AttachTraceIter,
iter_info: internal.NewPointer(unsafe.Pointer(&info)),
iter_info_len: uint32(unsafe.Sizeof(info)),
attr := sys.LinkCreateIterAttr{
ProgFd: uint32(progFd),
AttachType: sys.AttachType(ebpf.AttachTraceIter),
IterInfo: sys.NewPointer(unsafe.Pointer(&info)),
IterInfoLen: uint32(unsafe.Sizeof(info)),
}
fd, err := bpfLinkCreateIter(&attr)
fd, err := sys.LinkCreateIter(&attr)
if err != nil {
return nil, fmt.Errorf("can't link iterator: %w", err)
}
@@ -59,6 +59,8 @@ func AttachIter(opts IterOptions) (*Iter, error) {
}
// LoadPinnedIter loads a pinned iterator from a bpffs.
//
// Deprecated: use LoadPinnedLink instead.
func LoadPinnedIter(fileName string, opts *ebpf.LoadPinOptions) (*Iter, error) {
link, err := LoadPinnedRawLink(fileName, IterType, opts)
if err != nil {
@@ -77,16 +79,11 @@ type Iter struct {
//
// Reading from the returned reader triggers the BPF program.
func (it *Iter) Open() (io.ReadCloser, error) {
linkFd, err := it.fd.Value()
if err != nil {
return nil, err
attr := &sys.IterCreateAttr{
LinkFd: it.fd.Uint(),
}
attr := &bpfIterCreateAttr{
linkFd: linkFd,
}
fd, err := bpfIterCreate(attr)
fd, err := sys.IterCreate(attr)
if err != nil {
return nil, fmt.Errorf("can't create iterator: %w", err)
}
+61 -36
View File
@@ -12,7 +12,7 @@ import (
"unsafe"
"github.com/cilium/ebpf"
"github.com/cilium/ebpf/internal"
"github.com/cilium/ebpf/internal/sys"
"github.com/cilium/ebpf/internal/unix"
)
@@ -28,6 +28,13 @@ var (
type probeType uint8
type probeArgs struct {
symbol, group, path string
offset, refCtrOffset uint64
pid int
ret bool
}
const (
kprobeType probeType = iota
uprobeType
@@ -131,10 +138,17 @@ func kprobe(symbol string, prog *ebpf.Program, ret bool) (*perfEvent, error) {
return nil, fmt.Errorf("eBPF program type %s is not a Kprobe: %w", prog.Type(), errInvalidInput)
}
args := probeArgs{
pid: perfAllThreads,
symbol: platformPrefix(symbol),
ret: ret,
}
// Use kprobe PMU if the kernel has it available.
tp, err := pmuKprobe(platformPrefix(symbol), ret)
tp, err := pmuKprobe(args)
if errors.Is(err, os.ErrNotExist) {
tp, err = pmuKprobe(symbol, ret)
args.symbol = symbol
tp, err = pmuKprobe(args)
}
if err == nil {
return tp, nil
@@ -144,9 +158,11 @@ func kprobe(symbol string, prog *ebpf.Program, ret bool) (*perfEvent, error) {
}
// Use tracefs if kprobe PMU is missing.
tp, err = tracefsKprobe(platformPrefix(symbol), ret)
args.symbol = platformPrefix(symbol)
tp, err = tracefsKprobe(args)
if errors.Is(err, os.ErrNotExist) {
tp, err = tracefsKprobe(symbol, ret)
args.symbol = symbol
tp, err = tracefsKprobe(args)
}
if err != nil {
return nil, fmt.Errorf("creating trace event '%s' in tracefs: %w", symbol, err)
@@ -157,8 +173,8 @@ func kprobe(symbol string, prog *ebpf.Program, ret bool) (*perfEvent, error) {
// pmuKprobe opens a perf event based on the kprobe PMU.
// Returns os.ErrNotExist if the given symbol does not exist in the kernel.
func pmuKprobe(symbol string, ret bool) (*perfEvent, error) {
return pmuProbe(kprobeType, symbol, "", 0, perfAllThreads, ret)
func pmuKprobe(args probeArgs) (*perfEvent, error) {
return pmuProbe(kprobeType, args)
}
// pmuProbe opens a perf event based on a Performance Monitoring Unit.
@@ -168,7 +184,7 @@ func pmuKprobe(symbol string, ret bool) (*perfEvent, error) {
// 33ea4b24277b "perf/core: Implement the 'perf_uprobe' PMU"
//
// Returns ErrNotSupported if the kernel doesn't support perf_[k,u]probe PMU
func pmuProbe(typ probeType, symbol, path string, offset uint64, pid int, ret bool) (*perfEvent, error) {
func pmuProbe(typ probeType, args probeArgs) (*perfEvent, error) {
// Getting the PMU type will fail if the kernel doesn't support
// the perf_[k,u]probe PMU.
et, err := getPMUEventType(typ)
@@ -177,7 +193,7 @@ func pmuProbe(typ probeType, symbol, path string, offset uint64, pid int, ret bo
}
var config uint64
if ret {
if args.ret {
bit, err := typ.RetprobeBit()
if err != nil {
return nil, err
@@ -192,7 +208,7 @@ func pmuProbe(typ probeType, symbol, path string, offset uint64, pid int, ret bo
switch typ {
case kprobeType:
// Create a pointer to a NUL-terminated string for the kernel.
sp, err = unsafeStringPtr(symbol)
sp, err = unsafeStringPtr(args.symbol)
if err != nil {
return nil, err
}
@@ -203,11 +219,15 @@ func pmuProbe(typ probeType, symbol, path string, offset uint64, pid int, ret bo
Config: config, // Retprobe flag
}
case uprobeType:
sp, err = unsafeStringPtr(path)
sp, err = unsafeStringPtr(args.path)
if err != nil {
return nil, err
}
if args.refCtrOffset != 0 {
config |= args.refCtrOffset << uprobeRefCtrOffsetShift
}
attr = unix.PerfEventAttr{
// The minimum size required for PMU uprobes is PERF_ATTR_SIZE_VER1,
// since it added the config2 (Ext2) field. The Size field controls the
@@ -216,23 +236,23 @@ func pmuProbe(typ probeType, symbol, path string, offset uint64, pid int, ret bo
Size: unix.PERF_ATTR_SIZE_VER1,
Type: uint32(et), // PMU event type read from sysfs
Ext1: uint64(uintptr(sp)), // Uprobe path
Ext2: offset, // Uprobe offset
Config: config, // Retprobe flag
Ext2: args.offset, // Uprobe offset
Config: config, // RefCtrOffset, Retprobe flag
}
}
fd, err := unix.PerfEventOpen(&attr, pid, 0, -1, unix.PERF_FLAG_FD_CLOEXEC)
rawFd, err := unix.PerfEventOpen(&attr, args.pid, 0, -1, unix.PERF_FLAG_FD_CLOEXEC)
// Since commit 97c753e62e6c, ENOENT is correctly returned instead of EINVAL
// when trying to create a kretprobe for a missing symbol. Make sure ENOENT
// is returned to the caller.
if errors.Is(err, os.ErrNotExist) || errors.Is(err, unix.EINVAL) {
return nil, fmt.Errorf("symbol '%s' not found: %w", symbol, os.ErrNotExist)
return nil, fmt.Errorf("symbol '%s' not found: %w", args.symbol, os.ErrNotExist)
}
// Since at least commit cb9a19fe4aa51, ENOTSUPP is returned
// when attempting to set a uprobe on a trap instruction.
if errors.Is(err, unix.ENOTSUPP) {
return nil, fmt.Errorf("failed setting uprobe on offset %#x (possible trap insn): %w", offset, err)
return nil, fmt.Errorf("failed setting uprobe on offset %#x (possible trap insn): %w", args.offset, err)
}
if err != nil {
return nil, fmt.Errorf("opening perf event: %w", err)
@@ -241,18 +261,23 @@ func pmuProbe(typ probeType, symbol, path string, offset uint64, pid int, ret bo
// Ensure the string pointer is not collected before PerfEventOpen returns.
runtime.KeepAlive(sp)
fd, err := sys.NewFD(rawFd)
if err != nil {
return nil, err
}
// Kernel has perf_[k,u]probe PMU available, initialize perf event.
return &perfEvent{
fd: internal.NewFD(uint32(fd)),
fd: fd,
pmuID: et,
name: symbol,
typ: typ.PerfEventType(ret),
name: args.symbol,
typ: typ.PerfEventType(args.ret),
}, nil
}
// tracefsKprobe creates a Kprobe tracefs entry.
func tracefsKprobe(symbol string, ret bool) (*perfEvent, error) {
return tracefsProbe(kprobeType, symbol, "", 0, perfAllThreads, ret)
func tracefsKprobe(args probeArgs) (*perfEvent, error) {
return tracefsProbe(kprobeType, args)
}
// tracefsProbe creates a trace event by writing an entry to <tracefs>/[k,u]probe_events.
@@ -261,7 +286,7 @@ func tracefsKprobe(symbol string, ret bool) (*perfEvent, error) {
// Path and offset are only set in the case of uprobe(s) and are used to set
// the executable/library path on the filesystem and the offset where the probe is inserted.
// A perf event is then opened on the newly-created trace event and returned to the caller.
func tracefsProbe(typ probeType, symbol, path string, offset uint64, pid int, ret bool) (*perfEvent, error) {
func tracefsProbe(typ probeType, args probeArgs) (*perfEvent, error) {
// Generate a random string for each trace event we attempt to create.
// This value is used as the 'group' token in tracefs to allow creating
// multiple kprobe trace events with the same name.
@@ -269,32 +294,33 @@ func tracefsProbe(typ probeType, symbol, path string, offset uint64, pid int, re
if err != nil {
return nil, fmt.Errorf("randomizing group name: %w", err)
}
args.group = group
// Before attempting to create a trace event through tracefs,
// check if an event with the same group and name already exists.
// Kernels 4.x and earlier don't return os.ErrExist on writing a duplicate
// entry, so we need to rely on reads for detecting uniqueness.
_, err = getTraceEventID(group, symbol)
_, err = getTraceEventID(group, args.symbol)
if err == nil {
return nil, fmt.Errorf("trace event already exists: %s/%s", group, symbol)
return nil, fmt.Errorf("trace event already exists: %s/%s", group, args.symbol)
}
if err != nil && !errors.Is(err, os.ErrNotExist) {
return nil, fmt.Errorf("checking trace event %s/%s: %w", group, symbol, err)
return nil, fmt.Errorf("checking trace event %s/%s: %w", group, args.symbol, err)
}
// Create the [k,u]probe trace event using tracefs.
if err := createTraceFSProbeEvent(typ, group, symbol, path, offset, ret); err != nil {
if err := createTraceFSProbeEvent(typ, args); err != nil {
return nil, fmt.Errorf("creating probe entry on tracefs: %w", err)
}
// Get the newly-created trace event's id.
tid, err := getTraceEventID(group, symbol)
tid, err := getTraceEventID(group, args.symbol)
if err != nil {
return nil, fmt.Errorf("getting trace event id: %w", err)
}
// Kprobes are ephemeral tracepoints and share the same perf event type.
fd, err := openTracepointPerfEvent(tid, pid)
fd, err := openTracepointPerfEvent(tid, args.pid)
if err != nil {
return nil, err
}
@@ -302,9 +328,9 @@ func tracefsProbe(typ probeType, symbol, path string, offset uint64, pid int, re
return &perfEvent{
fd: fd,
group: group,
name: symbol,
name: args.symbol,
tracefsID: tid,
typ: typ.PerfEventType(ret),
typ: typ.PerfEventType(args.ret),
}, nil
}
@@ -312,7 +338,7 @@ func tracefsProbe(typ probeType, symbol, path string, offset uint64, pid int, re
// <tracefs>/[k,u]probe_events. Returns os.ErrNotExist if symbol is not a valid
// kernel symbol, or if it is not traceable with kprobes. Returns os.ErrExist
// if a probe with the same group and symbol already exists.
func createTraceFSProbeEvent(typ probeType, group, symbol, path string, offset uint64, ret bool) error {
func createTraceFSProbeEvent(typ probeType, args probeArgs) error {
// Open the kprobe_events file in tracefs.
f, err := os.OpenFile(typ.EventsPath(), os.O_APPEND|os.O_WRONLY, 0666)
if err != nil {
@@ -337,7 +363,7 @@ func createTraceFSProbeEvent(typ probeType, group, symbol, path string, offset u
// subsampling or rate limiting logic can be more accurately implemented in
// the eBPF program itself.
// See Documentation/kprobes.txt for more details.
pe = fmt.Sprintf("%s:%s/%s %s", probePrefix(ret), group, symbol, symbol)
pe = fmt.Sprintf("%s:%s/%s %s", probePrefix(args.ret), args.group, args.symbol, args.symbol)
case uprobeType:
// The uprobe_events syntax is as follows:
// p[:[GRP/]EVENT] PATH:OFFSET [FETCHARGS] : Set a probe
@@ -346,18 +372,17 @@ func createTraceFSProbeEvent(typ probeType, group, symbol, path string, offset u
//
// Some examples:
// r:ebpf_1234/readline /bin/bash:0x12345
// p:ebpf_5678/main_mySymbol /bin/mybin:0x12345
// p:ebpf_5678/main_mySymbol /bin/mybin:0x12345(0x123)
//
// See Documentation/trace/uprobetracer.txt for more details.
pathOffset := uprobePathOffset(path, offset)
pe = fmt.Sprintf("%s:%s/%s %s", probePrefix(ret), group, symbol, pathOffset)
pe = fmt.Sprintf("%s:%s/%s %s", probePrefix(args.ret), args.group, args.symbol, uprobeToken(args))
}
_, err = f.WriteString(pe)
// Since commit 97c753e62e6c, ENOENT is correctly returned instead of EINVAL
// when trying to create a kretprobe for a missing symbol. Make sure ENOENT
// is returned to the caller.
if errors.Is(err, os.ErrNotExist) || errors.Is(err, unix.EINVAL) {
return fmt.Errorf("symbol %s not found: %w", symbol, os.ErrNotExist)
return fmt.Errorf("symbol %s not found: %w", args.symbol, os.ErrNotExist)
}
if err != nil {
return fmt.Errorf("writing '%s' to '%s': %w", pe, typ.EventsPath(), err)
+163 -50
View File
@@ -1,12 +1,14 @@
package link
import (
"bytes"
"encoding/binary"
"fmt"
"unsafe"
"github.com/cilium/ebpf"
"github.com/cilium/ebpf/internal"
"github.com/cilium/ebpf/internal/btf"
"github.com/cilium/ebpf/internal/sys"
)
var ErrNotSupported = internal.ErrNotSupported
@@ -35,12 +37,53 @@ type Link interface {
// not called.
Close() error
// Info returns metadata on a link.
//
// May return an error wrapping ErrNotSupported.
Info() (*Info, error)
// Prevent external users from implementing this interface.
isLink()
}
// LoadPinnedLink loads a link that was persisted into a bpffs.
func LoadPinnedLink(fileName string, opts *ebpf.LoadPinOptions) (Link, error) {
raw, err := loadPinnedRawLink(fileName, opts)
if err != nil {
return nil, err
}
return wrapRawLink(raw)
}
// wrap a RawLink in a more specific type if possible.
//
// The function takes ownership of raw and closes it on error.
func wrapRawLink(raw *RawLink) (Link, error) {
info, err := raw.Info()
if err != nil {
raw.Close()
return nil, err
}
switch info.Type {
case RawTracepointType:
return &rawTracepoint{*raw}, nil
case TracingType:
return &tracing{*raw}, nil
case CgroupType:
return &linkCgroup{*raw}, nil
case IterType:
return &Iter{*raw}, nil
case NetNsType:
return &NetNsLink{*raw}, nil
default:
return raw, nil
}
}
// ID uniquely identifies a BPF link.
type ID uint32
type ID = sys.LinkID
// RawLinkOptions control the creation of a raw link.
type RawLinkOptions struct {
@@ -52,13 +95,58 @@ type RawLinkOptions struct {
Attach ebpf.AttachType
// BTF is the BTF of the attachment target.
BTF btf.TypeID
// Flags control the attach behaviour.
Flags uint32
}
// RawLinkInfo contains metadata on a link.
type RawLinkInfo struct {
// Info contains metadata on a link.
type Info struct {
Type Type
ID ID
Program ebpf.ProgramID
extra interface{}
}
// RawLinkInfo contains information on a raw link.
//
// Deprecated: use Info instead.
type RawLinkInfo = Info
type TracingInfo sys.TracingLinkInfo
type CgroupInfo sys.CgroupLinkInfo
type NetNsInfo sys.NetNsLinkInfo
type XDPInfo sys.XDPLinkInfo
// Tracing returns tracing type-specific link info.
//
// Returns nil if the type-specific link info isn't available.
func (r Info) Tracing() *TracingInfo {
e, _ := r.extra.(*TracingInfo)
return e
}
// Cgroup returns cgroup type-specific link info.
//
// Returns nil if the type-specific link info isn't available.
func (r Info) Cgroup() *CgroupInfo {
e, _ := r.extra.(*CgroupInfo)
return e
}
// NetNs returns netns type-specific link info.
//
// Returns nil if the type-specific link info isn't available.
func (r Info) NetNs() *NetNsInfo {
e, _ := r.extra.(*NetNsInfo)
return e
}
// ExtraNetNs returns XDP type-specific link info.
//
// Returns nil if the type-specific link info isn't available.
func (r Info) XDP() *XDPInfo {
e, _ := r.extra.(*XDPInfo)
return e
}
// RawLink is the low-level API to bpf_link.
@@ -66,7 +154,7 @@ type RawLinkInfo struct {
// You should consider using the higher level interfaces in this
// package instead.
type RawLink struct {
fd *internal.FD
fd *sys.FD
pinnedPath string
}
@@ -77,21 +165,22 @@ func AttachRawLink(opts RawLinkOptions) (*RawLink, error) {
}
if opts.Target < 0 {
return nil, fmt.Errorf("invalid target: %s", internal.ErrClosedFd)
return nil, fmt.Errorf("invalid target: %s", sys.ErrClosedFd)
}
progFd := opts.Program.FD()
if progFd < 0 {
return nil, fmt.Errorf("invalid program: %s", internal.ErrClosedFd)
return nil, fmt.Errorf("invalid program: %s", sys.ErrClosedFd)
}
attr := bpfLinkCreateAttr{
targetFd: uint32(opts.Target),
progFd: uint32(progFd),
attachType: opts.Attach,
targetBTFID: uint32(opts.BTF),
attr := sys.LinkCreateAttr{
TargetFd: uint32(opts.Target),
ProgFd: uint32(progFd),
AttachType: sys.AttachType(opts.Attach),
TargetBtfId: uint32(opts.BTF),
Flags: opts.Flags,
}
fd, err := bpfLinkCreate(&attr)
fd, err := sys.LinkCreate(&attr)
if err != nil {
return nil, fmt.Errorf("can't create link: %s", err)
}
@@ -103,13 +192,14 @@ func AttachRawLink(opts RawLinkOptions) (*RawLink, error) {
//
// Returns an error if the pinned link type doesn't match linkType. Pass
// UnspecifiedType to disable this behaviour.
//
// Deprecated: use LoadPinnedLink instead.
func LoadPinnedRawLink(fileName string, linkType Type, opts *ebpf.LoadPinOptions) (*RawLink, error) {
fd, err := internal.BPFObjGet(fileName, opts.Marshal())
link, err := loadPinnedRawLink(fileName, opts)
if err != nil {
return nil, fmt.Errorf("load pinned link: %w", err)
return nil, err
}
link := &RawLink{fd, fileName}
if linkType == UnspecifiedType {
return link, nil
}
@@ -117,7 +207,7 @@ func LoadPinnedRawLink(fileName string, linkType Type, opts *ebpf.LoadPinOptions
info, err := link.Info()
if err != nil {
link.Close()
return nil, fmt.Errorf("get pinned link info: %s", err)
return nil, fmt.Errorf("get pinned link info: %w", err)
}
if info.Type != linkType {
@@ -128,15 +218,23 @@ func LoadPinnedRawLink(fileName string, linkType Type, opts *ebpf.LoadPinOptions
return link, nil
}
func loadPinnedRawLink(fileName string, opts *ebpf.LoadPinOptions) (*RawLink, error) {
fd, err := sys.ObjGet(&sys.ObjGetAttr{
Pathname: sys.NewStringPointer(fileName),
FileFlags: opts.Marshal(),
})
if err != nil {
return nil, fmt.Errorf("load pinned link: %w", err)
}
return &RawLink{fd, fileName}, nil
}
func (l *RawLink) isLink() {}
// FD returns the raw file descriptor.
func (l *RawLink) FD() int {
fd, err := l.fd.Value()
if err != nil {
return -1
}
return int(fd)
return l.fd.Int()
}
// Close breaks the link.
@@ -185,49 +283,64 @@ type RawLinkUpdateOptions struct {
func (l *RawLink) UpdateArgs(opts RawLinkUpdateOptions) error {
newFd := opts.New.FD()
if newFd < 0 {
return fmt.Errorf("invalid program: %s", internal.ErrClosedFd)
return fmt.Errorf("invalid program: %s", sys.ErrClosedFd)
}
var oldFd int
if opts.Old != nil {
oldFd = opts.Old.FD()
if oldFd < 0 {
return fmt.Errorf("invalid replacement program: %s", internal.ErrClosedFd)
return fmt.Errorf("invalid replacement program: %s", sys.ErrClosedFd)
}
}
linkFd, err := l.fd.Value()
if err != nil {
return fmt.Errorf("can't update link: %s", err)
attr := sys.LinkUpdateAttr{
LinkFd: l.fd.Uint(),
NewProgFd: uint32(newFd),
OldProgFd: uint32(oldFd),
Flags: opts.Flags,
}
attr := bpfLinkUpdateAttr{
linkFd: linkFd,
newProgFd: uint32(newFd),
oldProgFd: uint32(oldFd),
flags: opts.Flags,
}
return bpfLinkUpdate(&attr)
}
// struct bpf_link_info
type bpfLinkInfo struct {
typ uint32
id uint32
prog_id uint32
return sys.LinkUpdate(&attr)
}
// Info returns metadata about the link.
func (l *RawLink) Info() (*RawLinkInfo, error) {
var info bpfLinkInfo
err := internal.BPFObjGetInfoByFD(l.fd, unsafe.Pointer(&info), unsafe.Sizeof(info))
if err != nil {
func (l *RawLink) Info() (*Info, error) {
var info sys.LinkInfo
if err := sys.ObjInfo(l.fd, &info); err != nil {
return nil, fmt.Errorf("link info: %s", err)
}
return &RawLinkInfo{
Type(info.typ),
ID(info.id),
ebpf.ProgramID(info.prog_id),
var extra interface{}
switch info.Type {
case CgroupType:
extra = &CgroupInfo{}
case IterType:
// not supported
case NetNsType:
extra = &NetNsInfo{}
case RawTracepointType:
// not supported
case TracingType:
extra = &TracingInfo{}
case XDPType:
extra = &XDPInfo{}
default:
return nil, fmt.Errorf("unknown link info type: %d", info.Type)
}
if info.Type != RawTracepointType && info.Type != IterType {
buf := bytes.NewReader(info.Extra[:])
err := binary.Read(buf, internal.NativeEndian, extra)
if err != nil {
return nil, fmt.Errorf("can not read extra link info: %w", err)
}
}
return &Info{
info.Type,
info.Id,
ebpf.ProgramID(info.ProgId),
extra,
}, nil
}
+5 -17
View File
@@ -6,14 +6,9 @@ import (
"github.com/cilium/ebpf"
)
// NetNsInfo contains metadata about a network namespace link.
type NetNsInfo struct {
RawLinkInfo
}
// NetNsLink is a program attached to a network namespace.
type NetNsLink struct {
*RawLink
RawLink
}
// AttachNetNs attaches a program to a network namespace.
@@ -37,24 +32,17 @@ func AttachNetNs(ns int, prog *ebpf.Program) (*NetNsLink, error) {
return nil, err
}
return &NetNsLink{link}, nil
return &NetNsLink{*link}, nil
}
// LoadPinnedNetNs loads a network namespace link from bpffs.
//
// Deprecated: use LoadPinnedLink instead.
func LoadPinnedNetNs(fileName string, opts *ebpf.LoadPinOptions) (*NetNsLink, error) {
link, err := LoadPinnedRawLink(fileName, NetNsType, opts)
if err != nil {
return nil, err
}
return &NetNsLink{link}, nil
}
// Info returns information about the link.
func (nns *NetNsLink) Info() (*NetNsInfo, error) {
info, err := nns.RawLink.Info()
if err != nil {
return nil, err
}
return &NetNsInfo{*info}, nil
return &NetNsLink{*link}, nil
}
+11 -13
View File
@@ -13,7 +13,7 @@ import (
"unsafe"
"github.com/cilium/ebpf"
"github.com/cilium/ebpf/internal"
"github.com/cilium/ebpf/internal/sys"
"github.com/cilium/ebpf/internal/unix"
)
@@ -82,7 +82,7 @@ type perfEvent struct {
// The event type determines the types of programs that can be attached.
typ perfEventType
fd *internal.FD
fd *sys.FD
}
func (pe *perfEvent) isLink() {}
@@ -109,17 +109,16 @@ func (pe *perfEvent) Update(prog *ebpf.Program) error {
return fmt.Errorf("can't replace eBPF program in perf event: %w", ErrNotSupported)
}
func (pe *perfEvent) Info() (*Info, error) {
return nil, fmt.Errorf("can't get perf event info: %w", ErrNotSupported)
}
func (pe *perfEvent) Close() error {
if pe.fd == nil {
return nil
}
pfd, err := pe.fd.Value()
if err != nil {
return fmt.Errorf("getting perf event fd: %w", err)
}
err = unix.IoctlSetInt(int(pfd), unix.PERF_EVENT_IOC_DISABLE, 0)
err := unix.IoctlSetInt(pe.fd.Int(), unix.PERF_EVENT_IOC_DISABLE, 0)
if err != nil {
return fmt.Errorf("disabling perf event: %w", err)
}
@@ -159,7 +158,7 @@ func (pe *perfEvent) attach(prog *ebpf.Program) error {
return errors.New("cannot attach to nil perf event")
}
if prog.FD() < 0 {
return fmt.Errorf("invalid program: %w", internal.ErrClosedFd)
return fmt.Errorf("invalid program: %w", sys.ErrClosedFd)
}
switch pe.typ {
case kprobeEvent, kretprobeEvent, uprobeEvent, uretprobeEvent:
@@ -174,8 +173,7 @@ func (pe *perfEvent) attach(prog *ebpf.Program) error {
return fmt.Errorf("unknown perf event type: %d", pe.typ)
}
// The ioctl below will fail when the fd is invalid.
kfd, _ := pe.fd.Value()
kfd := pe.fd.Int()
// Assign the eBPF program to the perf event.
err := unix.IoctlSetInt(int(kfd), unix.PERF_EVENT_IOC_SET_BPF, prog.FD())
@@ -235,7 +233,7 @@ func getPMUEventType(typ probeType) (uint64, error) {
// openTracepointPerfEvent opens a tracepoint-type perf event. System-wide
// [k,u]probes created by writing to <tracefs>/[k,u]probe_events are tracepoints
// behind the scenes, and can be attached to using these perf events.
func openTracepointPerfEvent(tid uint64, pid int) (*internal.FD, error) {
func openTracepointPerfEvent(tid uint64, pid int) (*sys.FD, error) {
attr := unix.PerfEventAttr{
Type: unix.PERF_TYPE_TRACEPOINT,
Config: tid,
@@ -249,7 +247,7 @@ func openTracepointPerfEvent(tid uint64, pid int) (*internal.FD, error) {
return nil, fmt.Errorf("opening tracepoint perf event: %w", err)
}
return internal.NewFD(uint32(fd)), nil
return sys.NewFD(fd)
}
// uint64FromFile reads a uint64 from a file. All elements of path are sanitized
+5 -5
View File
@@ -4,7 +4,7 @@ import (
"fmt"
"github.com/cilium/ebpf"
"github.com/cilium/ebpf/internal"
"github.com/cilium/ebpf/internal/sys"
)
type RawAttachProgramOptions struct {
@@ -34,7 +34,7 @@ func RawAttachProgram(opts RawAttachProgramOptions) error {
replaceFd = uint32(opts.Replace.FD())
}
attr := internal.BPFProgAttachAttr{
attr := sys.ProgAttachAttr{
TargetFd: uint32(opts.Target),
AttachBpfFd: uint32(opts.Program.FD()),
ReplaceBpfFd: replaceFd,
@@ -42,7 +42,7 @@ func RawAttachProgram(opts RawAttachProgramOptions) error {
AttachFlags: uint32(opts.Flags),
}
if err := internal.BPFProgAttach(&attr); err != nil {
if err := sys.ProgAttach(&attr); err != nil {
return fmt.Errorf("can't attach program: %w", err)
}
return nil
@@ -63,12 +63,12 @@ func RawDetachProgram(opts RawDetachProgramOptions) error {
return err
}
attr := internal.BPFProgDetachAttr{
attr := sys.ProgDetachAttr{
TargetFd: uint32(opts.Target),
AttachBpfFd: uint32(opts.Program.FD()),
AttachType: uint32(opts.Attach),
}
if err := internal.BPFProgDetach(&attr); err != nil {
if err := sys.ProgDetach(&attr); err != nil {
return fmt.Errorf("can't detach program: %w", err)
}
+43 -17
View File
@@ -1,10 +1,11 @@
package link
import (
"errors"
"fmt"
"github.com/cilium/ebpf"
"github.com/cilium/ebpf/internal"
"github.com/cilium/ebpf/internal/sys"
)
type RawTracepointOptions struct {
@@ -22,40 +23,65 @@ func AttachRawTracepoint(opts RawTracepointOptions) (Link, error) {
return nil, fmt.Errorf("invalid program type %s, expected RawTracepoint(Writable)", t)
}
if opts.Program.FD() < 0 {
return nil, fmt.Errorf("invalid program: %w", internal.ErrClosedFd)
return nil, fmt.Errorf("invalid program: %w", sys.ErrClosedFd)
}
fd, err := bpfRawTracepointOpen(&bpfRawTracepointOpenAttr{
name: internal.NewStringPointer(opts.Name),
fd: uint32(opts.Program.FD()),
fd, err := sys.RawTracepointOpen(&sys.RawTracepointOpenAttr{
Name: sys.NewStringPointer(opts.Name),
ProgFd: uint32(opts.Program.FD()),
})
if err != nil {
return nil, err
}
return &progAttachRawTracepoint{fd: fd}, nil
err = haveBPFLink()
if errors.Is(err, ErrNotSupported) {
// Prior to commit 70ed506c3bbc ("bpf: Introduce pinnable bpf_link abstraction")
// raw_tracepoints are just a plain fd.
return &simpleRawTracepoint{fd}, nil
}
if err != nil {
return nil, err
}
return &rawTracepoint{RawLink{fd: fd}}, nil
}
type progAttachRawTracepoint struct {
fd *internal.FD
type simpleRawTracepoint struct {
fd *sys.FD
}
var _ Link = (*progAttachRawTracepoint)(nil)
var _ Link = (*simpleRawTracepoint)(nil)
func (rt *progAttachRawTracepoint) isLink() {}
func (frt *simpleRawTracepoint) isLink() {}
func (rt *progAttachRawTracepoint) Close() error {
return rt.fd.Close()
func (frt *simpleRawTracepoint) Close() error {
return frt.fd.Close()
}
func (rt *progAttachRawTracepoint) Update(_ *ebpf.Program) error {
return fmt.Errorf("can't update raw_tracepoint: %w", ErrNotSupported)
func (frt *simpleRawTracepoint) Update(_ *ebpf.Program) error {
return fmt.Errorf("update raw_tracepoint: %w", ErrNotSupported)
}
func (rt *progAttachRawTracepoint) Pin(_ string) error {
return fmt.Errorf("can't pin raw_tracepoint: %w", ErrNotSupported)
func (frt *simpleRawTracepoint) Pin(string) error {
return fmt.Errorf("pin raw_tracepoint: %w", ErrNotSupported)
}
func (rt *progAttachRawTracepoint) Unpin() error {
func (frt *simpleRawTracepoint) Unpin() error {
return fmt.Errorf("unpin raw_tracepoint: %w", ErrNotSupported)
}
func (frt *simpleRawTracepoint) Info() (*Info, error) {
return nil, fmt.Errorf("can't get raw_tracepoint info: %w", ErrNotSupported)
}
type rawTracepoint struct {
RawLink
}
var _ Link = (*rawTracepoint)(nil)
func (rt *rawTracepoint) Update(_ *ebpf.Program) error {
return fmt.Errorf("update raw_tracepoint: %w", ErrNotSupported)
}
+40
View File
@@ -0,0 +1,40 @@
package link
import (
"syscall"
"github.com/cilium/ebpf"
"github.com/cilium/ebpf/internal/unix"
)
// AttachSocketFilter attaches a SocketFilter BPF program to a socket.
func AttachSocketFilter(conn syscall.Conn, program *ebpf.Program) error {
rawConn, err := conn.SyscallConn()
if err != nil {
return err
}
var ssoErr error
err = rawConn.Control(func(fd uintptr) {
ssoErr = syscall.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_ATTACH_BPF, program.FD())
})
if ssoErr != nil {
return ssoErr
}
return err
}
// DetachSocketFilter detaches a SocketFilter BPF program from a socket.
func DetachSocketFilter(conn syscall.Conn) error {
rawConn, err := conn.SyscallConn()
if err != nil {
return err
}
var ssoErr error
err = rawConn.Control(func(fd uintptr) {
ssoErr = syscall.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_DETACH_BPF, 0)
})
if ssoErr != nil {
return ssoErr
}
return err
}
+16 -104
View File
@@ -2,28 +2,26 @@ package link
import (
"errors"
"unsafe"
"github.com/cilium/ebpf"
"github.com/cilium/ebpf/asm"
"github.com/cilium/ebpf/internal"
"github.com/cilium/ebpf/internal/sys"
"github.com/cilium/ebpf/internal/unix"
)
// Type is the kind of link.
type Type uint32
type Type = sys.LinkType
// Valid link types.
//
// Equivalent to enum bpf_link_type.
const (
UnspecifiedType Type = iota
RawTracepointType
TracingType
CgroupType
IterType
NetNsType
XDPType
UnspecifiedType = sys.BPF_LINK_TYPE_UNSPEC
RawTracepointType = sys.BPF_LINK_TYPE_RAW_TRACEPOINT
TracingType = sys.BPF_LINK_TYPE_TRACING
CgroupType = sys.BPF_LINK_TYPE_CGROUP
IterType = sys.BPF_LINK_TYPE_ITER
NetNsType = sys.BPF_LINK_TYPE_NETNS
XDPType = sys.BPF_LINK_TYPE_XDP
)
var haveProgAttach = internal.FeatureTest("BPF_PROG_ATTACH", "4.10", func() error {
@@ -69,7 +67,7 @@ var haveProgAttachReplace = internal.FeatureTest("BPF_PROG_ATTACH atomic replace
// We know that we have BPF_PROG_ATTACH since we can load CGroupSKB programs.
// If passing BPF_F_REPLACE gives us EINVAL we know that the feature isn't
// present.
attr := internal.BPFProgAttachAttr{
attr := sys.ProgAttachAttr{
// We rely on this being checked after attachFlags.
TargetFd: ^uint32(0),
AttachBpfFd: uint32(prog.FD()),
@@ -77,7 +75,7 @@ var haveProgAttachReplace = internal.FeatureTest("BPF_PROG_ATTACH atomic replace
AttachFlags: uint32(flagReplace),
}
err = internal.BPFProgAttach(&attr)
err = sys.ProgAttach(&attr)
if errors.Is(err, unix.EINVAL) {
return internal.ErrNotSupported
}
@@ -87,73 +85,14 @@ var haveProgAttachReplace = internal.FeatureTest("BPF_PROG_ATTACH atomic replace
return err
})
type bpfLinkCreateAttr struct {
progFd uint32
targetFd uint32
attachType ebpf.AttachType
flags uint32
targetBTFID uint32
}
func bpfLinkCreate(attr *bpfLinkCreateAttr) (*internal.FD, error) {
ptr, err := internal.BPF(internal.BPF_LINK_CREATE, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
if err != nil {
return nil, err
}
return internal.NewFD(uint32(ptr)), nil
}
type bpfLinkCreateIterAttr struct {
prog_fd uint32
target_fd uint32
attach_type ebpf.AttachType
flags uint32
iter_info internal.Pointer
iter_info_len uint32
}
func bpfLinkCreateIter(attr *bpfLinkCreateIterAttr) (*internal.FD, error) {
ptr, err := internal.BPF(internal.BPF_LINK_CREATE, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
if err != nil {
return nil, err
}
return internal.NewFD(uint32(ptr)), nil
}
type bpfLinkUpdateAttr struct {
linkFd uint32
newProgFd uint32
flags uint32
oldProgFd uint32
}
func bpfLinkUpdate(attr *bpfLinkUpdateAttr) error {
_, err := internal.BPF(internal.BPF_LINK_UPDATE, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
return err
}
var haveBPFLink = internal.FeatureTest("bpf_link", "5.7", func() error {
prog, err := ebpf.NewProgram(&ebpf.ProgramSpec{
Type: ebpf.CGroupSKB,
AttachType: ebpf.AttachCGroupInetIngress,
License: "MIT",
Instructions: asm.Instructions{
asm.Mov.Imm(asm.R0, 0),
asm.Return(),
},
})
if err != nil {
return internal.ErrNotSupported
}
defer prog.Close()
attr := bpfLinkCreateAttr{
attr := sys.LinkCreateAttr{
// This is a hopefully invalid file descriptor, which triggers EBADF.
targetFd: ^uint32(0),
progFd: uint32(prog.FD()),
attachType: ebpf.AttachCGroupInetIngress,
TargetFd: ^uint32(0),
ProgFd: ^uint32(0),
AttachType: sys.AttachType(ebpf.AttachCGroupInetIngress),
}
_, err = bpfLinkCreate(&attr)
_, err := sys.LinkCreate(&attr)
if errors.Is(err, unix.EINVAL) {
return internal.ErrNotSupported
}
@@ -162,30 +101,3 @@ var haveBPFLink = internal.FeatureTest("bpf_link", "5.7", func() error {
}
return err
})
type bpfIterCreateAttr struct {
linkFd uint32
flags uint32
}
func bpfIterCreate(attr *bpfIterCreateAttr) (*internal.FD, error) {
ptr, err := internal.BPF(internal.BPF_ITER_CREATE, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
if err == nil {
return internal.NewFD(uint32(ptr)), nil
}
return nil, err
}
type bpfRawTracepointOpenAttr struct {
name internal.Pointer
fd uint32
_ uint32
}
func bpfRawTracepointOpen(attr *bpfRawTracepointOpenAttr) (*internal.FD, error) {
ptr, err := internal.BPF(internal.BPF_RAW_TRACEPOINT_OPEN, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
if err == nil {
return internal.NewFD(uint32(ptr)), nil
}
return nil, err
}
+153
View File
@@ -0,0 +1,153 @@
package link
import (
"fmt"
"github.com/cilium/ebpf"
"github.com/cilium/ebpf/internal/btf"
"github.com/cilium/ebpf/internal/sys"
)
type tracing struct {
RawLink
}
func (f *tracing) Update(new *ebpf.Program) error {
return fmt.Errorf("tracing update: %w", ErrNotSupported)
}
// AttachFreplace attaches the given eBPF program to the function it replaces.
//
// The program and name can either be provided at link time, or can be provided
// at program load time. If they were provided at load time, they should be nil
// and empty respectively here, as they will be ignored by the kernel.
// Examples:
//
// AttachFreplace(dispatcher, "function", replacement)
// AttachFreplace(nil, "", replacement)
func AttachFreplace(targetProg *ebpf.Program, name string, prog *ebpf.Program) (Link, error) {
if (name == "") != (targetProg == nil) {
return nil, fmt.Errorf("must provide both or neither of name and targetProg: %w", errInvalidInput)
}
if prog == nil {
return nil, fmt.Errorf("prog cannot be nil: %w", errInvalidInput)
}
if prog.Type() != ebpf.Extension {
return nil, fmt.Errorf("eBPF program type %s is not an Extension: %w", prog.Type(), errInvalidInput)
}
var (
target int
typeID btf.TypeID
)
if targetProg != nil {
info, err := targetProg.Info()
if err != nil {
return nil, err
}
btfID, ok := info.BTFID()
if !ok {
return nil, fmt.Errorf("could not get BTF ID for program %s: %w", info.Name, errInvalidInput)
}
btfHandle, err := btf.NewHandleFromID(btfID)
if err != nil {
return nil, err
}
defer btfHandle.Close()
var function *btf.Func
if err := btfHandle.Spec().TypeByName(name, &function); err != nil {
return nil, err
}
target = targetProg.FD()
typeID = function.ID()
}
link, err := AttachRawLink(RawLinkOptions{
Target: target,
Program: prog,
Attach: ebpf.AttachNone,
BTF: typeID,
})
if err != nil {
return nil, err
}
return &tracing{*link}, nil
}
// LoadPinnedFreplace loads a pinned iterator from a bpffs.
//
// Deprecated: use LoadPinnedLink instead.
func LoadPinnedFreplace(fileName string, opts *ebpf.LoadPinOptions) (Link, error) {
link, err := LoadPinnedRawLink(fileName, TracingType, opts)
if err != nil {
return nil, err
}
return &tracing{*link}, err
}
type TracingOptions struct {
// Program must be of type Tracing with attach type
// AttachTraceFEntry/AttachTraceFExit/AttachModifyReturn or
// AttachTraceRawTp.
Program *ebpf.Program
}
type LSMOptions struct {
// Program must be of type LSM with attach type
// AttachLSMMac.
Program *ebpf.Program
}
// attachBTFID links all BPF program types (Tracing/LSM) that they attach to a btf_id.
func attachBTFID(program *ebpf.Program) (Link, error) {
if program.FD() < 0 {
return nil, fmt.Errorf("invalid program %w", sys.ErrClosedFd)
}
fd, err := sys.RawTracepointOpen(&sys.RawTracepointOpenAttr{
ProgFd: uint32(program.FD()),
})
if err != nil {
return nil, err
}
raw := RawLink{fd: fd}
info, err := raw.Info()
if err != nil {
raw.Close()
return nil, err
}
if info.Type == RawTracepointType {
// Sadness upon sadness: a Tracing program with AttachRawTp returns
// a raw_tracepoint link. Other types return a tracing link.
return &rawTracepoint{raw}, nil
}
return &tracing{RawLink: RawLink{fd: fd}}, nil
}
// AttachTracing links a tracing (fentry/fexit/fmod_ret) BPF program or
// a BTF-powered raw tracepoint (tp_btf) BPF Program to a BPF hook defined
// in kernel modules.
func AttachTracing(opts TracingOptions) (Link, error) {
if t := opts.Program.Type(); t != ebpf.Tracing {
return nil, fmt.Errorf("invalid program type %s, expected Tracing", t)
}
return attachBTFID(opts.Program)
}
// AttachLSM links a Linux security module (LSM) BPF Program to a BPF
// hook defined in kernel modules.
func AttachLSM(opts LSMOptions) (Link, error) {
if t := opts.Program.Type(); t != ebpf.LSM {
return nil, fmt.Errorf("invalid program type %s, expected LSM", t)
}
return attachBTFID(opts.Program)
}
+66 -19
View File
@@ -26,6 +26,17 @@ var (
err error
}{}
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.FeatureTest("RefCtrOffsetPMU", "4.20", func() error {
_, err := os.Stat(uprobeRefCtrOffsetPMUPath)
if err != nil {
return internal.ErrNotSupported
}
return nil
})
// ErrNoSymbol indicates that the given symbol was not found
// in the ELF symbols table.
ErrNoSymbol = errors.New("not found")
@@ -48,11 +59,22 @@ type UprobeOptions struct {
// Only set the uprobe on the given process ID. Useful when tracing
// shared library calls or programs that have many running instances.
PID int
// Automatically manage SDT reference counts (semaphores).
//
// If this field is set, the Kernel will increment/decrement the
// semaphore located in the process memory at the provided address on
// probe attach/detach.
//
// See also:
// sourceware.org/systemtap/wiki/UserSpaceProbeImplementation (Semaphore Handling)
// github.com/torvalds/linux/commit/1cc33161a83d
// github.com/torvalds/linux/commit/a6ca88b241d5
RefCtrOffset uint64
}
// To open a new Executable, use:
//
// OpenExecutable("/bin/bash")
// OpenExecutable("/bin/bash")
//
// The returned value can then be used to open Uprobe(s).
func OpenExecutable(path string) (*Executable, error) {
@@ -161,7 +183,7 @@ func (ex *Executable) offset(symbol string) (uint64, error) {
// When using symbols which belongs to shared libraries,
// an offset must be provided via options:
//
// up, err := ex.Uprobe("main", prog, &UprobeOptions{Offset: 0x123})
// up, err := ex.Uprobe("main", prog, &UprobeOptions{Offset: 0x123})
//
// Losing the reference to the resulting Link (up) will close the Uprobe
// and prevent further execution of prog. The Link must be Closed during
@@ -193,7 +215,7 @@ func (ex *Executable) Uprobe(symbol string, prog *ebpf.Program, opts *UprobeOpti
// When using symbols which belongs to shared libraries,
// an offset must be provided via options:
//
// up, err := ex.Uretprobe("main", prog, &UprobeOptions{Offset: 0x123})
// up, err := ex.Uretprobe("main", prog, &UprobeOptions{Offset: 0x123})
//
// Losing the reference to the resulting Link (up) will close the Uprobe
// and prevent further execution of prog. The Link must be Closed during
@@ -225,11 +247,12 @@ func (ex *Executable) uprobe(symbol string, prog *ebpf.Program, opts *UprobeOpti
if prog.Type() != ebpf.Kprobe {
return nil, fmt.Errorf("eBPF program type %s is not Kprobe: %w", prog.Type(), errInvalidInput)
}
if opts == nil {
opts = &UprobeOptions{}
}
var offset uint64
if opts != nil && opts.Offset != 0 {
offset = opts.Offset
} else {
offset := opts.Offset
if offset == 0 {
off, err := ex.offset(symbol)
if err != nil {
return nil, err
@@ -237,13 +260,28 @@ func (ex *Executable) uprobe(symbol string, prog *ebpf.Program, opts *UprobeOpti
offset = off
}
pid := perfAllThreads
if opts != nil && opts.PID != 0 {
pid = opts.PID
pid := opts.PID
if pid == 0 {
pid = perfAllThreads
}
if opts.RefCtrOffset != 0 {
if err := haveRefCtrOffsetPMU(); err != nil {
return nil, fmt.Errorf("uprobe ref_ctr_offset: %w", err)
}
}
args := probeArgs{
symbol: symbol,
path: ex.path,
offset: offset,
pid: pid,
refCtrOffset: opts.RefCtrOffset,
ret: ret,
}
// Use uprobe PMU if the kernel has it available.
tp, err := pmuUprobe(symbol, ex.path, offset, pid, ret)
tp, err := pmuUprobe(args)
if err == nil {
return tp, nil
}
@@ -252,7 +290,8 @@ func (ex *Executable) uprobe(symbol string, prog *ebpf.Program, opts *UprobeOpti
}
// Use tracefs if uprobe PMU is missing.
tp, err = tracefsUprobe(uprobeSanitizedSymbol(symbol), ex.path, offset, pid, ret)
args.symbol = uprobeSanitizedSymbol(symbol)
tp, err = tracefsUprobe(args)
if err != nil {
return nil, fmt.Errorf("creating trace event '%s:%s' in tracefs: %w", ex.path, symbol, err)
}
@@ -261,13 +300,13 @@ func (ex *Executable) uprobe(symbol string, prog *ebpf.Program, opts *UprobeOpti
}
// pmuUprobe opens a perf event based on the uprobe PMU.
func pmuUprobe(symbol, path string, offset uint64, pid int, ret bool) (*perfEvent, error) {
return pmuProbe(uprobeType, symbol, path, offset, pid, ret)
func pmuUprobe(args probeArgs) (*perfEvent, error) {
return pmuProbe(uprobeType, args)
}
// tracefsUprobe creates a Uprobe tracefs entry.
func tracefsUprobe(symbol, path string, offset uint64, pid int, ret bool) (*perfEvent, error) {
return tracefsProbe(uprobeType, symbol, path, offset, pid, ret)
func tracefsUprobe(args probeArgs) (*perfEvent, error) {
return tracefsProbe(uprobeType, args)
}
// uprobeSanitizedSymbol replaces every invalid characted for the tracefs api with an underscore.
@@ -275,9 +314,17 @@ func uprobeSanitizedSymbol(symbol string) string {
return rgxUprobeSymbol.ReplaceAllString(symbol, "_")
}
// uprobePathOffset creates the PATH:OFFSET token for the tracefs api.
func uprobePathOffset(path string, offset uint64) string {
return fmt.Sprintf("%s:%#x", path, offset)
// uprobeToken creates the PATH:OFFSET(REF_CTR_OFFSET) token for the tracefs api.
func uprobeToken(args probeArgs) string {
po := fmt.Sprintf("%s:%#x", args.path, args.offset)
if args.refCtrOffset != 0 {
// This is not documented in Documentation/trace/uprobetracer.txt.
// elixir.bootlin.com/linux/v5.15-rc7/source/kernel/trace/trace.c#L5564
po += fmt.Sprintf("(%#x)", args.refCtrOffset)
}
return po
}
func uretprobeBit() (uint64, error) {
+54
View File
@@ -0,0 +1,54 @@
package link
import (
"fmt"
"github.com/cilium/ebpf"
)
// XDPAttachFlags represents how XDP program will be attached to interface.
type XDPAttachFlags uint32
const (
// XDPGenericMode (SKB) links XDP BPF program for drivers which do
// not yet support native XDP.
XDPGenericMode XDPAttachFlags = 1 << (iota + 1)
// XDPDriverMode links XDP BPF program into the drivers receive path.
XDPDriverMode
// XDPOffloadMode offloads the entire XDP BPF program into hardware.
XDPOffloadMode
)
type XDPOptions struct {
// Program must be an XDP BPF program.
Program *ebpf.Program
// Interface is the interface index to attach program to.
Interface int
// Flags is one of XDPAttachFlags (optional).
//
// Only one XDP mode should be set, without flag defaults
// to driver/generic mode (best effort).
Flags XDPAttachFlags
}
// AttachXDP links an XDP BPF program to an XDP hook.
func AttachXDP(opts XDPOptions) (Link, error) {
if t := opts.Program.Type(); t != ebpf.XDP {
return nil, fmt.Errorf("invalid program type %s, expected XDP", t)
}
if opts.Interface < 1 {
return nil, fmt.Errorf("invalid interface index: %d", opts.Interface)
}
rawLink, err := AttachRawLink(RawLinkOptions{
Program: opts.Program,
Attach: ebpf.AttachXDP,
Target: opts.Interface,
Flags: uint32(opts.Flags),
})
return rawLink, err
}
+103 -69
View File
@@ -1,47 +1,80 @@
package ebpf
import (
"bytes"
"encoding/binary"
"fmt"
"github.com/cilium/ebpf/asm"
"github.com/cilium/ebpf/internal/btf"
)
// link resolves bpf-to-bpf calls.
// The linker is responsible for resolving bpf-to-bpf calls between programs
// within an ELF. Each BPF program must be a self-contained binary blob,
// so when an instruction in one ELF program section wants to jump to
// a function in another, the linker needs to pull in the bytecode
// (and BTF info) of the target function and concatenate the instruction
// streams.
//
// Each library may contain multiple functions / labels, and is only linked
// if prog references one of these functions.
// Later on in the pipeline, all call sites are fixed up with relative jumps
// within this newly-created instruction stream to then finally hand off to
// the kernel with BPF_PROG_LOAD.
//
// Libraries also linked.
func link(prog *ProgramSpec, libs []*ProgramSpec) error {
var (
linked = make(map[*ProgramSpec]bool)
pending = []asm.Instructions{prog.Instructions}
insns asm.Instructions
)
for len(pending) > 0 {
insns, pending = pending[0], pending[1:]
for _, lib := range libs {
if linked[lib] {
continue
}
// Each function is denoted by an ELF symbol and the compiler takes care of
// register setup before each jump instruction.
needed, err := needSection(insns, lib.Instructions)
if err != nil {
return fmt.Errorf("linking %s: %w", lib.Name, err)
}
// populateReferences populates all of progs' Instructions and references
// with their full dependency chains including transient dependencies.
func populateReferences(progs map[string]*ProgramSpec) error {
type props struct {
insns asm.Instructions
refs map[string]*ProgramSpec
}
if !needed {
continue
}
out := make(map[string]props)
linked[lib] = true
prog.Instructions = append(prog.Instructions, lib.Instructions...)
pending = append(pending, lib.Instructions)
// Resolve and store direct references between all progs.
if err := findReferences(progs); err != nil {
return fmt.Errorf("finding references: %w", err)
}
if prog.BTF != nil && lib.BTF != nil {
if err := prog.BTF.Append(lib.BTF); err != nil {
return fmt.Errorf("linking BTF of %s: %w", lib.Name, err)
}
// Flatten all progs' instruction streams.
for name, prog := range progs {
insns, refs := prog.flatten(nil)
prop := props{
insns: insns,
refs: refs,
}
out[name] = prop
}
// Replace all progs' instructions and references
for name, props := range out {
progs[name].Instructions = props.insns
progs[name].references = props.refs
}
return nil
}
// findReferences finds bpf-to-bpf calls between progs and populates each
// prog's references field with its direct neighbours.
func findReferences(progs map[string]*ProgramSpec) error {
// Check all ProgramSpecs in the collection against each other.
for _, prog := range progs {
prog.references = make(map[string]*ProgramSpec)
// Look up call targets in progs and store pointers to their corresponding
// ProgramSpecs as direct references.
for refname := range prog.Instructions.FunctionReferences() {
ref := progs[refname]
// Call targets are allowed to be missing from an ELF. This occurs when
// a program calls into a forward function declaration that is left
// unimplemented. This is caught at load time during fixups.
if ref != nil {
prog.references[refname] = ref
}
}
}
@@ -49,39 +82,36 @@ func link(prog *ProgramSpec, libs []*ProgramSpec) error {
return nil
}
func needSection(insns, section asm.Instructions) (bool, error) {
// A map of symbols to the libraries which contain them.
symbols, err := section.SymbolOffsets()
if err != nil {
return false, err
// marshalFuncInfos returns the BTF func infos of all progs in order.
func marshalFuncInfos(layout []reference) ([]byte, error) {
if len(layout) == 0 {
return nil, nil
}
for _, ins := range insns {
if ins.Reference == "" {
continue
buf := bytes.NewBuffer(make([]byte, 0, binary.Size(&btf.FuncInfo{})*len(layout)))
for _, sym := range layout {
if err := sym.spec.BTF.FuncInfo.Marshal(buf, sym.offset); err != nil {
return nil, fmt.Errorf("marshaling prog %s func info: %w", sym.spec.Name, err)
}
if ins.OpCode.JumpOp() != asm.Call || ins.Src != asm.PseudoCall {
continue
}
if ins.Constant != -1 {
// This is already a valid call, no need to link again.
continue
}
if _, ok := symbols[ins.Reference]; !ok {
// Symbol isn't available in this section
continue
}
// At this point we know that at least one function in the
// library is called from insns, so we have to link it.
return true, nil
}
// None of the functions in the section are called.
return false, nil
return buf.Bytes(), nil
}
// marshalLineInfos returns the BTF line infos of all progs in order.
func marshalLineInfos(layout []reference) ([]byte, error) {
if len(layout) == 0 {
return nil, nil
}
buf := bytes.NewBuffer(make([]byte, 0, binary.Size(&btf.LineInfo{})*len(layout)))
for _, sym := range layout {
if err := sym.spec.BTF.LineInfos.Marshal(buf, sym.offset); err != nil {
return nil, fmt.Errorf("marshaling prog %s line infos: %w", sym.spec.Name, err)
}
}
return buf.Bytes(), nil
}
func fixupJumpsAndCalls(insns asm.Instructions) error {
@@ -111,28 +141,32 @@ func fixupJumpsAndCalls(insns asm.Instructions) error {
continue
}
symOffset, ok := symbolOffsets[ins.Reference]
switch {
case ins.IsFunctionCall() && ins.Constant == -1:
// Rewrite bpf to bpf call
callOffset, ok := symbolOffsets[ins.Reference]
case ins.IsFunctionReference() && ins.Constant == -1:
if !ok {
return fmt.Errorf("call at %d: reference to missing symbol %q", i, ins.Reference)
break
}
ins.Constant = int64(callOffset - offset - 1)
ins.Constant = int64(symOffset - offset - 1)
continue
case ins.OpCode.Class() == asm.JumpClass && ins.Offset == -1:
// Rewrite jump to label
jumpOffset, ok := symbolOffsets[ins.Reference]
case ins.OpCode.Class().IsJump() && ins.Offset == -1:
if !ok {
return fmt.Errorf("jump at %d: reference to missing symbol %q", i, ins.Reference)
break
}
ins.Offset = int16(jumpOffset - offset - 1)
ins.Offset = int16(symOffset - offset - 1)
continue
case ins.IsLoadFromMap() && ins.MapPtr() == -1:
return fmt.Errorf("map %s: %w", ins.Reference, errUnsatisfiedReference)
return fmt.Errorf("map %s: %w", ins.Reference, errUnsatisfiedMap)
default:
// no fixup needed
continue
}
return fmt.Errorf("%s at insn %d: symbol %q: %w", ins.OpCode, i, ins.Reference, errUnsatisfiedProgram)
}
// fixupBPFCalls replaces bpf_probe_read_{kernel,user}[_str] with bpf_probe_read[_str] on older kernels
+304 -124
View File
@@ -5,17 +5,22 @@ import (
"errors"
"fmt"
"io"
"math/rand"
"path/filepath"
"reflect"
"strings"
"time"
"unsafe"
"github.com/cilium/ebpf/internal"
"github.com/cilium/ebpf/internal/btf"
"github.com/cilium/ebpf/internal/sys"
"github.com/cilium/ebpf/internal/unix"
)
// Errors returned by Map and MapIterator methods.
var (
errFirstKeyNotFound = errors.New("first key not found")
ErrKeyNotExist = errors.New("key does not exist")
ErrKeyExist = errors.New("key already exists")
ErrIterationAborted = errors.New("iteration aborted")
@@ -67,9 +72,9 @@ type MapSpec struct {
InnerMap *MapSpec
// Extra trailing bytes found in the ELF map definition when using structs
// larger than libbpf's bpf_map_def. Must be empty before instantiating
// the MapSpec into a Map.
Extra bytes.Reader
// larger than libbpf's bpf_map_def. nil if no trailing bytes were present.
// Must be nil or empty before instantiating the MapSpec into a Map.
Extra *bytes.Reader
// The BTF associated with this map.
BTF *btf.Map
@@ -97,6 +102,12 @@ func (ms *MapSpec) Copy() *MapSpec {
return &cpy
}
// hasBTF returns true if the MapSpec has a valid BTF spec and if its
// map type supports associated BTF metadata in the kernel.
func (ms *MapSpec) hasBTF() bool {
return ms.BTF != nil && ms.Type.hasBTF()
}
func (ms *MapSpec) clampPerfEventArraySize() error {
if ms.Type != PerfEventArray {
return nil
@@ -151,7 +162,7 @@ func (ms *MapSpec) checkCompatibility(m *Map) error {
// if you require custom encoding.
type Map struct {
name string
fd *internal.FD
fd *sys.FD
typ MapType
keySize uint32
valueSize uint32
@@ -166,18 +177,19 @@ type Map struct {
//
// You should not use fd after calling this function.
func NewMapFromFD(fd int) (*Map, error) {
if fd < 0 {
return nil, errors.New("invalid fd")
f, err := sys.NewFD(fd)
if err != nil {
return nil, err
}
return newMapFromFD(internal.NewFD(uint32(fd)))
return newMapFromFD(f)
}
func newMapFromFD(fd *internal.FD) (*Map, error) {
func newMapFromFD(fd *sys.FD) (*Map, error) {
info, err := newMapInfoFromFd(fd)
if err != nil {
fd.Close()
return nil, fmt.Errorf("get map info: %s", err)
return nil, fmt.Errorf("get map info: %w", err)
}
return newMap(fd, info.Name, info.Type, info.KeySize, info.ValueSize, info.MaxEntries, info.Flags)
@@ -257,7 +269,7 @@ func newMapWithOptions(spec *MapSpec, opts MapOptions, handles *handleCache) (_
return nil, fmt.Errorf("pin type %d: %w", int(spec.Pinning), ErrNotSupported)
}
var innerFd *internal.FD
var innerFd *sys.FD
if spec.Type == ArrayOfMaps || spec.Type == HashOfMaps {
if spec.InnerMap == nil {
return nil, fmt.Errorf("%s requires InnerMap", spec.Type)
@@ -288,7 +300,7 @@ func newMapWithOptions(spec *MapSpec, opts MapOptions, handles *handleCache) (_
if spec.Pinning == PinByName {
path := filepath.Join(opts.PinPath, spec.Name)
if err := m.Pin(path); err != nil {
return nil, fmt.Errorf("pin map: %s", err)
return nil, fmt.Errorf("pin map: %w", err)
}
}
@@ -297,7 +309,7 @@ func newMapWithOptions(spec *MapSpec, opts MapOptions, handles *handleCache) (_
// 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 *internal.FD, opts MapOptions, handles *handleCache) (_ *Map, err error) {
func (spec *MapSpec) createMap(inner *sys.FD, opts MapOptions, handles *handleCache) (_ *Map, err error) {
closeOnError := func(closer io.Closer) {
if err != nil {
closer.Close()
@@ -310,8 +322,10 @@ func (spec *MapSpec) createMap(inner *internal.FD, opts MapOptions, handles *han
// additional 'inner_map_idx' and later 'numa_node' fields.
// In order to support loading these definitions, tolerate the presence of
// extra bytes, but require them to be zeroes.
if _, err := io.Copy(internal.DiscardZeroes{}, &spec.Extra); err != nil {
return nil, errors.New("extra contains unhandled non-zero bytes, drain before creating map")
if spec.Extra != nil {
if _, err := io.Copy(internal.DiscardZeroes{}, spec.Extra); err != nil {
return nil, errors.New("extra contains unhandled non-zero bytes, drain before creating map")
}
}
switch spec.Type {
@@ -360,49 +374,48 @@ func (spec *MapSpec) createMap(inner *internal.FD, opts MapOptions, handles *han
return nil, fmt.Errorf("map create: %w", err)
}
}
attr := internal.BPFMapCreateAttr{
MapType: uint32(spec.Type),
KeySize: spec.KeySize,
ValueSize: spec.ValueSize,
MaxEntries: spec.MaxEntries,
Flags: spec.Flags,
NumaNode: spec.NumaNode,
}
if inner != nil {
var err error
attr.InnerMapFd, err = inner.Value()
if err != nil {
if spec.Flags&unix.BPF_F_NO_PREALLOC > 0 {
if err := haveNoPreallocMaps(); err != nil {
return nil, fmt.Errorf("map create: %w", err)
}
}
if haveObjName() == nil {
attr.MapName = internal.NewBPFObjName(spec.Name)
attr := sys.MapCreateAttr{
MapType: sys.MapType(spec.Type),
KeySize: spec.KeySize,
ValueSize: spec.ValueSize,
MaxEntries: spec.MaxEntries,
MapFlags: spec.Flags,
NumaNode: spec.NumaNode,
}
var btfDisabled bool
if spec.BTF != nil {
if inner != nil {
attr.InnerMapFd = inner.Uint()
}
if haveObjName() == nil {
attr.MapName = sys.NewObjName(spec.Name)
}
if spec.hasBTF() {
handle, err := handles.btfHandle(spec.BTF.Spec)
btfDisabled = errors.Is(err, btf.ErrNotSupported)
if err != nil && !btfDisabled {
if err != nil && !errors.Is(err, btf.ErrNotSupported) {
return nil, fmt.Errorf("load BTF: %w", err)
}
if handle != nil {
attr.BTFFd = uint32(handle.FD())
attr.BTFKeyTypeID = uint32(spec.BTF.Key.ID())
attr.BTFValueTypeID = uint32(spec.BTF.Value.ID())
attr.BtfFd = uint32(handle.FD())
attr.BtfKeyTypeId = uint32(spec.BTF.Key.ID())
attr.BtfValueTypeId = uint32(spec.BTF.Value.ID())
}
}
fd, err := internal.BPFMapCreate(&attr)
fd, err := sys.MapCreate(&attr)
if err != nil {
if errors.Is(err, unix.EPERM) {
return nil, fmt.Errorf("map create: %w (MEMLOCK bay be too low, consider rlimit.RemoveMemlock)", err)
return nil, fmt.Errorf("map create: %w (MEMLOCK may be too low, consider rlimit.RemoveMemlock)", err)
}
if btfDisabled {
if !spec.hasBTF() {
return nil, fmt.Errorf("map create without BTF: %w", err)
}
return nil, fmt.Errorf("map create: %w", err)
@@ -419,7 +432,7 @@ func (spec *MapSpec) createMap(inner *internal.FD, opts MapOptions, handles *han
// newMap allocates and returns a new Map structure.
// Sets the fullValueSize on per-CPU maps.
func newMap(fd *internal.FD, name string, typ MapType, keySize, valueSize, maxEntries, flags uint32) (*Map, error) {
func newMap(fd *sys.FD, name string, typ MapType, keySize, valueSize, maxEntries, flags uint32) (*Map, error) {
m := &Map{
name,
fd,
@@ -482,6 +495,12 @@ func (m *Map) Info() (*MapInfo, error) {
return newMapInfoFromFd(m.fd)
}
// MapLookupFlags controls the behaviour of the map lookup calls.
type MapLookupFlags uint64
// LookupLock look up the value of a spin-locked map.
const LookupLock MapLookupFlags = 4
// Lookup retrieves a value from a Map.
//
// Calls Close() on valueOut if it is of type **Map or **Program,
@@ -490,7 +509,26 @@ func (m *Map) Info() (*MapInfo, error) {
// Returns an error if the key doesn't exist, see ErrKeyNotExist.
func (m *Map) Lookup(key, valueOut interface{}) error {
valuePtr, valueBytes := makeBuffer(valueOut, m.fullValueSize)
if err := m.lookup(key, valuePtr); err != nil {
if err := m.lookup(key, valuePtr, 0); err != nil {
return err
}
return m.unmarshalValue(valueOut, valueBytes)
}
// LookupWithFlags retrieves a value from a Map with flags.
//
// Passing LookupLock flag will look up the value of a spin-locked
// map without returning the lock. This must be specified if the
// elements contain a spinlock.
//
// Calls Close() on valueOut if it is of type **Map or **Program,
// and *valueOut is not nil.
//
// Returns an error if the key doesn't exist, see ErrKeyNotExist.
func (m *Map) LookupWithFlags(key, valueOut interface{}, flags MapLookupFlags) error {
valuePtr, valueBytes := makeBuffer(valueOut, m.fullValueSize)
if err := m.lookup(key, valuePtr, flags); err != nil {
return err
}
@@ -501,18 +539,18 @@ func (m *Map) Lookup(key, valueOut interface{}) error {
//
// Returns ErrKeyNotExist if the key doesn't exist.
func (m *Map) LookupAndDelete(key, valueOut interface{}) error {
valuePtr, valueBytes := makeBuffer(valueOut, m.fullValueSize)
return m.lookupAndDelete(key, valueOut, 0)
}
keyPtr, err := m.marshalKey(key)
if err != nil {
return fmt.Errorf("can't marshal key: %w", err)
}
if err := bpfMapLookupAndDelete(m.fd, keyPtr, valuePtr); err != nil {
return fmt.Errorf("lookup and delete failed: %w", err)
}
return m.unmarshalValue(valueOut, valueBytes)
// LookupAndDeleteWithFlags retrieves and deletes a value from a Map.
//
// Passing LookupLock flag will look up and delete the value of a spin-locked
// map without returning the lock. This must be specified if the elements
// contain a spinlock.
//
// Returns ErrKeyNotExist if the key doesn't exist.
func (m *Map) LookupAndDeleteWithFlags(key, valueOut interface{}, flags MapLookupFlags) error {
return m.lookupAndDelete(key, valueOut, flags)
}
// LookupBytes gets a value from Map.
@@ -520,9 +558,9 @@ func (m *Map) LookupAndDelete(key, valueOut interface{}) error {
// Returns a nil value if a key doesn't exist.
func (m *Map) LookupBytes(key interface{}) ([]byte, error) {
valueBytes := make([]byte, m.fullValueSize)
valuePtr := internal.NewSlicePointer(valueBytes)
valuePtr := sys.NewSlicePointer(valueBytes)
err := m.lookup(key, valuePtr)
err := m.lookup(key, valuePtr, 0)
if errors.Is(err, ErrKeyNotExist) {
return nil, nil
}
@@ -530,18 +568,47 @@ func (m *Map) LookupBytes(key interface{}) ([]byte, error) {
return valueBytes, err
}
func (m *Map) lookup(key interface{}, valueOut internal.Pointer) error {
func (m *Map) lookup(key interface{}, valueOut sys.Pointer, flags MapLookupFlags) error {
keyPtr, err := m.marshalKey(key)
if err != nil {
return fmt.Errorf("can't marshal key: %w", err)
}
if err = bpfMapLookupElem(m.fd, keyPtr, valueOut); err != nil {
return fmt.Errorf("lookup failed: %w", err)
attr := sys.MapLookupElemAttr{
MapFd: m.fd.Uint(),
Key: keyPtr,
Value: valueOut,
Flags: uint64(flags),
}
if err = sys.MapLookupElem(&attr); err != nil {
return fmt.Errorf("lookup: %w", wrapMapError(err))
}
return nil
}
func (m *Map) lookupAndDelete(key, valueOut interface{}, flags MapLookupFlags) error {
valuePtr, valueBytes := makeBuffer(valueOut, m.fullValueSize)
keyPtr, err := m.marshalKey(key)
if err != nil {
return fmt.Errorf("can't marshal key: %w", err)
}
attr := sys.MapLookupAndDeleteElemAttr{
MapFd: m.fd.Uint(),
Key: keyPtr,
Value: valuePtr,
Flags: uint64(flags),
}
if err := sys.MapLookupAndDeleteElem(&attr); err != nil {
return fmt.Errorf("lookup and delete: %w", wrapMapError(err))
}
return m.unmarshalValue(valueOut, valueBytes)
}
// MapUpdateFlags controls the behaviour of the Map.Update call.
//
// The exact semantics depend on the specific MapType.
@@ -554,6 +621,8 @@ const (
UpdateNoExist MapUpdateFlags = 1 << (iota - 1)
// UpdateExist updates an existing element.
UpdateExist
// UpdateLock updates elements under bpf_spin_lock.
UpdateLock
)
// Put replaces or creates a value in map.
@@ -575,8 +644,15 @@ func (m *Map) Update(key, value interface{}, flags MapUpdateFlags) error {
return fmt.Errorf("can't marshal value: %w", err)
}
if err = bpfMapUpdateElem(m.fd, keyPtr, valuePtr, uint64(flags)); err != nil {
return fmt.Errorf("update failed: %w", err)
attr := sys.MapUpdateElemAttr{
MapFd: m.fd.Uint(),
Key: keyPtr,
Value: valuePtr,
Flags: uint64(flags),
}
if err = sys.MapUpdateElem(&attr); err != nil {
return fmt.Errorf("update: %w", wrapMapError(err))
}
return nil
@@ -591,8 +667,13 @@ func (m *Map) Delete(key interface{}) error {
return fmt.Errorf("can't marshal key: %w", err)
}
if err = bpfMapDeleteElem(m.fd, keyPtr); err != nil {
return fmt.Errorf("delete failed: %w", err)
attr := sys.MapDeleteElemAttr{
MapFd: m.fd.Uint(),
Key: keyPtr,
}
if err = sys.MapDeleteElem(&attr); err != nil {
return fmt.Errorf("delete: %w", wrapMapError(err))
}
return nil
}
@@ -624,7 +705,7 @@ func (m *Map) NextKey(key, nextKeyOut interface{}) error {
// Returns nil if there are no more keys.
func (m *Map) NextKeyBytes(key interface{}) ([]byte, error) {
nextKey := make([]byte, m.keySize)
nextKeyPtr := internal.NewSlicePointer(nextKey)
nextKeyPtr := sys.NewSlicePointer(nextKey)
err := m.nextKey(key, nextKeyPtr)
if errors.Is(err, ErrKeyNotExist) {
@@ -634,9 +715,9 @@ func (m *Map) NextKeyBytes(key interface{}) ([]byte, error) {
return nextKey, err
}
func (m *Map) nextKey(key interface{}, nextKeyOut internal.Pointer) error {
func (m *Map) nextKey(key interface{}, nextKeyOut sys.Pointer) error {
var (
keyPtr internal.Pointer
keyPtr sys.Pointer
err error
)
@@ -647,12 +728,77 @@ func (m *Map) nextKey(key interface{}, nextKeyOut internal.Pointer) error {
}
}
if err = bpfMapGetNextKey(m.fd, keyPtr, nextKeyOut); err != nil {
return fmt.Errorf("next key failed: %w", err)
attr := sys.MapGetNextKeyAttr{
MapFd: m.fd.Uint(),
Key: keyPtr,
NextKey: nextKeyOut,
}
if err = sys.MapGetNextKey(&attr); err != nil {
// Kernels 4.4.131 and earlier return EFAULT instead of a pointer to the
// first map element when a nil key pointer is specified.
if key == nil && errors.Is(err, unix.EFAULT) {
var guessKey sys.Pointer
guessKey, err = m.guessNonExistentKey()
if err != nil {
return fmt.Errorf("can't guess starting key: %w", err)
}
// Retry the syscall with a valid non-existing key.
attr.Key = guessKey
if err = sys.MapGetNextKey(&attr); err == nil {
return nil
}
}
return fmt.Errorf("next key: %w", wrapMapError(err))
}
return nil
}
// guessNonExistentKey attempts to perform a map lookup that returns ENOENT.
// This is necessary on kernels before 4.4.132, since those don't support
// iterating maps from the start by providing an invalid key pointer.
func (m *Map) guessNonExistentKey() (startKey sys.Pointer, err error) {
// Provide an invalid value pointer to prevent a copy on the kernel side.
valuePtr := sys.NewPointer(unsafe.Pointer(^uintptr(0)))
randKey := make([]byte, int(m.keySize))
for i := 0; i < 4; i++ {
switch i {
// For hash maps, the 0 key is less likely to be occupied. They're often
// used for storing data related to pointers, and their access pattern is
// generally scattered across the keyspace.
case 0:
// An all-0xff key is guaranteed to be out of bounds of any array, since
// those have a fixed key size of 4 bytes. The only corner case being
// arrays with 2^32 max entries, but those are prohibitively expensive
// in many environments.
case 1:
for r := range randKey {
randKey[r] = 0xff
}
// Inspired by BCC, 0x55 is an alternating binary pattern (0101), so
// is unlikely to be taken.
case 2:
for r := range randKey {
randKey[r] = 0x55
}
// Last ditch effort, generate a random key.
case 3:
rand.New(rand.NewSource(time.Now().UnixNano())).Read(randKey)
}
err := m.lookup(randKey, valuePtr, 0)
if errors.Is(err, ErrKeyNotExist) {
return sys.NewSlicePointer(randKey), nil
}
}
return sys.Pointer{}, errFirstKeyNotFound
}
// BatchLookup looks up many elements in a map at once.
//
// "keysOut" and "valuesOut" must be of type slice, a pointer
@@ -664,7 +810,7 @@ func (m *Map) nextKey(key interface{}, nextKeyOut internal.Pointer) error {
// the end of all possible results, even when partial results
// are returned. It should be used to evaluate when lookup is "done".
func (m *Map) BatchLookup(prevKey, nextKeyOut, keysOut, valuesOut interface{}, opts *BatchOptions) (int, error) {
return m.batchLookup(internal.BPF_MAP_LOOKUP_BATCH, prevKey, nextKeyOut, keysOut, valuesOut, opts)
return m.batchLookup(sys.BPF_MAP_LOOKUP_BATCH, prevKey, nextKeyOut, keysOut, valuesOut, opts)
}
// BatchLookupAndDelete looks up many elements in a map at once,
@@ -679,10 +825,10 @@ func (m *Map) BatchLookup(prevKey, nextKeyOut, keysOut, valuesOut interface{}, o
// the end of all possible results, even when partial results
// are returned. It should be used to evaluate when lookup is "done".
func (m *Map) BatchLookupAndDelete(prevKey, nextKeyOut, keysOut, valuesOut interface{}, opts *BatchOptions) (int, error) {
return m.batchLookup(internal.BPF_MAP_LOOKUP_AND_DELETE_BATCH, prevKey, nextKeyOut, keysOut, valuesOut, opts)
return m.batchLookup(sys.BPF_MAP_LOOKUP_AND_DELETE_BATCH, prevKey, nextKeyOut, keysOut, valuesOut, opts)
}
func (m *Map) batchLookup(cmd internal.BPFCmd, startKey, nextKeyOut, keysOut, valuesOut interface{}, opts *BatchOptions) (int, error) {
func (m *Map) batchLookup(cmd sys.Cmd, startKey, nextKeyOut, keysOut, valuesOut interface{}, opts *BatchOptions) (int, error) {
if err := haveBatchAPI(); err != nil {
return 0, err
}
@@ -702,29 +848,36 @@ func (m *Map) batchLookup(cmd internal.BPFCmd, startKey, nextKeyOut, keysOut, va
return 0, fmt.Errorf("keysOut and valuesOut must be the same length")
}
keyBuf := make([]byte, count*int(m.keySize))
keyPtr := internal.NewSlicePointer(keyBuf)
keyPtr := sys.NewSlicePointer(keyBuf)
valueBuf := make([]byte, count*int(m.fullValueSize))
valuePtr := internal.NewSlicePointer(valueBuf)
valuePtr := sys.NewSlicePointer(valueBuf)
nextPtr, nextBuf := makeBuffer(nextKeyOut, int(m.keySize))
var (
startPtr internal.Pointer
err error
retErr error
)
attr := sys.MapLookupBatchAttr{
MapFd: m.fd.Uint(),
Keys: keyPtr,
Values: valuePtr,
Count: uint32(count),
OutBatch: nextPtr,
}
if opts != nil {
attr.ElemFlags = opts.ElemFlags
attr.Flags = opts.Flags
}
var err error
if startKey != nil {
startPtr, err = marshalPtr(startKey, int(m.keySize))
attr.InBatch, err = marshalPtr(startKey, int(m.keySize))
if err != nil {
return 0, err
}
}
nextPtr, nextBuf := makeBuffer(nextKeyOut, int(m.keySize))
ct, err := bpfMapBatch(cmd, m.fd, startPtr, nextPtr, keyPtr, valuePtr, uint32(count), opts)
if err != nil {
if !errors.Is(err, ErrKeyNotExist) {
return 0, err
}
retErr = ErrKeyNotExist
_, sysErr := sys.BPF(cmd, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
sysErr = wrapMapError(sysErr)
if sysErr != nil && !errors.Is(sysErr, unix.ENOENT) {
return 0, sysErr
}
err = m.unmarshalKey(nextKeyOut, nextBuf)
@@ -737,9 +890,10 @@ func (m *Map) batchLookup(cmd internal.BPFCmd, startKey, nextKeyOut, keysOut, va
}
err = unmarshalBytes(valuesOut, valueBuf)
if err != nil {
retErr = err
return 0, err
}
return int(ct), retErr
return int(attr.Count), sysErr
}
// BatchUpdate updates the map with multiple keys and values
@@ -763,7 +917,7 @@ func (m *Map) BatchUpdate(keys, values interface{}, opts *BatchOptions) (int, er
}
var (
count = keysValue.Len()
valuePtr internal.Pointer
valuePtr sys.Pointer
err error
)
if count != valuesValue.Len() {
@@ -777,9 +931,24 @@ func (m *Map) BatchUpdate(keys, values interface{}, opts *BatchOptions) (int, er
if err != nil {
return 0, err
}
var nilPtr internal.Pointer
ct, err := bpfMapBatch(internal.BPF_MAP_UPDATE_BATCH, m.fd, nilPtr, nilPtr, keyPtr, valuePtr, uint32(count), opts)
return int(ct), err
attr := sys.MapUpdateBatchAttr{
MapFd: m.fd.Uint(),
Keys: keyPtr,
Values: valuePtr,
Count: uint32(count),
}
if opts != nil {
attr.ElemFlags = opts.ElemFlags
attr.Flags = opts.Flags
}
err = sys.MapUpdateBatch(&attr)
if err != nil {
return int(attr.Count), fmt.Errorf("batch update: %w", wrapMapError(err))
}
return int(attr.Count), nil
}
// BatchDelete batch deletes entries in the map by keys.
@@ -800,9 +969,23 @@ func (m *Map) BatchDelete(keys interface{}, opts *BatchOptions) (int, error) {
if err != nil {
return 0, fmt.Errorf("cannot marshal keys: %v", err)
}
var nilPtr internal.Pointer
ct, err := bpfMapBatch(internal.BPF_MAP_DELETE_BATCH, m.fd, nilPtr, nilPtr, keyPtr, nilPtr, uint32(count), opts)
return int(ct), err
attr := sys.MapDeleteBatchAttr{
MapFd: m.fd.Uint(),
Keys: keyPtr,
Count: uint32(count),
}
if opts != nil {
attr.ElemFlags = opts.ElemFlags
attr.Flags = opts.Flags
}
if err = sys.MapDeleteBatch(&attr); err != nil {
return int(attr.Count), fmt.Errorf("batch delete: %w", wrapMapError(err))
}
return int(attr.Count), nil
}
// Iterate traverses a map.
@@ -830,14 +1013,7 @@ func (m *Map) Close() error {
//
// Calling this function is invalid after Close has been called.
func (m *Map) FD() int {
fd, err := m.fd.Value()
if err != nil {
// Best effort: -1 is the number most likely to be an
// invalid file descriptor.
return -1
}
return int(fd)
return m.fd.Int()
}
// Clone creates a duplicate of the Map.
@@ -912,7 +1088,11 @@ func (m *Map) Freeze() error {
return fmt.Errorf("can't freeze map: %w", err)
}
if err := bpfMapFreeze(m.fd); err != nil {
attr := sys.MapFreezeAttr{
MapFd: m.fd.Uint(),
}
if err := sys.MapFreeze(&attr); err != nil {
return fmt.Errorf("can't freeze map: %w", err)
}
return nil
@@ -936,13 +1116,13 @@ func (m *Map) finalize(spec *MapSpec) error {
return nil
}
func (m *Map) marshalKey(data interface{}) (internal.Pointer, error) {
func (m *Map) marshalKey(data interface{}) (sys.Pointer, error) {
if data == nil {
if m.keySize == 0 {
// Queues have a key length of zero, so passing nil here is valid.
return internal.NewPointer(nil), nil
return sys.NewPointer(nil), nil
}
return internal.Pointer{}, errors.New("can't use nil as key of map")
return sys.Pointer{}, errors.New("can't use nil as key of map")
}
return marshalPtr(data, int(m.keySize))
@@ -957,7 +1137,7 @@ func (m *Map) unmarshalKey(data interface{}, buf []byte) error {
return unmarshalBytes(data, buf)
}
func (m *Map) marshalValue(data interface{}) (internal.Pointer, error) {
func (m *Map) marshalValue(data interface{}) (sys.Pointer, error) {
if m.typ.hasPerCPUValue() {
return marshalPerCPUValue(data, int(m.valueSize))
}
@@ -970,13 +1150,13 @@ func (m *Map) marshalValue(data interface{}) (internal.Pointer, error) {
switch value := data.(type) {
case *Map:
if !m.typ.canStoreMap() {
return internal.Pointer{}, fmt.Errorf("can't store map in %s", m.typ)
return sys.Pointer{}, fmt.Errorf("can't store map in %s", m.typ)
}
buf, err = marshalMap(value, int(m.valueSize))
case *Program:
if !m.typ.canStoreProgram() {
return internal.Pointer{}, fmt.Errorf("can't store program in %s", m.typ)
return sys.Pointer{}, fmt.Errorf("can't store program in %s", m.typ)
}
buf, err = marshalProgram(value, int(m.valueSize))
@@ -985,10 +1165,10 @@ func (m *Map) marshalValue(data interface{}) (internal.Pointer, error) {
}
if err != nil {
return internal.Pointer{}, err
return sys.Pointer{}, err
}
return internal.NewSlicePointer(buf), nil
return sys.NewSlicePointer(buf), nil
}
func (m *Map) unmarshalValue(value interface{}, buf []byte) error {
@@ -1052,7 +1232,10 @@ func (m *Map) unmarshalValue(value interface{}, buf []byte) error {
// LoadPinnedMap loads a Map from a BPF file.
func LoadPinnedMap(fileName string, opts *LoadPinOptions) (*Map, error) {
fd, err := internal.BPFObjGet(fileName, opts.Marshal())
fd, err := sys.ObjGet(&sys.ObjGetAttr{
Pathname: sys.NewStringPointer(fileName),
FileFlags: opts.Marshal(),
})
if err != nil {
return nil, err
}
@@ -1081,13 +1264,8 @@ func marshalMap(m *Map, length int) ([]byte, error) {
return nil, fmt.Errorf("can't marshal map to %d bytes", length)
}
fd, err := m.fd.Value()
if err != nil {
return nil, err
}
buf := make([]byte, 4)
internal.NativeEndian.PutUint32(buf, fd)
internal.NativeEndian.PutUint32(buf, m.fd.Uint())
return buf, nil
}
@@ -1239,15 +1417,17 @@ func (mi *MapIterator) Err() error {
//
// Returns ErrNotExist, if there is no next eBPF map.
func MapGetNextID(startID MapID) (MapID, error) {
id, err := objGetNextID(internal.BPF_MAP_GET_NEXT_ID, uint32(startID))
return MapID(id), err
attr := &sys.MapGetNextIdAttr{Id: uint32(startID)}
return MapID(attr.NextId), sys.MapGetNextId(attr)
}
// NewMapFromID returns the map for a given id.
//
// Returns ErrNotExist, if there is no eBPF map with the given id.
func NewMapFromID(id MapID) (*Map, error) {
fd, err := internal.BPFObjGetFDByID(internal.BPF_MAP_GET_FD_BY_ID, uint32(id))
fd, err := sys.MapGetFdById(&sys.MapGetFdByIdAttr{
Id: uint32(id),
})
if err != nil {
return nil, err
}
@@ -1259,9 +1439,9 @@ func NewMapFromID(id MapID) (*Map, error) {
//
// Deprecated: use MapInfo.ID() instead.
func (m *Map) ID() (MapID, error) {
info, err := bpfGetMapInfoByFD(m.fd)
if err != nil {
var info sys.MapInfo
if err := sys.ObjInfo(m.fd, &info); err != nil {
return MapID(0), err
}
return MapID(info.id), nil
return MapID(info.Id), nil
}
+14 -13
View File
@@ -12,6 +12,7 @@ import (
"unsafe"
"github.com/cilium/ebpf/internal"
"github.com/cilium/ebpf/internal/sys"
)
// marshalPtr converts an arbitrary value into a pointer suitable
@@ -19,17 +20,17 @@ import (
//
// As an optimization, it returns the original value if it is an
// unsafe.Pointer.
func marshalPtr(data interface{}, length int) (internal.Pointer, error) {
func marshalPtr(data interface{}, length int) (sys.Pointer, error) {
if ptr, ok := data.(unsafe.Pointer); ok {
return internal.NewPointer(ptr), nil
return sys.NewPointer(ptr), nil
}
buf, err := marshalBytes(data, length)
if err != nil {
return internal.Pointer{}, err
return sys.Pointer{}, err
}
return internal.NewSlicePointer(buf), nil
return sys.NewSlicePointer(buf), nil
}
// marshalBytes converts an arbitrary value into a byte buffer.
@@ -73,13 +74,13 @@ func marshalBytes(data interface{}, length int) (buf []byte, err error) {
return buf, nil
}
func makeBuffer(dst interface{}, length int) (internal.Pointer, []byte) {
func makeBuffer(dst interface{}, length int) (sys.Pointer, []byte) {
if ptr, ok := dst.(unsafe.Pointer); ok {
return internal.NewPointer(ptr), nil
return sys.NewPointer(ptr), nil
}
buf := make([]byte, length)
return internal.NewSlicePointer(buf), buf
return sys.NewSlicePointer(buf), buf
}
var bytesReaderPool = sync.Pool{
@@ -164,21 +165,21 @@ func unmarshalBytes(data interface{}, buf []byte) error {
// Values are initialized to zero if the slice has less elements than CPUs.
//
// slice must have a type like []elementType.
func marshalPerCPUValue(slice interface{}, elemLength int) (internal.Pointer, error) {
func marshalPerCPUValue(slice interface{}, elemLength int) (sys.Pointer, error) {
sliceType := reflect.TypeOf(slice)
if sliceType.Kind() != reflect.Slice {
return internal.Pointer{}, errors.New("per-CPU value requires slice")
return sys.Pointer{}, errors.New("per-CPU value requires slice")
}
possibleCPUs, err := internal.PossibleCPUs()
if err != nil {
return internal.Pointer{}, err
return sys.Pointer{}, err
}
sliceValue := reflect.ValueOf(slice)
sliceLen := sliceValue.Len()
if sliceLen > possibleCPUs {
return internal.Pointer{}, fmt.Errorf("per-CPU value exceeds number of CPUs")
return sys.Pointer{}, fmt.Errorf("per-CPU value exceeds number of CPUs")
}
alignedElemLength := internal.Align(elemLength, 8)
@@ -188,14 +189,14 @@ func marshalPerCPUValue(slice interface{}, elemLength int) (internal.Pointer, er
elem := sliceValue.Index(i).Interface()
elemBytes, err := marshalBytes(elem, elemLength)
if err != nil {
return internal.Pointer{}, err
return sys.Pointer{}, err
}
offset := i * alignedElemLength
copy(buf[offset:offset+elemLength], elemBytes)
}
return internal.NewSlicePointer(buf), nil
return sys.NewSlicePointer(buf), nil
}
// unmarshalPerCPUValue decodes a buffer into a slice containing one value per
+223 -102
View File
@@ -14,13 +14,15 @@ import (
"github.com/cilium/ebpf/asm"
"github.com/cilium/ebpf/internal"
"github.com/cilium/ebpf/internal/btf"
"github.com/cilium/ebpf/internal/sys"
"github.com/cilium/ebpf/internal/unix"
)
// ErrNotSupported is returned whenever the kernel doesn't support a feature.
var ErrNotSupported = internal.ErrNotSupported
var errUnsatisfiedReference = errors.New("unsatisfied reference")
var errUnsatisfiedMap = errors.New("unsatisfied map reference")
var errUnsatisfiedProgram = errors.New("unsatisfied program reference")
// ProgramID represents the unique ID of an eBPF program.
type ProgramID uint32
@@ -61,11 +63,17 @@ type ProgramSpec struct {
// Type determines at which hook in the kernel a program will run.
Type ProgramType
AttachType AttachType
// Name of a kernel data structure or function to attach to. Its
// interpretation depends on Type and AttachType.
AttachTo string
// The program to attach to. Must be provided manually.
AttachTarget *Program
// The name of the ELF section this program orininated from.
SectionName string
Instructions asm.Instructions
// Flags is passed to the kernel and specifies additional program
@@ -91,6 +99,9 @@ type ProgramSpec struct {
// The byte order this program was compiled for, may be nil.
ByteOrder binary.ByteOrder
// Programs called by this ProgramSpec. Includes all dependencies.
references map[string]*ProgramSpec
}
// Copy returns a copy of the spec.
@@ -112,6 +123,83 @@ func (ps *ProgramSpec) Tag() (string, error) {
return ps.Instructions.Tag(internal.NativeEndian)
}
// flatten returns spec's full instruction stream including all of its
// dependencies and an expanded map of references that includes all symbols
// appearing in the instruction stream.
//
// Returns nil, nil if spec was already visited.
func (spec *ProgramSpec) flatten(visited map[*ProgramSpec]bool) (asm.Instructions, map[string]*ProgramSpec) {
if visited == nil {
visited = make(map[*ProgramSpec]bool)
}
// This program and its dependencies were already collected.
if visited[spec] {
return nil, nil
}
visited[spec] = true
// Start off with spec's direct references and instructions.
progs := spec.references
insns := spec.Instructions
// Recurse into each reference and append/merge its references into
// a temporary buffer as to not interfere with the resolution process.
for _, ref := range spec.references {
if ri, rp := ref.flatten(visited); ri != nil || rp != nil {
insns = append(insns, ri...)
// Merge nested references into the top-level scope.
for n, p := range rp {
progs[n] = p
}
}
}
return insns, progs
}
// A reference describes a byte offset an Symbol Instruction pointing
// to another ProgramSpec.
type reference struct {
offset uint64
spec *ProgramSpec
}
// layout returns a unique list of programs that must be included
// in spec's instruction stream when inserting it into the kernel.
// Always returns spec itself as the first entry in the chain.
func (spec *ProgramSpec) layout() ([]reference, error) {
out := []reference{{0, spec}}
name := spec.Instructions.Name()
var ins *asm.Instruction
iter := spec.Instructions.Iterate()
for iter.Next() {
ins = iter.Ins
// Skip non-symbols and symbols that describe the ProgramSpec itself,
// which is usually the first instruction in Instructions.
// ProgramSpec itself is already included and not present in references.
if ins.Symbol == "" || ins.Symbol == name {
continue
}
// Failure to look up a reference is not an error. There are existing tests
// with valid progs that contain multiple symbols and don't have references
// populated. Assume ProgramSpec is used similarly in the wild, so don't
// alter this behaviour.
ref := spec.references[ins.Symbol]
if ref != nil {
out = append(out, reference{iter.Offset.Bytes(), ref})
}
}
return out, nil
}
// Program represents BPF program loaded into the kernel.
//
// It is not safe to close a Program which is used by other goroutines.
@@ -120,7 +208,7 @@ type Program struct {
// otherwise it is empty.
VerifierLog string
fd *internal.FD
fd *sys.FD
name string
pinnedPath string
typ ProgramType
@@ -139,11 +227,15 @@ func NewProgram(spec *ProgramSpec) (*Program, error) {
// Loading a program for the first time will perform
// feature detection by loading small, temporary programs.
func NewProgramWithOptions(spec *ProgramSpec, opts ProgramOptions) (*Program, error) {
if spec == nil {
return nil, errors.New("can't load a program from a nil spec")
}
handles := newHandleCache()
defer handles.close()
prog, err := newProgramWithOptions(spec, opts, handles)
if errors.Is(err, errUnsatisfiedReference) {
if errors.Is(err, errUnsatisfiedMap) {
return nil, fmt.Errorf("cannot load program without loading its whole collection: %w", err)
}
return prog, err
@@ -154,6 +246,10 @@ func newProgramWithOptions(spec *ProgramSpec, opts ProgramOptions, handles *hand
return nil, errors.New("instructions cannot be empty")
}
if spec.Type == UnspecifiedProgram {
return nil, errors.New("can't load program of unspecified type")
}
if spec.ByteOrder != nil && spec.ByteOrder != internal.NativeEndian {
return nil, fmt.Errorf("can't load %s program on %s", spec.ByteOrder, internal.NativeEndian)
}
@@ -171,16 +267,16 @@ func newProgramWithOptions(spec *ProgramSpec, opts ProgramOptions, handles *hand
kv = v.Kernel()
}
attr := &internal.BPFProgLoadAttr{
ProgType: uint32(spec.Type),
attr := &sys.ProgLoadAttr{
ProgType: sys.ProgType(spec.Type),
ProgFlags: spec.Flags,
ExpectedAttachType: uint32(spec.AttachType),
License: internal.NewStringPointer(spec.License),
KernelVersion: kv,
ExpectedAttachType: sys.AttachType(spec.AttachType),
License: sys.NewStringPointer(spec.License),
KernVersion: kv,
}
if haveObjName() == nil {
attr.ProgName = internal.NewBPFObjName(spec.Name)
attr.ProgName = sys.NewObjName(spec.Name)
}
var err error
@@ -192,6 +288,11 @@ func newProgramWithOptions(spec *ProgramSpec, opts ProgramOptions, handles *hand
}
}
layout, err := spec.layout()
if err != nil {
return nil, fmt.Errorf("get program layout: %w", err)
}
var btfDisabled bool
var core btf.COREFixups
if spec.BTF != nil {
@@ -207,23 +308,23 @@ func newProgramWithOptions(spec *ProgramSpec, opts ProgramOptions, handles *hand
}
if handle != nil {
attr.ProgBTFFd = uint32(handle.FD())
attr.ProgBtfFd = uint32(handle.FD())
recSize, bytes, err := spec.BTF.LineInfos()
fib, err := marshalFuncInfos(layout)
if err != nil {
return nil, fmt.Errorf("get BTF line infos: %w", err)
return nil, err
}
attr.LineInfoRecSize = recSize
attr.LineInfoCnt = uint32(uint64(len(bytes)) / uint64(recSize))
attr.LineInfo = internal.NewSlicePointer(bytes)
attr.FuncInfoRecSize = uint32(binary.Size(btf.FuncInfo{}))
attr.FuncInfoCnt = uint32(len(fib)) / attr.FuncInfoRecSize
attr.FuncInfo = sys.NewSlicePointer(fib)
recSize, bytes, err = spec.BTF.FuncInfos()
lib, err := marshalLineInfos(layout)
if err != nil {
return nil, fmt.Errorf("get BTF function infos: %w", err)
return nil, err
}
attr.FuncInfoRecSize = recSize
attr.FuncInfoCnt = uint32(uint64(len(bytes)) / uint64(recSize))
attr.FuncInfo = internal.NewSlicePointer(bytes)
attr.LineInfoRecSize = uint32(binary.Size(btf.LineInfo{}))
attr.LineInfoCnt = uint32(len(lib)) / attr.LineInfoRecSize
attr.LineInfo = sys.NewSlicePointer(lib)
}
}
@@ -236,15 +337,15 @@ func newProgramWithOptions(spec *ProgramSpec, opts ProgramOptions, handles *hand
return nil, err
}
buf := bytes.NewBuffer(make([]byte, 0, len(spec.Instructions)*asm.InstructionSize))
buf := bytes.NewBuffer(make([]byte, 0, insns.Size()))
err = insns.Marshal(buf, internal.NativeEndian)
if err != nil {
return nil, err
}
bytecode := buf.Bytes()
attr.Instructions = internal.NewSlicePointer(bytecode)
attr.InsCount = uint32(len(bytecode) / asm.InstructionSize)
attr.Insns = sys.NewSlicePointer(bytecode)
attr.InsnCnt = uint32(len(bytecode) / asm.InstructionSize)
if spec.AttachTo != "" {
if spec.AttachTarget != nil {
@@ -274,7 +375,7 @@ func newProgramWithOptions(spec *ProgramSpec, opts ProgramOptions, handles *hand
return nil, err
}
if target != nil {
attr.AttachBTFID = uint32(target.ID())
attr.AttachBtfId = uint32(target.ID())
}
if spec.AttachTarget != nil {
attr.AttachProgFd = uint32(spec.AttachTarget.FD())
@@ -291,12 +392,12 @@ func newProgramWithOptions(spec *ProgramSpec, opts ProgramOptions, handles *hand
logBuf = make([]byte, logSize)
attr.LogLevel = opts.LogLevel
attr.LogSize = uint32(len(logBuf))
attr.LogBuf = internal.NewSlicePointer(logBuf)
attr.LogBuf = sys.NewSlicePointer(logBuf)
}
fd, err := internal.BPFProgLoad(attr)
fd, err := sys.ProgLoad(attr)
if err == nil {
return &Program{internal.CString(logBuf), fd, spec.Name, "", spec.Type}, nil
return &Program{unix.ByteSliceToString(logBuf), fd, spec.Name, "", spec.Type}, nil
}
logErr := err
@@ -305,18 +406,18 @@ func newProgramWithOptions(spec *ProgramSpec, opts ProgramOptions, handles *hand
logBuf = make([]byte, logSize)
attr.LogLevel = 1
attr.LogSize = uint32(len(logBuf))
attr.LogBuf = internal.NewSlicePointer(logBuf)
attr.LogBuf = sys.NewSlicePointer(logBuf)
fd, logErr = internal.BPFProgLoad(attr)
fd, logErr = sys.ProgLoad(attr)
if logErr == nil {
fd.Close()
}
}
if errors.Is(logErr, unix.EPERM) && logBuf[0] == 0 {
if errors.Is(logErr, unix.EPERM) && len(logBuf) > 0 && logBuf[0] == 0 {
// EPERM due to RLIMIT_MEMLOCK happens before the verifier, so we can
// check that the log is empty to reduce false positives.
return nil, fmt.Errorf("load program: %w (MEMLOCK bay be too low, consider rlimit.RemoveMemlock)", logErr)
return nil, fmt.Errorf("load program: %w (MEMLOCK may be too low, consider rlimit.RemoveMemlock)", logErr)
}
err = internal.ErrorWithLog(err, logBuf, logErr)
@@ -332,18 +433,21 @@ func newProgramWithOptions(spec *ProgramSpec, opts ProgramOptions, handles *hand
//
// Requires at least Linux 4.10.
func NewProgramFromFD(fd int) (*Program, error) {
if fd < 0 {
return nil, errors.New("invalid fd")
f, err := sys.NewFD(fd)
if err != nil {
return nil, err
}
return newProgramFromFD(internal.NewFD(uint32(fd)))
return newProgramFromFD(f)
}
// NewProgramFromID returns the program for a given id.
//
// Returns ErrNotExist, if there is no eBPF program with the given id.
func NewProgramFromID(id ProgramID) (*Program, error) {
fd, err := internal.BPFObjGetFDByID(internal.BPF_PROG_GET_FD_BY_ID, uint32(id))
fd, err := sys.ProgGetFdById(&sys.ProgGetFdByIdAttr{
Id: uint32(id),
})
if err != nil {
return nil, fmt.Errorf("get program by id: %w", err)
}
@@ -351,7 +455,7 @@ func NewProgramFromID(id ProgramID) (*Program, error) {
return newProgramFromFD(fd)
}
func newProgramFromFD(fd *internal.FD) (*Program, error) {
func newProgramFromFD(fd *sys.FD) (*Program, error) {
info, err := newProgramInfoFromFd(fd)
if err != nil {
fd.Close()
@@ -384,14 +488,7 @@ func (p *Program) Info() (*ProgramInfo, error) {
//
// It is invalid to call this function after Close has been called.
func (p *Program) FD() int {
fd, err := p.fd.Value()
if err != nil {
// Best effort: -1 is the number most likely to be an
// invalid file descriptor.
return -1
}
return int(fd)
return p.fd.Int()
}
// Clone creates a duplicate of the Program.
@@ -505,13 +602,13 @@ var haveProgTestRun = internal.FeatureTest("BPF_PROG_TEST_RUN", "4.12", func() e
// Programs require at least 14 bytes input
in := make([]byte, 14)
attr := bpfProgTestRunAttr{
fd: uint32(prog.FD()),
dataSizeIn: uint32(len(in)),
dataIn: internal.NewSlicePointer(in),
attr := sys.ProgRunAttr{
ProgFd: uint32(prog.FD()),
DataSizeIn: uint32(len(in)),
DataIn: sys.NewSlicePointer(in),
}
err = bpfProgTestRun(&attr)
err = sys.ProgRun(&attr)
if errors.Is(err, unix.EINVAL) {
// Check for EINVAL specifically, rather than err != nil since we
// otherwise misdetect due to insufficient permissions.
@@ -548,22 +645,17 @@ func (p *Program) testRun(in []byte, repeat int, reset func()) (uint32, []byte,
// See https://patchwork.ozlabs.org/cover/1006822/
out := make([]byte, len(in)+outputPad)
fd, err := p.fd.Value()
if err != nil {
return 0, nil, 0, err
}
attr := bpfProgTestRunAttr{
fd: fd,
dataSizeIn: uint32(len(in)),
dataSizeOut: uint32(len(out)),
dataIn: internal.NewSlicePointer(in),
dataOut: internal.NewSlicePointer(out),
repeat: uint32(repeat),
attr := sys.ProgRunAttr{
ProgFd: p.fd.Uint(),
DataSizeIn: uint32(len(in)),
DataSizeOut: uint32(len(out)),
DataIn: sys.NewSlicePointer(in),
DataOut: sys.NewSlicePointer(out),
Repeat: uint32(repeat),
}
for {
err = bpfProgTestRun(&attr)
err := sys.ProgRun(&attr)
if err == nil {
break
}
@@ -578,15 +670,15 @@ func (p *Program) testRun(in []byte, repeat int, reset func()) (uint32, []byte,
return 0, nil, 0, fmt.Errorf("can't run test: %w", err)
}
if int(attr.dataSizeOut) > cap(out) {
if int(attr.DataSizeOut) > cap(out) {
// Houston, we have a problem. The program created more data than we allocated,
// and the kernel wrote past the end of our buffer.
panic("kernel wrote past end of output buffer")
}
out = out[:int(attr.dataSizeOut)]
out = out[:int(attr.DataSizeOut)]
total := time.Duration(attr.duration) * time.Nanosecond
return attr.retval, out, total, nil
total := time.Duration(attr.Duration) * time.Nanosecond
return attr.Retval, out, total, nil
}
func unmarshalProgram(buf []byte) (*Program, error) {
@@ -605,13 +697,8 @@ func marshalProgram(p *Program, length int) ([]byte, error) {
return nil, fmt.Errorf("can't marshal program to %d bytes", length)
}
value, err := p.fd.Value()
if err != nil {
return nil, err
}
buf := make([]byte, 4)
internal.NativeEndian.PutUint32(buf, value)
internal.NativeEndian.PutUint32(buf, p.fd.Uint())
return buf, nil
}
@@ -623,19 +710,14 @@ func (p *Program) Attach(fd int, typ AttachType, flags AttachFlags) error {
return errors.New("invalid fd")
}
pfd, err := p.fd.Value()
if err != nil {
return err
}
attr := internal.BPFProgAttachAttr{
attr := sys.ProgAttachAttr{
TargetFd: uint32(fd),
AttachBpfFd: pfd,
AttachBpfFd: p.fd.Uint(),
AttachType: uint32(typ),
AttachFlags: uint32(flags),
}
return internal.BPFProgAttach(&attr)
return sys.ProgAttach(&attr)
}
// Detach a Program.
@@ -650,25 +732,23 @@ func (p *Program) Detach(fd int, typ AttachType, flags AttachFlags) error {
return errors.New("flags must be zero")
}
pfd, err := p.fd.Value()
if err != nil {
return err
}
attr := internal.BPFProgDetachAttr{
attr := sys.ProgAttachAttr{
TargetFd: uint32(fd),
AttachBpfFd: pfd,
AttachBpfFd: p.fd.Uint(),
AttachType: uint32(typ),
}
return internal.BPFProgDetach(&attr)
return sys.ProgAttach(&attr)
}
// LoadPinnedProgram loads a Program from a BPF file.
//
// Requires at least Linux 4.11.
func LoadPinnedProgram(fileName string, opts *LoadPinOptions) (*Program, error) {
fd, err := internal.BPFObjGet(fileName, opts.Marshal())
fd, err := sys.ObjGet(&sys.ObjGetAttr{
Pathname: sys.NewStringPointer(fileName),
FileFlags: opts.Marshal(),
})
if err != nil {
return nil, err
}
@@ -702,19 +782,32 @@ func SanitizeName(name string, replacement rune) string {
//
// Returns ErrNotExist, if there is no next eBPF program.
func ProgramGetNextID(startID ProgramID) (ProgramID, error) {
id, err := objGetNextID(internal.BPF_PROG_GET_NEXT_ID, uint32(startID))
return ProgramID(id), err
attr := &sys.ProgGetNextIdAttr{Id: uint32(startID)}
return ProgramID(attr.NextId), sys.ProgGetNextId(attr)
}
// ID returns the systemwide unique ID of the program.
//
// Deprecated: use ProgramInfo.ID() instead.
func (p *Program) ID() (ProgramID, error) {
info, err := bpfGetProgInfoByFD(p.fd, nil)
if err != nil {
var info sys.ProgInfo
if err := sys.ObjInfo(p.fd, &info); err != nil {
return ProgramID(0), err
}
return ProgramID(info.id), nil
return ProgramID(info.Id), nil
}
// BindMap binds map to the program and is only released once program is released.
//
// This may be used in cases where metadata should be associated with the program
// which otherwise does not contain any references to the map.
func (p *Program) BindMap(m *Map) error {
attr := &sys.ProgBindMapAttr{
ProgFd: uint32(p.FD()),
MapFd: uint32(m.FD()),
}
return sys.ProgBindMap(attr)
}
func resolveBTFType(spec *btf.Spec, name string, progType ProgramType, attachType AttachType) (btf.Type, error) {
@@ -723,7 +816,11 @@ func resolveBTFType(spec *btf.Spec, name string, progType ProgramType, attachTyp
a AttachType
}
var typeName, featureName string
var (
typeName, featureName string
isBTFTypeFunc = true
)
switch (match{progType, attachType}) {
case match{LSM, AttachLSMMac}:
typeName = "bpf_lsm_" + name
@@ -734,26 +831,50 @@ func resolveBTFType(spec *btf.Spec, name string, progType ProgramType, attachTyp
case match{Extension, AttachNone}:
typeName = name
featureName = fmt.Sprintf("freplace %s", name)
case match{Tracing, AttachTraceFEntry}:
typeName = name
featureName = fmt.Sprintf("fentry %s", name)
case match{Tracing, AttachTraceFExit}:
typeName = name
featureName = fmt.Sprintf("fexit %s", name)
case match{Tracing, AttachModifyReturn}:
typeName = name
featureName = fmt.Sprintf("fmod_ret %s", name)
case match{Tracing, AttachTraceRawTp}:
typeName = fmt.Sprintf("btf_trace_%s", name)
featureName = fmt.Sprintf("raw_tp %s", name)
isBTFTypeFunc = false
default:
return nil, nil
}
var (
target btf.Type
err error
)
if spec == nil {
var err error
spec, err = btf.LoadKernelSpec()
if err != nil {
return nil, fmt.Errorf("load kernel spec: %w", err)
}
}
var target *btf.Func
err := spec.FindType(typeName, &target)
if errors.Is(err, btf.ErrNotFound) {
return nil, &internal.UnsupportedFeatureError{
Name: featureName,
}
if isBTFTypeFunc {
var targetFunc *btf.Func
err = spec.TypeByName(typeName, &targetFunc)
target = targetFunc
} else {
var targetTypedef *btf.Typedef
err = spec.TypeByName(typeName, &targetTypedef)
target = targetTypedef
}
if err != nil {
if errors.Is(err, btf.ErrNotFound) {
return nil, &internal.UnsupportedFeatureError{
Name: featureName,
}
}
return nil, fmt.Errorf("resolve BTF for %s: %w", featureName, err)
}
+11 -4
View File
@@ -52,7 +52,7 @@ if [[ "${1:-}" = "--exec-vm" ]]; then
--rwdir="${testdir}=${testdir}" \
--rodir=/run/input="${input}" \
--rwdir=/run/output="${output}" \
--script-sh "PATH=\"$PATH\" \"$script\" --exec-test $cmd" \
--script-sh "PATH=\"$PATH\" CI_MAX_KERNEL_VERSION="${CI_MAX_KERNEL_VERSION:-}" \"$script\" --exec-test $cmd" \
--kopt possible_cpus=2; then # need at least two CPUs for some tests
exit 23
fi
@@ -90,22 +90,27 @@ fi
shift
readonly kernel="linux-${kernel_version}.bz"
readonly selftests="linux-${kernel_version}-selftests-bpf.bz"
readonly selftests="linux-${kernel_version}-selftests-bpf.tgz"
readonly input="$(mktemp -d)"
readonly tmp_dir="${TMPDIR:-/tmp}"
readonly branch="${BRANCH:-master}"
fetch() {
echo Fetching "${1}"
wget -nv -N -P "${tmp_dir}" "https://github.com/cilium/ci-kernels/raw/${branch}/${1}"
pushd "${tmp_dir}" > /dev/null
curl -s -L -O --fail --etag-compare "${1}.etag" --etag-save "${1}.etag" "https://github.com/cilium/ci-kernels/raw/${branch}/${1}"
local ret=$?
popd > /dev/null
return $ret
}
fetch "${kernel}"
cp "${tmp_dir}/${kernel}" "${input}/bzImage"
if fetch "${selftests}"; then
echo "Decompressing selftests"
mkdir "${input}/bpf"
tar --strip-components=4 -xjf "${tmp_dir}/${selftests}" -C "${input}/bpf"
tar --strip-components=4 -xf "${tmp_dir}/${selftests}" -C "${input}/bpf"
else
echo "No selftests found, disabling"
fi
@@ -117,6 +122,8 @@ fi
export GOFLAGS=-mod=readonly
export CGO_ENABLED=0
# LINUX_VERSION_CODE test compares this to discovered value.
export KERNEL_VERSION="${kernel_version}"
echo Testing on "${kernel_version}"
go test -exec "$script --exec-vm $input" "${args[@]}"
+53 -271
View File
@@ -5,10 +5,10 @@ import (
"errors"
"fmt"
"os"
"unsafe"
"github.com/cilium/ebpf/asm"
"github.com/cilium/ebpf/internal"
"github.com/cilium/ebpf/internal/sys"
"github.com/cilium/ebpf/internal/unix"
)
@@ -38,108 +38,9 @@ func invalidBPFObjNameChar(char rune) bool {
}
}
type bpfMapOpAttr struct {
mapFd uint32
padding uint32
key internal.Pointer
value internal.Pointer
flags uint64
}
type bpfBatchMapOpAttr struct {
inBatch internal.Pointer
outBatch internal.Pointer
keys internal.Pointer
values internal.Pointer
count uint32
mapFd uint32
elemFlags uint64
flags uint64
}
type bpfMapInfo struct {
map_type uint32 // since 4.12 1e2709769086
id uint32
key_size uint32
value_size uint32
max_entries uint32
map_flags uint32
name internal.BPFObjName // since 4.15 ad5b177bd73f
ifindex uint32 // since 4.16 52775b33bb50
btf_vmlinux_value_type_id uint32 // since 5.6 85d33df357b6
netns_dev uint64 // since 4.16 52775b33bb50
netns_ino uint64
btf_id uint32 // since 4.18 78958fca7ead
btf_key_type_id uint32 // since 4.18 9b2cf328b2ec
btf_value_type_id uint32
}
type bpfProgInfo struct {
prog_type uint32
id uint32
tag [unix.BPF_TAG_SIZE]byte
jited_prog_len uint32
xlated_prog_len uint32
jited_prog_insns internal.Pointer
xlated_prog_insns internal.Pointer
load_time uint64 // since 4.15 cb4d2b3f03d8
created_by_uid uint32
nr_map_ids uint32 // since 4.15 cb4d2b3f03d8
map_ids internal.Pointer
name internal.BPFObjName // since 4.15 067cae47771c
ifindex uint32
gpl_compatible uint32
netns_dev uint64
netns_ino uint64
nr_jited_ksyms uint32
nr_jited_func_lens uint32
jited_ksyms internal.Pointer
jited_func_lens internal.Pointer
btf_id uint32
func_info_rec_size uint32
func_info internal.Pointer
nr_func_info uint32
nr_line_info uint32
line_info internal.Pointer
jited_line_info internal.Pointer
nr_jited_line_info uint32
line_info_rec_size uint32
jited_line_info_rec_size uint32
nr_prog_tags uint32
prog_tags internal.Pointer
run_time_ns uint64
run_cnt uint64
}
type bpfProgTestRunAttr struct {
fd uint32
retval uint32
dataSizeIn uint32
dataSizeOut uint32
dataIn internal.Pointer
dataOut internal.Pointer
repeat uint32
duration uint32
}
type bpfMapFreezeAttr struct {
mapFd uint32
}
type bpfObjGetNextIDAttr struct {
startID uint32
nextID uint32
openFlags uint32
}
func bpfProgTestRun(attr *bpfProgTestRunAttr) error {
_, err := internal.BPF(internal.BPF_PROG_TEST_RUN, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
return err
}
var haveNestedMaps = internal.FeatureTest("nested maps", "4.12", func() error {
_, err := internal.BPFMapCreate(&internal.BPFMapCreateAttr{
MapType: uint32(ArrayOfMaps),
_, err := sys.MapCreate(&sys.MapCreateAttr{
MapType: sys.MapType(ArrayOfMaps),
KeySize: 4,
ValueSize: 4,
MaxEntries: 1,
@@ -158,12 +59,12 @@ var haveNestedMaps = internal.FeatureTest("nested maps", "4.12", func() error {
var haveMapMutabilityModifiers = internal.FeatureTest("read- and write-only maps", "5.2", 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 := internal.BPFMapCreate(&internal.BPFMapCreateAttr{
MapType: uint32(Array),
m, err := sys.MapCreate(&sys.MapCreateAttr{
MapType: sys.MapType(Array),
KeySize: 4,
ValueSize: 4,
MaxEntries: 1,
Flags: unix.BPF_F_RDONLY_PROG,
MapFlags: unix.BPF_F_RDONLY_PROG,
})
if err != nil {
return internal.ErrNotSupported
@@ -174,12 +75,12 @@ var haveMapMutabilityModifiers = internal.FeatureTest("read- and write-only maps
var haveMmapableMaps = internal.FeatureTest("mmapable maps", "5.5", func() error {
// This checks BPF_F_MMAPABLE, which appeared in 5.5 for array maps.
m, err := internal.BPFMapCreate(&internal.BPFMapCreateAttr{
MapType: uint32(Array),
m, err := sys.MapCreate(&sys.MapCreateAttr{
MapType: sys.MapType(Array),
KeySize: 4,
ValueSize: 4,
MaxEntries: 1,
Flags: unix.BPF_F_MMAPABLE,
MapFlags: unix.BPF_F_MMAPABLE,
})
if err != nil {
return internal.ErrNotSupported
@@ -190,12 +91,12 @@ var haveMmapableMaps = internal.FeatureTest("mmapable maps", "5.5", func() error
var haveInnerMaps = internal.FeatureTest("inner maps", "5.10", func() error {
// This checks BPF_F_INNER_MAP, which appeared in 5.10.
m, err := internal.BPFMapCreate(&internal.BPFMapCreateAttr{
MapType: uint32(Array),
m, err := sys.MapCreate(&sys.MapCreateAttr{
MapType: sys.MapType(Array),
KeySize: 4,
ValueSize: 4,
MaxEntries: 1,
Flags: unix.BPF_F_INNER_MAP,
MapFlags: unix.BPF_F_INNER_MAP,
})
if err != nil {
return internal.ErrNotSupported
@@ -204,111 +105,21 @@ var haveInnerMaps = internal.FeatureTest("inner maps", "5.10", func() error {
return nil
})
func bpfMapLookupElem(m *internal.FD, key, valueOut internal.Pointer) error {
fd, err := m.Value()
var haveNoPreallocMaps = internal.FeatureTest("prealloc maps", "4.6", 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,
})
if err != nil {
return err
return internal.ErrNotSupported
}
attr := bpfMapOpAttr{
mapFd: fd,
key: key,
value: valueOut,
}
_, err = internal.BPF(internal.BPF_MAP_LOOKUP_ELEM, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
return wrapMapError(err)
}
func bpfMapLookupAndDelete(m *internal.FD, key, valueOut internal.Pointer) error {
fd, err := m.Value()
if err != nil {
return err
}
attr := bpfMapOpAttr{
mapFd: fd,
key: key,
value: valueOut,
}
_, err = internal.BPF(internal.BPF_MAP_LOOKUP_AND_DELETE_ELEM, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
return wrapMapError(err)
}
func bpfMapUpdateElem(m *internal.FD, key, valueOut internal.Pointer, flags uint64) error {
fd, err := m.Value()
if err != nil {
return err
}
attr := bpfMapOpAttr{
mapFd: fd,
key: key,
value: valueOut,
flags: flags,
}
_, err = internal.BPF(internal.BPF_MAP_UPDATE_ELEM, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
return wrapMapError(err)
}
func bpfMapDeleteElem(m *internal.FD, key internal.Pointer) error {
fd, err := m.Value()
if err != nil {
return err
}
attr := bpfMapOpAttr{
mapFd: fd,
key: key,
}
_, err = internal.BPF(internal.BPF_MAP_DELETE_ELEM, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
return wrapMapError(err)
}
func bpfMapGetNextKey(m *internal.FD, key, nextKeyOut internal.Pointer) error {
fd, err := m.Value()
if err != nil {
return err
}
attr := bpfMapOpAttr{
mapFd: fd,
key: key,
value: nextKeyOut,
}
_, err = internal.BPF(internal.BPF_MAP_GET_NEXT_KEY, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
return wrapMapError(err)
}
func objGetNextID(cmd internal.BPFCmd, start uint32) (uint32, error) {
attr := bpfObjGetNextIDAttr{
startID: start,
}
_, err := internal.BPF(cmd, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
return attr.nextID, err
}
func bpfMapBatch(cmd internal.BPFCmd, m *internal.FD, inBatch, outBatch, keys, values internal.Pointer, count uint32, opts *BatchOptions) (uint32, error) {
fd, err := m.Value()
if err != nil {
return 0, err
}
attr := bpfBatchMapOpAttr{
inBatch: inBatch,
outBatch: outBatch,
keys: keys,
values: values,
count: count,
mapFd: fd,
}
if opts != nil {
attr.elemFlags = opts.ElemFlags
attr.flags = opts.Flags
}
_, err = internal.BPF(cmd, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
// always return count even on an error, as things like update might partially be fulfilled.
return attr.count, wrapMapError(err)
}
_ = m.Close()
return nil
})
func wrapMapError(err error) error {
if err == nil {
@@ -316,15 +127,15 @@ func wrapMapError(err error) error {
}
if errors.Is(err, unix.ENOENT) {
return internal.SyscallError(ErrKeyNotExist, unix.ENOENT)
return sys.Error(ErrKeyNotExist, unix.ENOENT)
}
if errors.Is(err, unix.EEXIST) {
return internal.SyscallError(ErrKeyExist, unix.EEXIST)
return sys.Error(ErrKeyExist, unix.EEXIST)
}
if errors.Is(err, unix.ENOTSUPP) {
return internal.SyscallError(ErrNotSupported, unix.ENOTSUPP)
return sys.Error(ErrNotSupported, unix.ENOTSUPP)
}
if errors.Is(err, unix.E2BIG) {
@@ -334,51 +145,16 @@ func wrapMapError(err error) error {
return err
}
func bpfMapFreeze(m *internal.FD) error {
fd, err := m.Value()
if err != nil {
return err
}
attr := bpfMapFreezeAttr{
mapFd: fd,
}
_, err = internal.BPF(internal.BPF_MAP_FREEZE, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
return err
}
func bpfGetProgInfoByFD(fd *internal.FD, ids []MapID) (*bpfProgInfo, error) {
var info bpfProgInfo
if len(ids) > 0 {
info.nr_map_ids = uint32(len(ids))
info.map_ids = internal.NewPointer(unsafe.Pointer(&ids[0]))
}
if err := internal.BPFObjGetInfoByFD(fd, unsafe.Pointer(&info), unsafe.Sizeof(info)); err != nil {
return nil, fmt.Errorf("can't get program info: %w", err)
}
return &info, nil
}
func bpfGetMapInfoByFD(fd *internal.FD) (*bpfMapInfo, error) {
var info bpfMapInfo
err := internal.BPFObjGetInfoByFD(fd, unsafe.Pointer(&info), unsafe.Sizeof(info))
if err != nil {
return nil, fmt.Errorf("can't get map info: %w", err)
}
return &info, nil
}
var haveObjName = internal.FeatureTest("object names", "4.15", func() error {
attr := internal.BPFMapCreateAttr{
MapType: uint32(Array),
attr := sys.MapCreateAttr{
MapType: sys.MapType(Array),
KeySize: 4,
ValueSize: 4,
MaxEntries: 1,
MapName: internal.NewBPFObjName("feature_test"),
MapName: sys.NewObjName("feature_test"),
}
fd, err := internal.BPFMapCreate(&attr)
fd, err := sys.MapCreate(&attr)
if err != nil {
return internal.ErrNotSupported
}
@@ -392,15 +168,15 @@ var objNameAllowsDot = internal.FeatureTest("dot in object names", "5.2", func()
return err
}
attr := internal.BPFMapCreateAttr{
MapType: uint32(Array),
attr := sys.MapCreateAttr{
MapType: sys.MapType(Array),
KeySize: 4,
ValueSize: 4,
MaxEntries: 1,
MapName: internal.NewBPFObjName(".test"),
MapName: sys.NewObjName(".test"),
}
fd, err := internal.BPFMapCreate(&attr)
fd, err := sys.MapCreate(&attr)
if err != nil {
return internal.ErrNotSupported
}
@@ -411,24 +187,30 @@ var objNameAllowsDot = internal.FeatureTest("dot in object names", "5.2", func()
var haveBatchAPI = internal.FeatureTest("map batch api", "5.6", func() error {
var maxEntries uint32 = 2
attr := internal.BPFMapCreateAttr{
MapType: uint32(Hash),
attr := sys.MapCreateAttr{
MapType: sys.MapType(Hash),
KeySize: 4,
ValueSize: 4,
MaxEntries: maxEntries,
}
fd, err := internal.BPFMapCreate(&attr)
fd, err := sys.MapCreate(&attr)
if err != nil {
return internal.ErrNotSupported
}
defer fd.Close()
keys := []uint32{1, 2}
values := []uint32{3, 4}
kp, _ := marshalPtr(keys, 8)
vp, _ := marshalPtr(values, 8)
nilPtr := internal.NewPointer(nil)
_, err = bpfMapBatch(internal.BPF_MAP_UPDATE_BATCH, fd, nilPtr, nilPtr, kp, vp, maxEntries, nil)
err = sys.MapUpdateBatch(&sys.MapUpdateBatchAttr{
MapFd: fd.Uint(),
Keys: kp,
Values: vp,
Count: maxEntries,
})
if err != nil {
return internal.ErrNotSupported
}
@@ -444,17 +226,17 @@ var haveProbeReadKernel = internal.FeatureTest("bpf_probe_read_kernel", "5.5", f
asm.FnProbeReadKernel.Call(),
asm.Return(),
}
buf := bytes.NewBuffer(make([]byte, 0, len(insns)*asm.InstructionSize))
buf := bytes.NewBuffer(make([]byte, 0, insns.Size()))
if err := insns.Marshal(buf, internal.NativeEndian); err != nil {
return err
}
bytecode := buf.Bytes()
fd, err := internal.BPFProgLoad(&internal.BPFProgLoadAttr{
ProgType: uint32(Kprobe),
License: internal.NewStringPointer("GPL"),
Instructions: internal.NewSlicePointer(bytecode),
InsCount: uint32(len(bytecode) / asm.InstructionSize),
fd, err := sys.ProgLoad(&sys.ProgLoadAttr{
ProgType: sys.ProgType(Kprobe),
License: sys.NewStringPointer("GPL"),
Insns: sys.NewSlicePointer(bytecode),
InsnCnt: uint32(len(bytecode) / asm.InstructionSize),
})
if err != nil {
return internal.ErrNotSupported
+14 -2
View File
@@ -11,7 +11,7 @@ import (
type MapType uint32
// Max returns the latest supported MapType.
func (_ MapType) Max() MapType {
func (MapType) Max() MapType {
return maxMapType - 1
}
@@ -126,11 +126,22 @@ func (mt MapType) canStoreProgram() bool {
return mt == ProgramArray
}
// hasBTF returns true if the map type supports BTF key/value metadata.
func (mt MapType) hasBTF() bool {
switch mt {
case PerfEventArray, CGroupArray, StackTrace, ArrayOfMaps, HashOfMaps, DevMap,
DevMapHash, CPUMap, XSKMap, SockMap, SockHash, Queue, Stack, RingBuf:
return false
default:
return true
}
}
// ProgramType of the eBPF program
type ProgramType uint32
// Max return the latest supported ProgramType.
func (_ ProgramType) Max() ProgramType {
func (ProgramType) Max() ProgramType {
return maxProgramType - 1
}
@@ -167,6 +178,7 @@ const (
Extension
LSM
SkLookup
Syscall
maxProgramType
)
+4 -3
View File
@@ -86,12 +86,13 @@ func _() {
_ = x[Extension-28]
_ = x[LSM-29]
_ = x[SkLookup-30]
_ = x[maxProgramType-31]
_ = x[Syscall-31]
_ = x[maxProgramType-32]
}
const _ProgramType_name = "UnspecifiedProgramSocketFilterKprobeSchedCLSSchedACTTracePointXDPPerfEventCGroupSKBCGroupSockLWTInLWTOutLWTXmitSockOpsSkSKBCGroupDeviceSkMsgRawTracepointCGroupSockAddrLWTSeg6LocalLircMode2SkReuseportFlowDissectorCGroupSysctlRawTracepointWritableCGroupSockoptTracingStructOpsExtensionLSMSkLookupmaxProgramType"
const _ProgramType_name = "UnspecifiedProgramSocketFilterKprobeSchedCLSSchedACTTracePointXDPPerfEventCGroupSKBCGroupSockLWTInLWTOutLWTXmitSockOpsSkSKBCGroupDeviceSkMsgRawTracepointCGroupSockAddrLWTSeg6LocalLircMode2SkReuseportFlowDissectorCGroupSysctlRawTracepointWritableCGroupSockoptTracingStructOpsExtensionLSMSkLookupSyscallmaxProgramType"
var _ProgramType_index = [...]uint16{0, 18, 30, 36, 44, 52, 62, 65, 74, 83, 93, 98, 104, 111, 118, 123, 135, 140, 153, 167, 179, 188, 199, 212, 224, 245, 258, 265, 274, 283, 286, 294, 308}
var _ProgramType_index = [...]uint16{0, 18, 30, 36, 44, 52, 62, 65, 74, 83, 93, 98, 104, 111, 118, 123, 135, 140, 153, 167, 179, 188, 199, 212, 224, 245, 258, 265, 274, 283, 286, 294, 301, 315}
func (i ProgramType) String() string {
if i >= ProgramType(len(_ProgramType_index)-1) {
+2 -1
View File
@@ -2,12 +2,13 @@
## explicit
github.com/checkpoint-restore/go-criu/v5
github.com/checkpoint-restore/go-criu/v5/rpc
# github.com/cilium/ebpf v0.7.0
# github.com/cilium/ebpf v0.8.0
## explicit
github.com/cilium/ebpf
github.com/cilium/ebpf/asm
github.com/cilium/ebpf/internal
github.com/cilium/ebpf/internal/btf
github.com/cilium/ebpf/internal/sys
github.com/cilium/ebpf/internal/unix
github.com/cilium/ebpf/link
# github.com/containerd/console v1.0.3