From abc156b442a4b7233bea8eb4fe942581b7b33f53 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Aug 2026 13:42:35 +0000 Subject: [PATCH 1/2] build(deps): bump github.com/cilium/ebpf from 0.17.3 to 0.22.0 Bumps [github.com/cilium/ebpf](https://github.com/cilium/ebpf) from 0.17.3 to 0.22.0. - [Commits](https://github.com/cilium/ebpf/compare/v0.17.3...v0.22.0) --- updated-dependencies: - dependency-name: github.com/cilium/ebpf dependency-version: 0.22.0 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 8 +- vendor/github.com/cilium/ebpf/.gitattributes | 3 + vendor/github.com/cilium/ebpf/.golangci.yaml | 46 +- vendor/github.com/cilium/ebpf/CODEOWNERS | 18 +- vendor/github.com/cilium/ebpf/Makefile | 101 ++- vendor/github.com/cilium/ebpf/README.md | 7 +- vendor/github.com/cilium/ebpf/asm/alu.go | 2 +- vendor/github.com/cilium/ebpf/asm/func.go | 239 +----- vendor/github.com/cilium/ebpf/asm/func_lin.go | 223 +++++ .../github.com/cilium/ebpf/asm/func_string.go | 53 +- vendor/github.com/cilium/ebpf/asm/func_win.go | 45 ++ .../github.com/cilium/ebpf/asm/instruction.go | 247 +++--- vendor/github.com/cilium/ebpf/asm/jump.go | 4 +- .../github.com/cilium/ebpf/asm/jump_string.go | 6 +- .../github.com/cilium/ebpf/asm/load_store.go | 129 ++- .../cilium/ebpf/asm/load_store_string.go | 4 +- vendor/github.com/cilium/ebpf/asm/metadata.go | 8 +- vendor/github.com/cilium/ebpf/asm/opcode.go | 44 +- .../cilium/ebpf/asm/opcode_string.go | 5 +- vendor/github.com/cilium/ebpf/asm/register.go | 1 + .../cilium/ebpf/attachtype_string.go | 28 +- vendor/github.com/cilium/ebpf/btf/btf.go | 469 ++++------- .../github.com/cilium/ebpf/btf/btf_types.go | 225 +++--- .../cilium/ebpf/btf/btf_types_string.go | 15 +- vendor/github.com/cilium/ebpf/btf/core.go | 27 +- vendor/github.com/cilium/ebpf/btf/dedup.go | 633 +++++++++++++++ vendor/github.com/cilium/ebpf/btf/ext_info.go | 134 ++- vendor/github.com/cilium/ebpf/btf/feature.go | 4 +- vendor/github.com/cilium/ebpf/btf/format.go | 8 +- vendor/github.com/cilium/ebpf/btf/handle.go | 49 +- vendor/github.com/cilium/ebpf/btf/kernel.go | 249 ++++-- vendor/github.com/cilium/ebpf/btf/marshal.go | 269 +++--- vendor/github.com/cilium/ebpf/btf/strings.go | 137 ++-- .../github.com/cilium/ebpf/btf/traversal.go | 240 +++--- vendor/github.com/cilium/ebpf/btf/types.go | 578 ++----------- .../github.com/cilium/ebpf/btf/unmarshal.go | 764 ++++++++++++++++++ vendor/github.com/cilium/ebpf/collection.go | 344 ++++---- .../cilium/ebpf/collection_other.go | 9 + .../cilium/ebpf/collection_windows.go | 136 ++++ vendor/github.com/cilium/ebpf/cpu.go | 49 -- vendor/github.com/cilium/ebpf/cpu_other.go | 13 + vendor/github.com/cilium/ebpf/cpu_windows.go | 11 + vendor/github.com/cilium/ebpf/elf_reader.go | 674 ++++++++++----- vendor/github.com/cilium/ebpf/elf_sections.go | 4 + vendor/github.com/cilium/ebpf/features/doc.go | 19 + .../github.com/cilium/ebpf/features/link.go | 157 ++++ vendor/github.com/cilium/ebpf/features/map.go | 362 +++++++++ .../github.com/cilium/ebpf/features/misc.go | 135 ++++ .../github.com/cilium/ebpf/features/prog.go | 332 ++++++++ .../cilium/ebpf/features/version.go | 18 + vendor/github.com/cilium/ebpf/info.go | 439 +++++++--- .../github.com/cilium/ebpf/internal/buffer.go | 31 - .../github.com/cilium/ebpf/internal/deque.go | 5 +- .../cilium/ebpf/internal/efw/enums.go | 65 ++ .../ebpf/internal/efw/error_reporting.go | 155 ++++ .../github.com/cilium/ebpf/internal/efw/fd.go | 34 + .../cilium/ebpf/internal/efw/map.go | 109 +++ .../cilium/ebpf/internal/efw/module.go | 36 + .../cilium/ebpf/internal/efw/native.go | 44 + .../cilium/ebpf/internal/efw/object.go | 117 +++ .../cilium/ebpf/internal/efw/proc.go | 50 ++ .../cilium/ebpf/internal/efw/program.go | 39 + .../cilium/ebpf/internal/efw/result.go | 57 ++ .../internal/efw/result_string_windows.go | 57 ++ .../cilium/ebpf/internal/efw/structs.go | 36 + .../cilium/ebpf/internal/endian_le.go | 2 +- .../cilium/ebpf/internal/feature.go | 52 +- .../github.com/cilium/ebpf/internal/goos.go | 7 - .../cilium/ebpf/internal/kallsyms/kallsyms.go | 182 +---- .../cilium/ebpf/internal/kallsyms/reader.go | 45 +- .../cilium/ebpf/internal/kconfig/kconfig.go | 4 +- .../cilium/ebpf/internal/linux/auxv.go | 3 +- .../cilium/ebpf/internal/linux/cpu.go | 45 ++ .../ebpf/internal/mountinfo/mountinfo.go | 150 ++++ vendor/github.com/cilium/ebpf/internal/nil.go | 24 + .../github.com/cilium/ebpf/internal/output.go | 9 +- .../ebpf/internal/platform/constants.go | 62 ++ .../cilium/ebpf/internal/platform/platform.go | 42 + .../ebpf/internal/platform/platform_linux.go | 3 + .../ebpf/internal/platform/platform_other.go | 5 + .../internal/platform/platform_windows.go | 3 + .../cilium/ebpf/internal/sys/doc.go | 2 +- .../github.com/cilium/ebpf/internal/sys/fd.go | 142 +--- .../cilium/ebpf/internal/sys/fd_other.go | 72 ++ .../cilium/ebpf/internal/sys/fd_windows.go | 60 ++ .../sys/{pinning.go => pinning_other.go} | 2 + .../ebpf/internal/sys/pinning_windows.go | 44 + .../cilium/ebpf/internal/sys/ptr.go | 54 +- .../cilium/ebpf/internal/sys/ptr_32_be.go | 2 + .../cilium/ebpf/internal/sys/ptr_32_le.go | 2 + .../cilium/ebpf/internal/sys/ptr_64.go | 2 + .../cilium/ebpf/internal/sys/signals.go | 2 + .../cilium/ebpf/internal/sys/syscall.go | 67 +- .../cilium/ebpf/internal/sys/syscall_other.go | 108 +++ .../ebpf/internal/sys/syscall_windows.go | 69 ++ .../cilium/ebpf/internal/sys/token.go | 226 ++++++ .../cilium/ebpf/internal/sys/types.go | 350 ++++++-- .../cilium/ebpf/internal/sys/types_string.go | 247 ++++++ .../cilium/ebpf/internal/sysenc/buffer.go | 2 +- .../cilium/ebpf/internal/sysenc/marshal.go | 28 +- .../ebpf/internal/testutils/fdtrace/main.go | 31 - .../{fdtrace => testmain}/fd_trace.go | 2 +- .../ebpf/internal/testutils/testmain/main.go | 58 ++ .../internal/testutils/testmain/windows.go | 219 +++++ .../cilium/ebpf/internal/tracefs/kprobe.go | 79 +- .../ebpf/internal/tracefs/probetype_string.go | 5 +- .../ebpf/internal/unix/errno_windows.go | 3 +- .../ebpf/internal/unix/strings_other.go | 4 + .../ebpf/internal/unix/strings_windows.go | 4 + .../cilium/ebpf/internal/unix/types_linux.go | 216 +++-- .../cilium/ebpf/internal/unix/types_other.go | 33 + .../cilium/ebpf/internal/version.go | 5 +- vendor/github.com/cilium/ebpf/link/anchor.go | 2 + vendor/github.com/cilium/ebpf/link/cgroup.go | 6 + vendor/github.com/cilium/ebpf/link/iter.go | 6 +- vendor/github.com/cilium/ebpf/link/kprobe.go | 7 +- .../cilium/ebpf/link/kprobe_multi.go | 126 +-- vendor/github.com/cilium/ebpf/link/link.go | 268 +----- .../github.com/cilium/ebpf/link/link_other.go | 382 +++++++++ .../cilium/ebpf/link/link_windows.go | 48 ++ .../github.com/cilium/ebpf/link/netfilter.go | 42 +- vendor/github.com/cilium/ebpf/link/netkit.go | 2 + vendor/github.com/cilium/ebpf/link/netns.go | 4 +- .../github.com/cilium/ebpf/link/perf_event.go | 89 +- vendor/github.com/cilium/ebpf/link/program.go | 7 +- vendor/github.com/cilium/ebpf/link/query.go | 18 +- .../cilium/ebpf/link/raw_tracepoint.go | 22 + .../cilium/ebpf/link/socket_filter.go | 2 + .../github.com/cilium/ebpf/link/struct_ops.go | 51 ++ .../github.com/cilium/ebpf/link/syscalls.go | 22 +- vendor/github.com/cilium/ebpf/link/tcx.go | 2 + .../github.com/cilium/ebpf/link/tracepoint.go | 2 + vendor/github.com/cilium/ebpf/link/tracing.go | 8 +- vendor/github.com/cilium/ebpf/link/uprobe.go | 125 ++- .../cilium/ebpf/link/uprobe_multi.go | 113 +-- vendor/github.com/cilium/ebpf/link/xdp.go | 2 + vendor/github.com/cilium/ebpf/linker.go | 205 ++--- vendor/github.com/cilium/ebpf/map.go | 407 +++++++--- vendor/github.com/cilium/ebpf/marshalers.go | 14 +- vendor/github.com/cilium/ebpf/memory.go | 38 +- .../github.com/cilium/ebpf/memory_unsafe.go | 340 ++++++++ .../cilium/ebpf/memory_unsafe_tag.go | 7 + vendor/github.com/cilium/ebpf/prog.go | 489 +++++++---- vendor/github.com/cilium/ebpf/struct_ops.go | 250 ++++++ vendor/github.com/cilium/ebpf/syscalls.go | 100 ++- vendor/github.com/cilium/ebpf/types.go | 114 ++- vendor/github.com/cilium/ebpf/types_string.go | 67 +- .../github.com/cilium/ebpf/types_windows.go | 57 ++ vendor/github.com/cilium/ebpf/variable.go | 174 ++-- vendor/modules.txt | 10 +- 151 files changed, 10926 insertions(+), 4041 deletions(-) create mode 100644 vendor/github.com/cilium/ebpf/asm/func_lin.go create mode 100644 vendor/github.com/cilium/ebpf/asm/func_win.go create mode 100644 vendor/github.com/cilium/ebpf/btf/dedup.go create mode 100644 vendor/github.com/cilium/ebpf/btf/unmarshal.go create mode 100644 vendor/github.com/cilium/ebpf/collection_other.go create mode 100644 vendor/github.com/cilium/ebpf/collection_windows.go create mode 100644 vendor/github.com/cilium/ebpf/cpu_other.go create mode 100644 vendor/github.com/cilium/ebpf/cpu_windows.go create mode 100644 vendor/github.com/cilium/ebpf/features/doc.go create mode 100644 vendor/github.com/cilium/ebpf/features/link.go create mode 100644 vendor/github.com/cilium/ebpf/features/map.go create mode 100644 vendor/github.com/cilium/ebpf/features/misc.go create mode 100644 vendor/github.com/cilium/ebpf/features/prog.go create mode 100644 vendor/github.com/cilium/ebpf/features/version.go delete mode 100644 vendor/github.com/cilium/ebpf/internal/buffer.go create mode 100644 vendor/github.com/cilium/ebpf/internal/efw/enums.go create mode 100644 vendor/github.com/cilium/ebpf/internal/efw/error_reporting.go create mode 100644 vendor/github.com/cilium/ebpf/internal/efw/fd.go create mode 100644 vendor/github.com/cilium/ebpf/internal/efw/map.go create mode 100644 vendor/github.com/cilium/ebpf/internal/efw/module.go create mode 100644 vendor/github.com/cilium/ebpf/internal/efw/native.go create mode 100644 vendor/github.com/cilium/ebpf/internal/efw/object.go create mode 100644 vendor/github.com/cilium/ebpf/internal/efw/proc.go create mode 100644 vendor/github.com/cilium/ebpf/internal/efw/program.go create mode 100644 vendor/github.com/cilium/ebpf/internal/efw/result.go create mode 100644 vendor/github.com/cilium/ebpf/internal/efw/result_string_windows.go create mode 100644 vendor/github.com/cilium/ebpf/internal/efw/structs.go delete mode 100644 vendor/github.com/cilium/ebpf/internal/goos.go create mode 100644 vendor/github.com/cilium/ebpf/internal/linux/cpu.go create mode 100644 vendor/github.com/cilium/ebpf/internal/mountinfo/mountinfo.go create mode 100644 vendor/github.com/cilium/ebpf/internal/nil.go create mode 100644 vendor/github.com/cilium/ebpf/internal/platform/constants.go create mode 100644 vendor/github.com/cilium/ebpf/internal/platform/platform.go create mode 100644 vendor/github.com/cilium/ebpf/internal/platform/platform_linux.go create mode 100644 vendor/github.com/cilium/ebpf/internal/platform/platform_other.go create mode 100644 vendor/github.com/cilium/ebpf/internal/platform/platform_windows.go create mode 100644 vendor/github.com/cilium/ebpf/internal/sys/fd_other.go create mode 100644 vendor/github.com/cilium/ebpf/internal/sys/fd_windows.go rename vendor/github.com/cilium/ebpf/internal/sys/{pinning.go => pinning_other.go} (98%) create mode 100644 vendor/github.com/cilium/ebpf/internal/sys/pinning_windows.go create mode 100644 vendor/github.com/cilium/ebpf/internal/sys/syscall_other.go create mode 100644 vendor/github.com/cilium/ebpf/internal/sys/syscall_windows.go create mode 100644 vendor/github.com/cilium/ebpf/internal/sys/token.go create mode 100644 vendor/github.com/cilium/ebpf/internal/sys/types_string.go delete mode 100644 vendor/github.com/cilium/ebpf/internal/testutils/fdtrace/main.go rename vendor/github.com/cilium/ebpf/internal/testutils/{fdtrace => testmain}/fd_trace.go (99%) create mode 100644 vendor/github.com/cilium/ebpf/internal/testutils/testmain/main.go create mode 100644 vendor/github.com/cilium/ebpf/internal/testutils/testmain/windows.go create mode 100644 vendor/github.com/cilium/ebpf/link/link_other.go create mode 100644 vendor/github.com/cilium/ebpf/link/link_windows.go create mode 100644 vendor/github.com/cilium/ebpf/link/struct_ops.go create mode 100644 vendor/github.com/cilium/ebpf/memory_unsafe.go create mode 100644 vendor/github.com/cilium/ebpf/memory_unsafe_tag.go create mode 100644 vendor/github.com/cilium/ebpf/struct_ops.go create mode 100644 vendor/github.com/cilium/ebpf/types_windows.go diff --git a/go.mod b/go.mod index 520931f93a..b09479baa2 100644 --- a/go.mod +++ b/go.mod @@ -75,7 +75,7 @@ require ( github.com/agnivade/levenshtein v1.2.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/checkpoint-restore/go-criu/v7 v7.2.0 // indirect - github.com/cilium/ebpf v0.17.3 // indirect + github.com/cilium/ebpf v0.22.0 // indirect github.com/containerd/continuity v0.5.0 // indirect github.com/containerd/fifo v1.1.0 // indirect github.com/containerd/protobuild v0.3.0 // indirect diff --git a/go.sum b/go.sum index aa7043f6e7..dbc74cd811 100644 --- a/go.sum +++ b/go.sum @@ -411,8 +411,8 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5P github.com/chzyer/readline v1.5.0/go.mod h1:x22KAscuvRqlLoK9CsoYsmxoXZMMFVyOl86cAH8qUic= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/chzyer/test v0.0.0-20210722231415-061457976a23/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/cilium/ebpf v0.17.3 h1:FnP4r16PWYSE4ux6zN+//jMcW4nMVRvuTLVTvCjyyjg= -github.com/cilium/ebpf v0.17.3/go.mod h1:G5EDHij8yiLzaqn0WjyfJHvRa+3aDlReIaLVRMvOyJk= +github.com/cilium/ebpf v0.22.0 h1:v2ktp0roffpMOj2MMf3idtCQZOsAoC4BJbAJN+ke2bY= +github.com/cilium/ebpf v0.22.0/go.mod h1:CDzZbe2hC5JjlDC+CY3KFCzlYwN4gbxppYM+Z10bQt4= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= @@ -569,8 +569,8 @@ github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvSc github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= -github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI= -github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow= +github.com/go-quicktest/qt v1.101.1-0.20240301121107-c6c8733fa1e6 h1:teYtXy9B7y5lHTp8V9KPxpYRAVA7dozigQcMiBust1s= +github.com/go-quicktest/qt v1.101.1-0.20240301121107-c6c8733fa1e6/go.mod h1:p4lGIVX+8Wa6ZPNDvqcxq36XpUDLh42FLetFU7odllI= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= diff --git a/vendor/github.com/cilium/ebpf/.gitattributes b/vendor/github.com/cilium/ebpf/.gitattributes index 113f97b980..ea7c9a89c3 100644 --- a/vendor/github.com/cilium/ebpf/.gitattributes +++ b/vendor/github.com/cilium/ebpf/.gitattributes @@ -1 +1,4 @@ +# Force line ending normalisation +* text=auto +# Show types.go in the PR diff view by default internal/sys/types.go linguist-generated=false diff --git a/vendor/github.com/cilium/ebpf/.golangci.yaml b/vendor/github.com/cilium/ebpf/.golangci.yaml index 8298e59ed8..8f88708b2c 100644 --- a/vendor/github.com/cilium/ebpf/.golangci.yaml +++ b/vendor/github.com/cilium/ebpf/.golangci.yaml @@ -1,31 +1,29 @@ ---- +version: "2" linters: - disable-all: true + default: none enable: - - goimports - - gosimple + - depguard - govet - ineffassign - misspell - - staticcheck - - typecheck - unused + settings: + depguard: + rules: + no-x-sys-unix: + files: + - '!**/internal/unix/*.go' + - '!**/examples/**/*.go' + - '!**/docs/**/*.go' + deny: + - pkg: golang.org/x/sys/unix + desc: use internal/unix instead + +formatters: + enable: - gofmt - - depguard -linters-settings: - goimports: - # A comma-separated list of prefixes, which, if set, checks import paths - # with the given prefixes are grouped after 3rd-party packages. - # Default: "" - local-prefixes: github.com/cilium/ebpf - depguard: - rules: - no-x-sys-unix: - files: - # Filenames are matched against absolute paths, include **/ at the start. - - '!**/internal/unix/*.go' - - '!**/examples/**/*.go' - - '!**/docs/**/*.go' - deny: - - pkg: golang.org/x/sys/unix - desc: use internal/unix instead + - goimports + settings: + goimports: + local-prefixes: + - github.com/cilium/ebpf diff --git a/vendor/github.com/cilium/ebpf/CODEOWNERS b/vendor/github.com/cilium/ebpf/CODEOWNERS index 0f76dce85c..be87f42251 100644 --- a/vendor/github.com/cilium/ebpf/CODEOWNERS +++ b/vendor/github.com/cilium/ebpf/CODEOWNERS @@ -1,13 +1,17 @@ * @cilium/ebpf-lib-maintainers -features/ @rgo3 -link/ @mmat11 +/features/ @rgo3 +/link/ @mmat11 @rgo3 -perf/ @florianl -ringbuf/ @florianl +/perf/ @florianl +/ringbuf/ @florianl -btf/ @dylandreimerink +/btf/ @dylandreimerink -cmd/bpf2go/ @mejedi +/docs/ @cilium/ebpf-go-reviewers @ti-mo -docs/ @ti-mo +# Windows specific code. +/docs/**/windows*.md @cilium/ebpf-go-windows-reviewers +/internal/efw @cilium/ebpf-go-windows-reviewers +windows/ @cilium/ebpf-go-windows-reviewers # Folders +*windows*.go @cilium/ebpf-go-windows-reviewers # Go code diff --git a/vendor/github.com/cilium/ebpf/Makefile b/vendor/github.com/cilium/ebpf/Makefile index e0fe974920..d697871e69 100644 --- a/vendor/github.com/cilium/ebpf/Makefile +++ b/vendor/github.com/cilium/ebpf/Makefile @@ -1,30 +1,51 @@ # 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-17 -STRIP ?= llvm-strip-17 -OBJCOPY ?= llvm-objcopy-17 -CFLAGS := -O2 -g -Wall -Werror $(CFLAGS) +CLANG ?= clang-22 +STRIP ?= llvm-strip-22 +OBJCOPY ?= llvm-objcopy-22 +CFLAGS := -O2 -g -Wall -Werror -mcpu=v2 $(CFLAGS) CI_KERNEL_URL ?= https://github.com/cilium/ci-kernels/raw/master/ # 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), --log-driver=none, --user "${UIDGID}") +CONTAINER_ENGINE ?= $(if $(shell command -v podman),podman,docker) + +# Configure container runtime arguments based on the container engine. +CONTAINER_RUN_ARGS := \ + --env MAKEFLAGS \ + --env BPF2GO_CC="$(CLANG)" \ + --env BPF2GO_CFLAGS="$(CFLAGS)" \ + --env HOME=/tmp \ + -v "${REPODIR}":/ebpf -w /ebpf \ + -v "$(shell go env GOCACHE)":/tmp/.cache/go-build \ + -v "$(shell go env GOPATH)":/go \ + -v "$(shell go env GOMODCACHE)":/go/pkg/mod + +ifeq ($(CONTAINER_ENGINE), podman) +CONTAINER_RUN_ARGS += --log-driver=none --security-opt label=disable +else +CONTAINER_RUN_ARGS += --user "$(shell stat -c '%u:%g' ${REPODIR})" +endif IMAGE := $(shell cat ${REPODIR}/testdata/docker/IMAGE) VERSION := $(shell cat ${REPODIR}/testdata/docker/VERSION) +TARGETS_EL := \ + testdata/linked1 \ + testdata/linked2 \ + testdata/linked + TARGETS := \ - testdata/loader-clang-11 \ testdata/loader-clang-14 \ + testdata/loader-clang-17 \ testdata/loader-$(CLANG) \ + testdata/loader_nobtf \ testdata/manyprogs \ testdata/btf_map_init \ testdata/invalid_map \ @@ -46,6 +67,8 @@ TARGETS := \ testdata/constants \ testdata/errors \ testdata/variables \ + testdata/arena \ + testdata/struct_ops \ btf/testdata/relocs \ btf/testdata/relocs_read \ btf/testdata/relocs_read_tgt \ @@ -53,27 +76,27 @@ TARGETS := \ btf/testdata/tags \ cmd/bpf2go/testdata/minimal -.PHONY: all clean container-all container-shell generate +HEADERS := $(wildcard testdata/*.h) + +.PHONY: all clean godirs container-all container-shell generate format -.DEFAULT_TARGET = container-all +.DEFAULT_GOAL = container-all + +# Make sure Go-related dirs exist before starting containerized builds, +# otherwise podman will fail to start. +godirs: + mkdir -p $(shell go env GOCACHE) $(shell go env GOPATH) $(shell go env GOMODCACHE) # Build all ELF binaries using a containerized LLVM toolchain. -container-all: +container-all: godirs +${CONTAINER_ENGINE} run --rm -ti ${CONTAINER_RUN_ARGS} \ - -v "${REPODIR}":/ebpf -w /ebpf --env MAKEFLAGS \ - --env HOME="/tmp" \ - --env BPF2GO_CC="$(CLANG)" \ - --env BPF2GO_FLAGS="-fdebug-prefix-map=/ebpf=. $(CFLAGS)" \ "${IMAGE}:${VERSION}" \ - make all + $(MAKE) all # (debug) Drop the user into a shell inside the container as root. # Set BPF2GO_ envs to make 'make generate' just work. -container-shell: - ${CONTAINER_ENGINE} run --rm -ti \ - -v "${REPODIR}":/ebpf -w /ebpf \ - --env BPF2GO_CC="$(CLANG)" \ - --env BPF2GO_FLAGS="-fdebug-prefix-map=/ebpf=. $(CFLAGS)" \ +container-shell: godirs + ${CONTAINER_ENGINE} run --rm -ti ${CONTAINER_RUN_ARGS} \ "${IMAGE}:${VERSION}" clean: @@ -83,32 +106,44 @@ clean: format: find . -type f -name "*.c" | xargs clang-format -i -all: format $(addsuffix -el.elf,$(TARGETS)) $(addsuffix -eb.elf,$(TARGETS)) generate +all: format testdata update-external-deps ln -srf testdata/loader-$(CLANG)-el.elf testdata/loader-el.elf ln -srf testdata/loader-$(CLANG)-eb.elf testdata/loader-eb.elf + $(MAKE) generate generate: - go generate -run "internal/cmd/gentypes" ./... - go generate -skip "internal/cmd/gentypes" ./... + go generate -run "gentypes" ./... + go generate -run "stringer" ./... + go generate -skip "(gentypes|stringer)" ./... + +testdata: $(addsuffix -el.elf,$(TARGETS)) $(addsuffix -eb.elf,$(TARGETS)) $(addsuffix -el.elf,$(TARGETS_EL)) -testdata/loader-%-el.elf: testdata/loader.c +testdata/loader-%-el.elf: testdata/loader.c $(HEADERS) $* $(CFLAGS) -target bpfel -c $< -o $@ $(STRIP) -g $@ -testdata/loader-%-eb.elf: testdata/loader.c +testdata/loader-%-eb.elf: testdata/loader.c $(HEADERS) $* $(CFLAGS) -target bpfeb -c $< -o $@ $(STRIP) -g $@ -%-el.elf: %.c +testdata/loader_nobtf-el.elf: testdata/loader.c $(HEADERS) + $(CLANG) $(CFLAGS) -g0 -D__NOBTF__ -target bpfel -c $< -o $@ + +testdata/loader_nobtf-eb.elf: testdata/loader.c $(HEADERS) + $(CLANG) $(CFLAGS) -g0 -D__NOBTF__ -target bpfeb -c $< -o $@ + +%-el.elf: %.c $(HEADERS) $(CLANG) $(CFLAGS) -target bpfel -c $< -o $@ $(STRIP) -g $@ -%-eb.elf : %.c +%-eb.elf: %.c $(HEADERS) $(CLANG) $(CFLAGS) -target bpfeb -c $< -o $@ $(STRIP) -g $@ -.PHONY: update-kernel-deps -update-kernel-deps: export KERNEL_VERSION?=6.8 -update-kernel-deps: - ./testdata/sh/update-kernel-deps.sh - $(MAKE) container-all +testdata/linked-el.elf: testdata/linked1-el.elf testdata/linked2-el.elf + bpftool gen object $@ $^ + +.PHONY: update-external-deps +update-external-deps: + ./scripts/update-kernel-deps.sh + ./scripts/update-efw-deps.sh diff --git a/vendor/github.com/cilium/ebpf/README.md b/vendor/github.com/cilium/ebpf/README.md index 8238256c8e..01a154c615 100644 --- a/vendor/github.com/cilium/ebpf/README.md +++ b/vendor/github.com/cilium/ebpf/README.md @@ -59,8 +59,11 @@ This library includes the following packages: * A version of Go that is [supported by upstream](https://golang.org/doc/devel/release.html#policy) -* CI is run against kernel.org LTS releases. >= 4.4 should work but EOL'ed versions - are not supported. +* Linux (amd64, arm64): CI is run against kernel.org LTS releases. >= 4.4 should work but EOL'ed + versions are not supported. +* Windows (amd64): CI is run against Windows Server 2022. Only the latest eBPF for Windows + release is supported. +* Other architectures are best effort. 32bit arches are not supported. ## License diff --git a/vendor/github.com/cilium/ebpf/asm/alu.go b/vendor/github.com/cilium/ebpf/asm/alu.go index 282233d327..a4ae722127 100644 --- a/vendor/github.com/cilium/ebpf/asm/alu.go +++ b/vendor/github.com/cilium/ebpf/asm/alu.go @@ -1,6 +1,6 @@ package asm -//go:generate go run golang.org/x/tools/cmd/stringer@latest -output alu_string.go -type=Source,Endianness,ALUOp +//go:generate go tool stringer -output alu_string.go -type=Source,Endianness,ALUOp // Source of ALU / ALU64 / Branch operations // diff --git a/vendor/github.com/cilium/ebpf/asm/func.go b/vendor/github.com/cilium/ebpf/asm/func.go index 7a59acf383..fe75c75784 100644 --- a/vendor/github.com/cilium/ebpf/asm/func.go +++ b/vendor/github.com/cilium/ebpf/asm/func.go @@ -1,239 +1,18 @@ package asm -//go:generate go run golang.org/x/tools/cmd/stringer@latest -output func_string.go -type=BuiltinFunc +import "github.com/cilium/ebpf/internal/platform" + +//go:generate go tool stringer -output func_string.go -type=BuiltinFunc // BuiltinFunc is a built-in eBPF function. -type BuiltinFunc int32 +type BuiltinFunc uint32 -// eBPF built-in functions -// -// You can regenerate this list using the following gawk script: +// BuiltinFuncForPlatform returns a platform specific function constant. // -// /FN\(.+\),/ { -// match($1, /\(([a-z_0-9]+),/, r) -// split(r[1], p, "_") -// printf "Fn" -// for (i in p) { -// printf "%s%s", toupper(substr(p[i], 1, 1)), substr(p[i], 2) -// } -// print "" -// } -// -// The script expects include/uapi/linux/bpf.h as it's input. -const ( - FnUnspec BuiltinFunc = iota - FnMapLookupElem - FnMapUpdateElem - FnMapDeleteElem - FnProbeRead - FnKtimeGetNs - FnTracePrintk - FnGetPrandomU32 - FnGetSmpProcessorId - FnSkbStoreBytes - FnL3CsumReplace - FnL4CsumReplace - FnTailCall - FnCloneRedirect - FnGetCurrentPidTgid - FnGetCurrentUidGid - FnGetCurrentComm - FnGetCgroupClassid - FnSkbVlanPush - FnSkbVlanPop - FnSkbGetTunnelKey - FnSkbSetTunnelKey - FnPerfEventRead - FnRedirect - FnGetRouteRealm - FnPerfEventOutput - FnSkbLoadBytes - FnGetStackid - FnCsumDiff - FnSkbGetTunnelOpt - FnSkbSetTunnelOpt - FnSkbChangeProto - FnSkbChangeType - FnSkbUnderCgroup - FnGetHashRecalc - FnGetCurrentTask - FnProbeWriteUser - FnCurrentTaskUnderCgroup - FnSkbChangeTail - FnSkbPullData - FnCsumUpdate - FnSetHashInvalid - FnGetNumaNodeId - FnSkbChangeHead - FnXdpAdjustHead - FnProbeReadStr - FnGetSocketCookie - FnGetSocketUid - FnSetHash - FnSetsockopt - FnSkbAdjustRoom - FnRedirectMap - FnSkRedirectMap - FnSockMapUpdate - FnXdpAdjustMeta - FnPerfEventReadValue - FnPerfProgReadValue - FnGetsockopt - FnOverrideReturn - FnSockOpsCbFlagsSet - FnMsgRedirectMap - FnMsgApplyBytes - FnMsgCorkBytes - FnMsgPullData - FnBind - FnXdpAdjustTail - FnSkbGetXfrmState - FnGetStack - FnSkbLoadBytesRelative - FnFibLookup - FnSockHashUpdate - FnMsgRedirectHash - FnSkRedirectHash - FnLwtPushEncap - FnLwtSeg6StoreBytes - FnLwtSeg6AdjustSrh - FnLwtSeg6Action - FnRcRepeat - FnRcKeydown - FnSkbCgroupId - FnGetCurrentCgroupId - FnGetLocalStorage - FnSkSelectReuseport - FnSkbAncestorCgroupId - FnSkLookupTcp - FnSkLookupUdp - FnSkRelease - FnMapPushElem - FnMapPopElem - FnMapPeekElem - FnMsgPushData - FnMsgPopData - FnRcPointerRel - FnSpinLock - FnSpinUnlock - FnSkFullsock - FnTcpSock - FnSkbEcnSetCe - FnGetListenerSock - FnSkcLookupTcp - FnTcpCheckSyncookie - FnSysctlGetName - FnSysctlGetCurrentValue - FnSysctlGetNewValue - FnSysctlSetNewValue - FnStrtol - FnStrtoul - FnSkStorageGet - FnSkStorageDelete - FnSendSignal - FnTcpGenSyncookie - FnSkbOutput - FnProbeReadUser - FnProbeReadKernel - FnProbeReadUserStr - FnProbeReadKernelStr - FnTcpSendAck - FnSendSignalThread - FnJiffies64 - FnReadBranchRecords - FnGetNsCurrentPidTgid - FnXdpOutput - FnGetNetnsCookie - FnGetCurrentAncestorCgroupId - FnSkAssign - FnKtimeGetBootNs - FnSeqPrintf - FnSeqWrite - FnSkCgroupId - FnSkAncestorCgroupId - FnRingbufOutput - FnRingbufReserve - FnRingbufSubmit - FnRingbufDiscard - FnRingbufQuery - FnCsumLevel - FnSkcToTcp6Sock - FnSkcToTcpSock - FnSkcToTcpTimewaitSock - FnSkcToTcpRequestSock - FnSkcToUdp6Sock - FnGetTaskStack - FnLoadHdrOpt - FnStoreHdrOpt - FnReserveHdrOpt - FnInodeStorageGet - FnInodeStorageDelete - FnDPath - FnCopyFromUser - FnSnprintfBtf - FnSeqPrintfBtf - FnSkbCgroupClassid - FnRedirectNeigh - FnPerCpuPtr - FnThisCpuPtr - FnRedirectPeer - FnTaskStorageGet - FnTaskStorageDelete - FnGetCurrentTaskBtf - FnBprmOptsSet - FnKtimeGetCoarseNs - FnImaInodeHash - FnSockFromFile - FnCheckMtu - FnForEachMapElem - FnSnprintf - FnSysBpf - FnBtfFindByNameKind - FnSysClose - FnTimerInit - FnTimerSetCallback - FnTimerStart - FnTimerCancel - FnGetFuncIp - FnGetAttachCookie - FnTaskPtRegs - FnGetBranchSnapshot - FnTraceVprintk - FnSkcToUnixSock - FnKallsymsLookupName - FnFindVma - FnLoop - FnStrncmp - FnGetFuncArg - FnGetFuncRet - FnGetFuncArgCnt - FnGetRetval - FnSetRetval - FnXdpGetBuffLen - FnXdpLoadBytes - FnXdpStoreBytes - FnCopyFromUserTask - FnSkbSetTstamp - FnImaFileHash - FnKptrXchg - FnMapLookupPercpuElem - FnSkcToMptcpSock - FnDynptrFromMem - FnRingbufReserveDynptr - FnRingbufSubmitDynptr - FnRingbufDiscardDynptr - FnDynptrRead - FnDynptrWrite - FnDynptrData - FnTcpRawGenSyncookieIpv4 - FnTcpRawGenSyncookieIpv6 - FnTcpRawCheckSyncookieIpv4 - FnTcpRawCheckSyncookieIpv6 - FnKtimeGetTaiNs - FnUserRingbufDrain - FnCgrpStorageGet - FnCgrpStorageDelete -) +// Use this if the library doesn't provide a constant yet. +func BuiltinFuncForPlatform(plat string, value uint32) (BuiltinFunc, error) { + return platform.EncodeConstant[BuiltinFunc](plat, value) +} // Call emits a function call. func (fn BuiltinFunc) Call() Instruction { diff --git a/vendor/github.com/cilium/ebpf/asm/func_lin.go b/vendor/github.com/cilium/ebpf/asm/func_lin.go new file mode 100644 index 0000000000..1dd026d62d --- /dev/null +++ b/vendor/github.com/cilium/ebpf/asm/func_lin.go @@ -0,0 +1,223 @@ +// Code generated by internal/cmd/genfunctions.awk; DO NOT EDIT. + +package asm + +// Code in this file is derived from Linux, available under the GPL-2.0 WITH Linux-syscall-note. + +import "github.com/cilium/ebpf/internal/platform" + +// Built-in functions (Linux). +const ( + FnUnspec = BuiltinFunc(platform.LinuxTag | 0) //lint:ignore SA4016 consistency + FnMapLookupElem = BuiltinFunc(platform.LinuxTag | 1) + FnMapUpdateElem = BuiltinFunc(platform.LinuxTag | 2) + FnMapDeleteElem = BuiltinFunc(platform.LinuxTag | 3) + FnProbeRead = BuiltinFunc(platform.LinuxTag | 4) + FnKtimeGetNs = BuiltinFunc(platform.LinuxTag | 5) + FnTracePrintk = BuiltinFunc(platform.LinuxTag | 6) + FnGetPrandomU32 = BuiltinFunc(platform.LinuxTag | 7) + FnGetSmpProcessorId = BuiltinFunc(platform.LinuxTag | 8) + FnSkbStoreBytes = BuiltinFunc(platform.LinuxTag | 9) + FnL3CsumReplace = BuiltinFunc(platform.LinuxTag | 10) + FnL4CsumReplace = BuiltinFunc(platform.LinuxTag | 11) + FnTailCall = BuiltinFunc(platform.LinuxTag | 12) + FnCloneRedirect = BuiltinFunc(platform.LinuxTag | 13) + FnGetCurrentPidTgid = BuiltinFunc(platform.LinuxTag | 14) + FnGetCurrentUidGid = BuiltinFunc(platform.LinuxTag | 15) + FnGetCurrentComm = BuiltinFunc(platform.LinuxTag | 16) + FnGetCgroupClassid = BuiltinFunc(platform.LinuxTag | 17) + FnSkbVlanPush = BuiltinFunc(platform.LinuxTag | 18) + FnSkbVlanPop = BuiltinFunc(platform.LinuxTag | 19) + FnSkbGetTunnelKey = BuiltinFunc(platform.LinuxTag | 20) + FnSkbSetTunnelKey = BuiltinFunc(platform.LinuxTag | 21) + FnPerfEventRead = BuiltinFunc(platform.LinuxTag | 22) + FnRedirect = BuiltinFunc(platform.LinuxTag | 23) + FnGetRouteRealm = BuiltinFunc(platform.LinuxTag | 24) + FnPerfEventOutput = BuiltinFunc(platform.LinuxTag | 25) + FnSkbLoadBytes = BuiltinFunc(platform.LinuxTag | 26) + FnGetStackid = BuiltinFunc(platform.LinuxTag | 27) + FnCsumDiff = BuiltinFunc(platform.LinuxTag | 28) + FnSkbGetTunnelOpt = BuiltinFunc(platform.LinuxTag | 29) + FnSkbSetTunnelOpt = BuiltinFunc(platform.LinuxTag | 30) + FnSkbChangeProto = BuiltinFunc(platform.LinuxTag | 31) + FnSkbChangeType = BuiltinFunc(platform.LinuxTag | 32) + FnSkbUnderCgroup = BuiltinFunc(platform.LinuxTag | 33) + FnGetHashRecalc = BuiltinFunc(platform.LinuxTag | 34) + FnGetCurrentTask = BuiltinFunc(platform.LinuxTag | 35) + FnProbeWriteUser = BuiltinFunc(platform.LinuxTag | 36) + FnCurrentTaskUnderCgroup = BuiltinFunc(platform.LinuxTag | 37) + FnSkbChangeTail = BuiltinFunc(platform.LinuxTag | 38) + FnSkbPullData = BuiltinFunc(platform.LinuxTag | 39) + FnCsumUpdate = BuiltinFunc(platform.LinuxTag | 40) + FnSetHashInvalid = BuiltinFunc(platform.LinuxTag | 41) + FnGetNumaNodeId = BuiltinFunc(platform.LinuxTag | 42) + FnSkbChangeHead = BuiltinFunc(platform.LinuxTag | 43) + FnXdpAdjustHead = BuiltinFunc(platform.LinuxTag | 44) + FnProbeReadStr = BuiltinFunc(platform.LinuxTag | 45) + FnGetSocketCookie = BuiltinFunc(platform.LinuxTag | 46) + FnGetSocketUid = BuiltinFunc(platform.LinuxTag | 47) + FnSetHash = BuiltinFunc(platform.LinuxTag | 48) + FnSetsockopt = BuiltinFunc(platform.LinuxTag | 49) + FnSkbAdjustRoom = BuiltinFunc(platform.LinuxTag | 50) + FnRedirectMap = BuiltinFunc(platform.LinuxTag | 51) + FnSkRedirectMap = BuiltinFunc(platform.LinuxTag | 52) + FnSockMapUpdate = BuiltinFunc(platform.LinuxTag | 53) + FnXdpAdjustMeta = BuiltinFunc(platform.LinuxTag | 54) + FnPerfEventReadValue = BuiltinFunc(platform.LinuxTag | 55) + FnPerfProgReadValue = BuiltinFunc(platform.LinuxTag | 56) + FnGetsockopt = BuiltinFunc(platform.LinuxTag | 57) + FnOverrideReturn = BuiltinFunc(platform.LinuxTag | 58) + FnSockOpsCbFlagsSet = BuiltinFunc(platform.LinuxTag | 59) + FnMsgRedirectMap = BuiltinFunc(platform.LinuxTag | 60) + FnMsgApplyBytes = BuiltinFunc(platform.LinuxTag | 61) + FnMsgCorkBytes = BuiltinFunc(platform.LinuxTag | 62) + FnMsgPullData = BuiltinFunc(platform.LinuxTag | 63) + FnBind = BuiltinFunc(platform.LinuxTag | 64) + FnXdpAdjustTail = BuiltinFunc(platform.LinuxTag | 65) + FnSkbGetXfrmState = BuiltinFunc(platform.LinuxTag | 66) + FnGetStack = BuiltinFunc(platform.LinuxTag | 67) + FnSkbLoadBytesRelative = BuiltinFunc(platform.LinuxTag | 68) + FnFibLookup = BuiltinFunc(platform.LinuxTag | 69) + FnSockHashUpdate = BuiltinFunc(platform.LinuxTag | 70) + FnMsgRedirectHash = BuiltinFunc(platform.LinuxTag | 71) + FnSkRedirectHash = BuiltinFunc(platform.LinuxTag | 72) + FnLwtPushEncap = BuiltinFunc(platform.LinuxTag | 73) + FnLwtSeg6StoreBytes = BuiltinFunc(platform.LinuxTag | 74) + FnLwtSeg6AdjustSrh = BuiltinFunc(platform.LinuxTag | 75) + FnLwtSeg6Action = BuiltinFunc(platform.LinuxTag | 76) + FnRcRepeat = BuiltinFunc(platform.LinuxTag | 77) + FnRcKeydown = BuiltinFunc(platform.LinuxTag | 78) + FnSkbCgroupId = BuiltinFunc(platform.LinuxTag | 79) + FnGetCurrentCgroupId = BuiltinFunc(platform.LinuxTag | 80) + FnGetLocalStorage = BuiltinFunc(platform.LinuxTag | 81) + FnSkSelectReuseport = BuiltinFunc(platform.LinuxTag | 82) + FnSkbAncestorCgroupId = BuiltinFunc(platform.LinuxTag | 83) + FnSkLookupTcp = BuiltinFunc(platform.LinuxTag | 84) + FnSkLookupUdp = BuiltinFunc(platform.LinuxTag | 85) + FnSkRelease = BuiltinFunc(platform.LinuxTag | 86) + FnMapPushElem = BuiltinFunc(platform.LinuxTag | 87) + FnMapPopElem = BuiltinFunc(platform.LinuxTag | 88) + FnMapPeekElem = BuiltinFunc(platform.LinuxTag | 89) + FnMsgPushData = BuiltinFunc(platform.LinuxTag | 90) + FnMsgPopData = BuiltinFunc(platform.LinuxTag | 91) + FnRcPointerRel = BuiltinFunc(platform.LinuxTag | 92) + FnSpinLock = BuiltinFunc(platform.LinuxTag | 93) + FnSpinUnlock = BuiltinFunc(platform.LinuxTag | 94) + FnSkFullsock = BuiltinFunc(platform.LinuxTag | 95) + FnTcpSock = BuiltinFunc(platform.LinuxTag | 96) + FnSkbEcnSetCe = BuiltinFunc(platform.LinuxTag | 97) + FnGetListenerSock = BuiltinFunc(platform.LinuxTag | 98) + FnSkcLookupTcp = BuiltinFunc(platform.LinuxTag | 99) + FnTcpCheckSyncookie = BuiltinFunc(platform.LinuxTag | 100) + FnSysctlGetName = BuiltinFunc(platform.LinuxTag | 101) + FnSysctlGetCurrentValue = BuiltinFunc(platform.LinuxTag | 102) + FnSysctlGetNewValue = BuiltinFunc(platform.LinuxTag | 103) + FnSysctlSetNewValue = BuiltinFunc(platform.LinuxTag | 104) + FnStrtol = BuiltinFunc(platform.LinuxTag | 105) + FnStrtoul = BuiltinFunc(platform.LinuxTag | 106) + FnSkStorageGet = BuiltinFunc(platform.LinuxTag | 107) + FnSkStorageDelete = BuiltinFunc(platform.LinuxTag | 108) + FnSendSignal = BuiltinFunc(platform.LinuxTag | 109) + FnTcpGenSyncookie = BuiltinFunc(platform.LinuxTag | 110) + FnSkbOutput = BuiltinFunc(platform.LinuxTag | 111) + FnProbeReadUser = BuiltinFunc(platform.LinuxTag | 112) + FnProbeReadKernel = BuiltinFunc(platform.LinuxTag | 113) + FnProbeReadUserStr = BuiltinFunc(platform.LinuxTag | 114) + FnProbeReadKernelStr = BuiltinFunc(platform.LinuxTag | 115) + FnTcpSendAck = BuiltinFunc(platform.LinuxTag | 116) + FnSendSignalThread = BuiltinFunc(platform.LinuxTag | 117) + FnJiffies64 = BuiltinFunc(platform.LinuxTag | 118) + FnReadBranchRecords = BuiltinFunc(platform.LinuxTag | 119) + FnGetNsCurrentPidTgid = BuiltinFunc(platform.LinuxTag | 120) + FnXdpOutput = BuiltinFunc(platform.LinuxTag | 121) + FnGetNetnsCookie = BuiltinFunc(platform.LinuxTag | 122) + FnGetCurrentAncestorCgroupId = BuiltinFunc(platform.LinuxTag | 123) + FnSkAssign = BuiltinFunc(platform.LinuxTag | 124) + FnKtimeGetBootNs = BuiltinFunc(platform.LinuxTag | 125) + FnSeqPrintf = BuiltinFunc(platform.LinuxTag | 126) + FnSeqWrite = BuiltinFunc(platform.LinuxTag | 127) + FnSkCgroupId = BuiltinFunc(platform.LinuxTag | 128) + FnSkAncestorCgroupId = BuiltinFunc(platform.LinuxTag | 129) + FnRingbufOutput = BuiltinFunc(platform.LinuxTag | 130) + FnRingbufReserve = BuiltinFunc(platform.LinuxTag | 131) + FnRingbufSubmit = BuiltinFunc(platform.LinuxTag | 132) + FnRingbufDiscard = BuiltinFunc(platform.LinuxTag | 133) + FnRingbufQuery = BuiltinFunc(platform.LinuxTag | 134) + FnCsumLevel = BuiltinFunc(platform.LinuxTag | 135) + FnSkcToTcp6Sock = BuiltinFunc(platform.LinuxTag | 136) + FnSkcToTcpSock = BuiltinFunc(platform.LinuxTag | 137) + FnSkcToTcpTimewaitSock = BuiltinFunc(platform.LinuxTag | 138) + FnSkcToTcpRequestSock = BuiltinFunc(platform.LinuxTag | 139) + FnSkcToUdp6Sock = BuiltinFunc(platform.LinuxTag | 140) + FnGetTaskStack = BuiltinFunc(platform.LinuxTag | 141) + FnLoadHdrOpt = BuiltinFunc(platform.LinuxTag | 142) + FnStoreHdrOpt = BuiltinFunc(platform.LinuxTag | 143) + FnReserveHdrOpt = BuiltinFunc(platform.LinuxTag | 144) + FnInodeStorageGet = BuiltinFunc(platform.LinuxTag | 145) + FnInodeStorageDelete = BuiltinFunc(platform.LinuxTag | 146) + FnDPath = BuiltinFunc(platform.LinuxTag | 147) + FnCopyFromUser = BuiltinFunc(platform.LinuxTag | 148) + FnSnprintfBtf = BuiltinFunc(platform.LinuxTag | 149) + FnSeqPrintfBtf = BuiltinFunc(platform.LinuxTag | 150) + FnSkbCgroupClassid = BuiltinFunc(platform.LinuxTag | 151) + FnRedirectNeigh = BuiltinFunc(platform.LinuxTag | 152) + FnPerCpuPtr = BuiltinFunc(platform.LinuxTag | 153) + FnThisCpuPtr = BuiltinFunc(platform.LinuxTag | 154) + FnRedirectPeer = BuiltinFunc(platform.LinuxTag | 155) + FnTaskStorageGet = BuiltinFunc(platform.LinuxTag | 156) + FnTaskStorageDelete = BuiltinFunc(platform.LinuxTag | 157) + FnGetCurrentTaskBtf = BuiltinFunc(platform.LinuxTag | 158) + FnBprmOptsSet = BuiltinFunc(platform.LinuxTag | 159) + FnKtimeGetCoarseNs = BuiltinFunc(platform.LinuxTag | 160) + FnImaInodeHash = BuiltinFunc(platform.LinuxTag | 161) + FnSockFromFile = BuiltinFunc(platform.LinuxTag | 162) + FnCheckMtu = BuiltinFunc(platform.LinuxTag | 163) + FnForEachMapElem = BuiltinFunc(platform.LinuxTag | 164) + FnSnprintf = BuiltinFunc(platform.LinuxTag | 165) + FnSysBpf = BuiltinFunc(platform.LinuxTag | 166) + FnBtfFindByNameKind = BuiltinFunc(platform.LinuxTag | 167) + FnSysClose = BuiltinFunc(platform.LinuxTag | 168) + FnTimerInit = BuiltinFunc(platform.LinuxTag | 169) + FnTimerSetCallback = BuiltinFunc(platform.LinuxTag | 170) + FnTimerStart = BuiltinFunc(platform.LinuxTag | 171) + FnTimerCancel = BuiltinFunc(platform.LinuxTag | 172) + FnGetFuncIp = BuiltinFunc(platform.LinuxTag | 173) + FnGetAttachCookie = BuiltinFunc(platform.LinuxTag | 174) + FnTaskPtRegs = BuiltinFunc(platform.LinuxTag | 175) + FnGetBranchSnapshot = BuiltinFunc(platform.LinuxTag | 176) + FnTraceVprintk = BuiltinFunc(platform.LinuxTag | 177) + FnSkcToUnixSock = BuiltinFunc(platform.LinuxTag | 178) + FnKallsymsLookupName = BuiltinFunc(platform.LinuxTag | 179) + FnFindVma = BuiltinFunc(platform.LinuxTag | 180) + FnLoop = BuiltinFunc(platform.LinuxTag | 181) + FnStrncmp = BuiltinFunc(platform.LinuxTag | 182) + FnGetFuncArg = BuiltinFunc(platform.LinuxTag | 183) + FnGetFuncRet = BuiltinFunc(platform.LinuxTag | 184) + FnGetFuncArgCnt = BuiltinFunc(platform.LinuxTag | 185) + FnGetRetval = BuiltinFunc(platform.LinuxTag | 186) + FnSetRetval = BuiltinFunc(platform.LinuxTag | 187) + FnXdpGetBuffLen = BuiltinFunc(platform.LinuxTag | 188) + FnXdpLoadBytes = BuiltinFunc(platform.LinuxTag | 189) + FnXdpStoreBytes = BuiltinFunc(platform.LinuxTag | 190) + FnCopyFromUserTask = BuiltinFunc(platform.LinuxTag | 191) + FnSkbSetTstamp = BuiltinFunc(platform.LinuxTag | 192) + FnImaFileHash = BuiltinFunc(platform.LinuxTag | 193) + FnKptrXchg = BuiltinFunc(platform.LinuxTag | 194) + FnMapLookupPercpuElem = BuiltinFunc(platform.LinuxTag | 195) + FnSkcToMptcpSock = BuiltinFunc(platform.LinuxTag | 196) + FnDynptrFromMem = BuiltinFunc(platform.LinuxTag | 197) + FnRingbufReserveDynptr = BuiltinFunc(platform.LinuxTag | 198) + FnRingbufSubmitDynptr = BuiltinFunc(platform.LinuxTag | 199) + FnRingbufDiscardDynptr = BuiltinFunc(platform.LinuxTag | 200) + FnDynptrRead = BuiltinFunc(platform.LinuxTag | 201) + FnDynptrWrite = BuiltinFunc(platform.LinuxTag | 202) + FnDynptrData = BuiltinFunc(platform.LinuxTag | 203) + FnTcpRawGenSyncookieIpv4 = BuiltinFunc(platform.LinuxTag | 204) + FnTcpRawGenSyncookieIpv6 = BuiltinFunc(platform.LinuxTag | 205) + FnTcpRawCheckSyncookieIpv4 = BuiltinFunc(platform.LinuxTag | 206) + FnTcpRawCheckSyncookieIpv6 = BuiltinFunc(platform.LinuxTag | 207) + FnKtimeGetTaiNs = BuiltinFunc(platform.LinuxTag | 208) + FnUserRingbufDrain = BuiltinFunc(platform.LinuxTag | 209) + FnCgrpStorageGet = BuiltinFunc(platform.LinuxTag | 210) + FnCgrpStorageDelete = BuiltinFunc(platform.LinuxTag | 211) +) diff --git a/vendor/github.com/cilium/ebpf/asm/func_string.go b/vendor/github.com/cilium/ebpf/asm/func_string.go index 6c4ba37892..2598379745 100644 --- a/vendor/github.com/cilium/ebpf/asm/func_string.go +++ b/vendor/github.com/cilium/ebpf/asm/func_string.go @@ -220,15 +220,60 @@ func _() { _ = x[FnUserRingbufDrain-209] _ = x[FnCgrpStorageGet-210] _ = x[FnCgrpStorageDelete-211] + _ = x[WindowsFnMapLookupElem-268435457] + _ = x[WindowsFnMapUpdateElem-268435458] + _ = x[WindowsFnMapDeleteElem-268435459] + _ = x[WindowsFnMapLookupAndDeleteElem-268435460] + _ = x[WindowsFnTailCall-268435461] + _ = x[WindowsFnGetPrandomU32-268435462] + _ = x[WindowsFnKtimeGetBootNs-268435463] + _ = x[WindowsFnGetSmpProcessorId-268435464] + _ = x[WindowsFnKtimeGetNs-268435465] + _ = x[WindowsFnCsumDiff-268435466] + _ = x[WindowsFnRingbufOutput-268435467] + _ = x[WindowsFnTracePrintk2-268435468] + _ = x[WindowsFnTracePrintk3-268435469] + _ = x[WindowsFnTracePrintk4-268435470] + _ = x[WindowsFnTracePrintk5-268435471] + _ = x[WindowsFnMapPushElem-268435472] + _ = x[WindowsFnMapPopElem-268435473] + _ = x[WindowsFnMapPeekElem-268435474] + _ = x[WindowsFnGetCurrentPidTgid-268435475] + _ = x[WindowsFnGetCurrentLogonId-268435476] + _ = x[WindowsFnIsCurrentAdmin-268435477] + _ = x[WindowsFnMemcpyS-268435478] + _ = x[WindowsFnMemcmpS-268435479] + _ = x[WindowsFnMemset-268435480] + _ = x[WindowsFnMemmoveS-268435481] + _ = x[WindowsFnGetSocketCookie-268435482] + _ = x[WindowsFnStrncpyS-268435483] + _ = x[WindowsFnStrncatS-268435484] + _ = x[WindowsFnStrnlenS-268435485] + _ = x[WindowsFnKtimeGetBootMs-268435486] + _ = x[WindowsFnKtimeGetMs-268435487] + _ = x[WindowsFnPerfEventOutput-268435488] + _ = x[WindowsFnGetCurrentProcessStartKey-268435489] + _ = x[WindowsFnGetCurrentThreadCreateTime-268435490] } -const _BuiltinFunc_name = "FnUnspecFnMapLookupElemFnMapUpdateElemFnMapDeleteElemFnProbeReadFnKtimeGetNsFnTracePrintkFnGetPrandomU32FnGetSmpProcessorIdFnSkbStoreBytesFnL3CsumReplaceFnL4CsumReplaceFnTailCallFnCloneRedirectFnGetCurrentPidTgidFnGetCurrentUidGidFnGetCurrentCommFnGetCgroupClassidFnSkbVlanPushFnSkbVlanPopFnSkbGetTunnelKeyFnSkbSetTunnelKeyFnPerfEventReadFnRedirectFnGetRouteRealmFnPerfEventOutputFnSkbLoadBytesFnGetStackidFnCsumDiffFnSkbGetTunnelOptFnSkbSetTunnelOptFnSkbChangeProtoFnSkbChangeTypeFnSkbUnderCgroupFnGetHashRecalcFnGetCurrentTaskFnProbeWriteUserFnCurrentTaskUnderCgroupFnSkbChangeTailFnSkbPullDataFnCsumUpdateFnSetHashInvalidFnGetNumaNodeIdFnSkbChangeHeadFnXdpAdjustHeadFnProbeReadStrFnGetSocketCookieFnGetSocketUidFnSetHashFnSetsockoptFnSkbAdjustRoomFnRedirectMapFnSkRedirectMapFnSockMapUpdateFnXdpAdjustMetaFnPerfEventReadValueFnPerfProgReadValueFnGetsockoptFnOverrideReturnFnSockOpsCbFlagsSetFnMsgRedirectMapFnMsgApplyBytesFnMsgCorkBytesFnMsgPullDataFnBindFnXdpAdjustTailFnSkbGetXfrmStateFnGetStackFnSkbLoadBytesRelativeFnFibLookupFnSockHashUpdateFnMsgRedirectHashFnSkRedirectHashFnLwtPushEncapFnLwtSeg6StoreBytesFnLwtSeg6AdjustSrhFnLwtSeg6ActionFnRcRepeatFnRcKeydownFnSkbCgroupIdFnGetCurrentCgroupIdFnGetLocalStorageFnSkSelectReuseportFnSkbAncestorCgroupIdFnSkLookupTcpFnSkLookupUdpFnSkReleaseFnMapPushElemFnMapPopElemFnMapPeekElemFnMsgPushDataFnMsgPopDataFnRcPointerRelFnSpinLockFnSpinUnlockFnSkFullsockFnTcpSockFnSkbEcnSetCeFnGetListenerSockFnSkcLookupTcpFnTcpCheckSyncookieFnSysctlGetNameFnSysctlGetCurrentValueFnSysctlGetNewValueFnSysctlSetNewValueFnStrtolFnStrtoulFnSkStorageGetFnSkStorageDeleteFnSendSignalFnTcpGenSyncookieFnSkbOutputFnProbeReadUserFnProbeReadKernelFnProbeReadUserStrFnProbeReadKernelStrFnTcpSendAckFnSendSignalThreadFnJiffies64FnReadBranchRecordsFnGetNsCurrentPidTgidFnXdpOutputFnGetNetnsCookieFnGetCurrentAncestorCgroupIdFnSkAssignFnKtimeGetBootNsFnSeqPrintfFnSeqWriteFnSkCgroupIdFnSkAncestorCgroupIdFnRingbufOutputFnRingbufReserveFnRingbufSubmitFnRingbufDiscardFnRingbufQueryFnCsumLevelFnSkcToTcp6SockFnSkcToTcpSockFnSkcToTcpTimewaitSockFnSkcToTcpRequestSockFnSkcToUdp6SockFnGetTaskStackFnLoadHdrOptFnStoreHdrOptFnReserveHdrOptFnInodeStorageGetFnInodeStorageDeleteFnDPathFnCopyFromUserFnSnprintfBtfFnSeqPrintfBtfFnSkbCgroupClassidFnRedirectNeighFnPerCpuPtrFnThisCpuPtrFnRedirectPeerFnTaskStorageGetFnTaskStorageDeleteFnGetCurrentTaskBtfFnBprmOptsSetFnKtimeGetCoarseNsFnImaInodeHashFnSockFromFileFnCheckMtuFnForEachMapElemFnSnprintfFnSysBpfFnBtfFindByNameKindFnSysCloseFnTimerInitFnTimerSetCallbackFnTimerStartFnTimerCancelFnGetFuncIpFnGetAttachCookieFnTaskPtRegsFnGetBranchSnapshotFnTraceVprintkFnSkcToUnixSockFnKallsymsLookupNameFnFindVmaFnLoopFnStrncmpFnGetFuncArgFnGetFuncRetFnGetFuncArgCntFnGetRetvalFnSetRetvalFnXdpGetBuffLenFnXdpLoadBytesFnXdpStoreBytesFnCopyFromUserTaskFnSkbSetTstampFnImaFileHashFnKptrXchgFnMapLookupPercpuElemFnSkcToMptcpSockFnDynptrFromMemFnRingbufReserveDynptrFnRingbufSubmitDynptrFnRingbufDiscardDynptrFnDynptrReadFnDynptrWriteFnDynptrDataFnTcpRawGenSyncookieIpv4FnTcpRawGenSyncookieIpv6FnTcpRawCheckSyncookieIpv4FnTcpRawCheckSyncookieIpv6FnKtimeGetTaiNsFnUserRingbufDrainFnCgrpStorageGetFnCgrpStorageDelete" +const ( + _BuiltinFunc_name_0 = "FnUnspecFnMapLookupElemFnMapUpdateElemFnMapDeleteElemFnProbeReadFnKtimeGetNsFnTracePrintkFnGetPrandomU32FnGetSmpProcessorIdFnSkbStoreBytesFnL3CsumReplaceFnL4CsumReplaceFnTailCallFnCloneRedirectFnGetCurrentPidTgidFnGetCurrentUidGidFnGetCurrentCommFnGetCgroupClassidFnSkbVlanPushFnSkbVlanPopFnSkbGetTunnelKeyFnSkbSetTunnelKeyFnPerfEventReadFnRedirectFnGetRouteRealmFnPerfEventOutputFnSkbLoadBytesFnGetStackidFnCsumDiffFnSkbGetTunnelOptFnSkbSetTunnelOptFnSkbChangeProtoFnSkbChangeTypeFnSkbUnderCgroupFnGetHashRecalcFnGetCurrentTaskFnProbeWriteUserFnCurrentTaskUnderCgroupFnSkbChangeTailFnSkbPullDataFnCsumUpdateFnSetHashInvalidFnGetNumaNodeIdFnSkbChangeHeadFnXdpAdjustHeadFnProbeReadStrFnGetSocketCookieFnGetSocketUidFnSetHashFnSetsockoptFnSkbAdjustRoomFnRedirectMapFnSkRedirectMapFnSockMapUpdateFnXdpAdjustMetaFnPerfEventReadValueFnPerfProgReadValueFnGetsockoptFnOverrideReturnFnSockOpsCbFlagsSetFnMsgRedirectMapFnMsgApplyBytesFnMsgCorkBytesFnMsgPullDataFnBindFnXdpAdjustTailFnSkbGetXfrmStateFnGetStackFnSkbLoadBytesRelativeFnFibLookupFnSockHashUpdateFnMsgRedirectHashFnSkRedirectHashFnLwtPushEncapFnLwtSeg6StoreBytesFnLwtSeg6AdjustSrhFnLwtSeg6ActionFnRcRepeatFnRcKeydownFnSkbCgroupIdFnGetCurrentCgroupIdFnGetLocalStorageFnSkSelectReuseportFnSkbAncestorCgroupIdFnSkLookupTcpFnSkLookupUdpFnSkReleaseFnMapPushElemFnMapPopElemFnMapPeekElemFnMsgPushDataFnMsgPopDataFnRcPointerRelFnSpinLockFnSpinUnlockFnSkFullsockFnTcpSockFnSkbEcnSetCeFnGetListenerSockFnSkcLookupTcpFnTcpCheckSyncookieFnSysctlGetNameFnSysctlGetCurrentValueFnSysctlGetNewValueFnSysctlSetNewValueFnStrtolFnStrtoulFnSkStorageGetFnSkStorageDeleteFnSendSignalFnTcpGenSyncookieFnSkbOutputFnProbeReadUserFnProbeReadKernelFnProbeReadUserStrFnProbeReadKernelStrFnTcpSendAckFnSendSignalThreadFnJiffies64FnReadBranchRecordsFnGetNsCurrentPidTgidFnXdpOutputFnGetNetnsCookieFnGetCurrentAncestorCgroupIdFnSkAssignFnKtimeGetBootNsFnSeqPrintfFnSeqWriteFnSkCgroupIdFnSkAncestorCgroupIdFnRingbufOutputFnRingbufReserveFnRingbufSubmitFnRingbufDiscardFnRingbufQueryFnCsumLevelFnSkcToTcp6SockFnSkcToTcpSockFnSkcToTcpTimewaitSockFnSkcToTcpRequestSockFnSkcToUdp6SockFnGetTaskStackFnLoadHdrOptFnStoreHdrOptFnReserveHdrOptFnInodeStorageGetFnInodeStorageDeleteFnDPathFnCopyFromUserFnSnprintfBtfFnSeqPrintfBtfFnSkbCgroupClassidFnRedirectNeighFnPerCpuPtrFnThisCpuPtrFnRedirectPeerFnTaskStorageGetFnTaskStorageDeleteFnGetCurrentTaskBtfFnBprmOptsSetFnKtimeGetCoarseNsFnImaInodeHashFnSockFromFileFnCheckMtuFnForEachMapElemFnSnprintfFnSysBpfFnBtfFindByNameKindFnSysCloseFnTimerInitFnTimerSetCallbackFnTimerStartFnTimerCancelFnGetFuncIpFnGetAttachCookieFnTaskPtRegsFnGetBranchSnapshotFnTraceVprintkFnSkcToUnixSockFnKallsymsLookupNameFnFindVmaFnLoopFnStrncmpFnGetFuncArgFnGetFuncRetFnGetFuncArgCntFnGetRetvalFnSetRetvalFnXdpGetBuffLenFnXdpLoadBytesFnXdpStoreBytesFnCopyFromUserTaskFnSkbSetTstampFnImaFileHashFnKptrXchgFnMapLookupPercpuElemFnSkcToMptcpSockFnDynptrFromMemFnRingbufReserveDynptrFnRingbufSubmitDynptrFnRingbufDiscardDynptrFnDynptrReadFnDynptrWriteFnDynptrDataFnTcpRawGenSyncookieIpv4FnTcpRawGenSyncookieIpv6FnTcpRawCheckSyncookieIpv4FnTcpRawCheckSyncookieIpv6FnKtimeGetTaiNsFnUserRingbufDrainFnCgrpStorageGetFnCgrpStorageDelete" + _BuiltinFunc_name_1 = "WindowsFnMapLookupElemWindowsFnMapUpdateElemWindowsFnMapDeleteElemWindowsFnMapLookupAndDeleteElemWindowsFnTailCallWindowsFnGetPrandomU32WindowsFnKtimeGetBootNsWindowsFnGetSmpProcessorIdWindowsFnKtimeGetNsWindowsFnCsumDiffWindowsFnRingbufOutputWindowsFnTracePrintk2WindowsFnTracePrintk3WindowsFnTracePrintk4WindowsFnTracePrintk5WindowsFnMapPushElemWindowsFnMapPopElemWindowsFnMapPeekElemWindowsFnGetCurrentPidTgidWindowsFnGetCurrentLogonIdWindowsFnIsCurrentAdminWindowsFnMemcpySWindowsFnMemcmpSWindowsFnMemsetWindowsFnMemmoveSWindowsFnGetSocketCookieWindowsFnStrncpySWindowsFnStrncatSWindowsFnStrnlenSWindowsFnKtimeGetBootMsWindowsFnKtimeGetMsWindowsFnPerfEventOutputWindowsFnGetCurrentProcessStartKeyWindowsFnGetCurrentThreadCreateTime" +) -var _BuiltinFunc_index = [...]uint16{0, 8, 23, 38, 53, 64, 76, 89, 104, 123, 138, 153, 168, 178, 193, 212, 230, 246, 264, 277, 289, 306, 323, 338, 348, 363, 380, 394, 406, 416, 433, 450, 466, 481, 497, 512, 528, 544, 568, 583, 596, 608, 624, 639, 654, 669, 683, 700, 714, 723, 735, 750, 763, 778, 793, 808, 828, 847, 859, 875, 894, 910, 925, 939, 952, 958, 973, 990, 1000, 1022, 1033, 1049, 1066, 1082, 1096, 1115, 1133, 1148, 1158, 1169, 1182, 1202, 1219, 1238, 1259, 1272, 1285, 1296, 1309, 1321, 1334, 1347, 1359, 1373, 1383, 1395, 1407, 1416, 1429, 1446, 1460, 1479, 1494, 1517, 1536, 1555, 1563, 1572, 1586, 1603, 1615, 1632, 1643, 1658, 1675, 1693, 1713, 1725, 1743, 1754, 1773, 1794, 1805, 1821, 1849, 1859, 1875, 1886, 1896, 1908, 1928, 1943, 1959, 1974, 1990, 2004, 2015, 2030, 2044, 2066, 2087, 2102, 2116, 2128, 2141, 2156, 2173, 2193, 2200, 2214, 2227, 2241, 2259, 2274, 2285, 2297, 2311, 2327, 2346, 2365, 2378, 2396, 2410, 2424, 2434, 2450, 2460, 2468, 2487, 2497, 2508, 2526, 2538, 2551, 2562, 2579, 2591, 2610, 2624, 2639, 2659, 2668, 2674, 2683, 2695, 2707, 2722, 2733, 2744, 2759, 2773, 2788, 2806, 2820, 2833, 2843, 2864, 2880, 2895, 2917, 2938, 2960, 2972, 2985, 2997, 3021, 3045, 3071, 3097, 3112, 3130, 3146, 3165} +var ( + _BuiltinFunc_index_0 = [...]uint16{0, 8, 23, 38, 53, 64, 76, 89, 104, 123, 138, 153, 168, 178, 193, 212, 230, 246, 264, 277, 289, 306, 323, 338, 348, 363, 380, 394, 406, 416, 433, 450, 466, 481, 497, 512, 528, 544, 568, 583, 596, 608, 624, 639, 654, 669, 683, 700, 714, 723, 735, 750, 763, 778, 793, 808, 828, 847, 859, 875, 894, 910, 925, 939, 952, 958, 973, 990, 1000, 1022, 1033, 1049, 1066, 1082, 1096, 1115, 1133, 1148, 1158, 1169, 1182, 1202, 1219, 1238, 1259, 1272, 1285, 1296, 1309, 1321, 1334, 1347, 1359, 1373, 1383, 1395, 1407, 1416, 1429, 1446, 1460, 1479, 1494, 1517, 1536, 1555, 1563, 1572, 1586, 1603, 1615, 1632, 1643, 1658, 1675, 1693, 1713, 1725, 1743, 1754, 1773, 1794, 1805, 1821, 1849, 1859, 1875, 1886, 1896, 1908, 1928, 1943, 1959, 1974, 1990, 2004, 2015, 2030, 2044, 2066, 2087, 2102, 2116, 2128, 2141, 2156, 2173, 2193, 2200, 2214, 2227, 2241, 2259, 2274, 2285, 2297, 2311, 2327, 2346, 2365, 2378, 2396, 2410, 2424, 2434, 2450, 2460, 2468, 2487, 2497, 2508, 2526, 2538, 2551, 2562, 2579, 2591, 2610, 2624, 2639, 2659, 2668, 2674, 2683, 2695, 2707, 2722, 2733, 2744, 2759, 2773, 2788, 2806, 2820, 2833, 2843, 2864, 2880, 2895, 2917, 2938, 2960, 2972, 2985, 2997, 3021, 3045, 3071, 3097, 3112, 3130, 3146, 3165} + _BuiltinFunc_index_1 = [...]uint16{0, 22, 44, 66, 97, 114, 136, 159, 185, 204, 221, 243, 264, 285, 306, 327, 347, 366, 386, 412, 438, 461, 477, 493, 508, 525, 549, 566, 583, 600, 623, 642, 666, 700, 735} +) func (i BuiltinFunc) String() string { - if i < 0 || i >= BuiltinFunc(len(_BuiltinFunc_index)-1) { + switch { + case i <= 211: + return _BuiltinFunc_name_0[_BuiltinFunc_index_0[i]:_BuiltinFunc_index_0[i+1]] + case 268435457 <= i && i <= 268435490: + i -= 268435457 + return _BuiltinFunc_name_1[_BuiltinFunc_index_1[i]:_BuiltinFunc_index_1[i+1]] + default: return "BuiltinFunc(" + strconv.FormatInt(int64(i), 10) + ")" } - return _BuiltinFunc_name[_BuiltinFunc_index[i]:_BuiltinFunc_index[i+1]] } diff --git a/vendor/github.com/cilium/ebpf/asm/func_win.go b/vendor/github.com/cilium/ebpf/asm/func_win.go new file mode 100644 index 0000000000..609ed90560 --- /dev/null +++ b/vendor/github.com/cilium/ebpf/asm/func_win.go @@ -0,0 +1,45 @@ +// Code generated by internal/cmd/genwinfunctions.awk; DO NOT EDIT. + +package asm + +// Code in this file is derived from eBPF for Windows, available under the MIT License. + +import "github.com/cilium/ebpf/internal/platform" + +// Built-in functions (Windows). +const ( + WindowsFnMapLookupElem = BuiltinFunc(platform.WindowsTag | 1) + WindowsFnMapUpdateElem = BuiltinFunc(platform.WindowsTag | 2) + WindowsFnMapDeleteElem = BuiltinFunc(platform.WindowsTag | 3) + WindowsFnMapLookupAndDeleteElem = BuiltinFunc(platform.WindowsTag | 4) + WindowsFnTailCall = BuiltinFunc(platform.WindowsTag | 5) + WindowsFnGetPrandomU32 = BuiltinFunc(platform.WindowsTag | 6) + WindowsFnKtimeGetBootNs = BuiltinFunc(platform.WindowsTag | 7) + WindowsFnGetSmpProcessorId = BuiltinFunc(platform.WindowsTag | 8) + WindowsFnKtimeGetNs = BuiltinFunc(platform.WindowsTag | 9) + WindowsFnCsumDiff = BuiltinFunc(platform.WindowsTag | 10) + WindowsFnRingbufOutput = BuiltinFunc(platform.WindowsTag | 11) + WindowsFnTracePrintk2 = BuiltinFunc(platform.WindowsTag | 12) + WindowsFnTracePrintk3 = BuiltinFunc(platform.WindowsTag | 13) + WindowsFnTracePrintk4 = BuiltinFunc(platform.WindowsTag | 14) + WindowsFnTracePrintk5 = BuiltinFunc(platform.WindowsTag | 15) + WindowsFnMapPushElem = BuiltinFunc(platform.WindowsTag | 16) + WindowsFnMapPopElem = BuiltinFunc(platform.WindowsTag | 17) + WindowsFnMapPeekElem = BuiltinFunc(platform.WindowsTag | 18) + WindowsFnGetCurrentPidTgid = BuiltinFunc(platform.WindowsTag | 19) + WindowsFnGetCurrentLogonId = BuiltinFunc(platform.WindowsTag | 20) + WindowsFnIsCurrentAdmin = BuiltinFunc(platform.WindowsTag | 21) + WindowsFnMemcpyS = BuiltinFunc(platform.WindowsTag | 22) + WindowsFnMemcmpS = BuiltinFunc(platform.WindowsTag | 23) + WindowsFnMemset = BuiltinFunc(platform.WindowsTag | 24) + WindowsFnMemmoveS = BuiltinFunc(platform.WindowsTag | 25) + WindowsFnGetSocketCookie = BuiltinFunc(platform.WindowsTag | 26) + WindowsFnStrncpyS = BuiltinFunc(platform.WindowsTag | 27) + WindowsFnStrncatS = BuiltinFunc(platform.WindowsTag | 28) + WindowsFnStrnlenS = BuiltinFunc(platform.WindowsTag | 29) + WindowsFnKtimeGetBootMs = BuiltinFunc(platform.WindowsTag | 30) + WindowsFnKtimeGetMs = BuiltinFunc(platform.WindowsTag | 31) + WindowsFnPerfEventOutput = BuiltinFunc(platform.WindowsTag | 32) + WindowsFnGetCurrentProcessStartKey = BuiltinFunc(platform.WindowsTag | 33) + WindowsFnGetCurrentThreadCreateTime = BuiltinFunc(platform.WindowsTag | 34) +) diff --git a/vendor/github.com/cilium/ebpf/asm/instruction.go b/vendor/github.com/cilium/ebpf/asm/instruction.go index 86b384c02a..f966a234bd 100644 --- a/vendor/github.com/cilium/ebpf/asm/instruction.go +++ b/vendor/github.com/cilium/ebpf/asm/instruction.go @@ -2,15 +2,19 @@ package asm import ( "crypto/sha1" + "crypto/sha256" "encoding/binary" "encoding/hex" "errors" "fmt" + "hash" "io" "math" "sort" "strings" + "github.com/cilium/ebpf/internal" + "github.com/cilium/ebpf/internal/platform" "github.com/cilium/ebpf/internal/sys" ) @@ -41,11 +45,19 @@ type Instruction struct { Metadata Metadata } +// Width returns how many raw BPF instructions the Instruction occupies within +// an instruction stream. For example, an Instruction encoding a 64-bit value +// will typically occupy 2 raw instructions, while a 32-bit constant can be +// encoded in a single raw instruction. +func (ins *Instruction) Width() RawInstructionOffset { + return RawInstructionOffset(ins.OpCode.rawInstructions()) +} + // Unmarshal decodes a BPF instruction. -func (ins *Instruction) Unmarshal(r io.Reader, bo binary.ByteOrder) (uint64, error) { +func (ins *Instruction) Unmarshal(r io.Reader, bo binary.ByteOrder, platform string) error { data := make([]byte, InstructionSize) if _, err := io.ReadFull(r, data); err != nil { - return 0, err + return err } ins.OpCode = OpCode(data[0]) @@ -60,7 +72,23 @@ func (ins *Instruction) Unmarshal(r io.Reader, bo binary.ByteOrder) (uint64, err ins.Offset = int16(bo.Uint16(data[2:4])) - if ins.OpCode.Class().IsALU() { + // 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.IsBuiltinCall() { + if ins.Constant >= 0 { + // Leave negative constants from the instruction stream + // unchanged. These are sometimes used as placeholders for later + // patching. + // This relies on not having a valid platform tag with a high bit set. + fn, err := BuiltinFuncForPlatform(platform, uint32(ins.Constant)) + if err != nil { + return err + } + ins.Constant = int64(fn) + } + } else if ins.OpCode.Class().IsALU() { switch ins.OpCode.ALUOp() { case Div: if ins.Offset == 1 { @@ -85,33 +113,35 @@ func (ins *Instruction) Unmarshal(r io.Reader, bo binary.ByteOrder) (uint64, err ins.Offset = 0 } } + } else if ins.OpCode.Class() == StXClass && + ins.OpCode.Mode() == AtomicMode { + // For atomic ops, part of the opcode is stored in the + // constant field. Shift over 8 bytes so we can OR with the actual opcode and + // apply `atomicMask` to avoid merging unknown bits that may be added in the future. + ins.OpCode |= (OpCode((ins.Constant << 8)) & atomicMask) } - // 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 + return 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") + return errors.New("64bit immediate is missing second half") } // 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") + return errors.New("64bit immediate has non-zero fields") } cons1 := uint32(ins.Constant) cons2 := int32(bo.Uint32(data[4:8])) ins.Constant = int64(cons2)<<32 | int64(cons1) - return 2 * InstructionSize, nil + return nil } // Marshal encodes a BPF instruction. @@ -133,7 +163,14 @@ func (ins Instruction) Marshal(w io.Writer, bo binary.ByteOrder) (uint64, error) return 0, fmt.Errorf("can't marshal registers: %s", err) } - if ins.OpCode.Class().IsALU() { + if ins.IsBuiltinCall() { + fn := BuiltinFunc(ins.Constant) + plat, value := platform.DecodeConstant(fn) + if plat != platform.Native { + return 0, fmt.Errorf("function %s (%s): %w", fn, plat, internal.ErrNotSupportedOnOS) + } + cons = int32(value) + } else if ins.OpCode.Class().IsALU() { newOffset := int16(0) switch ins.OpCode.ALUOp() { case SDiv: @@ -156,6 +193,9 @@ func (ins Instruction) Marshal(w io.Writer, bo binary.ByteOrder) (uint64, error) return 0, fmt.Errorf("extended ALU opcodes should have an .Offset of 0: %s", ins) } ins.Offset = newOffset + } else if atomic := ins.OpCode.AtomicOp(); atomic != InvalidAtomic { + ins.OpCode = ins.OpCode &^ atomicMask + ins.Constant = int64(atomic >> 8) } op, err := ins.OpCode.bpfOpCode() @@ -203,22 +243,6 @@ func (ins *Instruction) AssociateMap(m FDer) error { return nil } -// RewriteMapPtr changes an instruction to use a new map fd. -// -// Returns an error if the instruction doesn't load a map. -// -// Deprecated: use AssociateMap instead. If you cannot provide a Map, -// wrap an fd in a type implementing FDer. -func (ins *Instruction) RewriteMapPtr(fd int) error { - if !ins.IsLoadFromMap() { - return errors.New("not a load from a map") - } - - ins.encodeMapFD(fd) - - return nil -} - func (ins *Instruction) encodeMapFD(fd int) { // Preserve the offset value for direct map loads. offset := uint64(ins.Constant) & (math.MaxUint32 << 32) @@ -226,22 +250,6 @@ func (ins *Instruction) encodeMapFD(fd int) { ins.Constant = int64(offset | rawFd) } -// MapPtr returns the map fd for this instruction. -// -// The result is undefined if the instruction is not a load from a map, -// see IsLoadFromMap. -// -// Deprecated: use Map() instead. -func (ins *Instruction) MapPtr() int { - // If there is a map associated with the instruction, return its FD. - if fd := ins.Metadata.Get(mapMeta{}); fd != nil { - return fd.(FDer).FD() - } - - // Fall back to the fd stored in the Constant field - return ins.mapFd() -} - // mapFd returns the map file descriptor stored in the 32 least significant // bits of ins' Constant field. func (ins *Instruction) mapFd() int { @@ -367,8 +375,8 @@ func (ins Instruction) Format(f fmt.State, c rune) { fmt.Fprintf(f, "dst: %s src: %s imm: %d", ins.Dst, ins.Src, ins.Constant) case MemMode, MemSXMode: fmt.Fprintf(f, "dst: %s src: %s off: %d imm: %d", ins.Dst, ins.Src, ins.Offset, ins.Constant) - case XAddMode: - fmt.Fprintf(f, "dst: %s src: %s", ins.Dst, ins.Src) + case AtomicMode: + fmt.Fprintf(f, "dst: %s src: %s off: %d", ins.Dst, ins.Src, ins.Offset) } case cls.IsALU(): @@ -388,13 +396,19 @@ func (ins Instruction) Format(f fmt.State, c rune) { } case cls.IsJump(): - fmt.Fprintf(f, "%v ", op) switch jop := op.JumpOp(); jop { case Call: + fmt.Fprintf(f, "%v ", op) switch ins.Src { case PseudoCall: - // bpf-to-bpf call - fmt.Fprint(f, ins.Constant) + // bpf-to-bpf call. For JIT-compiled programs, the kernel stores + // the PC offset in the 'off' field (ins.Offset) rather than 'imm' + // (ins.Constant). See kernel bpf_insn_prepare_dump in verifier.c. + if ins.Offset != 0 { + fmt.Fprint(f, ins.Offset) + } else { + fmt.Fprint(f, ins.Constant) + } case PseudoKfuncCall: // kfunc call fmt.Fprintf(f, "Kfunc(%d)", ins.Constant) @@ -403,13 +417,23 @@ func (ins Instruction) Format(f fmt.State, c rune) { } case Ja: + fmt.Fprintf(f, "%v ", op) if ins.OpCode.Class() == Jump32Class { fmt.Fprintf(f, "imm: %d", ins.Constant) } else { fmt.Fprintf(f, "off: %d", ins.Offset) } + case JCOND: + switch ins.Src { + case PseudoMayGoto: + fmt.Fprintf(f, "JCond may_goto off: %d", ins.Offset) + default: + fmt.Fprintf(f, "%v", op) + } + default: + fmt.Fprintf(f, "%v ", op) fmt.Fprintf(f, "dst: %s off: %d ", ins.Dst, ins.Offset) if op.Source() == ImmSource { fmt.Fprintf(f, "imm: %d", ins.Constant) @@ -456,13 +480,6 @@ func (ins Instruction) WithSymbol(name string) Instruction { return ins } -// Sym creates a symbol. -// -// Deprecated: use WithSymbol instead. -func (ins Instruction) Sym(name string) Instruction { - return ins.WithSymbol(name) -} - // Symbol returns the value ins has been marked with using WithSymbol, // otherwise returns an empty string. A symbol is often an Instruction // at the start of a function body. @@ -529,29 +546,24 @@ type FDer interface { // 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 - } - +// AppendInstructions decodes [Instruction] from r and appends them to insns. +func AppendInstructions(insns Instructions, r io.Reader, bo binary.ByteOrder, platform string) (Instructions, error) { var offset uint64 for { var ins Instruction - n, err := ins.Unmarshal(r, bo) + err := ins.Unmarshal(r, bo, platform) if errors.Is(err, io.EOF) { break } if err != nil { - return fmt.Errorf("offset %d: %w", offset, err) + return nil, fmt.Errorf("offset %d: %w", offset, err) } - *insns = append(*insns, ins) - offset += n + insns = append(insns, ins) + offset += ins.Size() } - return nil + return insns, nil } // Name returns the name of the function insns belongs to, if any. @@ -607,39 +619,6 @@ func (insns Instructions) AssociateMap(symbol string, m FDer) error { return nil } -// RewriteMapPtr rewrites all loads of a specific map pointer to a new fd. -// -// Returns ErrUnreferencedSymbol if the symbol isn't used. -// -// Deprecated: use AssociateMap instead. -func (insns Instructions) RewriteMapPtr(symbol string, fd int) error { - if symbol == "" { - return errors.New("empty symbol") - } - - var found bool - for i := range insns { - ins := &insns[i] - if ins.Reference() != symbol { - continue - } - - if !ins.IsLoadFromMap() { - return errors.New("not a load from a map") - } - - ins.encodeMapFD(fd) - - found = true - } - - if !found { - return fmt.Errorf("symbol %s: %w", symbol, ErrUnreferencedSymbol) - } - - return nil -} - // SymbolOffsets returns the set of symbols and their offset in // the instructions. func (insns Instructions) SymbolOffsets() (map[string]int, error) { @@ -792,18 +771,72 @@ func (insns Instructions) Marshal(w io.Writer, bo binary.ByteOrder) error { // It mirrors bpf_prog_calc_tag in the kernel and so can be compared // to ProgramInfo.Tag to figure out whether a loaded program matches // certain instructions. +// +// Deprecated: The value produced by this method no longer matches tags produced +// by the kernel since Linux 6.18. Use [Instructions.HasTag] instead. func (insns Instructions) Tag(bo binary.ByteOrder) (string, error) { + // We cannot determine which hashing function to use without probing the kernel. + // So use the legacy SHA-1 implementation and deprecate this method. + return insns.tagSha1(bo) +} + +// HasTag returns true if the given tag matches the kernel tag of insns. +func (insns Instructions) HasTag(tag string, bo binary.ByteOrder) (bool, error) { + sha256Tag, err := insns.tagSha256(bo) + if err != nil { + return false, fmt.Errorf("hashing sha256: %w", err) + } + if tag == sha256Tag { + return true, nil + } + + sha1Tag, err := insns.tagSha1(bo) + if err != nil { + return false, fmt.Errorf("hashing sha1: %w", err) + } + + return tag == sha1Tag, nil +} + +// tagSha1 calculates the kernel tag for a series of instructions. +// +// It mirrors bpf_prog_calc_tag in kernels up to v6.18 and can be compared to +// ProgramInfo.Tag to figure out whether a loaded Program matches insns. +func (insns Instructions) tagSha1(bo binary.ByteOrder) (string, error) { h := sha1.New() + if err := insns.hash(h, bo); err != nil { + return "", err + } + return hex.EncodeToString(h.Sum(nil)[:sys.BPF_TAG_SIZE]), nil +} + +// tagSha256 calculates the kernel tag for a series of instructions. +// +// It mirrors bpf_prog_calc_tag in the kernel and can be compared to +// ProgramInfo.Tag to figure out whether a loaded Program matches insns. +func (insns Instructions) tagSha256(bo binary.ByteOrder) (string, error) { + h := sha256.New() + if err := insns.hash(h, bo); err != nil { + return "", err + } + return hex.EncodeToString(h.Sum(nil)[:sys.BPF_TAG_SIZE]), nil +} + +// hash calculates the hash of the instruction stream. Map load instructions +// are zeroed out, since these contain map file descriptors or pointers to +// maps, which will be different from load to load and would make the hash +// non-deterministic. +func (insns Instructions) hash(h hash.Hash, bo binary.ByteOrder) error { for i, ins := range insns { if ins.IsLoadFromMap() { ins.Constant = 0 } _, err := ins.Marshal(h, bo) if err != nil { - return "", fmt.Errorf("instruction %d: %w", i, err) + return fmt.Errorf("instruction %d: %w", i, err) } } - return hex.EncodeToString(h.Sum(nil)[:sys.BPF_TAG_SIZE]), nil + return nil } // encodeFunctionReferences populates the Offset (or Constant, depending on @@ -943,11 +976,3 @@ func newBPFRegisters(dst, src Register, bo binary.ByteOrder) (bpfRegisters, erro return 0, fmt.Errorf("unrecognized ByteOrder %T", bo) } } - -// IsUnreferencedSymbol returns true if err was caused by -// an unreferenced symbol. -// -// Deprecated: use errors.Is(err, asm.ErrUnreferencedSymbol). -func IsUnreferencedSymbol(err error) bool { - return errors.Is(err, ErrUnreferencedSymbol) -} diff --git a/vendor/github.com/cilium/ebpf/asm/jump.go b/vendor/github.com/cilium/ebpf/asm/jump.go index 2738d736b2..e8a78093bd 100644 --- a/vendor/github.com/cilium/ebpf/asm/jump.go +++ b/vendor/github.com/cilium/ebpf/asm/jump.go @@ -1,6 +1,6 @@ package asm -//go:generate go run golang.org/x/tools/cmd/stringer@latest -output jump_string.go -type=JumpOp +//go:generate go tool stringer -output jump_string.go -type=JumpOp // JumpOp affect control flow. // @@ -44,6 +44,8 @@ const ( JSLT JumpOp = 0xc0 // JSLE jumps by offset if signed r <= signed imm JSLE JumpOp = 0xd0 + // JCOND is a conditional pseudo jump to encode the may_goto instruction + JCOND JumpOp = 0xe0 ) // Return emits an exit instruction. diff --git a/vendor/github.com/cilium/ebpf/asm/jump_string.go b/vendor/github.com/cilium/ebpf/asm/jump_string.go index 85a4aaffa5..240cb7c09a 100644 --- a/vendor/github.com/cilium/ebpf/asm/jump_string.go +++ b/vendor/github.com/cilium/ebpf/asm/jump_string.go @@ -23,9 +23,10 @@ func _() { _ = x[JLE-176] _ = x[JSLT-192] _ = x[JSLE-208] + _ = x[JCOND-224] } -const _JumpOp_name = "JaJEqJGTJGEJSetJNEJSGTJSGECallExitJLTJLEJSLTJSLEInvalidJumpOp" +const _JumpOp_name = "JaJEqJGTJGEJSetJNEJSGTJSGECallExitJLTJLEJSLTJSLEJCONDInvalidJumpOp" var _JumpOp_map = map[JumpOp]string{ 0: _JumpOp_name[0:2], @@ -42,7 +43,8 @@ var _JumpOp_map = map[JumpOp]string{ 176: _JumpOp_name[37:40], 192: _JumpOp_name[40:44], 208: _JumpOp_name[44:48], - 255: _JumpOp_name[48:61], + 224: _JumpOp_name[48:53], + 255: _JumpOp_name[53:66], } func (i JumpOp) String() string { diff --git a/vendor/github.com/cilium/ebpf/asm/load_store.go b/vendor/github.com/cilium/ebpf/asm/load_store.go index cdb5c5cfa4..a32a9b3187 100644 --- a/vendor/github.com/cilium/ebpf/asm/load_store.go +++ b/vendor/github.com/cilium/ebpf/asm/load_store.go @@ -1,6 +1,8 @@ package asm -//go:generate go run golang.org/x/tools/cmd/stringer@latest -output load_store_string.go -type=Mode,Size +import "fmt" + +//go:generate go tool stringer -output load_store_string.go -type=Mode,Size // Mode for load and store operations // @@ -26,10 +28,119 @@ const ( MemMode Mode = 0x60 // MemSXMode - load from memory, sign extension MemSXMode Mode = 0x80 - // XAddMode - add atomically across processors. - XAddMode Mode = 0xc0 + // AtomicMode - add atomically across processors. + AtomicMode Mode = 0xc0 +) + +const atomicMask OpCode = 0x0001_ff00 + +type AtomicOp uint32 + +const ( + InvalidAtomic AtomicOp = 0xffff_ffff + + // AddAtomic - add src to memory address dst atomically + AddAtomic AtomicOp = AtomicOp(Add) << 8 + // FetchAdd - add src to memory address dst atomically, store result in src + FetchAdd AtomicOp = AddAtomic | fetch + // AndAtomic - bitwise AND src with memory address at dst atomically + AndAtomic AtomicOp = AtomicOp(And) << 8 + // FetchAnd - bitwise AND src with memory address at dst atomically, store result in src + FetchAnd AtomicOp = AndAtomic | fetch + // OrAtomic - bitwise OR src with memory address at dst atomically + OrAtomic AtomicOp = AtomicOp(Or) << 8 + // FetchOr - bitwise OR src with memory address at dst atomically, store result in src + FetchOr AtomicOp = OrAtomic | fetch + // XorAtomic - bitwise XOR src with memory address at dst atomically + XorAtomic AtomicOp = AtomicOp(Xor) << 8 + // FetchXor - bitwise XOR src with memory address at dst atomically, store result in src + FetchXor AtomicOp = XorAtomic | fetch + + // Xchg - atomically exchange the old value with the new value + // + // src gets populated with the old value of *(size *)(dst + offset). + Xchg AtomicOp = 0x0000_e000 | fetch + // CmpXchg - atomically compare and exchange the old value with the new value + // + // Compares R0 and *(size *)(dst + offset), writes src to *(size *)(dst + offset) on match. + // R0 gets populated with the old value of *(size *)(dst + offset), even if no exchange occurs. + CmpXchg AtomicOp = 0x0000_f000 | fetch + + // fetch modifier for copy-modify-write atomics + fetch AtomicOp = 0x0000_0100 + // loadAcquire - atomically load with acquire semantics + loadAcquire AtomicOp = 0x0001_0000 + // storeRelease - atomically store with release semantics + storeRelease AtomicOp = 0x0001_1000 ) +func (op AtomicOp) String() string { + var name string + switch op { + case AddAtomic, AndAtomic, OrAtomic, XorAtomic: + name = ALUOp(op >> 8).String() + case FetchAdd, FetchAnd, FetchOr, FetchXor: + name = "Fetch" + ALUOp((op^fetch)>>8).String() + case Xchg: + name = "Xchg" + case CmpXchg: + name = "CmpXchg" + case loadAcquire: + name = "LdAcq" + case storeRelease: + name = "StRel" + default: + name = fmt.Sprintf("AtomicOp(%#x)", uint32(op)) + } + + return name +} + +func (op AtomicOp) OpCode(size Size) OpCode { + switch op { + case AddAtomic, AndAtomic, OrAtomic, XorAtomic, + FetchAdd, FetchAnd, FetchOr, FetchXor, + Xchg, CmpXchg: + switch size { + case Byte, Half: + // 8-bit and 16-bit atomic copy-modify-write atomics are not supported + return InvalidOpCode + } + } + + return OpCode(StXClass).SetMode(AtomicMode).SetSize(size).SetAtomicOp(op) +} + +// Mem emits `*(size *)(dst + offset) (op) src`. +func (op AtomicOp) Mem(dst, src Register, size Size, offset int16) Instruction { + return Instruction{ + OpCode: op.OpCode(size), + Dst: dst, + Src: src, + Offset: offset, + } +} + +// Emits `lock-acquire dst = *(size *)(src + offset)`. +func LoadAcquire(dst, src Register, size Size, offset int16) Instruction { + return Instruction{ + OpCode: loadAcquire.OpCode(size), + Dst: dst, + Src: src, + Offset: offset, + } +} + +// Emits `lock-release *(size *)(dst + offset) = src`. +func StoreRelease(dst, src Register, size Size, offset int16) Instruction { + return Instruction{ + OpCode: storeRelease.OpCode(size), + Dst: dst, + Src: src, + Offset: offset, + } +} + // Size of load and store operations // // msb lsb @@ -202,6 +313,10 @@ func StoreImmOp(size Size) OpCode { // StoreImm emits `*(size *)(dst + offset) = value`. func StoreImm(dst Register, offset int16, value int64, size Size) Instruction { + if size == DWord { + return Instruction{OpCode: InvalidOpCode} + } + return Instruction{ OpCode: StoreImmOp(size), Dst: dst, @@ -212,14 +327,10 @@ func StoreImm(dst Register, offset int16, value int64, size Size) Instruction { // StoreXAddOp returns the OpCode to atomically add a register to a value in memory. func StoreXAddOp(size Size) OpCode { - return OpCode(StXClass).SetMode(XAddMode).SetSize(size) + return AddAtomic.OpCode(size) } // StoreXAdd atomically adds src to *dst. func StoreXAdd(dst, src Register, size Size) Instruction { - return Instruction{ - OpCode: StoreXAddOp(size), - Dst: dst, - Src: src, - } + return AddAtomic.Mem(dst, src, size, 0) } diff --git a/vendor/github.com/cilium/ebpf/asm/load_store_string.go b/vendor/github.com/cilium/ebpf/asm/load_store_string.go index c48080327c..bbed58b66f 100644 --- a/vendor/github.com/cilium/ebpf/asm/load_store_string.go +++ b/vendor/github.com/cilium/ebpf/asm/load_store_string.go @@ -14,7 +14,7 @@ func _() { _ = x[IndMode-64] _ = x[MemMode-96] _ = x[MemSXMode-128] - _ = x[XAddMode-192] + _ = x[AtomicMode-192] } const ( @@ -23,7 +23,7 @@ const ( _Mode_name_2 = "IndMode" _Mode_name_3 = "MemMode" _Mode_name_4 = "MemSXMode" - _Mode_name_5 = "XAddMode" + _Mode_name_5 = "AtomicMode" _Mode_name_6 = "InvalidMode" ) diff --git a/vendor/github.com/cilium/ebpf/asm/metadata.go b/vendor/github.com/cilium/ebpf/asm/metadata.go index dd368a9360..60b8fe31f3 100644 --- a/vendor/github.com/cilium/ebpf/asm/metadata.go +++ b/vendor/github.com/cilium/ebpf/asm/metadata.go @@ -7,13 +7,13 @@ type Metadata struct { type metaElement struct { next *metaElement - key, value interface{} + key, value any } // Find the element containing key. // // Returns nil if there is no such element. -func (m *Metadata) find(key interface{}) *metaElement { +func (m *Metadata) find(key any) *metaElement { for e := m.head; e != nil; e = e.next { if e.key == key { return e @@ -49,7 +49,7 @@ func (m *Metadata) remove(r *metaElement) { // // If value is nil, the key is removed. Avoids modifying old metadata by // copying if necessary. -func (m *Metadata) Set(key, value interface{}) { +func (m *Metadata) Set(key, value any) { if e := m.find(key); e != nil { if e.value == value { // Key is present and the value is the same. Nothing to do. @@ -72,7 +72,7 @@ func (m *Metadata) Set(key, value interface{}) { // Get the value of a key. // // Returns nil if no value with the given key is present. -func (m *Metadata) Get(key interface{}) interface{} { +func (m *Metadata) Get(key any) any { if e := m.find(key); e != nil { return e.value } diff --git a/vendor/github.com/cilium/ebpf/asm/opcode.go b/vendor/github.com/cilium/ebpf/asm/opcode.go index 1dfd0b171a..9b2f80f0ac 100644 --- a/vendor/github.com/cilium/ebpf/asm/opcode.go +++ b/vendor/github.com/cilium/ebpf/asm/opcode.go @@ -5,7 +5,7 @@ import ( "strings" ) -//go:generate go run golang.org/x/tools/cmd/stringer@latest -output opcode_string.go -type=Class +//go:generate go tool stringer -output opcode_string.go -type=Class // Class of operations // @@ -71,24 +71,29 @@ func (cls Class) isJumpOrALU() bool { // // The encoding varies based on a 3-bit Class: // -// 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 -// ??? | CLS +// 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +// ??? | CLS // // For ALUClass and ALUCLass32: // -// 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 -// OPC |S| CLS +// 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +// 0 | OPC |S| CLS // // For LdClass, LdXclass, StClass and StXClass: // -// 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 -// 0 | MDE |SIZ| CLS +// 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +// 0 | MDE |SIZ| CLS +// +// For StXClass where MDE == AtomicMode: +// +// 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +// 0 | ATOMIC OP | MDE |SIZ| CLS // // For JumpClass, Jump32Class: // -// 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 -// 0 | OPC |S| CLS -type OpCode uint16 +// 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +// 0 | OPC |S| CLS +type OpCode uint32 // InvalidOpCode is returned by setters on OpCode const InvalidOpCode OpCode = 0xffff @@ -138,6 +143,14 @@ func (op OpCode) Size() Size { return Size(op & sizeMask) } +// AtomicOp returns the type of atomic operation. +func (op OpCode) AtomicOp() AtomicOp { + if op.Class() != StXClass || op.Mode() != AtomicMode { + return InvalidAtomic + } + return AtomicOp(op & atomicMask) +} + // Source returns the source for branch and ALU operations. func (op OpCode) Source() Source { if !op.Class().isJumpOrALU() || op.ALUOp() == Swap { @@ -199,6 +212,13 @@ func (op OpCode) SetSize(size Size) OpCode { return (op & ^sizeMask) | OpCode(size) } +func (op OpCode) SetAtomicOp(atomic AtomicOp) OpCode { + if op.Class() != StXClass || op.Mode() != AtomicMode || !valid(OpCode(atomic), atomicMask) { + return InvalidOpCode + } + return (op & ^atomicMask) | OpCode(atomic) +} + // SetSource sets the source on jump and ALU operations. // // Returns InvalidOpCode if op is of the wrong class. @@ -247,6 +267,10 @@ func (op OpCode) String() string { mode := op.Mode() f.WriteString(strings.TrimSuffix(mode.String(), "Mode")) + if atomic := op.AtomicOp(); atomic != InvalidAtomic { + f.WriteString(strings.TrimSuffix(atomic.String(), "Atomic")) + } + switch op.Size() { case DWord: f.WriteString("DW") diff --git a/vendor/github.com/cilium/ebpf/asm/opcode_string.go b/vendor/github.com/cilium/ebpf/asm/opcode_string.go index 58bc3e7e7f..07825e0dd1 100644 --- a/vendor/github.com/cilium/ebpf/asm/opcode_string.go +++ b/vendor/github.com/cilium/ebpf/asm/opcode_string.go @@ -23,8 +23,9 @@ const _Class_name = "LdClassLdXClassStClassStXClassALUClassJumpClassJump32ClassA var _Class_index = [...]uint8{0, 7, 15, 22, 30, 38, 47, 58, 68} func (i Class) String() string { - if i >= Class(len(_Class_index)-1) { + idx := int(i) - 0 + if i < 0 || idx >= len(_Class_index)-1 { return "Class(" + strconv.FormatInt(int64(i), 10) + ")" } - return _Class_name[_Class_index[i]:_Class_index[i+1]] + return _Class_name[_Class_index[idx]:_Class_index[idx+1]] } diff --git a/vendor/github.com/cilium/ebpf/asm/register.go b/vendor/github.com/cilium/ebpf/asm/register.go index 457a3b8a88..baf03aa42f 100644 --- a/vendor/github.com/cilium/ebpf/asm/register.go +++ b/vendor/github.com/cilium/ebpf/asm/register.go @@ -40,6 +40,7 @@ const ( PseudoCall = R1 // BPF_PSEUDO_CALL PseudoFunc = R4 // BPF_PSEUDO_FUNC PseudoKfuncCall = R2 // BPF_PSEUDO_KFUNC_CALL + PseudoMayGoto = R0 // BPF_MAY_GOTO ) func (r Register) String() string { diff --git a/vendor/github.com/cilium/ebpf/attachtype_string.go b/vendor/github.com/cilium/ebpf/attachtype_string.go index 84445a3256..efed516b62 100644 --- a/vendor/github.com/cilium/ebpf/attachtype_string.go +++ b/vendor/github.com/cilium/ebpf/attachtype_string.go @@ -66,15 +66,35 @@ func _() { _ = x[AttachCgroupUnixGetsockname-53] _ = x[AttachNetkitPrimary-54] _ = x[AttachNetkitPeer-55] + _ = x[AttachWindowsXDP-268435457] + _ = x[AttachWindowsBind-268435458] + _ = x[AttachWindowsCGroupInet4Connect-268435459] + _ = x[AttachWindowsCGroupInet6Connect-268435460] + _ = x[AttachWindowsCgroupInet4RecvAccept-268435461] + _ = x[AttachWindowsCgroupInet6RecvAccept-268435462] + _ = x[AttachWindowsCGroupSockOps-268435463] + _ = x[AttachWindowsSample-268435464] + _ = x[AttachWindowsXDPTest-268435465] } -const _AttachType_name = "NoneCGroupInetEgressCGroupInetSockCreateCGroupSockOpsSkSKBStreamParserSkSKBStreamVerdictCGroupDeviceSkMsgVerdictCGroupInet4BindCGroupInet6BindCGroupInet4ConnectCGroupInet6ConnectCGroupInet4PostBindCGroupInet6PostBindCGroupUDP4SendmsgCGroupUDP6SendmsgLircMode2FlowDissectorCGroupSysctlCGroupUDP4RecvmsgCGroupUDP6RecvmsgCGroupGetsockoptCGroupSetsockoptTraceRawTpTraceFEntryTraceFExitModifyReturnLSMMacTraceIterCgroupInet4GetPeernameCgroupInet6GetPeernameCgroupInet4GetSocknameCgroupInet6GetSocknameXDPDevMapCgroupInetSockReleaseXDPCPUMapSkLookupXDPSkSKBVerdictSkReuseportSelectSkReuseportSelectOrMigratePerfEventTraceKprobeMultiLSMCgroupStructOpsNetfilterTCXIngressTCXEgressTraceUprobeMultiCgroupUnixConnectCgroupUnixSendmsgCgroupUnixRecvmsgCgroupUnixGetpeernameCgroupUnixGetsocknameNetkitPrimaryNetkitPeerTraceKprobeSession" +const ( + _AttachType_name_0 = "NoneCGroupInetEgressCGroupInetSockCreateCGroupSockOpsSkSKBStreamParserSkSKBStreamVerdictCGroupDeviceSkMsgVerdictCGroupInet4BindCGroupInet6BindCGroupInet4ConnectCGroupInet6ConnectCGroupInet4PostBindCGroupInet6PostBindCGroupUDP4SendmsgCGroupUDP6SendmsgLircMode2FlowDissectorCGroupSysctlCGroupUDP4RecvmsgCGroupUDP6RecvmsgCGroupGetsockoptCGroupSetsockoptTraceRawTpTraceFEntryTraceFExitModifyReturnLSMMacTraceIterCgroupInet4GetPeernameCgroupInet6GetPeernameCgroupInet4GetSocknameCgroupInet6GetSocknameXDPDevMapCgroupInetSockReleaseXDPCPUMapSkLookupXDPSkSKBVerdictSkReuseportSelectSkReuseportSelectOrMigratePerfEventTraceKprobeMultiLSMCgroupStructOpsNetfilterTCXIngressTCXEgressTraceUprobeMultiCgroupUnixConnectCgroupUnixSendmsgCgroupUnixRecvmsgCgroupUnixGetpeernameCgroupUnixGetsocknameNetkitPrimaryNetkitPeerTraceKprobeSession" + _AttachType_name_1 = "WindowsXDPWindowsBindWindowsCGroupInet4ConnectWindowsCGroupInet6ConnectWindowsCgroupInet4RecvAcceptWindowsCgroupInet6RecvAcceptWindowsCGroupSockOpsWindowsSampleWindowsXDPTest" +) -var _AttachType_index = [...]uint16{0, 4, 20, 40, 53, 70, 88, 100, 112, 127, 142, 160, 178, 197, 216, 233, 250, 259, 272, 284, 301, 318, 334, 350, 360, 371, 381, 393, 399, 408, 430, 452, 474, 496, 505, 526, 535, 543, 546, 558, 575, 601, 610, 626, 635, 644, 653, 663, 672, 688, 705, 722, 739, 760, 781, 794, 804, 822} +var ( + _AttachType_index_0 = [...]uint16{0, 4, 20, 40, 53, 70, 88, 100, 112, 127, 142, 160, 178, 197, 216, 233, 250, 259, 272, 284, 301, 318, 334, 350, 360, 371, 381, 393, 399, 408, 430, 452, 474, 496, 505, 526, 535, 543, 546, 558, 575, 601, 610, 626, 635, 644, 653, 663, 672, 688, 705, 722, 739, 760, 781, 794, 804, 822} + _AttachType_index_1 = [...]uint8{0, 10, 21, 46, 71, 99, 127, 147, 160, 174} +) func (i AttachType) String() string { - if i >= AttachType(len(_AttachType_index)-1) { + switch { + case i <= 56: + return _AttachType_name_0[_AttachType_index_0[i]:_AttachType_index_0[i+1]] + case 268435457 <= i && i <= 268435465: + i -= 268435457 + return _AttachType_name_1[_AttachType_index_1[i]:_AttachType_index_1[i+1]] + default: return "AttachType(" + strconv.FormatInt(int64(i), 10) + ")" } - return _AttachType_name[_AttachType_index[i]:_AttachType_index[i+1]] } diff --git a/vendor/github.com/cilium/ebpf/btf/btf.go b/vendor/github.com/cilium/ebpf/btf/btf.go index e8f80c1399..3411569295 100644 --- a/vendor/github.com/cilium/ebpf/btf/btf.go +++ b/vendor/github.com/cilium/ebpf/btf/btf.go @@ -1,16 +1,16 @@ package btf import ( - "bufio" "debug/elf" - "encoding/binary" "errors" "fmt" "io" + "iter" + "maps" "math" "os" "reflect" - "sync" + "slices" "github.com/cilium/ebpf/internal" "github.com/cilium/ebpf/internal/sys" @@ -29,154 +29,24 @@ var ( // ID represents the unique ID of a BTF object. type ID = sys.BTFID -// immutableTypes is a set of types which musn't be changed. -type immutableTypes struct { - // All types contained by the spec, not including types from the base in - // case the spec was parsed from split BTF. - types []Type - - // Type IDs indexed by type. - typeIDs map[Type]TypeID - - // The ID of the first type in types. - firstTypeID TypeID - - // Types indexed by essential name. - // Includes all struct flavors and types with the same name. - namedTypes map[essentialName][]TypeID - - // Byte order of the types. This affects things like struct member order - // when using bitfields. - byteOrder binary.ByteOrder -} - -func (s *immutableTypes) typeByID(id TypeID) (Type, bool) { - if id < s.firstTypeID { - return nil, false - } - - index := int(id - s.firstTypeID) - if index >= len(s.types) { - return nil, false - } - - return s.types[index], true -} - -// mutableTypes is a set of types which may be changed. -type mutableTypes struct { - imm immutableTypes - mu sync.RWMutex // protects copies below - copies map[Type]Type // map[orig]copy - copiedTypeIDs map[Type]TypeID // map[copy]origID -} - -// add a type to the set of mutable types. -// -// Copies type and all of its children once. Repeated calls with the same type -// do not copy again. -func (mt *mutableTypes) add(typ Type, typeIDs map[Type]TypeID) Type { - mt.mu.RLock() - cpy, ok := mt.copies[typ] - mt.mu.RUnlock() - - if ok { - // Fast path: the type has been copied before. - return cpy - } - - // modifyGraphPreorder copies the type graph node by node, so we can't drop - // the lock in between. - mt.mu.Lock() - defer mt.mu.Unlock() - - return copyType(typ, typeIDs, mt.copies, mt.copiedTypeIDs) -} - -// copy a set of mutable types. -func (mt *mutableTypes) copy() *mutableTypes { - if mt == nil { - return nil - } - - // Prevent concurrent modification of mt.copiedTypeIDs. - mt.mu.RLock() - defer mt.mu.RUnlock() - - mtCopy := &mutableTypes{ - mt.imm, - sync.RWMutex{}, - make(map[Type]Type, len(mt.copies)), - make(map[Type]TypeID, len(mt.copiedTypeIDs)), - } - - copiesOfCopies := make(map[Type]Type, len(mt.copies)) - for orig, copy := range mt.copies { - // NB: We make a copy of copy, not orig, so that changes to mutable types - // are preserved. - copyOfCopy := copyType(copy, mt.copiedTypeIDs, copiesOfCopies, mtCopy.copiedTypeIDs) - mtCopy.copies[orig] = copyOfCopy - } - - return mtCopy -} - -func (mt *mutableTypes) typeID(typ Type) (TypeID, error) { - if _, ok := typ.(*Void); ok { - // Equality is weird for void, since it is a zero sized type. - return 0, nil - } - - mt.mu.RLock() - defer mt.mu.RUnlock() - - id, ok := mt.copiedTypeIDs[typ] - if !ok { - return 0, fmt.Errorf("no ID for type %s: %w", typ, ErrNotFound) - } - - return id, nil +type elfData struct { + sectionSizes map[string]uint32 + symbolOffsets map[elfSymbol]uint32 + fixups map[Type]bool } -func (mt *mutableTypes) typeByID(id TypeID) (Type, bool) { - immT, ok := mt.imm.typeByID(id) - if !ok { - return nil, false - } - - return mt.add(immT, mt.imm.typeIDs), true -} - -func (mt *mutableTypes) anyTypesByName(name string) ([]Type, error) { - immTypes := mt.imm.namedTypes[newEssentialName(name)] - if len(immTypes) == 0 { - return nil, fmt.Errorf("type name %s: %w", name, ErrNotFound) - } - - // Return a copy to prevent changes to namedTypes. - result := make([]Type, 0, len(immTypes)) - for _, id := range immTypes { - immT, ok := mt.imm.typeByID(id) - if !ok { - return nil, fmt.Errorf("no type with ID %d", id) - } - - // Match against the full name, not just the essential one - // in case the type being looked up is a struct flavor. - if immT.TypeName() == name { - result = append(result, mt.add(immT, mt.imm.typeIDs)) - } - } - return result, nil +type elfSymbol struct { + section string + name string } // Spec allows querying a set of Types and loading the set into the // kernel. type Spec struct { - *mutableTypes + *decoder - // String table from ELF. - strings *stringTable + // Additional data from ELF, may be nil. + elf *elfData } // LoadSpec opens file and calls LoadSpecFromReader on it. @@ -197,11 +67,12 @@ func LoadSpec(file string) (*Spec, error) { func LoadSpecFromReader(rd io.ReaderAt) (*Spec, error) { file, err := internal.NewSafeELFFile(rd) if err != nil { - if bo := guessRawBTFByteOrder(rd); bo != nil { - return loadRawSpec(io.NewSectionReader(rd, 0, math.MaxInt64), bo, nil) + raw, err := io.ReadAll(io.NewSectionReader(rd, 0, math.MaxInt64)) + if err != nil { + return nil, fmt.Errorf("read raw BTF: %w", err) } - return nil, err + return loadRawSpec(raw, nil) } return loadSpecFromELF(file) @@ -237,13 +108,13 @@ func LoadSpecAndExtInfosFromReader(rd io.ReaderAt) (*Spec, *ExtInfos, error) { // Some ELF symbols (e.g. in vmlinux) may point to virtual memory that is well // beyond this range. Since these symbols cannot be described by BTF info, // ignore them here. -func symbolOffsets(file *internal.SafeELFFile) (map[symbol]uint32, error) { +func symbolOffsets(file *internal.SafeELFFile) (map[elfSymbol]uint32, error) { symbols, err := file.Symbols() if err != nil { return nil, fmt.Errorf("can't read symbols: %v", err) } - offsets := make(map[symbol]uint32) + offsets := make(map[elfSymbol]uint32) for _, sym := range symbols { if idx := sym.Section; idx >= elf.SHN_LORESERVE && idx <= elf.SHN_HIRESERVE { // Ignore things like SHN_ABS @@ -260,7 +131,7 @@ func symbolOffsets(file *internal.SafeELFFile) (map[symbol]uint32, error) { } secName := file.Sections[sym.Section].Name - offsets[symbol{secName, sym.Name}] = uint32(sym.Value) + offsets[elfSymbol{secName, sym.Name}] = uint32(sym.Value) } return offsets, nil @@ -298,145 +169,86 @@ func loadSpecFromELF(file *internal.SafeELFFile) (*Spec, error) { return nil, err } - if btfSection.ReaderAt == nil { - return nil, fmt.Errorf("compressed BTF is not supported") + rawBTF, err := btfSection.Data() + if err != nil { + return nil, fmt.Errorf("reading .BTF section: %w", err) } - spec, err := loadRawSpec(btfSection.ReaderAt, file.ByteOrder, nil) + spec, err := loadRawSpec(rawBTF, nil) if err != nil { return nil, err } - err = fixupDatasec(spec.imm.types, sectionSizes, offsets) - if err != nil { - return nil, err + if spec.decoder.byteOrder != file.ByteOrder { + return nil, fmt.Errorf("BTF byte order %s does not match ELF byte order %s", spec.decoder.byteOrder, file.ByteOrder) + } + + spec.elf = &elfData{ + sectionSizes, + offsets, + make(map[Type]bool), } return spec, nil } -func loadRawSpec(btf io.ReaderAt, bo binary.ByteOrder, base *Spec) (*Spec, error) { +func loadRawSpec(btf []byte, base *Spec) (*Spec, error) { var ( + baseDecoder *decoder baseStrings *stringTable - firstTypeID TypeID err error ) if base != nil { - if base.imm.firstTypeID != 0 { - return nil, fmt.Errorf("can't use split BTF as base") - } - + baseDecoder = base.decoder baseStrings = base.strings - - firstTypeID, err = base.nextTypeID() - if err != nil { - return nil, err - } } - types, rawStrings, err := parseBTF(btf, bo, baseStrings, base) + header, _, bo, err := parseBTFHeader(btf) if err != nil { - return nil, err + return nil, fmt.Errorf("parsing .BTF header: %v", err) } - typeIDs, typesByName := indexTypes(types, firstTypeID) - - return &Spec{ - &mutableTypes{ - immutableTypes{ - types, - typeIDs, - firstTypeID, - typesByName, - bo, - }, - sync.RWMutex{}, - make(map[Type]Type), - make(map[Type]TypeID), - }, - rawStrings, - }, nil -} - -func indexTypes(types []Type, firstTypeID TypeID) (map[Type]TypeID, map[essentialName][]TypeID) { - namedTypes := 0 - for _, typ := range types { - if typ.TypeName() != "" { - // Do a pre-pass to figure out how big types by name has to be. - // Most types have unique names, so it's OK to ignore essentialName - // here. - namedTypes++ - } + if header.HdrLen > uint32(len(btf)) { + return nil, fmt.Errorf("BTF header length is out of bounds") } + btf = btf[header.HdrLen:] - typeIDs := make(map[Type]TypeID, len(types)) - typesByName := make(map[essentialName][]TypeID, namedTypes) - - for i, typ := range types { - id := firstTypeID + TypeID(i) - typeIDs[typ] = id - - if name := newEssentialName(typ.TypeName()); name != "" { - typesByName[name] = append(typesByName[name], id) - } + if uint64(header.StringOff)+uint64(header.StringLen) > uint64(len(btf)) { + return nil, fmt.Errorf("string table is out of bounds") } + stringsSection := btf[header.StringOff : header.StringOff+header.StringLen] - return typeIDs, typesByName -} - -func guessRawBTFByteOrder(r io.ReaderAt) binary.ByteOrder { - buf := new(bufio.Reader) - for _, bo := range []binary.ByteOrder{ - binary.LittleEndian, - binary.BigEndian, - } { - buf.Reset(io.NewSectionReader(r, 0, math.MaxInt64)) - if _, err := parseBTFHeader(buf, 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.ReaderAt, bo binary.ByteOrder, baseStrings *stringTable, base *Spec) ([]Type, *stringTable, error) { - buf := internal.NewBufferedSectionReader(btf, 0, math.MaxInt64) - header, err := parseBTFHeader(buf, bo) + rawStrings, err := newStringTable(stringsSection, baseStrings) if err != nil { - return nil, nil, fmt.Errorf("parsing .BTF header: %v", err) + return nil, fmt.Errorf("read string section: %w", err) } - rawStrings, err := readStringTable(io.NewSectionReader(btf, header.stringStart(), int64(header.StringLen)), - baseStrings) - if err != nil { - return nil, nil, fmt.Errorf("can't read type names: %w", err) + if uint64(header.TypeOff)+uint64(header.TypeLen) > uint64(len(btf)) { + return nil, fmt.Errorf("types section is out of bounds") } + typesSection := btf[header.TypeOff : header.TypeOff+header.TypeLen] - buf.Reset(io.NewSectionReader(btf, header.typeStart(), int64(header.TypeLen))) - types, err := readAndInflateTypes(buf, bo, header.TypeLen, rawStrings, base) + decoder, err := newDecoder(typesSection, bo, rawStrings, baseDecoder) if err != nil { - return nil, nil, err + return nil, err } - return types, rawStrings, nil -} - -type symbol struct { - section string - name string + return &Spec{decoder, nil}, nil } // fixupDatasec attempts to patch up missing info in Datasecs and its members by // supplementing them with information from the ELF headers and symbol table. -func fixupDatasec(types []Type, sectionSizes map[string]uint32, offsets map[symbol]uint32) error { - for _, typ := range types { - ds, ok := typ.(*Datasec) - if !ok { - continue +func (elf *elfData) fixupDatasec(typ Type) error { + if elf == nil { + return nil + } + + if ds, ok := typ.(*Datasec); ok { + if elf.fixups[ds] { + return nil } + elf.fixups[ds] = true name := ds.Name @@ -459,7 +271,7 @@ func fixupDatasec(types []Type, sectionSizes map[string]uint32, offsets map[symb } } - continue + return nil case ".kconfig": // .kconfig has a size of 0 and has all members' offsets set to 0. // Fix up all offsets and set the Datasec's size. @@ -472,21 +284,21 @@ func fixupDatasec(types []Type, sectionSizes map[string]uint32, offsets map[symb vsi.Type.(*Var).Linkage = GlobalVar } - continue + return nil } if ds.Size != 0 { - continue + return nil } - ds.Size, ok = sectionSizes[name] + ds.Size, ok = elf.sectionSizes[name] if !ok { return fmt.Errorf("data section %s: missing size", name) } for i := range ds.Vars { symName := ds.Vars[i].Type.TypeName() - ds.Vars[i].Offset, ok = offsets[symbol{name, symName}] + ds.Vars[i].Offset, ok = elf.symbolOffsets[elfSymbol{name, symName}] if !ok { return fmt.Errorf("data section %s: missing offset for symbol %s", name, symName) } @@ -530,36 +342,29 @@ func fixupDatasecLayout(ds *Datasec) error { return nil } -// Copy creates a copy of Spec. +// Copy a Spec. +// +// All contained types are duplicated while preserving any modifications made +// to them. func (s *Spec) Copy() *Spec { if s == nil { return nil } - return &Spec{ - s.mutableTypes.copy(), - s.strings, + cpy := &Spec{ + s.decoder.Copy(), + nil, } -} - -type sliceWriter []byte -func (sw sliceWriter) Write(p []byte) (int, error) { - if len(p) != len(sw) { - return 0, errors.New("size doesn't match") + if s.elf != nil { + cpy.elf = &elfData{ + s.elf.sectionSizes, + s.elf.symbolOffsets, + maps.Clone(s.elf.fixups), + } } - return copy(sw, p), nil -} - -// nextTypeID returns the next unallocated type ID or an error if there are no -// more type IDs. -func (s *Spec) nextTypeID() (TypeID, error) { - id := s.imm.firstTypeID + TypeID(len(s.imm.types)) - if id < s.imm.firstTypeID { - return 0, fmt.Errorf("no more type IDs") - } - return id, nil + return cpy } // TypeByID returns the BTF Type with the given type ID. @@ -567,9 +372,13 @@ func (s *Spec) nextTypeID() (TypeID, error) { // 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) { - typ, ok := s.typeByID(id) - if !ok { - return nil, fmt.Errorf("look up type with ID %d (first ID is %d): %w", id, s.imm.firstTypeID, ErrNotFound) + typ, err := s.decoder.TypeByID(id) + if err != nil { + return nil, fmt.Errorf("inflate type: %w", err) + } + + if err := s.elf.fixupDatasec(typ); err != nil { + return nil, err } return typ, nil @@ -579,7 +388,7 @@ func (s *Spec) TypeByID(id TypeID) (Type, error) { // // Returns an error wrapping [ErrNotFound] if the type isn't part of the Spec. func (s *Spec) TypeID(typ Type) (TypeID, error) { - return s.mutableTypes.typeID(typ) + return s.decoder.TypeID(typ) } // AnyTypesByName returns a list of BTF Types with the given name. @@ -590,7 +399,25 @@ func (s *Spec) TypeID(typ Type) (TypeID, error) { // // Returns an error wrapping ErrNotFound if no matching Type exists in the Spec. func (s *Spec) AnyTypesByName(name string) ([]Type, error) { - return s.mutableTypes.anyTypesByName(name) + types, err := s.TypesByName(newEssentialName(name)) + if err != nil { + return nil, err + } + + for i := 0; i < len(types); i++ { + // Match against the full name, not just the essential one + // in case the type being looked up is a struct flavor. + if types[i].TypeName() != name { + types = slices.Delete(types, i, i+1) + continue + } + + if err := s.elf.fixupDatasec(types[i]); err != nil { + return nil, err + } + } + + return types, nil } // AnyTypeByName returns a Type with the given name. @@ -618,15 +445,18 @@ func (s *Spec) AnyTypeByName(name string) (Type, error) { // // Returns an error wrapping ErrNotFound if no matching Type exists in the Spec. // Returns an error wrapping ErrMultipleTypes if multiple candidates are found. -func (s *Spec) TypeByName(name string, typ interface{}) error { - typeInterface := reflect.TypeOf((*Type)(nil)).Elem() +func (s *Spec) TypeByName(name string, typ any) error { + if err := internal.IsNilPointer(typ); err != nil { + return fmt.Errorf("type argument: %w", err) + } + + typeInterface := reflect.TypeFor[Type]() // typ may be **T or *Type typValue := reflect.ValueOf(typ) - if typValue.Kind() != reflect.Ptr { + if typValue.Kind() != reflect.Pointer { return fmt.Errorf("%T is not a pointer", typ) } - typPtr := typValue.Elem() if !typPtr.CanSet() { return fmt.Errorf("%T cannot be set", typ) @@ -635,6 +465,9 @@ func (s *Spec) TypeByName(name string, typ interface{}) error { wanted := typPtr.Type() if wanted == typeInterface { // This is *Type. Unwrap the value's type. + if typPtr.IsNil() { + return fmt.Errorf("%T points to a nil Type", typ) + } wanted = typPtr.Elem().Type() } @@ -669,45 +502,55 @@ func (s *Spec) TypeByName(name string, typ interface{}) error { return nil } +// LoadSplitSpec loads split BTF from the given file. +// +// Types from base are used to resolve references in the split BTF. +// The returned Spec only contains types from the split BTF, not from the base. +func LoadSplitSpec(file string, base *Spec) (*Spec, error) { + fh, err := os.Open(file) + if err != nil { + return nil, err + } + defer fh.Close() + + return LoadSplitSpecFromReader(fh, base) +} + // LoadSplitSpecFromReader loads split BTF from a reader. // // Types from base are used to resolve references in the split BTF. // The returned Spec only contains types from the split BTF, not from the base. func LoadSplitSpecFromReader(r io.ReaderAt, base *Spec) (*Spec, error) { - return loadRawSpec(r, internal.NativeEndian, base) -} + raw, err := io.ReadAll(io.NewSectionReader(r, 0, math.MaxInt64)) + if err != nil { + return nil, fmt.Errorf("read raw BTF: %w", err) + } -// TypesIterator iterates over types of a given spec. -type TypesIterator struct { - spec *Spec - id TypeID - done bool - // The last visited type in the spec. - Type Type + return loadRawSpec(raw, base) } -// Iterate returns the types iterator. -func (s *Spec) Iterate() *TypesIterator { - return &TypesIterator{spec: s, id: s.imm.firstTypeID} -} +// All iterates over all types. +func (s *Spec) All() iter.Seq2[Type, error] { + return func(yield func(Type, error) bool) { + for id := s.firstTypeID; ; id++ { + typ, err := s.TypeByID(id) + if errors.Is(err, ErrNotFound) { + return + } else if err != nil { + yield(nil, err) + return + } + + // Skip declTags, during unmarshaling declTags become `Tags` fields of other types. + // We keep them in the spec to avoid holes in the ID space, but for the purposes of + // iteration, they are not useful to the user. + if _, ok := typ.(*declTag); ok { + continue + } -// Next returns true as long as there are any remaining types. -func (iter *TypesIterator) Next() bool { - if iter.done { - return false - } - - var ok bool - iter.Type, ok = iter.spec.typeByID(iter.id) - iter.id++ - iter.done = !ok - if !iter.done { - // Skip declTags, during unmarshaling declTags become `Tags` fields of other types. - // We keep them in the spec to avoid holes in the ID space, but for the purposes of - // iteration, they are not useful to the user. - if _, ok := iter.Type.(*declTag); ok { - return iter.Next() + if !yield(typ, nil) { + return + } } } - return !iter.done } diff --git a/vendor/github.com/cilium/ebpf/btf/btf_types.go b/vendor/github.com/cilium/ebpf/btf/btf_types.go index 320311b332..469baf60cd 100644 --- a/vendor/github.com/cilium/ebpf/btf/btf_types.go +++ b/vendor/github.com/cilium/ebpf/btf/btf_types.go @@ -4,13 +4,10 @@ import ( "encoding/binary" "errors" "fmt" - "io" "unsafe" - - "github.com/cilium/ebpf/internal" ) -//go:generate go run golang.org/x/tools/cmd/stringer@latest -linecomment -output=btf_types_string.go -type=FuncLinkage,VarLinkage,btfKind +//go:generate go tool stringer -linecomment -output=btf_types_string.go -type=FuncLinkage,VarLinkage,btfKind // btfKind describes a Type. type btfKind uint8 @@ -87,50 +84,69 @@ type btfHeader struct { StringLen uint32 } -// 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) +type btfLayout struct { + Off uint32 + Len uint32 } // parseBTFHeader parses the header of the .BTF section. -func parseBTFHeader(r io.Reader, bo binary.ByteOrder) (*btfHeader, error) { +func parseBTFHeader(buf []byte) (*btfHeader, *btfLayout, binary.ByteOrder, error) { var header btfHeader - if err := binary.Read(r, bo, &header); err != nil { - return nil, fmt.Errorf("can't read header: %v", err) + var bo binary.ByteOrder + for _, order := range []binary.ByteOrder{binary.LittleEndian, binary.BigEndian} { + n, err := binary.Decode(buf, order, &header) + if err != nil { + return nil, nil, nil, fmt.Errorf("read header: %v", err) + } + + if header.Magic != btfMagic { + continue + } + + buf = buf[n:] + bo = order + break } - if header.Magic != btfMagic { - return nil, fmt.Errorf("incorrect magic value %v", header.Magic) + if bo == nil { + return nil, nil, nil, fmt.Errorf("no valid BTF header") } if header.Version != 1 { - return nil, fmt.Errorf("unexpected version %v", header.Version) + return nil, nil, nil, fmt.Errorf("unexpected version %v", header.Version) } if header.Flags != 0 { - return nil, fmt.Errorf("unsupported flags %v", header.Flags) + return nil, nil, 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") + return nil, nil, nil, errors.New("header length shorter than minimum BTF header size") } - if _, err := io.CopyN(internal.DiscardZeroes{}, r, remainder); err != nil { - return nil, fmt.Errorf("header padding: %v", err) + if len(buf) < int(remainder) { + return nil, nil, nil, errors.New("header length exceeds available data") } - return &header, nil -} + var layout btfLayout + if remainder >= int64(binary.Size(&btfLayout{})) { + n, err := binary.Decode(buf, bo, &layout) + if err != nil { + return nil, nil, nil, fmt.Errorf("read layout offset and length: %v", err) + } + buf = buf[n:] + remainder -= int64(n) + } -var btfTypeLen = binary.Size(btfType{}) + for _, b := range buf[:remainder] { + if b != 0 { + return nil, nil, nil, errors.New("header contains non-zero trailer") + } + } + + return &header, &layout, bo, nil +} // btfType is equivalent to struct btf_type in Documentation/bpf/btf.rst. type btfType struct { @@ -268,30 +284,53 @@ func (bt *btfType) SetSize(size uint32) { bt.SizeType = size } -func (bt *btfType) Marshal(w io.Writer, bo binary.ByteOrder) error { - buf := make([]byte, unsafe.Sizeof(*bt)) +func (bt *btfType) Encode(buf []byte, bo binary.ByteOrder) (int, error) { + if len(buf) < btfTypeSize { + return 0, fmt.Errorf("not enough bytes to marshal btfType") + } bo.PutUint32(buf[0:], bt.NameOff) bo.PutUint32(buf[4:], bt.Info) bo.PutUint32(buf[8:], bt.SizeType) - _, err := w.Write(buf) - return err -} - -type rawType struct { - btfType - data interface{} + return btfTypeSize, nil } -func (rt *rawType) Marshal(w io.Writer, bo binary.ByteOrder) error { - if err := rt.btfType.Marshal(w, bo); err != nil { - return err +// DataLen returns the length of additional type specific data in bytes. +func (bt *btfType) DataLen() (int, error) { + switch bt.Kind() { + case kindInt: + return int(unsafe.Sizeof(btfInt{})), nil + case kindPointer: + case kindArray: + return int(unsafe.Sizeof(btfArray{})), nil + case kindStruct: + fallthrough + case kindUnion: + return int(unsafe.Sizeof(btfMember{})) * bt.Vlen(), nil + case kindEnum: + return int(unsafe.Sizeof(btfEnum{})) * bt.Vlen(), nil + case kindForward: + case kindTypedef: + case kindVolatile: + case kindConst: + case kindRestrict: + case kindFunc: + case kindFuncProto: + return int(unsafe.Sizeof(btfParam{})) * bt.Vlen(), nil + case kindVar: + return int(unsafe.Sizeof(btfVariable{})), nil + case kindDatasec: + return int(unsafe.Sizeof(btfVarSecinfo{})) * bt.Vlen(), nil + case kindFloat: + case kindDeclTag: + return int(unsafe.Sizeof(btfDeclTag{})), nil + case kindTypeTag: + case kindEnum64: + return int(unsafe.Sizeof(btfEnum64{})) * bt.Vlen(), nil + default: + return 0, fmt.Errorf("unknown kind: %v", bt.Kind()) } - if rt.data == nil { - return nil - } - - return binary.Write(w, bo, rt.data) + return 0, nil } // btfInt encodes additional data for integers. @@ -376,21 +415,15 @@ type btfMember struct { var btfMemberLen = int(unsafe.Sizeof(btfMember{})) -func unmarshalBtfMembers(members []btfMember, b []byte, bo binary.ByteOrder) (int, error) { - off := 0 - for i := range members { - if off+btfMemberLen > len(b) { - return 0, fmt.Errorf("not enough bytes to unmarshal btfMember %d", i) - } - - members[i].NameOff = bo.Uint32(b[off+0:]) - members[i].Type = TypeID(bo.Uint32(b[off+4:])) - members[i].Offset = bo.Uint32(b[off+8:]) - - off += btfMemberLen +func unmarshalBtfMember(bm *btfMember, b []byte, bo binary.ByteOrder) (int, error) { + if btfMemberLen > len(b) { + return 0, fmt.Errorf("not enough bytes to unmarshal btfMember") } - return off, nil + bm.NameOff = bo.Uint32(b[0:]) + bm.Type = TypeID(bo.Uint32(b[4:])) + bm.Offset = bo.Uint32(b[8:]) + return btfMemberLen, nil } type btfVarSecinfo struct { @@ -401,21 +434,15 @@ type btfVarSecinfo struct { var btfVarSecinfoLen = int(unsafe.Sizeof(btfVarSecinfo{})) -func unmarshalBtfVarSecInfos(secinfos []btfVarSecinfo, b []byte, bo binary.ByteOrder) (int, error) { - off := 0 - for i := range secinfos { - if off+btfVarSecinfoLen > len(b) { - return 0, fmt.Errorf("not enough bytes to unmarshal btfVarSecinfo %d", i) - } - - secinfos[i].Type = TypeID(bo.Uint32(b[off+0:])) - secinfos[i].Offset = bo.Uint32(b[off+4:]) - secinfos[i].Size = bo.Uint32(b[off+8:]) - - off += btfVarSecinfoLen +func unmarshalBtfVarSecInfo(bvsi *btfVarSecinfo, b []byte, bo binary.ByteOrder) (int, error) { + if len(b) < btfVarSecinfoLen { + return 0, fmt.Errorf("not enough bytes to unmarshal btfVarSecinfo") } - return off, nil + bvsi.Type = TypeID(bo.Uint32(b[0:])) + bvsi.Offset = bo.Uint32(b[4:]) + bvsi.Size = bo.Uint32(b[8:]) + return btfVarSecinfoLen, nil } type btfVariable struct { @@ -440,20 +467,14 @@ type btfEnum struct { var btfEnumLen = int(unsafe.Sizeof(btfEnum{})) -func unmarshalBtfEnums(enums []btfEnum, b []byte, bo binary.ByteOrder) (int, error) { - off := 0 - for i := range enums { - if off+btfEnumLen > len(b) { - return 0, fmt.Errorf("not enough bytes to unmarshal btfEnum %d", i) - } - - enums[i].NameOff = bo.Uint32(b[off+0:]) - enums[i].Val = bo.Uint32(b[off+4:]) - - off += btfEnumLen +func unmarshalBtfEnum(be *btfEnum, b []byte, bo binary.ByteOrder) (int, error) { + if btfEnumLen > len(b) { + return 0, fmt.Errorf("not enough bytes to unmarshal btfEnum") } - return off, nil + be.NameOff = bo.Uint32(b[0:]) + be.Val = bo.Uint32(b[4:]) + return btfEnumLen, nil } type btfEnum64 struct { @@ -464,21 +485,16 @@ type btfEnum64 struct { var btfEnum64Len = int(unsafe.Sizeof(btfEnum64{})) -func unmarshalBtfEnums64(enums []btfEnum64, b []byte, bo binary.ByteOrder) (int, error) { - off := 0 - for i := range enums { - if off+btfEnum64Len > len(b) { - return 0, fmt.Errorf("not enough bytes to unmarshal btfEnum64 %d", i) - } - - enums[i].NameOff = bo.Uint32(b[off+0:]) - enums[i].ValLo32 = bo.Uint32(b[off+4:]) - enums[i].ValHi32 = bo.Uint32(b[off+8:]) - - off += btfEnum64Len +func unmarshalBtfEnum64(enum *btfEnum64, b []byte, bo binary.ByteOrder) (int, error) { + if len(b) < btfEnum64Len { + return 0, fmt.Errorf("not enough bytes to unmarshal btfEnum64") } - return off, nil + enum.NameOff = bo.Uint32(b[0:]) + enum.ValLo32 = bo.Uint32(b[4:]) + enum.ValHi32 = bo.Uint32(b[8:]) + + return btfEnum64Len, nil } type btfParam struct { @@ -488,20 +504,15 @@ type btfParam struct { var btfParamLen = int(unsafe.Sizeof(btfParam{})) -func unmarshalBtfParams(params []btfParam, b []byte, bo binary.ByteOrder) (int, error) { - off := 0 - for i := range params { - if off+btfParamLen > len(b) { - return 0, fmt.Errorf("not enough bytes to unmarshal btfParam %d", i) - } - - params[i].NameOff = bo.Uint32(b[off+0:]) - params[i].Type = TypeID(bo.Uint32(b[off+4:])) - - off += btfParamLen +func unmarshalBtfParam(param *btfParam, b []byte, bo binary.ByteOrder) (int, error) { + if len(b) < btfParamLen { + return 0, fmt.Errorf("not enough bytes to unmarshal btfParam") } - return off, nil + param.NameOff = bo.Uint32(b[0:]) + param.Type = TypeID(bo.Uint32(b[4:])) + + return btfParamLen, nil } type btfDeclTag struct { diff --git a/vendor/github.com/cilium/ebpf/btf/btf_types_string.go b/vendor/github.com/cilium/ebpf/btf/btf_types_string.go index b7a1b80d15..a9d2d82b66 100644 --- a/vendor/github.com/cilium/ebpf/btf/btf_types_string.go +++ b/vendor/github.com/cilium/ebpf/btf/btf_types_string.go @@ -18,10 +18,11 @@ const _FuncLinkage_name = "staticglobalextern" var _FuncLinkage_index = [...]uint8{0, 6, 12, 18} func (i FuncLinkage) String() string { - if i < 0 || i >= FuncLinkage(len(_FuncLinkage_index)-1) { + idx := int(i) - 0 + if i < 0 || idx >= len(_FuncLinkage_index)-1 { return "FuncLinkage(" + strconv.FormatInt(int64(i), 10) + ")" } - return _FuncLinkage_name[_FuncLinkage_index[i]:_FuncLinkage_index[i+1]] + return _FuncLinkage_name[_FuncLinkage_index[idx]:_FuncLinkage_index[idx+1]] } func _() { // An "invalid array index" compiler error signifies that the constant values have changed. @@ -37,10 +38,11 @@ const _VarLinkage_name = "staticglobalextern" var _VarLinkage_index = [...]uint8{0, 6, 12, 18} func (i VarLinkage) String() string { - if i < 0 || i >= VarLinkage(len(_VarLinkage_index)-1) { + idx := int(i) - 0 + if i < 0 || idx >= len(_VarLinkage_index)-1 { return "VarLinkage(" + strconv.FormatInt(int64(i), 10) + ")" } - return _VarLinkage_name[_VarLinkage_index[i]:_VarLinkage_index[i+1]] + return _VarLinkage_name[_VarLinkage_index[idx]:_VarLinkage_index[idx+1]] } func _() { // An "invalid array index" compiler error signifies that the constant values have changed. @@ -73,8 +75,9 @@ const _btfKind_name = "UnknownIntPointerArrayStructUnionEnumForwardTypedefVolati var _btfKind_index = [...]uint8{0, 7, 10, 17, 22, 28, 33, 37, 44, 51, 59, 64, 72, 76, 85, 88, 95, 100, 107, 114, 120} func (i btfKind) String() string { - if i >= btfKind(len(_btfKind_index)-1) { + idx := int(i) - 0 + if i < 0 || idx >= len(_btfKind_index)-1 { return "btfKind(" + strconv.FormatInt(int64(i), 10) + ")" } - return _btfKind_name[_btfKind_index[i]:_btfKind_index[i+1]] + return _btfKind_name[_btfKind_index[idx]:_btfKind_index[idx+1]] } diff --git a/vendor/github.com/cilium/ebpf/btf/core.go b/vendor/github.com/cilium/ebpf/btf/core.go index ee89f98331..f128011dde 100644 --- a/vendor/github.com/cilium/ebpf/btf/core.go +++ b/vendor/github.com/cilium/ebpf/btf/core.go @@ -6,11 +6,12 @@ import ( "fmt" "math" "reflect" - "slices" "strconv" "strings" "github.com/cilium/ebpf/asm" + "github.com/cilium/ebpf/internal" + "github.com/cilium/ebpf/internal/platform" ) // Code in this file is derived from libbpf, which is available under a BSD @@ -46,6 +47,10 @@ func (f *COREFixup) String() string { } func (f *COREFixup) Apply(ins *asm.Instruction) error { + if !platform.IsLinux { + return fmt.Errorf("CO-RE fixup: %w", internal.ErrNotSupportedOnOS) + } + if f.poison { // Relocation is poisoned, replace the instruction with an invalid one. if ins.OpCode.IsDWordLoad() { @@ -205,8 +210,8 @@ func CORERelocate(relos []*CORERelocation, targets []*Spec, bo binary.ByteOrder, resolveTargetTypeID := targets[0].TypeID for _, target := range targets { - if bo != target.imm.byteOrder { - return nil, fmt.Errorf("can't relocate %s against %s", bo, target.imm.byteOrder) + if bo != target.byteOrder { + return nil, fmt.Errorf("can't relocate %s against %s", bo, target.byteOrder) } } @@ -259,16 +264,14 @@ func CORERelocate(relos []*CORERelocation, targets []*Spec, bo binary.ByteOrder, var targetTypes []Type for _, target := range targets { - namedTypeIDs := target.imm.namedTypes[essentialName] - targetTypes = slices.Grow(targetTypes, len(namedTypeIDs)) - for _, id := range namedTypeIDs { - typ, err := target.TypeByID(id) - if err != nil { - return nil, err - } - - targetTypes = append(targetTypes, typ) + namedTypes, err := target.TypesByName(essentialName) + if errors.Is(err, ErrNotFound) { + continue + } else if err != nil { + return nil, err } + + targetTypes = append(targetTypes, namedTypes...) } fixups, err := coreCalculateFixups(group.relos, targetTypes, bo, resolveTargetTypeID) diff --git a/vendor/github.com/cilium/ebpf/btf/dedup.go b/vendor/github.com/cilium/ebpf/btf/dedup.go new file mode 100644 index 0000000000..783ea09e85 --- /dev/null +++ b/vendor/github.com/cilium/ebpf/btf/dedup.go @@ -0,0 +1,633 @@ +package btf + +import ( + "errors" + "fmt" + "hash/maphash" + "slices" +) + +// deduper deduplicates BTF types by finding all types in a Type graph that are +// Equivalent and replaces them with a single instance. +// +// See doc comments in types.go to understand the various ways in which Types +// can relate to each other and how they are compared for equality. We separate +// Identity (same memory location), Equivalence (same shape/layout), and +// Compatibility (CO-RE compatible) to be explicit about intent. +// +// This deduper opportunistically uses a combination of Identity and Equivalence +// to find types that can be deduplicated. +type deduper struct { + visited map[Type]struct{} + hashCache map[hashCacheKey]uint64 + + // Set of types that have been deduplicated. + done map[Type]Type + + // Map of hash to types with that hash. + hashed map[uint64][]Type + eqCache map[typKey]bool + + seed maphash.Seed +} + +func newDeduper() *deduper { + return &deduper{ + make(map[Type]struct{}), + make(map[hashCacheKey]uint64), + make(map[Type]Type), + make(map[uint64][]Type), + make(map[typKey]bool), + maphash.MakeSeed(), + } +} + +func (d *deduper) deduplicate(t Type) (Type, error) { + // If we have already attempted to deduplicate this exact type, return the + // result. + if done, ok := d.done[t]; ok { + return done, nil + } + + // Visit the subtree, if a type has children, attempt to replace it with a + // deduplicated version of those children. + for t := range postorder(t, d.visited) { + for c := range children(t) { + var err error + *c, err = d.hashInsert(*c) + if err != nil { + return nil, err + } + } + } + + // Finally, deduplicate the root type itself. + return d.hashInsert(t) +} + +// hashInsert attempts to deduplicate t by hashing it and comparing against +// other types with the same hash. Returns the Type to be used as the common +// substitute at this position in the graph. +func (d *deduper) hashInsert(t Type) (Type, error) { + // If we have deduplicated this type before, return the result of that + // deduplication. + if done, ok := d.done[t]; ok { + return done, nil + } + + // Compute the hash of this type. Types with the same hash are candidates for + // deduplication. + hash, err := d.hash(t, -1) + if err != nil { + return nil, err + } + + // A hash collision is possible, so we need to compare against all candidates + // with the same hash. + for _, candidate := range d.hashed[hash] { + // Pre-size the visited slice, experimentation on VMLinux shows a capacity + // of 16 to give the best performance. + const visitedCapacity = 16 + err := d.typesEquivalent(candidate, t, make([]Type, 0, visitedCapacity)) + if errors.Is(err, errNotEquivalent) { + continue + } + if err != nil { + return nil, err + } + + // Found a Type that's both Equivalent and hashes to the same value, choose + // it as the deduplicated version. + d.done[t] = candidate + + return candidate, nil + } + + d.hashed[hash] = append(d.hashed[hash], t) + + return t, nil +} + +// The hash of a Type is the same given its pointer and depth budget. +type hashCacheKey struct { + t Type + depthBudget int +} + +// hash computes a hash for t. The produced hash is the same for Types which +// are similar. The hash can collide such that two different Types may produce +// the same hash, so equivalence must be checked explicitly. It will recurse +// into children. The initial call should use a depthBudget of -1. +func (d *deduper) hash(t Type, depthBudget int) (uint64, error) { + if depthBudget == 0 { + return 0, nil + } + + h := &maphash.Hash{} + h.SetSeed(d.seed) + + switch t := t.(type) { + case *Void: + maphash.WriteComparable(h, kindUnknown) + + case *Int: + maphash.WriteComparable(h, kindInt) + maphash.WriteComparable(h, *t) + + case *Pointer: + maphash.WriteComparable(h, kindPointer) + // If the depth budget is positive, decrement it every time we follow a + // pointer. + if depthBudget > 0 { + depthBudget-- + } + + // If this is the first time we are following a pointer, set the depth + // budget. This limits amount of recursion we do when hashing pointers that + // form cycles. This is cheaper than tracking visited types and works + // because hash collisions are allowed. + if depthBudget < 0 { + depthBudget = 1 + + // Double pointers are common in C. However, with a depth budget of 1, all + // double pointers would hash the same, causing a performance issue when + // checking equivalence. So we give double pointers a bit more budget. + if _, ok := t.Target.(*Pointer); ok { + depthBudget = 2 + } + } + sub, err := d.hash(t.Target, depthBudget) + if err != nil { + return 0, err + } + maphash.WriteComparable(h, sub) + + case *Array: + maphash.WriteComparable(h, kindArray) + maphash.WriteComparable(h, t.Nelems) + sub, err := d.hash(t.Index, depthBudget) + if err != nil { + return 0, err + } + maphash.WriteComparable(h, sub) + _, err = d.hash(t.Type, depthBudget) + if err != nil { + return 0, err + } + maphash.WriteComparable(h, sub) + + case *Struct, *Union: + // Check the cache to avoid recomputing the hash for this type and depth + // budget. + key := hashCacheKey{t, depthBudget} + if cached, ok := d.hashCache[key]; ok { + return cached, nil + } + + var members []Member + switch t := t.(type) { + case *Struct: + maphash.WriteComparable(h, kindStruct) + maphash.WriteComparable(h, t.Name) + maphash.WriteComparable(h, t.Size) + members = t.Members + + case *Union: + maphash.WriteComparable(h, kindUnion) + maphash.WriteComparable(h, t.Name) + maphash.WriteComparable(h, t.Size) + members = t.Members + } + + maphash.WriteComparable(h, len(members)) + for _, m := range members { + maphash.WriteComparable(h, m.Name) + maphash.WriteComparable(h, m.Offset) + sub, err := d.hash(m.Type, depthBudget) + if err != nil { + return 0, err + } + maphash.WriteComparable(h, sub) + } + + sum := h.Sum64() + d.hashCache[key] = sum + return sum, nil + + case *Enum: + maphash.WriteComparable(h, kindEnum) + maphash.WriteComparable(h, t.Name) + maphash.WriteComparable(h, t.Size) + maphash.WriteComparable(h, t.Signed) + for _, v := range t.Values { + maphash.WriteComparable(h, v) + } + + case *Fwd: + maphash.WriteComparable(h, kindForward) + maphash.WriteComparable(h, *t) + + case *Typedef: + maphash.WriteComparable(h, kindTypedef) + maphash.WriteComparable(h, t.Name) + sub, err := d.hash(t.Type, depthBudget) + if err != nil { + return 0, err + } + maphash.WriteComparable(h, sub) + + case *Volatile: + maphash.WriteComparable(h, kindVolatile) + sub, err := d.hash(t.Type, depthBudget) + if err != nil { + return 0, err + } + maphash.WriteComparable(h, sub) + + case *Const: + maphash.WriteComparable(h, kindConst) + sub, err := d.hash(t.Type, depthBudget) + if err != nil { + return 0, err + } + maphash.WriteComparable(h, sub) + + case *Restrict: + maphash.WriteComparable(h, kindRestrict) + sub, err := d.hash(t.Type, depthBudget) + if err != nil { + return 0, err + } + maphash.WriteComparable(h, sub) + + case *Func: + maphash.WriteComparable(h, kindFunc) + maphash.WriteComparable(h, t.Name) + sub, err := d.hash(t.Type, depthBudget) + if err != nil { + return 0, err + } + maphash.WriteComparable(h, sub) + + case *FuncProto: + // It turns out that pointers to function prototypes are common in C code, + // function pointers. Function prototypes frequently have similar patterns + // of [ptr, ptr] -> int, or [ptr, ptr, ptr] -> int. Causing frequent hash + // collisions, for the default depth budget of 1. So allow one additional + // level of pointers when we encounter a function prototype. + if depthBudget >= 0 { + depthBudget++ + } + + maphash.WriteComparable(h, kindFuncProto) + for _, p := range t.Params { + maphash.WriteComparable(h, p.Name) + sub, err := d.hash(p.Type, depthBudget) + if err != nil { + return 0, err + } + maphash.WriteComparable(h, sub) + } + sub, err := d.hash(t.Return, depthBudget) + if err != nil { + return 0, err + } + maphash.WriteComparable(h, sub) + + case *Var: + maphash.WriteComparable(h, kindVar) + maphash.WriteComparable(h, t.Name) + maphash.WriteComparable(h, t.Linkage) + sub, err := d.hash(t.Type, depthBudget) + if err != nil { + return 0, err + } + maphash.WriteComparable(h, sub) + + case *Datasec: + maphash.WriteComparable(h, kindDatasec) + maphash.WriteComparable(h, t.Name) + for _, v := range t.Vars { + maphash.WriteComparable(h, v.Offset) + maphash.WriteComparable(h, v.Size) + sub, err := d.hash(v.Type, depthBudget) + if err != nil { + return 0, err + } + maphash.WriteComparable(h, sub) + } + + case *declTag: + maphash.WriteComparable(h, kindDeclTag) + maphash.WriteComparable(h, t.Value) + maphash.WriteComparable(h, t.Index) + sub, err := d.hash(t.Type, depthBudget) + if err != nil { + return 0, err + } + maphash.WriteComparable(h, sub) + + case *TypeTag: + maphash.WriteComparable(h, kindTypeTag) + maphash.WriteComparable(h, t.Value) + sub, err := d.hash(t.Type, depthBudget) + if err != nil { + return 0, err + } + maphash.WriteComparable(h, sub) + + case *Float: + maphash.WriteComparable(h, kindFloat) + maphash.WriteComparable(h, *t) + + default: + return 0, fmt.Errorf("unsupported type for hashing: %T", t) + } + + return h.Sum64(), nil +} + +type typKey struct { + a Type + b Type +} + +var errNotEquivalent = errors.New("types are not equivalent") + +// typesEquivalent checks if two types are Equivalent. +func (d *deduper) typesEquivalent(ta, tb Type, visited []Type) error { + // Fast path: if Types are Identical, they are also Equivalent. + if ta == tb { + return nil + } + + switch a := ta.(type) { + case *Void: + if _, ok := tb.(*Void); ok { + return nil + } + return errNotEquivalent + + case *Int: + b, ok := tb.(*Int) + if !ok { + return errNotEquivalent + } + if a.Name != b.Name || a.Size != b.Size || a.Encoding != b.Encoding { + return errNotEquivalent + } + return nil + + case *Enum: + b, ok := tb.(*Enum) + if !ok { + return errNotEquivalent + } + if a.Name != b.Name || len(a.Values) != len(b.Values) { + return errNotEquivalent + } + for i := range a.Values { + if a.Values[i].Name != b.Values[i].Name || a.Values[i].Value != b.Values[i].Value { + return errNotEquivalent + } + } + return nil + + case *Fwd: + b, ok := tb.(*Fwd) + if !ok { + return errNotEquivalent + } + if a.Name != b.Name || a.Kind != b.Kind { + return errNotEquivalent + } + return nil + + case *Float: + b, ok := tb.(*Float) + if !ok { + return errNotEquivalent + } + if a.Name != b.Name || a.Size != b.Size { + return errNotEquivalent + } + return nil + + case *Array: + b, ok := tb.(*Array) + if !ok { + return errNotEquivalent + } + + if a.Nelems != b.Nelems { + return errNotEquivalent + } + if err := d.typesEquivalent(a.Index, b.Index, visited); err != nil { + return err + } + if err := d.typesEquivalent(a.Type, b.Type, visited); err != nil { + return err + } + return nil + + case *Pointer: + b, ok := tb.(*Pointer) + if !ok { + return errNotEquivalent + } + + // Detect cycles by tracking visited types. Assume types are Equivalent if + // we have already visited this type in the current Equivalence check. + if slices.Contains(visited, ta) { + return nil + } + visited = append(visited, ta) + + return d.typesEquivalent(a.Target, b.Target, visited) + + case *Struct, *Union: + // Use a cache to avoid recomputation. We only do this for composite types + // since they are where types fan out the most. For other types, the + // overhead of the lookup and update outweighs performance benefits. + cacheKey := typKey{a: ta, b: tb} + if equal, ok := d.eqCache[cacheKey]; ok { + if equal { + return nil + } + return errNotEquivalent + } + + compErr := d.compositeEquivalent(ta, tb, visited) + d.eqCache[cacheKey] = compErr == nil + + return compErr + + case *Typedef: + b, ok := tb.(*Typedef) + if !ok { + return errNotEquivalent + } + if a.Name != b.Name { + return errNotEquivalent + } + return d.typesEquivalent(a.Type, b.Type, visited) + + case *Volatile: + b, ok := tb.(*Volatile) + if !ok { + return errNotEquivalent + } + return d.typesEquivalent(a.Type, b.Type, visited) + + case *Const: + b, ok := tb.(*Const) + if !ok { + return errNotEquivalent + } + return d.typesEquivalent(a.Type, b.Type, visited) + + case *Restrict: + b, ok := tb.(*Restrict) + if !ok { + return errNotEquivalent + } + return d.typesEquivalent(a.Type, b.Type, visited) + + case *Func: + b, ok := tb.(*Func) + if !ok { + return errNotEquivalent + } + if a.Name != b.Name { + return errNotEquivalent + } + return d.typesEquivalent(a.Type, b.Type, visited) + + case *FuncProto: + b, ok := tb.(*FuncProto) + if !ok { + return errNotEquivalent + } + + if err := d.typesEquivalent(a.Return, b.Return, visited); err != nil { + return err + } + if len(a.Params) != len(b.Params) { + return errNotEquivalent + } + for i := range a.Params { + if a.Params[i].Name != b.Params[i].Name { + return errNotEquivalent + } + if err := d.typesEquivalent(a.Params[i].Type, b.Params[i].Type, visited); err != nil { + return err + } + } + return nil + + case *Var: + b, ok := tb.(*Var) + if !ok { + return errNotEquivalent + } + if a.Name != b.Name { + return errNotEquivalent + } + if err := d.typesEquivalent(a.Type, b.Type, visited); err != nil { + return err + } + if a.Linkage != b.Linkage { + return errNotEquivalent + } + return nil + + case *Datasec: + b, ok := tb.(*Datasec) + if !ok { + return errNotEquivalent + } + if a.Name != b.Name || len(a.Vars) != len(b.Vars) { + return errNotEquivalent + } + for i := range a.Vars { + if a.Vars[i].Offset != b.Vars[i].Offset || + a.Vars[i].Size != b.Vars[i].Size { + return errNotEquivalent + } + + if err := d.typesEquivalent(a.Vars[i].Type, b.Vars[i].Type, visited); err != nil { + return err + } + } + return nil + + case *declTag: + b, ok := tb.(*declTag) + if !ok { + return errNotEquivalent + } + if a.Value != b.Value || a.Index != b.Index { + return errNotEquivalent + } + return d.typesEquivalent(a.Type, b.Type, visited) + + case *TypeTag: + b, ok := tb.(*TypeTag) + if !ok { + return errNotEquivalent + } + if a.Value != b.Value { + return errNotEquivalent + } + if err := d.typesEquivalent(a.Type, b.Type, visited); err != nil { + return err + } + return nil + + default: + return fmt.Errorf("unsupported type for equivalence: %T", a) + } +} + +// compositeEquivalent checks if two composite types (Struct or Union) are +// Equivalent. +func (d *deduper) compositeEquivalent(at, bt Type, visited []Type) error { + var ma, mb []Member + switch a := at.(type) { + case *Struct: + b, ok := bt.(*Struct) + if !ok { + return errNotEquivalent + } + + if a.Name != b.Name || a.Size != b.Size || len(a.Members) != len(b.Members) { + return errNotEquivalent + } + ma = a.Members + mb = b.Members + + case *Union: + b, ok := bt.(*Union) + if !ok { + return errNotEquivalent + } + + if a.Name != b.Name || a.Size != b.Size || len(a.Members) != len(b.Members) { + return errNotEquivalent + } + ma = a.Members + mb = b.Members + } + + for i := range ma { + if ma[i].Name != mb[i].Name || ma[i].Offset != mb[i].Offset { + return errNotEquivalent + } + + if err := d.typesEquivalent(ma[i].Type, mb[i].Type, visited); err != nil { + return err + } + } + + return nil +} diff --git a/vendor/github.com/cilium/ebpf/btf/ext_info.go b/vendor/github.com/cilium/ebpf/btf/ext_info.go index 71dbba8061..2e2aa629f1 100644 --- a/vendor/github.com/cilium/ebpf/btf/ext_info.go +++ b/vendor/github.com/cilium/ebpf/btf/ext_info.go @@ -13,12 +13,23 @@ import ( "github.com/cilium/ebpf/internal" ) -// ExtInfos contains ELF section metadata. +// ExtInfos contains raw, per-section extended BTF metadata from the .BTF.ext +// ELF section. type ExtInfos struct { - // The slices are sorted by offset in ascending order. - funcInfos map[string]FuncOffsets - lineInfos map[string]LineOffsets - relocationInfos map[string]CORERelocationInfos + Funcs map[string]FuncOffsets + Lines map[string]LineOffsets + CORERelos map[string]CORERelocationOffsets +} + +// Section returns the FuncOffsets, LineOffsets and CORERelocationOffsets for +// the given section name. Returns all nils if ExtInfos is nil, or individual +// nils if there is no metadata of that type for the section. +func (ei *ExtInfos) Section(name string) (FuncOffsets, LineOffsets, CORERelocationOffsets) { + if ei == nil { + return nil, nil, nil + } + + return ei.Funcs[name], ei.Lines[name], ei.CORERelos[name] } // loadExtInfosFromELF parses ext infos from the .BTF.ext section in an ELF. @@ -91,7 +102,7 @@ func loadExtInfos(r io.ReaderAt, bo binary.ByteOrder, spec *Spec) (*ExtInfos, er return nil, fmt.Errorf("parsing CO-RE relocation info: %w", err) } - coreRelos := make(map[string]CORERelocationInfos, len(btfCORERelos)) + coreRelos := make(map[string]CORERelocationOffsets, len(btfCORERelos)) for section, brs := range btfCORERelos { coreRelos[section], err = newRelocationInfos(brs, spec, spec.strings) if err != nil { @@ -102,46 +113,6 @@ func loadExtInfos(r io.ReaderAt, bo binary.ByteOrder, spec *Spec) (*ExtInfos, er return &ExtInfos{funcInfos, lineInfos, coreRelos}, nil } -type ( - funcInfoMeta struct{} - coreRelocationMeta struct{} -) - -// Assign per-section metadata from BTF to a section's instructions. -func (ei *ExtInfos) Assign(insns asm.Instructions, section string) { - funcInfos := ei.funcInfos[section] - lineInfos := ei.lineInfos[section] - reloInfos := ei.relocationInfos[section] - - AssignMetadataToInstructions(insns, funcInfos, lineInfos, reloInfos) -} - -// Assign per-instruction metadata to the instructions in insns. -func AssignMetadataToInstructions( - insns asm.Instructions, - funcInfos FuncOffsets, - lineInfos LineOffsets, - reloInfos CORERelocationInfos, -) { - iter := insns.Iterate() - for iter.Next() { - if len(funcInfos) > 0 && funcInfos[0].Offset == iter.Offset { - *iter.Ins = WithFuncMetadata(*iter.Ins, funcInfos[0].Func) - funcInfos = funcInfos[1:] - } - - if len(lineInfos) > 0 && lineInfos[0].Offset == iter.Offset { - *iter.Ins = iter.Ins.WithSource(lineInfos[0].Line) - lineInfos = lineInfos[1:] - } - - if len(reloInfos.infos) > 0 && reloInfos.infos[0].offset == iter.Offset { - iter.Ins.Metadata.Set(coreRelocationMeta{}, reloInfos.infos[0].relo) - reloInfos.infos = reloInfos.infos[1:] - } - } -} - // MarshalExtInfos encodes function and line info embedded in insns into kernel // wire format. // @@ -335,8 +306,8 @@ func parseExtInfoRecordSize(r io.Reader, bo binary.ByteOrder) (uint32, error) { return recordSize, nil } -// FuncOffsets is a sorted slice of FuncOffset. -type FuncOffsets []FuncOffset +// FuncOffsets is a slice of FuncOffsets sorted by offset. +type FuncOffsets = []FuncOffset // The size of a FuncInfo in BTF wire format. var FuncInfoSize = uint32(binary.Size(bpfFuncInfo{})) @@ -466,7 +437,7 @@ func parseFuncInfoRecords(r io.Reader, bo binary.ByteOrder, recordSize uint32, r return nil, fmt.Errorf("expected FuncInfo record size %d, but BTF blob contains %d", exp, got) } - for i := uint32(0); i < recordNum; i++ { + for range recordNum { if err := binary.Read(r, bo, &fi); err != nil { return nil, fmt.Errorf("can't read function info: %v", err) } @@ -518,8 +489,8 @@ func (li *Line) String() string { return li.line } -// LineOffsets contains a sorted list of line infos. -type LineOffsets []LineOffset +// LineOffsets is a slice of LineOffsets sorted by offset. +type LineOffsets = []LineOffset // LineOffset represents a line info and its raw instruction offset. type LineOffset struct { @@ -559,12 +530,12 @@ func LoadLineInfos(reader io.Reader, bo binary.ByteOrder, recordNum uint32, spec } func newLineInfo(li bpfLineInfo, strings *stringTable) (LineOffset, error) { - line, err := strings.Lookup(li.LineOff) + line, err := strings.LookupCached(li.LineOff) if err != nil { return LineOffset{}, fmt.Errorf("lookup of line: %w", err) } - fileName, err := strings.Lookup(li.FileNameOff) + fileName, err := strings.LookupCached(li.FileNameOff) if err != nil { return LineOffset{}, fmt.Errorf("lookup of filename: %w", err) } @@ -671,9 +642,15 @@ func parseLineInfoRecords(r io.Reader, bo binary.ByteOrder, recordSize uint32, r return nil, fmt.Errorf("expected LineInfo record size %d, but BTF blob contains %d", exp, got) } - out := make([]bpfLineInfo, recordNum) - if err := binary.Read(r, bo, out); err != nil { - return nil, fmt.Errorf("can't read line info: %v", err) + out := make([]bpfLineInfo, 0) + chunk := make([]bpfLineInfo, min(1024, recordNum)) + for remaining := recordNum; remaining > 0; { + n := min(uint32(1024), remaining) + if err := binary.Read(r, bo, chunk[:n]); err != nil { + return nil, fmt.Errorf("can't read line info: %v", err) + } + out = append(out, chunk[:n]...) + remaining -= n } if offsetInBytes { @@ -713,22 +690,32 @@ func (cr *CORERelocation) String() string { return fmt.Sprintf("CORERelocation(%s, %s[%s], local_id=%d)", cr.kind, cr.typ, cr.accessor, cr.id) } +type coreRelocationMeta struct{} + +// CORERelocationMetadata returns the CORERelocation associated with ins. func CORERelocationMetadata(ins *asm.Instruction) *CORERelocation { relo, _ := ins.Metadata.Get(coreRelocationMeta{}).(*CORERelocation) return relo } -// CORERelocationInfos contains a sorted list of co:re relocation infos. -type CORERelocationInfos struct { - infos []coreRelocationInfo +// WithCORERelocationMetadata associates a CORERelocation with ins and returns +// the modified Instruction. +func WithCORERelocationMetadata(ins asm.Instruction, relo *CORERelocation) asm.Instruction { + ins.Metadata.Set(coreRelocationMeta{}, relo) + return ins } -type coreRelocationInfo struct { - relo *CORERelocation - offset asm.RawInstructionOffset +// CORERelocationOffsets is a slice of CORERelocationOffsets sorted by offset. +type CORERelocationOffsets = []CORERelocationOffset + +// CORERelocationOffset represents a CO-RE relocation and an offset at which it +// should be applied. +type CORERelocationOffset struct { + Relo *CORERelocation + Offset asm.RawInstructionOffset } -func newRelocationInfo(relo bpfCORERelo, spec *Spec, strings *stringTable) (*coreRelocationInfo, error) { +func newRelocationInfo(relo bpfCORERelo, spec *Spec, strings *stringTable) (*CORERelocationOffset, error) { typ, err := spec.TypeByID(relo.TypeID) if err != nil { return nil, err @@ -744,7 +731,7 @@ func newRelocationInfo(relo bpfCORERelo, spec *Spec, strings *stringTable) (*cor return nil, fmt.Errorf("accessor %q: %s", accessorStr, err) } - return &coreRelocationInfo{ + return &CORERelocationOffset{ &CORERelocation{ typ, accessor, @@ -755,20 +742,21 @@ func newRelocationInfo(relo bpfCORERelo, spec *Spec, strings *stringTable) (*cor }, nil } -func newRelocationInfos(brs []bpfCORERelo, spec *Spec, strings *stringTable) (CORERelocationInfos, error) { - rs := CORERelocationInfos{ - infos: make([]coreRelocationInfo, 0, len(brs)), - } +func newRelocationInfos(brs []bpfCORERelo, spec *Spec, strings *stringTable) (CORERelocationOffsets, error) { + rs := make(CORERelocationOffsets, 0, len(brs)) + for _, br := range brs { relo, err := newRelocationInfo(br, spec, strings) if err != nil { - return CORERelocationInfos{}, fmt.Errorf("offset %d: %w", br.InsnOff, err) + return nil, fmt.Errorf("offset %d: %w", br.InsnOff, err) } - rs.infos = append(rs.infos, *relo) + rs = append(rs, *relo) } - sort.Slice(rs.infos, func(i, j int) bool { - return rs.infos[i].offset < rs.infos[j].offset + + sort.Slice(rs, func(i, j int) bool { + return rs[i].Offset < rs[j].Offset }) + return rs, nil } @@ -812,7 +800,7 @@ func parseCOREReloRecords(r io.Reader, bo binary.ByteOrder, recordNum uint32) ([ var out []bpfCORERelo var relo bpfCORERelo - for i := uint32(0); i < recordNum; i++ { + for range recordNum { if err := binary.Read(r, bo, &relo); err != nil { return nil, fmt.Errorf("can't read CO-RE relocation: %v", err) } diff --git a/vendor/github.com/cilium/ebpf/btf/feature.go b/vendor/github.com/cilium/ebpf/btf/feature.go index e71c707fe4..df365d42f5 100644 --- a/vendor/github.com/cilium/ebpf/btf/feature.go +++ b/vendor/github.com/cilium/ebpf/btf/feature.go @@ -135,7 +135,7 @@ var haveEnum64 = internal.NewFeatureTest("ENUM64", func() error { }, "6.0") func probeBTF(typ Type) error { - b, err := NewBuilder([]Type{typ}) + b, err := NewBuilder([]Type{typ}, nil) if err != nil { return err } @@ -146,7 +146,7 @@ func probeBTF(typ Type) error { } fd, err := sys.BtfLoad(&sys.BtfLoadAttr{ - Btf: sys.NewSlicePointer(buf), + Btf: sys.SlicePointer(buf), BtfSize: uint32(len(buf)), }) diff --git a/vendor/github.com/cilium/ebpf/btf/format.go b/vendor/github.com/cilium/ebpf/btf/format.go index 3e0dedaa2b..7deca334ae 100644 --- a/vendor/github.com/cilium/ebpf/btf/format.go +++ b/vendor/github.com/cilium/ebpf/btf/format.go @@ -56,7 +56,7 @@ func (gf *GoFormatter) enumIdentifier(name, element string) string { // // It encodes https://golang.org/ref/spec#Type_declarations: // -// type foo struct { bar uint32; } +// type foo struct { _ structs.HostLayout; bar uint32; } // type bar int32 func (gf *GoFormatter) writeTypeDecl(name string, typ Type) error { if name == "" { @@ -114,7 +114,7 @@ func (gf *GoFormatter) writeType(typ Type, depth int) error { // // It encodes https://golang.org/ref/spec#TypeLit. // -// struct { bar uint32; } +// struct { _ structs.HostLayout; bar uint32; } // uint32 func (gf *GoFormatter) writeTypeLit(typ Type, depth int) error { depth++ @@ -208,7 +208,7 @@ func (gf *GoFormatter) writeIntLit(i *Int) error { } func (gf *GoFormatter) writeStructLit(size uint32, members []Member, depth int) error { - gf.w.WriteString("struct { ") + gf.w.WriteString("struct { _ structs.HostLayout; ") prevOffset := uint32(0) skippedBitfield := false @@ -298,7 +298,7 @@ func (gf *GoFormatter) writeStructField(m Member, depth int) error { } func (gf *GoFormatter) writeDatasecLit(ds *Datasec, depth int) error { - gf.w.WriteString("struct { ") + gf.w.WriteString("struct { _ structs.HostLayout; ") prevOffset := uint32(0) for i, vsi := range ds.Vars { diff --git a/vendor/github.com/cilium/ebpf/btf/handle.go b/vendor/github.com/cilium/ebpf/btf/handle.go index adfa6fed4b..6b432f5aec 100644 --- a/vendor/github.com/cilium/ebpf/btf/handle.go +++ b/vendor/github.com/cilium/ebpf/btf/handle.go @@ -1,13 +1,13 @@ package btf import ( - "bytes" "errors" "fmt" "math" "os" "github.com/cilium/ebpf/internal" + "github.com/cilium/ebpf/internal/platform" "github.com/cilium/ebpf/internal/sys" "github.com/cilium/ebpf/internal/unix" ) @@ -43,12 +43,16 @@ func NewHandle(b *Builder) (*Handle, error) { func NewHandleFromRawBTF(btf []byte) (*Handle, error) { const minLogSize = 64 * 1024 + if platform.IsWindows { + return nil, fmt.Errorf("btf: handle: %w", internal.ErrNotSupportedOnOS) + } + if uint64(len(btf)) > math.MaxUint32 { return nil, errors.New("BTF exceeds the maximum size") } attr := &sys.BtfLoadAttr{ - Btf: sys.NewSlicePointer(btf), + Btf: sys.SlicePointer(btf), BtfSize: uint32(len(btf)), } @@ -91,10 +95,14 @@ func NewHandleFromRawBTF(btf []byte) (*Handle, error) { logBuf = make([]byte, logSize) attr.BtfLogSize = logSize - attr.BtfLogBuf = sys.NewSlicePointer(logBuf) + attr.BtfLogBuf = sys.SlicePointer(logBuf) attr.BtfLogLevel = 1 } + if errors.Is(err, sys.ErrTokenCapabilities) { + return nil, fmt.Errorf("load btf: %w", err) + } + if err := haveBTF(); err != nil { return nil, err } @@ -110,6 +118,10 @@ func NewHandleFromRawBTF(btf []byte) (*Handle, error) { // // Requires CAP_SYS_ADMIN. func NewHandleFromID(id ID) (*Handle, error) { + if platform.IsWindows { + return nil, fmt.Errorf("btf: handle: %w", internal.ErrNotSupportedOnOS) + } + fd, err := sys.BtfGetFdById(&sys.BtfGetFdByIdAttr{ Id: uint32(id), }) @@ -133,7 +145,8 @@ func NewHandleFromID(id ID) (*Handle, error) { func (h *Handle) Spec(base *Spec) (*Spec, error) { var btfInfo sys.BtfInfo btfBuffer := make([]byte, h.size) - btfInfo.Btf, btfInfo.BtfSize = sys.NewSlicePointerLen(btfBuffer) + btfInfo.Btf = sys.SlicePointer(btfBuffer) + btfInfo.BtfSize = uint32(len(btfBuffer)) if err := sys.ObjInfo(h.fd, &btfInfo); err != nil { return nil, err @@ -143,7 +156,7 @@ func (h *Handle) Spec(base *Spec) (*Spec, error) { return nil, fmt.Errorf("missing base types") } - return loadRawSpec(bytes.NewReader(btfBuffer), internal.NativeEndian, base) + return loadRawSpec(btfBuffer, base) } // Close destroys the handle. @@ -162,6 +175,24 @@ func (h *Handle) FD() int { return h.fd.Int() } +// Clone creates a duplicate of the handle. +// +// Closing the duplicate does not affect the original, and vice versa. +// +// Cloning a nil Handle returns nil. +func (h *Handle) Clone() (*Handle, error) { + if h == nil { + return nil, nil + } + + dup, err := h.fd.Dup() + if err != nil { + return nil, fmt.Errorf("can't clone handle: %w", err) + } + + return &Handle{dup, h.size, h.needsKernelBase}, nil +} + // Info returns metadata about the handle. func (h *Handle) Info() (*HandleInfo, error) { return newHandleInfoFromFD(h.fd) @@ -204,7 +235,8 @@ func newHandleInfoFromFD(fd *sys.FD) (*HandleInfo, error) { btfInfo.BtfSize = 0 nameBuffer := make([]byte, btfInfo.NameLen) - btfInfo.Name, btfInfo.NameLen = sys.NewSlicePointerLen(nameBuffer) + btfInfo.Name = sys.SlicePointer(nameBuffer) + btfInfo.NameLen = uint32(len(nameBuffer)) if err := sys.ObjInfo(fd, &btfInfo); err != nil { return nil, err } @@ -242,6 +274,11 @@ type HandleIterator struct { // Returns true if another BTF object was found. Call [HandleIterator.Err] after // the function returns false. func (it *HandleIterator) Next() bool { + if platform.IsWindows { + it.err = fmt.Errorf("btf: %w", internal.ErrNotSupportedOnOS) + return false + } + id := it.ID for { attr := &sys.BtfGetNextIdAttr{Id: id} diff --git a/vendor/github.com/cilium/ebpf/btf/kernel.go b/vendor/github.com/cilium/ebpf/btf/kernel.go index 1a9321f054..8e9084b51e 100644 --- a/vendor/github.com/cilium/ebpf/btf/kernel.go +++ b/vendor/github.com/cilium/ebpf/btf/kernel.go @@ -5,113 +5,90 @@ import ( "fmt" "os" "path/filepath" + "runtime" + "slices" + "sort" "sync" "github.com/cilium/ebpf/internal" "github.com/cilium/ebpf/internal/linux" + "github.com/cilium/ebpf/internal/platform" + "github.com/cilium/ebpf/internal/unix" ) -var kernelBTF = struct { - sync.RWMutex - kernel *Spec - modules map[string]*Spec -}{ - modules: make(map[string]*Spec), -} - -// FlushKernelSpec removes any cached kernel type information. -func FlushKernelSpec() { - kernelBTF.Lock() - defer kernelBTF.Unlock() - - kernelBTF.kernel = nil - kernelBTF.modules = make(map[string]*Spec) -} - // LoadKernelSpec returns the current kernel's BTF information. // -// Defaults to /sys/kernel/btf/vmlinux and falls back to scanning the file system -// for vmlinux ELFs. Returns an error wrapping ErrNotSupported if BTF is not enabled. +// Defaults to /sys/kernel/btf/vmlinux and falls back to scanning the file +// system for vmlinux ELFs. Returns an error wrapping [ErrNotSupported] if BTF +// is not enabled. +// +// Consider using [Cache] instead. func LoadKernelSpec() (*Spec, error) { - kernelBTF.RLock() - spec := kernelBTF.kernel - kernelBTF.RUnlock() + if platform.IsWindows { + return nil, internal.ErrNotSupportedOnOS + } - if spec == nil { - kernelBTF.Lock() - defer kernelBTF.Unlock() + fh, err := os.Open("/sys/kernel/btf/vmlinux") + if err == nil { + defer fh.Close() - spec = kernelBTF.kernel - } + info, err := fh.Stat() + if err != nil { + return nil, fmt.Errorf("stat vmlinux: %w", err) + } - if spec != nil { - return spec.Copy(), nil + // NB: It's not safe to mmap arbitrary files because mmap(2) doesn't + // guarantee that changes made after mmap are not visible in the mapping. + // + // This is not a problem for vmlinux, since it is always a read-only file. + raw, err := unix.Mmap(int(fh.Fd()), 0, int(info.Size()), unix.PROT_READ, unix.MAP_PRIVATE) + if err != nil { + return LoadSplitSpecFromReader(fh, nil) + } + + spec, err := loadRawSpec(raw, nil) + if err != nil { + _ = unix.Munmap(raw) + return nil, fmt.Errorf("load vmlinux: %w", err) + } + + runtime.AddCleanup(spec.decoder.sharedBuf, func(b []byte) { + _ = unix.Munmap(b) + }, raw) + + return spec, nil } - spec, _, err := loadKernelSpec() + file, err := findVMLinux() if err != nil { return nil, err } + defer file.Close() - kernelBTF.kernel = spec - return spec.Copy(), nil + spec, err := LoadSpecFromReader(file) + return spec, err } // LoadKernelModuleSpec returns the BTF information for the named kernel module. // +// Using [Cache.Module] is faster when loading BTF for more than one module. +// // Defaults to /sys/kernel/btf/. // Returns an error wrapping ErrNotSupported if BTF is not enabled. // Returns an error wrapping fs.ErrNotExist if BTF for the specific module doesn't exist. func LoadKernelModuleSpec(module string) (*Spec, error) { - kernelBTF.RLock() - spec := kernelBTF.modules[module] - kernelBTF.RUnlock() - - if spec != nil { - return spec.Copy(), nil - } - base, err := LoadKernelSpec() - if err != nil { - return nil, fmt.Errorf("load kernel spec: %w", err) - } - - kernelBTF.Lock() - defer kernelBTF.Unlock() - - if spec = kernelBTF.modules[module]; spec != nil { - return spec.Copy(), nil - } - - spec, err = loadKernelModuleSpec(module, base) if err != nil { return nil, err } - - kernelBTF.modules[module] = spec - return spec.Copy(), nil + return loadKernelModuleSpec(module, base) } -func loadKernelSpec() (_ *Spec, fallback bool, _ error) { - fh, err := os.Open("/sys/kernel/btf/vmlinux") - if err == nil { - defer fh.Close() - - spec, err := loadRawSpec(fh, internal.NativeEndian, nil) - return spec, false, err - } - - file, err := findVMLinux() - if err != nil { - return nil, false, err +func loadKernelModuleSpec(module string, base *Spec) (*Spec, error) { + if platform.IsWindows { + return nil, internal.ErrNotSupportedOnOS } - defer file.Close() - - spec, err := LoadSpecFromReader(file) - return spec, true, err -} -func loadKernelModuleSpec(module string, base *Spec) (*Spec, error) { dir, file := filepath.Split(module) if dir != "" || filepath.Ext(file) != "" { return nil, fmt.Errorf("invalid module name %q", module) @@ -123,11 +100,15 @@ func loadKernelModuleSpec(module string, base *Spec) (*Spec, error) { } defer fh.Close() - return loadRawSpec(fh, internal.NativeEndian, base) + return LoadSplitSpecFromReader(fh, base) } // findVMLinux scans multiple well-known paths for vmlinux kernel images. func findVMLinux() (*os.File, error) { + if platform.IsWindows { + return nil, fmt.Errorf("find vmlinux: %w", internal.ErrNotSupportedOnOS) + } + release, err := linux.KernelRelease() if err != nil { return nil, err @@ -155,3 +136,123 @@ func findVMLinux() (*os.File, error) { return nil, fmt.Errorf("no BTF found for kernel version %s: %w", release, internal.ErrNotSupported) } + +// Cache allows to amortise the cost of decoding BTF across multiple call-sites. +// +// It is safe for concurrent use. +type Cache struct { + mu sync.RWMutex + kernelTypes *Spec + moduleTypes map[string]*Spec + loadedModules []string +} + +// NewCache returns an empty Cache. +// +// Pass it to [CollectionOptions] to share kernel BTF across multiple +// Collection loads. +func NewCache() *Cache { + return &Cache{} +} + +// Kernel is equivalent to [LoadKernelSpec], except that repeated calls do +// not copy the Spec. +// +// Returns an error wrapping [ErrNotSupported] if BTF is not enabled. +func (c *Cache) Kernel() (*Spec, error) { + c.mu.RLock() + if c.kernelTypes != nil { + spec := c.kernelTypes + c.mu.RUnlock() + return spec, nil + } + c.mu.RUnlock() + + c.mu.Lock() + defer c.mu.Unlock() + return c.kernel() +} + +func (c *Cache) kernel() (*Spec, error) { + if c.kernelTypes != nil { + return c.kernelTypes, nil + } + + var err error + c.kernelTypes, err = LoadKernelSpec() + return c.kernelTypes, err +} + +// Module is equivalent to [LoadKernelModuleSpec], except that repeated calls do +// not copy the spec. +// +// All modules also share the return value of [Kernel] as their base. +func (c *Cache) Module(name string) (*Spec, error) { + c.mu.RLock() + if spec := c.moduleTypes[name]; spec != nil { + c.mu.RUnlock() + return spec, nil + } + c.mu.RUnlock() + + c.mu.Lock() + defer c.mu.Unlock() + + if spec := c.moduleTypes[name]; spec != nil { + return spec, nil + } + + if c.moduleTypes == nil { + c.moduleTypes = make(map[string]*Spec) + } + + base, err := c.kernel() + if err != nil { + return nil, err + } + + spec, err := loadKernelModuleSpec(name, base) + if err != nil { + return nil, err + } + + c.moduleTypes[name] = spec + return spec, nil +} + +// Modules returns a sorted list of all loaded modules. +func (c *Cache) Modules() ([]string, error) { + c.mu.RLock() + if c.loadedModules != nil { + modules := slices.Clone(c.loadedModules) + c.mu.RUnlock() + return modules, nil + } + c.mu.RUnlock() + + c.mu.Lock() + defer c.mu.Unlock() + + if c.loadedModules != nil { + return slices.Clone(c.loadedModules), nil + } + + btfDir, err := os.Open("/sys/kernel/btf") + if err != nil { + return nil, err + } + defer btfDir.Close() + + entries, err := btfDir.Readdirnames(-1) + if err != nil { + return nil, err + } + + entries = slices.DeleteFunc(entries, func(s string) bool { + return s == "vmlinux" + }) + + sort.Strings(entries) + c.loadedModules = entries + return slices.Clone(entries), nil +} diff --git a/vendor/github.com/cilium/ebpf/btf/marshal.go b/vendor/github.com/cilium/ebpf/btf/marshal.go index d7204e6247..1086605030 100644 --- a/vendor/github.com/cilium/ebpf/btf/marshal.go +++ b/vendor/github.com/cilium/ebpf/btf/marshal.go @@ -1,7 +1,6 @@ package btf import ( - "bytes" "encoding/binary" "errors" "fmt" @@ -9,6 +8,7 @@ import ( "math" "slices" "sync" + "unsafe" "github.com/cilium/ebpf/internal" ) @@ -45,7 +45,6 @@ type encoder struct { MarshalOptions pending internal.Deque[Type] - buf *bytes.Buffer strings *stringTableBuilder ids map[Type]TypeID visited map[Type]struct{} @@ -79,6 +78,13 @@ type Builder struct { stableIDs map[Type]TypeID // Explicitly added strings. strings *stringTableBuilder + // Deduplication data structure. + deduper *deduper +} + +type BuilderOptions struct { + // Deduplicate enables type deduplication. + Deduplicate bool } // NewBuilder creates a Builder from a list of types. @@ -86,11 +92,20 @@ type Builder struct { // It is more efficient than calling [Add] individually. // // Returns an error if adding any of the types fails. -func NewBuilder(types []Type) (*Builder, error) { +func NewBuilder(types []Type, opts *BuilderOptions) (*Builder, error) { + if opts == nil { + opts = &BuilderOptions{} + } + b := &Builder{ make([]Type, 0, len(types)), make(map[Type]TypeID, len(types)), nil, + nil, + } + + if opts.Deduplicate { + b.deduper = newDeduper() } for _, typ := range types { @@ -114,13 +129,25 @@ func (b *Builder) Empty() bool { // // See [Type] for details on identity. func (b *Builder) Add(typ Type) (TypeID, error) { + if _, ok := typ.(*Void); ok { + // Equality is weird for void, since it is a zero sized type. + return 0, nil + } + + if err := internal.IsNilPointer(typ); err != nil { + return 0, fmt.Errorf("invalid type: %w", err) + } + if b.stableIDs == nil { b.stableIDs = make(map[Type]TypeID) } - if _, ok := typ.(*Void); ok { - // Equality is weird for void, since it is a zero sized type. - return 0, nil + if b.deduper != nil { + var err error + typ, err = b.deduper.deduplicate(typ) + if err != nil { + return 0, err + } } if ds, ok := typ.(*Datasec); ok { @@ -145,6 +172,19 @@ func (b *Builder) Add(typ Type) (TypeID, error) { return id, nil } +// Spec marshals the Builder's types and returns a new Spec to query them. +// +// The resulting Spec does not share any state with the Builder, subsequent +// additions to the Builder will not affect the Spec. +func (b *Builder) Spec() (*Spec, error) { + buf, err := b.Marshal(make([]byte, 0), nil) + if err != nil { + return nil, err + } + + return loadRawSpec(buf, nil) +} + // Marshal encodes all types in the Marshaler into BTF wire format. // // opts may be nil. @@ -166,12 +206,8 @@ func (b *Builder) Marshal(buf []byte, opts *MarshalOptions) ([]byte, error) { // Reserve space for the BTF header. buf = slices.Grow(buf, btfHeaderLen)[:btfHeaderLen] - w := internal.NewBuffer(buf) - defer internal.PutBuffer(w) - e := encoder{ MarshalOptions: *opts, - buf: w, strings: stb, lastID: TypeID(len(b.types)), visited: make(map[Type]struct{}, len(b.types)), @@ -199,15 +235,16 @@ func (b *Builder) Marshal(buf []byte, opts *MarshalOptions) ([]byte, error) { e.pending.Push(typ) } - if err := e.deflatePending(); err != nil { + buf, err := e.deflatePending(buf) + if err != nil { return nil, err } - length := e.buf.Len() + length := len(buf) typeLen := uint32(length - btfHeaderLen) stringLen := e.strings.Length() - buf = e.strings.AppendEncoded(e.buf.Bytes()) + buf = e.strings.AppendEncoded(buf) // Fill out the header, and write it out. header := &btfHeader{ @@ -221,7 +258,7 @@ func (b *Builder) Marshal(buf []byte, opts *MarshalOptions) ([]byte, error) { StringLen: uint32(stringLen), } - err := binary.Write(sliceWriter(buf[:btfHeaderLen]), e.Order, header) + _, err = binary.Encode(buf[:btfHeaderLen], e.Order, header) if err != nil { return nil, fmt.Errorf("write header: %v", err) } @@ -243,28 +280,27 @@ func (b *Builder) addString(str string) (uint32, error) { return b.strings.Add(str) } -func (e *encoder) allocateIDs(root Type) (err error) { - visitInPostorder(root, e.visited, func(typ Type) bool { +func (e *encoder) allocateIDs(root Type) error { + for typ := range postorder(root, e.visited) { if _, ok := typ.(*Void); ok { - return true + continue } if _, ok := e.ids[typ]; ok { - return true + continue } id := e.lastID + 1 if id < e.lastID { - err = errors.New("type ID overflow") - return false + return errors.New("type ID overflow") } e.pending.Push(typ) e.ids[typ] = id e.lastID = id - return true - }) - return + } + + return nil } // id returns the ID for the given type or panics with an error. @@ -281,7 +317,7 @@ func (e *encoder) id(typ Type) TypeID { return id } -func (e *encoder) deflatePending() error { +func (e *encoder) deflatePending(buf []byte) ([]byte, error) { // Declare root outside of the loop to avoid repeated heap allocations. var root Type @@ -289,20 +325,22 @@ func (e *encoder) deflatePending() error { root = e.pending.Shift() // Allocate IDs for all children of typ, including transitive dependencies. - if err := e.allocateIDs(root); err != nil { - return err + err := e.allocateIDs(root) + if err != nil { + return nil, err } - if err := e.deflateType(root); err != nil { + buf, err = e.deflateType(buf, root) + if err != nil { id := e.ids[root] - return fmt.Errorf("deflate %v with ID %d: %w", root, id, err) + return nil, fmt.Errorf("deflate %v with ID %d: %w", root, id, err) } } - return nil + return buf, nil } -func (e *encoder) deflateType(typ Type) (err error) { +func (e *encoder) deflateType(buf []byte, typ Type) (_ []byte, err error) { defer func() { if r := recover(); r != nil { var ok bool @@ -313,18 +351,22 @@ func (e *encoder) deflateType(typ Type) (err error) { } }() - var raw rawType + var raw btfType raw.NameOff, err = e.strings.Add(typ.TypeName()) if err != nil { - return err + return nil, err } + // Reserve space for the btfType header. + start := len(buf) + buf = append(buf, make([]byte, unsafe.Sizeof(raw))...) + switch v := typ.(type) { case *Void: - return errors.New("Void is implicit in BTF wire format") + return nil, errors.New("Void is implicit in BTF wire format") case *Int: - e.deflateInt(&raw, v) + buf, err = e.deflateInt(buf, &raw, v) case *Pointer: raw.SetKind(kindPointer) @@ -332,25 +374,25 @@ func (e *encoder) deflateType(typ Type) (err error) { case *Array: raw.SetKind(kindArray) - raw.data = &btfArray{ + buf, err = binary.Append(buf, e.Order, &btfArray{ e.id(v.Type), e.id(v.Index), v.Nelems, - } + }) case *Struct: raw.SetKind(kindStruct) raw.SetSize(v.Size) - raw.data, err = e.convertMembers(&raw.btfType, v.Members) + buf, err = e.deflateMembers(buf, &raw, v.Members) case *Union: - err = e.deflateUnion(&raw, v) + buf, err = e.deflateUnion(buf, &raw, v) case *Enum: if v.Size == 8 { - err = e.deflateEnum64(&raw, v) + buf, err = e.deflateEnum64(buf, &raw, v) } else { - err = e.deflateEnum(&raw, v) + buf, err = e.deflateEnum(buf, &raw, v) } case *Fwd: @@ -383,41 +425,46 @@ func (e *encoder) deflateType(typ Type) (err error) { raw.SetKind(kindFuncProto) raw.SetType(e.id(v.Return)) raw.SetVlen(len(v.Params)) - raw.data, err = e.deflateFuncParams(v.Params) + buf, err = e.deflateFuncParams(buf, v.Params) case *Var: raw.SetKind(kindVar) raw.SetType(e.id(v.Type)) - raw.data = btfVariable{uint32(v.Linkage)} + buf, err = binary.Append(buf, e.Order, btfVariable{uint32(v.Linkage)}) case *Datasec: raw.SetKind(kindDatasec) raw.SetSize(v.Size) raw.SetVlen(len(v.Vars)) - raw.data = e.deflateVarSecinfos(v.Vars) + buf, err = e.deflateVarSecinfos(buf, v.Vars) case *Float: raw.SetKind(kindFloat) raw.SetSize(v.Size) case *declTag: - err = e.deflateDeclTag(&raw, v) + buf, err = e.deflateDeclTag(buf, &raw, v) case *TypeTag: err = e.deflateTypeTag(&raw, v) default: - return fmt.Errorf("don't know how to deflate %T", v) + return nil, fmt.Errorf("don't know how to deflate %T", v) } if err != nil { - return err + return nil, err + } + + header := buf[start : start+int(unsafe.Sizeof(raw))] + if _, err = raw.Encode(header, e.Order); err != nil { + return nil, err } - return raw.Marshal(e.buf, e.Order) + return buf, nil } -func (e *encoder) deflateInt(raw *rawType, i *Int) { +func (e *encoder) deflateInt(buf []byte, raw *btfType, i *Int) ([]byte, error) { raw.SetKind(kindInt) raw.SetSize(i.Size) @@ -426,40 +473,47 @@ func (e *encoder) deflateInt(raw *rawType, i *Int) { // We need to set bits in addition to size, since btf_type_int_is_regular // otherwise flags this as a bitfield. bi.SetBits(byte(i.Size) * 8) - raw.data = bi + return binary.Append(buf, e.Order, bi) } -func (e *encoder) deflateDeclTag(raw *rawType, tag *declTag) (err error) { +func (e *encoder) deflateDeclTag(buf []byte, raw *btfType, tag *declTag) ([]byte, error) { // Replace a decl tag with an integer for compatibility with <5.16 kernels, // following libbpf behaviour. if e.ReplaceDeclTags { typ := &Int{"decl_tag_placeholder", 1, Unsigned} - e.deflateInt(raw, typ) + buf, err := e.deflateInt(buf, raw, typ) + if err != nil { + return nil, err + } // Add the placeholder type name to the string table. The encoder added the // original type name before this call. raw.NameOff, err = e.strings.Add(typ.TypeName()) - return + return buf, err } + var err error raw.SetKind(kindDeclTag) raw.SetType(e.id(tag.Type)) - raw.data = &btfDeclTag{uint32(tag.Index)} raw.NameOff, err = e.strings.Add(tag.Value) - return + if err != nil { + return nil, err + } + + return binary.Append(buf, e.Order, btfDeclTag{uint32(tag.Index)}) } -func (e *encoder) deflateConst(raw *rawType, c *Const) { +func (e *encoder) deflateConst(raw *btfType, c *Const) { raw.SetKind(kindConst) raw.SetType(e.id(c.Type)) } -func (e *encoder) deflateTypeTag(raw *rawType, tag *TypeTag) (err error) { +func (e *encoder) deflateTypeTag(raw *btfType, tag *TypeTag) (err error) { // Replace a type tag with a const qualifier for compatibility with <5.17 // kernels, following libbpf behaviour. if e.ReplaceTypeTags { e.deflateConst(raw, &Const{tag.Type}) - return + return nil } raw.SetKind(kindTypeTag) @@ -468,16 +522,17 @@ func (e *encoder) deflateTypeTag(raw *rawType, tag *TypeTag) (err error) { return } -func (e *encoder) deflateUnion(raw *rawType, union *Union) (err error) { +func (e *encoder) deflateUnion(buf []byte, raw *btfType, union *Union) ([]byte, error) { raw.SetKind(kindUnion) raw.SetSize(union.Size) - raw.data, err = e.convertMembers(&raw.btfType, union.Members) - return + return e.deflateMembers(buf, raw, union.Members) } -func (e *encoder) convertMembers(header *btfType, members []Member) ([]btfMember, error) { - bms := make([]btfMember, 0, len(members)) +func (e *encoder) deflateMembers(buf []byte, header *btfType, members []Member) ([]byte, error) { + var bm btfMember isBitfield := false + + buf = slices.Grow(buf, len(members)*int(unsafe.Sizeof(bm))) for _, member := range members { isBitfield = isBitfield || member.BitfieldSize > 0 @@ -491,30 +546,35 @@ func (e *encoder) convertMembers(header *btfType, members []Member) ([]btfMember return nil, err } - bms = append(bms, btfMember{ + bm = btfMember{ nameOff, e.id(member.Type), uint32(offset), - }) + } + + buf, err = binary.Append(buf, e.Order, &bm) + if err != nil { + return nil, err + } } header.SetVlen(len(members)) header.SetBitfield(isBitfield) - return bms, nil + return buf, nil } -func (e *encoder) deflateEnum(raw *rawType, enum *Enum) (err error) { +func (e *encoder) deflateEnum(buf []byte, raw *btfType, enum *Enum) ([]byte, error) { raw.SetKind(kindEnum) raw.SetSize(enum.Size) raw.SetVlen(len(enum.Values)) // Signedness appeared together with ENUM64 support. raw.SetSigned(enum.Signed && !e.ReplaceEnum64) - raw.data, err = e.deflateEnumValues(enum) - return + return e.deflateEnumValues(buf, enum) } -func (e *encoder) deflateEnumValues(enum *Enum) ([]btfEnum, error) { - bes := make([]btfEnum, 0, len(enum.Values)) +func (e *encoder) deflateEnumValues(buf []byte, enum *Enum) ([]byte, error) { + var be btfEnum + buf = slices.Grow(buf, len(enum.Values)*int(unsafe.Sizeof(be))) for _, value := range enum.Values { nameOff, err := e.strings.Add(value.Name) if err != nil { @@ -531,16 +591,21 @@ func (e *encoder) deflateEnumValues(enum *Enum) ([]btfEnum, error) { } } - bes = append(bes, btfEnum{ + be = btfEnum{ nameOff, uint32(value.Value), - }) + } + + buf, err = binary.Append(buf, e.Order, &be) + if err != nil { + return nil, err + } } - return bes, nil + return buf, nil } -func (e *encoder) deflateEnum64(raw *rawType, enum *Enum) (err error) { +func (e *encoder) deflateEnum64(buf []byte, raw *btfType, enum *Enum) ([]byte, error) { if e.ReplaceEnum64 { // Replace the ENUM64 with a union of fields with the correct size. // This matches libbpf behaviour on purpose. @@ -553,7 +618,7 @@ func (e *encoder) deflateEnum64(raw *rawType, enum *Enum) (err error) { placeholder.Encoding = Signed } if err := e.allocateIDs(placeholder); err != nil { - return fmt.Errorf("add enum64 placeholder: %w", err) + return nil, fmt.Errorf("add enum64 placeholder: %w", err) } members := make([]Member, 0, len(enum.Values)) @@ -564,61 +629,79 @@ func (e *encoder) deflateEnum64(raw *rawType, enum *Enum) (err error) { }) } - return e.deflateUnion(raw, &Union{enum.Name, enum.Size, members, nil}) + return e.deflateUnion(buf, raw, &Union{enum.Name, enum.Size, members, nil}) } raw.SetKind(kindEnum64) raw.SetSize(enum.Size) raw.SetVlen(len(enum.Values)) raw.SetSigned(enum.Signed) - raw.data, err = e.deflateEnum64Values(enum.Values) - return + return e.deflateEnum64Values(buf, enum.Values) } -func (e *encoder) deflateEnum64Values(values []EnumValue) ([]btfEnum64, error) { - bes := make([]btfEnum64, 0, len(values)) +func (e *encoder) deflateEnum64Values(buf []byte, values []EnumValue) ([]byte, error) { + var be btfEnum64 + buf = slices.Grow(buf, len(values)*int(unsafe.Sizeof(be))) for _, value := range values { nameOff, err := e.strings.Add(value.Name) if err != nil { return nil, err } - bes = append(bes, btfEnum64{ + be = btfEnum64{ nameOff, uint32(value.Value), uint32(value.Value >> 32), - }) + } + + buf, err = binary.Append(buf, e.Order, &be) + if err != nil { + return nil, err + } } - return bes, nil + return buf, nil } -func (e *encoder) deflateFuncParams(params []FuncParam) ([]btfParam, error) { - bps := make([]btfParam, 0, len(params)) +func (e *encoder) deflateFuncParams(buf []byte, params []FuncParam) ([]byte, error) { + var bp btfParam + buf = slices.Grow(buf, len(params)*int(unsafe.Sizeof(bp))) for _, param := range params { nameOff, err := e.strings.Add(param.Name) if err != nil { return nil, err } - bps = append(bps, btfParam{ + bp = btfParam{ nameOff, e.id(param.Type), - }) + } + + buf, err = binary.Append(buf, e.Order, &bp) + if err != nil { + return nil, err + } } - return bps, nil + return buf, nil } -func (e *encoder) deflateVarSecinfos(vars []VarSecinfo) []btfVarSecinfo { - vsis := make([]btfVarSecinfo, 0, len(vars)) +func (e *encoder) deflateVarSecinfos(buf []byte, vars []VarSecinfo) ([]byte, error) { + var vsi btfVarSecinfo + var err error + buf = slices.Grow(buf, len(vars)*int(unsafe.Sizeof(vsi))) for _, v := range vars { - vsis = append(vsis, btfVarSecinfo{ + vsi = btfVarSecinfo{ e.id(v.Type), v.Offset, v.Size, - }) + } + + buf, err = binary.Append(buf, e.Order, vsi) + if err != nil { + return nil, err + } } - return vsis + return buf, nil } // MarshalMapKV creates a BTF object containing a map key and value. diff --git a/vendor/github.com/cilium/ebpf/btf/strings.go b/vendor/github.com/cilium/ebpf/btf/strings.go index 7c31461c30..3e3bd688e0 100644 --- a/vendor/github.com/cilium/ebpf/btf/strings.go +++ b/vendor/github.com/cilium/ebpf/btf/strings.go @@ -1,21 +1,24 @@ package btf import ( - "bufio" "bytes" "errors" "fmt" "io" "maps" - "slices" "strings" + "sync" ) +// stringTable contains a sequence of null-terminated strings. +// +// It is safe for concurrent use. type stringTable struct { - base *stringTable - offsets []uint32 - prevIdx int - strings []string + base *stringTable + bytes []byte + + mu sync.Mutex + cache map[uint32]string } // sizedReader is implemented by bytes.Reader, io.SectionReader, strings.Reader, etc. @@ -25,93 +28,103 @@ type sizedReader interface { } func readStringTable(r sizedReader, base *stringTable) (*stringTable, error) { + bytes := make([]byte, r.Size()) + if _, err := io.ReadFull(r, bytes); err != nil { + return nil, err + } + + return newStringTable(bytes, base) +} + +func newStringTable(bytes []byte, base *stringTable) (*stringTable, error) { // When parsing split BTF's string table, the first entry offset is derived // from the last entry offset of the base BTF. firstStringOffset := uint32(0) if base != nil { - idx := len(base.offsets) - 1 - firstStringOffset = base.offsets[idx] + uint32(len(base.strings[idx])) + 1 + firstStringOffset = uint32(len(base.bytes)) } - // Derived from vmlinux BTF. - const averageStringLength = 16 - - n := int(r.Size() / averageStringLength) - offsets := make([]uint32, 0, n) - strings := make([]string, 0, n) + if len(bytes) > 0 { + if bytes[len(bytes)-1] != 0 { + return nil, errors.New("string table isn't null terminated") + } - offset := firstStringOffset - scanner := bufio.NewScanner(r) - scanner.Split(splitNull) - for scanner.Scan() { - str := scanner.Text() - offsets = append(offsets, offset) - strings = append(strings, str) - offset += uint32(len(str)) + 1 - } - if err := scanner.Err(); err != nil { - return nil, err + if firstStringOffset == 0 && bytes[0] != 0 { + return nil, errors.New("first item in string table is non-empty") + } } - if len(strings) == 0 { - return nil, errors.New("string table is empty") + return &stringTable{base: base, bytes: bytes}, nil +} + +func (st *stringTable) Lookup(offset uint32) (string, error) { + // Fast path: zero offset is the empty string, looked up frequently. + if offset == 0 { + return "", nil } - if firstStringOffset == 0 && strings[0] != "" { - return nil, errors.New("first item in string table is non-empty") + b, err := st.lookupSlow(offset) + return string(b), err +} + +func (st *stringTable) LookupBytes(offset uint32) ([]byte, error) { + // Fast path: zero offset is the empty string, looked up frequently. + if offset == 0 { + return nil, nil } - return &stringTable{base, offsets, 0, strings}, nil + return st.lookupSlow(offset) } -func splitNull(data []byte, atEOF bool) (advance int, token []byte, err error) { - i := bytes.IndexByte(data, 0) - if i == -1 { - if atEOF && len(data) > 0 { - return 0, nil, errors.New("string table isn't null terminated") +func (st *stringTable) lookupSlow(offset uint32) ([]byte, error) { + if st.base != nil { + n := uint32(len(st.base.bytes)) + if offset < n { + return st.base.lookupSlow(offset) } - return 0, nil, nil + offset -= n } - return i + 1, data[:i], nil -} + if offset > uint32(len(st.bytes)) { + return nil, fmt.Errorf("offset %d is out of bounds of string table", offset) + } -func (st *stringTable) Lookup(offset uint32) (string, error) { - if st.base != nil && offset <= st.base.offsets[len(st.base.offsets)-1] { - return st.base.lookup(offset) + if offset > 0 && st.bytes[offset-1] != 0 { + return nil, fmt.Errorf("offset %d is not the beginning of a string", offset) } - return st.lookup(offset) + + i := bytes.IndexByte(st.bytes[offset:], 0) + if i == -1 { + return nil, fmt.Errorf("string at offset %d is not null terminated", offset) + } + return st.bytes[offset : offset+uint32(i)], nil } -func (st *stringTable) lookup(offset uint32) (string, error) { +// LookupCache returns the string at the given offset, caching the result +// for future lookups. +func (cst *stringTable) LookupCached(offset uint32) (string, error) { // Fast path: zero offset is the empty string, looked up frequently. - if offset == 0 && st.base == nil { + if offset == 0 { return "", nil } - // Accesses tend to be globally increasing, so check if the next string is - // the one we want. This skips the binary search in about 50% of cases. - if st.prevIdx+1 < len(st.offsets) && st.offsets[st.prevIdx+1] == offset { - st.prevIdx++ - return st.strings[st.prevIdx], nil - } + cst.mu.Lock() + defer cst.mu.Unlock() - i, found := slices.BinarySearch(st.offsets, offset) - if !found { - return "", fmt.Errorf("offset %d isn't start of a string", offset) + if str, ok := cst.cache[offset]; ok { + return str, nil } - // Set the new increment index, but only if its greater than the current. - if i > st.prevIdx+1 { - st.prevIdx = i + str, err := cst.Lookup(offset) + if err != nil { + return "", err } - return st.strings[i], nil -} - -// Num returns the number of strings in the table. -func (st *stringTable) Num() int { - return len(st.strings) + if cst.cache == nil { + cst.cache = make(map[uint32]string) + } + cst.cache[offset] = str + return str, nil } // stringTableBuilder builds BTF string tables. diff --git a/vendor/github.com/cilium/ebpf/btf/traversal.go b/vendor/github.com/cilium/ebpf/btf/traversal.go index 13647d931f..57c1dc27e8 100644 --- a/vendor/github.com/cilium/ebpf/btf/traversal.go +++ b/vendor/github.com/cilium/ebpf/btf/traversal.go @@ -2,16 +2,21 @@ package btf import ( "fmt" + "iter" ) // Functions to traverse a cyclic graph of types. The below was very useful: // https://eli.thegreenplace.net/2015/directed-graph-traversal-orderings-and-applications-to-data-flow-analysis/#post-order-and-reverse-post-order -// Visit all types reachable from root in postorder. -// -// Traversal stops if yield returns false. -// -// Returns false if traversal was aborted. +// postorder yields all types reachable from root in post order. +func postorder(root Type, visited map[Type]struct{}) iter.Seq[Type] { + return func(yield func(Type) bool) { + visitInPostorder(root, visited, yield) + } +} + +// visitInPostorder is a separate function to avoid arguments escaping +// to the heap. Don't change the setup without re-running the benchmarks. func visitInPostorder(root Type, visited map[Type]struct{}, yield func(typ Type) bool) bool { if _, ok := visited[root]; ok { return true @@ -21,139 +26,134 @@ func visitInPostorder(root Type, visited map[Type]struct{}, yield func(typ Type) } visited[root] = struct{}{} - cont := children(root, func(child *Type) bool { - return visitInPostorder(*child, visited, yield) - }) - if !cont { - return false + for child := range children(root) { + if !visitInPostorder(*child, visited, yield) { + return false + } } return yield(root) } -// children calls yield on each child of typ. -// -// Traversal stops if yield returns false. -// -// Returns false if traversal was aborted. -func children(typ Type, yield func(child *Type) bool) bool { - // Explicitly type switch on the most common types to allow the inliner to - // do its work. This avoids allocating intermediate slices from walk() on - // the heap. - var tags []string - switch v := typ.(type) { - case *Void, *Int, *Enum, *Fwd, *Float, *declTag: - // No children to traverse. - // declTags is declared as a leaf type since it's parsed into .Tags fields of other types - // during unmarshaling. - case *Pointer: - if !yield(&v.Target) { - return false - } - case *Array: - if !yield(&v.Index) { - return false - } - if !yield(&v.Type) { - return false - } - case *Struct: - for i := range v.Members { - if !yield(&v.Members[i].Type) { - return false - } - for _, t := range v.Members[i].Tags { - var tag Type = &declTag{v, t, i} - if !yield(&tag) { - return false - } +// children yields all direct descendants of typ. +func children(typ Type) iter.Seq[*Type] { + return func(yield func(*Type) bool) { + // Explicitly type switch on the most common types to allow the inliner to + // do its work. This avoids allocating intermediate slices from walk() on + // the heap. + var tags []string + switch v := typ.(type) { + case *Void, *Int, *Enum, *Fwd, *Float, *declTag: + // No children to traverse. + // declTags is declared as a leaf type since it's parsed into .Tags fields of other types + // during unmarshaling. + case *Pointer: + if !yield(&v.Target) { + return } - } - tags = v.Tags - case *Union: - for i := range v.Members { - if !yield(&v.Members[i].Type) { - return false - } - for _, t := range v.Members[i].Tags { - var tag Type = &declTag{v, t, i} - if !yield(&tag) { - return false + case *Array: + if !yield(&v.Index) { + return + } + if !yield(&v.Type) { + return + } + case *Struct: + for i := range v.Members { + if !yield(&v.Members[i].Type) { + return + } + for _, t := range v.Members[i].Tags { + var tag Type = &declTag{v, t, i} + if !yield(&tag) { + return + } } } - } - tags = v.Tags - case *Typedef: - if !yield(&v.Type) { - return false - } - tags = v.Tags - case *Volatile: - if !yield(&v.Type) { - return false - } - case *Const: - if !yield(&v.Type) { - return false - } - case *Restrict: - if !yield(&v.Type) { - return false - } - case *Func: - if !yield(&v.Type) { - return false - } - if fp, ok := v.Type.(*FuncProto); ok { - for i := range fp.Params { - if len(v.ParamTags) <= i { - continue + tags = v.Tags + case *Union: + for i := range v.Members { + if !yield(&v.Members[i].Type) { + return } - for _, t := range v.ParamTags[i] { + for _, t := range v.Members[i].Tags { var tag Type = &declTag{v, t, i} if !yield(&tag) { - return false + return } } } - } - tags = v.Tags - case *FuncProto: - if !yield(&v.Return) { - return false - } - for i := range v.Params { - if !yield(&v.Params[i].Type) { - return false + tags = v.Tags + case *Typedef: + if !yield(&v.Type) { + return } - } - case *Var: - if !yield(&v.Type) { - return false - } - tags = v.Tags - case *Datasec: - for i := range v.Vars { - if !yield(&v.Vars[i].Type) { - return false + tags = v.Tags + case *Volatile: + if !yield(&v.Type) { + return } + case *Const: + if !yield(&v.Type) { + return + } + case *Restrict: + if !yield(&v.Type) { + return + } + case *Func: + if !yield(&v.Type) { + return + } + if fp, ok := v.Type.(*FuncProto); ok { + for i := range fp.Params { + if len(v.ParamTags) <= i { + continue + } + for _, t := range v.ParamTags[i] { + var tag Type = &declTag{v, t, i} + if !yield(&tag) { + return + } + } + } + } + tags = v.Tags + case *FuncProto: + if !yield(&v.Return) { + return + } + for i := range v.Params { + if !yield(&v.Params[i].Type) { + return + } + } + case *Var: + if !yield(&v.Type) { + return + } + tags = v.Tags + case *Datasec: + for i := range v.Vars { + if !yield(&v.Vars[i].Type) { + return + } + } + case *TypeTag: + if !yield(&v.Type) { + return + } + case *cycle: + // cycle has children, but we ignore them deliberately. + default: + panic(fmt.Sprintf("don't know how to walk Type %T", v)) } - case *TypeTag: - if !yield(&v.Type) { - return false - } - case *cycle: - // cycle has children, but we ignore them deliberately. - default: - panic(fmt.Sprintf("don't know how to walk Type %T", v)) - } - for _, t := range tags { - var tag Type = &declTag{typ, t, -1} - if !yield(&tag) { - return false + for _, t := range tags { + var tag Type = &declTag{typ, t, -1} + if !yield(&tag) { + return + } } } - - return true } diff --git a/vendor/github.com/cilium/ebpf/btf/types.go b/vendor/github.com/cilium/ebpf/btf/types.go index dbcdf9dd7a..f24beeb983 100644 --- a/vendor/github.com/cilium/ebpf/btf/types.go +++ b/vendor/github.com/cilium/ebpf/btf/types.go @@ -1,12 +1,10 @@ package btf import ( - "encoding/binary" "errors" "fmt" "io" "math" - "slices" "strings" "github.com/cilium/ebpf/asm" @@ -23,15 +21,25 @@ type TypeID = sys.TypeID // Type represents a type described by BTF. // -// Identity of Type follows the [Go specification]: two Types are considered -// equal if they have the same concrete type and the same dynamic value, aka -// they point at the same location in memory. This means that the following -// Types are considered distinct even though they have the same "shape". +// A Type has three properties where compared to other Types. +// +// Identity: follows the [Go specification], two Types are considered identical +// if they have the same concrete type and the same dynamic value, aka they point +// at the same location in memory. This means that the following Types are +// considered distinct even though they have the same "shape". // // a := &Int{Size: 1} // b := &Int{Size: 1} // a != b // +// Equivalence: two Types are considered equivalent if they have the same shape +// and thus are functionally interchangeable, even if they are located at different +// memory addresses. The above two Int types are equivalent. +// +// Compatibility: two Types are considered compatible according to the rules of CO-RE +// see [coreAreTypesCompatible] for details. This is a non-commutative property, +// so A may be compatible with B, but B not compatible with A. +// // [Go specification]: https://go.dev/ref/spec#Comparison_operators type Type interface { // Type can be formatted using the %s and %v verbs. %s outputs only the @@ -52,7 +60,7 @@ type Type interface { // Make a copy of the type, without copying Type members. copy() Type - // New implementations must update walkType. + // New implementations must update children, deduper.typeHash, and typesEquivalent. } var ( @@ -172,8 +180,15 @@ type Struct struct { Tags []string } +// Format supports the width option to %v to limit or extend the number of +// struct member names printed (default 5). +// +// For example, %1v will print only the first member name followed by '...': +// +// Struct:"struct"[fields=3 fieldNames=[a ...]] func (s *Struct) Format(fs fmt.State, verb rune) { - formatType(fs, verb, s, "fields=", len(s.Members)) + max, _ := fs.Width() + formatType(fs, verb, s, "fields=", len(s.Members), "fieldNames=", memberNames(s.Members, max)) } func (s *Struct) TypeName() string { return s.Name } @@ -200,8 +215,15 @@ type Union struct { Tags []string } +// Format supports the width option to %v to limit or extend the number of +// union member names printed (default 5). +// +// For example, %1v will print only the first member name followed by '...': +// +// Union:"union"[fields=3 fieldNames=[a ...]] func (u *Union) Format(fs fmt.State, verb rune) { - formatType(fs, verb, u, "fields=", len(u.Members)) + max, _ := fs.Width() + formatType(fs, verb, u, "fields=", len(u.Members), "fieldNames=", memberNames(u.Members, max)) } func (u *Union) TypeName() string { return u.Name } @@ -219,6 +241,29 @@ func (u *Union) members() []Member { return u.Members } +// memberNames returns the names of members, or its index in the list of members +// if the name is empty. +// +// Returns up to max entries (default 5), followed by '...'. +func memberNames(members []Member, max int) []string { + if max <= 0 { + max = 5 + } + names := make([]string, min(len(members), max+1)) + for i, m := range members { + if i >= max { + names[i] = "..." + break + } + if m.Name == "" { + names[i] = fmt.Sprintf("<%d>", i) + continue + } + names[i] = m.Name + } + return names +} + func copyMembers(orig []Member) []Member { cpy := make([]Member, len(orig)) copy(cpy, orig) @@ -430,6 +475,8 @@ type Func struct { ParamTags [][]string } +type funcInfoMeta struct{} + func FuncMetadata(ins *asm.Instruction) *Func { fn, _ := ins.Metadata.Get(funcInfoMeta{}).(*Func) return fn @@ -646,7 +693,7 @@ func Sizeof(typ Type) (int, error) { elem int64 ) - for i := 0; i < maxResolveDepth; i++ { + for range maxResolveDepth { switch v := typ.(type) { case *Array: if n > 0 && int64(v.Nelems) > math.MaxInt64/n { @@ -740,520 +787,15 @@ func copyType(typ Type, ids map[Type]TypeID, copies map[Type]Type, copiedIDs map copiedIDs[cpy] = id } - children(cpy, func(child *Type) bool { + for child := range children(cpy) { *child = copyType(*child, ids, copies, copiedIDs) - return true - }) + } return cpy } type typeDeque = internal.Deque[*Type] -// readAndInflateTypes reads the raw btf type info and turns it into a graph -// of Types connected via pointers. -// -// If base is provided, then the types are considered to be of a split BTF -// (e.g., a kernel module). -// -// Returns 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 readAndInflateTypes(r io.Reader, bo binary.ByteOrder, typeLen uint32, rawStrings *stringTable, base *Spec) ([]Type, error) { - // because of the interleaving between types and struct members it is difficult to - // precompute the numbers of raw types this will parse - // this "guess" is a good first estimation - sizeOfbtfType := uintptr(btfTypeLen) - tyMaxCount := uintptr(typeLen) / sizeOfbtfType / 2 - types := make([]Type, 0, tyMaxCount) - - // Void is defined to always be type ID 0, and is thus omitted from BTF. - types = append(types, (*Void)(nil)) - - firstTypeID := TypeID(0) - if base != nil { - var err error - firstTypeID, err = base.nextTypeID() - if err != nil { - return nil, err - } - - // Split BTF doesn't contain Void. - types = types[:0] - } - - type fixupDef struct { - id TypeID - typ *Type - } - - var fixups []fixupDef - fixup := func(id TypeID, typ *Type) { - if id < firstTypeID { - if baseType, err := base.TypeByID(id); err == nil { - *typ = baseType - return - } - } - - idx := int(id - firstTypeID) - if idx < len(types) { - // We've already inflated this type, fix it up immediately. - *typ = types[idx] - return - } - - fixups = append(fixups, fixupDef{id, typ}) - } - - type bitfieldFixupDef struct { - id TypeID - m *Member - } - - var ( - legacyBitfields = make(map[TypeID][2]Bits) // offset, size - bitfieldFixups []bitfieldFixupDef - ) - convertMembers := func(raw []btfMember, kindFlag bool) ([]Member, error) { - // NB: The fixup below relies on pre-allocating this array to - // work, since otherwise append might re-allocate members. - members := make([]Member, 0, len(raw)) - for i, btfMember := range raw { - name, err := rawStrings.Lookup(btfMember.NameOff) - if err != nil { - return nil, fmt.Errorf("can't get name for member %d: %w", i, err) - } - - members = append(members, Member{ - Name: name, - Offset: Bits(btfMember.Offset), - }) - - m := &members[i] - fixup(raw[i].Type, &m.Type) - - if kindFlag { - m.BitfieldSize = Bits(btfMember.Offset >> 24) - m.Offset &= 0xffffff - // We ignore legacy bitfield definitions if the current composite - // is a new-style bitfield. This is kind of safe since offset and - // size on the type of the member must be zero if kindFlat is set - // according to spec. - continue - } - - // This may be a legacy bitfield, try to fix it up. - data, ok := legacyBitfields[raw[i].Type] - if ok { - // Bingo! - m.Offset += data[0] - m.BitfieldSize = data[1] - continue - } - - if m.Type != nil { - // We couldn't find a legacy bitfield, but we know that the member's - // type has already been inflated. Hence we know that it can't be - // a legacy bitfield and there is nothing left to do. - continue - } - - // We don't have fixup data, and the type we're pointing - // at hasn't been inflated yet. No choice but to defer - // the fixup. - bitfieldFixups = append(bitfieldFixups, bitfieldFixupDef{ - raw[i].Type, - m, - }) - } - return members, nil - } - - var ( - buf = make([]byte, 1024) - header btfType - bInt btfInt - bArr btfArray - bMembers []btfMember - bEnums []btfEnum - bParams []btfParam - bVariable btfVariable - bSecInfos []btfVarSecinfo - bDeclTag btfDeclTag - bEnums64 []btfEnum64 - ) - - var declTags []*declTag - for { - var ( - id = firstTypeID + TypeID(len(types)) - typ Type - ) - - if _, err := io.ReadFull(r, buf[:btfTypeLen]); err == io.EOF { - break - } else if err != nil { - return nil, fmt.Errorf("can't read type info for id %v: %v", id, err) - } - - if _, err := unmarshalBtfType(&header, buf[:btfTypeLen], bo); err != nil { - return nil, fmt.Errorf("can't unmarshal type info for id %v: %v", id, err) - } - - if id < firstTypeID { - return nil, fmt.Errorf("no more type IDs") - } - - name, err := rawStrings.Lookup(header.NameOff) - if err != nil { - return nil, fmt.Errorf("get name for type id %d: %w", id, err) - } - - switch header.Kind() { - case kindInt: - size := header.Size() - buf = buf[:btfIntLen] - if _, err := io.ReadFull(r, buf); err != nil { - return nil, fmt.Errorf("can't read btfInt, id: %d: %w", id, err) - } - if _, err := unmarshalBtfInt(&bInt, buf, bo); err != nil { - return nil, fmt.Errorf("can't unmarshal btfInt, id: %d: %w", id, err) - } - if bInt.Offset() > 0 || bInt.Bits().Bytes() != size { - legacyBitfields[id] = [2]Bits{bInt.Offset(), bInt.Bits()} - } - typ = &Int{name, header.Size(), bInt.Encoding()} - - case kindPointer: - ptr := &Pointer{nil} - fixup(header.Type(), &ptr.Target) - typ = ptr - - case kindArray: - buf = buf[:btfArrayLen] - if _, err := io.ReadFull(r, buf); err != nil { - return nil, fmt.Errorf("can't read btfArray, id: %d: %w", id, err) - } - if _, err := unmarshalBtfArray(&bArr, buf, bo); err != nil { - return nil, fmt.Errorf("can't unmarshal btfArray, id: %d: %w", id, err) - } - - arr := &Array{nil, nil, bArr.Nelems} - fixup(bArr.IndexType, &arr.Index) - fixup(bArr.Type, &arr.Type) - typ = arr - - case kindStruct: - vlen := header.Vlen() - bMembers = slices.Grow(bMembers[:0], vlen)[:vlen] - buf = slices.Grow(buf[:0], vlen*btfMemberLen)[:vlen*btfMemberLen] - if _, err := io.ReadFull(r, buf); err != nil { - return nil, fmt.Errorf("can't read btfMembers, id: %d: %w", id, err) - } - if _, err := unmarshalBtfMembers(bMembers, buf, bo); err != nil { - return nil, fmt.Errorf("can't unmarshal btfMembers, id: %d: %w", id, err) - } - - members, err := convertMembers(bMembers, header.Bitfield()) - if err != nil { - return nil, fmt.Errorf("struct %s (id %d): %w", name, id, err) - } - typ = &Struct{name, header.Size(), members, nil} - - case kindUnion: - vlen := header.Vlen() - bMembers = slices.Grow(bMembers[:0], vlen)[:vlen] - buf = slices.Grow(buf[:0], vlen*btfMemberLen)[:vlen*btfMemberLen] - if _, err := io.ReadFull(r, buf); err != nil { - return nil, fmt.Errorf("can't read btfMembers, id: %d: %w", id, err) - } - if _, err := unmarshalBtfMembers(bMembers, buf, bo); err != nil { - return nil, fmt.Errorf("can't unmarshal btfMembers, id: %d: %w", id, err) - } - - members, err := convertMembers(bMembers, header.Bitfield()) - if err != nil { - return nil, fmt.Errorf("union %s (id %d): %w", name, id, err) - } - typ = &Union{name, header.Size(), members, nil} - - case kindEnum: - vlen := header.Vlen() - bEnums = slices.Grow(bEnums[:0], vlen)[:vlen] - buf = slices.Grow(buf[:0], vlen*btfEnumLen)[:vlen*btfEnumLen] - if _, err := io.ReadFull(r, buf); err != nil { - return nil, fmt.Errorf("can't read btfEnums, id: %d: %w", id, err) - } - if _, err := unmarshalBtfEnums(bEnums, buf, bo); err != nil { - return nil, fmt.Errorf("can't unmarshal btfEnums, id: %d: %w", id, err) - } - - vals := make([]EnumValue, 0, vlen) - signed := header.Signed() - for i, btfVal := range bEnums { - name, err := rawStrings.Lookup(btfVal.NameOff) - if err != nil { - return nil, fmt.Errorf("get name for enum value %d: %s", i, err) - } - value := uint64(btfVal.Val) - if signed { - // Sign extend values to 64 bit. - value = uint64(int32(btfVal.Val)) - } - vals = append(vals, EnumValue{name, value}) - } - typ = &Enum{name, header.Size(), signed, vals} - - case kindForward: - typ = &Fwd{name, header.FwdKind()} - - case kindTypedef: - typedef := &Typedef{name, nil, nil} - fixup(header.Type(), &typedef.Type) - typ = typedef - - case kindVolatile: - volatile := &Volatile{nil} - fixup(header.Type(), &volatile.Type) - typ = volatile - - case kindConst: - cnst := &Const{nil} - fixup(header.Type(), &cnst.Type) - typ = cnst - - case kindRestrict: - restrict := &Restrict{nil} - fixup(header.Type(), &restrict.Type) - typ = restrict - - case kindFunc: - fn := &Func{name, nil, header.Linkage(), nil, nil} - fixup(header.Type(), &fn.Type) - typ = fn - - case kindFuncProto: - vlen := header.Vlen() - bParams = slices.Grow(bParams[:0], vlen)[:vlen] - buf = slices.Grow(buf[:0], vlen*btfParamLen)[:vlen*btfParamLen] - if _, err := io.ReadFull(r, buf); err != nil { - return nil, fmt.Errorf("can't read btfParams, id: %d: %w", id, err) - } - if _, err := unmarshalBtfParams(bParams, buf, bo); err != nil { - return nil, fmt.Errorf("can't unmarshal btfParams, id: %d: %w", id, err) - } - - params := make([]FuncParam, 0, vlen) - for i, param := range bParams { - name, err := rawStrings.Lookup(param.NameOff) - if err != nil { - return nil, fmt.Errorf("get name for func proto parameter %d: %s", i, err) - } - params = append(params, FuncParam{ - Name: name, - }) - } - for i := range params { - fixup(bParams[i].Type, ¶ms[i].Type) - } - - fp := &FuncProto{nil, params} - fixup(header.Type(), &fp.Return) - typ = fp - - case kindVar: - buf = buf[:btfVariableLen] - if _, err := io.ReadFull(r, buf); err != nil { - return nil, fmt.Errorf("can't read btfVariable, id: %d: %w", id, err) - } - if _, err := unmarshalBtfVariable(&bVariable, buf, bo); err != nil { - return nil, fmt.Errorf("can't read btfVariable, id: %d: %w", id, err) - } - - v := &Var{name, nil, VarLinkage(bVariable.Linkage), nil} - fixup(header.Type(), &v.Type) - typ = v - - case kindDatasec: - vlen := header.Vlen() - bSecInfos = slices.Grow(bSecInfos[:0], vlen)[:vlen] - buf = slices.Grow(buf[:0], vlen*btfVarSecinfoLen)[:vlen*btfVarSecinfoLen] - if _, err := io.ReadFull(r, buf); err != nil { - return nil, fmt.Errorf("can't read btfVarSecInfos, id: %d: %w", id, err) - } - if _, err := unmarshalBtfVarSecInfos(bSecInfos, buf, bo); err != nil { - return nil, fmt.Errorf("can't unmarshal btfVarSecInfos, id: %d: %w", id, err) - } - - vars := make([]VarSecinfo, 0, vlen) - for _, btfVar := range bSecInfos { - vars = append(vars, VarSecinfo{ - Offset: btfVar.Offset, - Size: btfVar.Size, - }) - } - for i := range vars { - fixup(bSecInfos[i].Type, &vars[i].Type) - } - typ = &Datasec{name, header.Size(), vars} - - case kindFloat: - typ = &Float{name, header.Size()} - - case kindDeclTag: - buf = buf[:btfDeclTagLen] - if _, err := io.ReadFull(r, buf); err != nil { - return nil, fmt.Errorf("can't read btfDeclTag, id: %d: %w", id, err) - } - if _, err := unmarshalBtfDeclTag(&bDeclTag, buf, bo); err != nil { - return nil, fmt.Errorf("can't read btfDeclTag, id: %d: %w", id, err) - } - - btfIndex := bDeclTag.ComponentIdx - if uint64(btfIndex) > math.MaxInt { - return nil, fmt.Errorf("type id %d: index exceeds int", id) - } - - dt := &declTag{nil, name, int(int32(btfIndex))} - fixup(header.Type(), &dt.Type) - typ = dt - - declTags = append(declTags, dt) - - case kindTypeTag: - tt := &TypeTag{nil, name} - fixup(header.Type(), &tt.Type) - typ = tt - - case kindEnum64: - vlen := header.Vlen() - bEnums64 = slices.Grow(bEnums64[:0], vlen)[:vlen] - buf = slices.Grow(buf[:0], vlen*btfEnum64Len)[:vlen*btfEnum64Len] - if _, err := io.ReadFull(r, buf); err != nil { - return nil, fmt.Errorf("can't read btfEnum64s, id: %d: %w", id, err) - } - if _, err := unmarshalBtfEnums64(bEnums64, buf, bo); err != nil { - return nil, fmt.Errorf("can't unmarshal btfEnum64s, id: %d: %w", id, err) - } - - vals := make([]EnumValue, 0, vlen) - for i, btfVal := range bEnums64 { - name, err := rawStrings.Lookup(btfVal.NameOff) - if err != nil { - return nil, fmt.Errorf("get name for enum64 value %d: %s", i, err) - } - value := (uint64(btfVal.ValHi32) << 32) | uint64(btfVal.ValLo32) - vals = append(vals, EnumValue{name, value}) - } - typ = &Enum{name, header.Size(), header.Signed(), vals} - - default: - return nil, fmt.Errorf("type id %d: unknown kind: %v", id, header.Kind()) - } - - types = append(types, typ) - } - - for _, fixup := range fixups { - if fixup.id < firstTypeID { - return nil, fmt.Errorf("fixup for base type id %d is not expected", fixup.id) - } - - idx := int(fixup.id - firstTypeID) - if idx >= len(types) { - return nil, fmt.Errorf("reference to invalid type id: %d", fixup.id) - } - - *fixup.typ = types[idx] - } - - for _, bitfieldFixup := range bitfieldFixups { - if bitfieldFixup.id < firstTypeID { - return nil, fmt.Errorf("bitfield fixup from split to base types is not expected") - } - - data, ok := legacyBitfields[bitfieldFixup.id] - if ok { - // This is indeed a legacy bitfield, fix it up. - bitfieldFixup.m.Offset += data[0] - bitfieldFixup.m.BitfieldSize = data[1] - } - } - - for _, dt := range declTags { - switch t := dt.Type.(type) { - case *Var: - if dt.Index != -1 { - return nil, fmt.Errorf("type %s: component idx %d is not -1", dt, dt.Index) - } - t.Tags = append(t.Tags, dt.Value) - - case *Typedef: - if dt.Index != -1 { - return nil, fmt.Errorf("type %s: component idx %d is not -1", dt, dt.Index) - } - t.Tags = append(t.Tags, dt.Value) - - case composite: - if dt.Index >= 0 { - members := t.members() - if dt.Index >= len(members) { - return nil, fmt.Errorf("type %s: component idx %d exceeds members of %s", dt, dt.Index, t) - } - - members[dt.Index].Tags = append(members[dt.Index].Tags, dt.Value) - continue - } - - if dt.Index == -1 { - switch t2 := t.(type) { - case *Struct: - t2.Tags = append(t2.Tags, dt.Value) - case *Union: - t2.Tags = append(t2.Tags, dt.Value) - } - - continue - } - - return nil, fmt.Errorf("type %s: decl tag for type %s has invalid component idx", dt, t) - - case *Func: - fp, ok := t.Type.(*FuncProto) - if !ok { - return nil, fmt.Errorf("type %s: %s is not a FuncProto", dt, t.Type) - } - - // Ensure the number of argument tag lists equals the number of arguments - if len(t.ParamTags) == 0 { - t.ParamTags = make([][]string, len(fp.Params)) - } - - if dt.Index >= 0 { - if dt.Index >= len(fp.Params) { - return nil, fmt.Errorf("type %s: component idx %d exceeds params of %s", dt, dt.Index, t) - } - - t.ParamTags[dt.Index] = append(t.ParamTags[dt.Index], dt.Value) - continue - } - - if dt.Index == -1 { - t.Tags = append(t.Tags, dt.Value) - continue - } - - return nil, fmt.Errorf("type %s: decl tag for type %s has invalid component idx", dt, t) - - default: - return nil, fmt.Errorf("type %s: decl tag for type %s is not supported", dt, t) - } - } - - return types, nil -} - // essentialName represents the name of a BTF type stripped of any flavor // suffixes after a ___ delimiter. type essentialName string @@ -1347,7 +889,7 @@ type formattableType interface { // Handles cyclical types by only printing cycles up to a certain depth. Elements // in extra are separated by spaces unless the preceding element is a string // ending in '='. -func formatType(f fmt.State, verb rune, t formattableType, extra ...interface{}) { +func formatType(f fmt.State, verb rune, t formattableType, extra ...any) { if verb != 'v' && verb != 's' { fmt.Fprintf(f, "{UNRECOGNIZED: %c}", verb) return diff --git a/vendor/github.com/cilium/ebpf/btf/unmarshal.go b/vendor/github.com/cilium/ebpf/btf/unmarshal.go new file mode 100644 index 0000000000..b56b5a0681 --- /dev/null +++ b/vendor/github.com/cilium/ebpf/btf/unmarshal.go @@ -0,0 +1,764 @@ +package btf + +import ( + "bytes" + "encoding/binary" + "fmt" + "hash/maphash" + "io" + "iter" + "maps" + "math" + "slices" + "sync" +) + +// sharedBuf is a buffer which may be shared between multiple decoders. +// +// It must not be modified. Some sharedBuf may be backed by an mmap-ed file, in +// which case the sharedBuf has a finalizer. sharedBuf must therefore always be +// passed as a pointer. +type sharedBuf struct { + raw []byte +} + +type decoder struct { + // Immutable fields, may be shared. + + base *decoder + byteOrder binary.ByteOrder + *sharedBuf + strings *stringTable + // The ID for offsets[0]. + firstTypeID TypeID + // Map from TypeID to offset of the marshaled data in raw. Contains an entry + // for each TypeID, including 0 aka Void. The offset for Void is invalid. + offsets []int + declTags map[TypeID][]TypeID + // An index from essentialName to TypeID. + namedTypes *fuzzyStringIndex + + // Protection for mutable fields below. + mu sync.Mutex + types map[TypeID]Type + typeIDs map[Type]TypeID + legacyBitfields map[TypeID][2]Bits // offset, size +} + +func newDecoder(raw []byte, bo binary.ByteOrder, strings *stringTable, base *decoder) (*decoder, error) { + firstTypeID := TypeID(0) + if base != nil { + if base.byteOrder != bo { + return nil, fmt.Errorf("can't use %v base with %v split BTF", base.byteOrder, bo) + } + + if base.firstTypeID != 0 { + return nil, fmt.Errorf("can't use split BTF as base") + } + + firstTypeID = TypeID(len(base.offsets)) + } + + var header btfType + var numTypes, numDeclTags, numNamedTypes int + + for _, err := range allBtfTypeOffsets(raw, bo, &header) { + if err != nil { + return nil, err + } + + numTypes++ + + if header.Kind() == kindDeclTag { + numDeclTags++ + } + + if header.NameOff != 0 { + numNamedTypes++ + } + } + + if firstTypeID == 0 { + // Allocate an extra slot for Void so we don't have to deal with + // constant off by one issues. + numTypes++ + } + + offsets := make([]int, 0, numTypes) + declTags := make(map[TypeID][]TypeID, numDeclTags) + namedTypes := newFuzzyStringIndex(numNamedTypes) + + if firstTypeID == 0 { + // Add a sentinel for Void. + offsets = append(offsets, math.MaxInt) + } + + id := firstTypeID + TypeID(len(offsets)) + for offset := range allBtfTypeOffsets(raw, bo, &header) { + if id < firstTypeID { + return nil, fmt.Errorf("no more type IDs") + } + + offsets = append(offsets, offset) + + if header.Kind() == kindDeclTag { + declTags[header.Type()] = append(declTags[header.Type()], id) + } + + // Build named type index. + name, err := strings.LookupBytes(header.NameOff) + if err != nil { + return nil, fmt.Errorf("lookup type name for id %v: %w", id, err) + } + + if len(name) > 0 { + if i := bytes.Index(name, []byte("___")); i != -1 { + // Flavours are rare. It's cheaper to find the first index for some + // reason. + i = bytes.LastIndex(name, []byte("___")) + name = name[:i] + } + + namedTypes.Add(name, id) + } + + id++ + } + + namedTypes.Build() + + return &decoder{ + base, + bo, + &sharedBuf{raw}, + strings, + firstTypeID, + offsets, + declTags, + namedTypes, + sync.Mutex{}, + make(map[TypeID]Type), + make(map[Type]TypeID), + make(map[TypeID][2]Bits), + }, nil +} + +func allBtfTypeOffsets(buf []byte, bo binary.ByteOrder, header *btfType) iter.Seq2[int, error] { + return func(yield func(int, error) bool) { + for offset := 0; offset < len(buf); { + start := offset + + n, err := unmarshalBtfType(header, buf[offset:], bo) + if err != nil { + yield(-1, fmt.Errorf("unmarshal type header: %w", err)) + return + } + offset += n + + n, err = header.DataLen() + if err != nil { + yield(-1, err) + return + } + offset += n + + if offset > len(buf) { + yield(-1, fmt.Errorf("auxiliary type data: %w", io.ErrUnexpectedEOF)) + return + } + + if !yield(start, nil) { + return + } + } + } +} + +// Copy performs a deep copy of a decoder and its base. +func (d *decoder) Copy() *decoder { + if d == nil { + return nil + } + + return d.copy(nil) +} + +func (d *decoder) copy(copiedTypes map[Type]Type) *decoder { + if d == nil { + return nil + } + + d.mu.Lock() + defer d.mu.Unlock() + + if copiedTypes == nil { + copiedTypes = make(map[Type]Type, len(d.types)) + } + + types := make(map[TypeID]Type, len(d.types)) + typeIDs := make(map[Type]TypeID, len(d.typeIDs)) + for id, typ := range d.types { + types[id] = copyType(typ, d.typeIDs, copiedTypes, typeIDs) + } + + return &decoder{ + d.base.copy(copiedTypes), + d.byteOrder, + d.sharedBuf, + d.strings, + d.firstTypeID, + d.offsets, + d.declTags, + d.namedTypes, + sync.Mutex{}, + types, + typeIDs, + maps.Clone(d.legacyBitfields), + } +} + +// TypeID returns the ID for a Type previously obtained via [TypeByID]. +func (d *decoder) TypeID(typ Type) (TypeID, error) { + if _, ok := typ.(*Void); ok { + // Equality is weird for void, since it is a zero sized type. + return 0, nil + } + + d.mu.Lock() + defer d.mu.Unlock() + + id, ok := d.typeIDs[typ] + if !ok { + return 0, fmt.Errorf("no ID for type %s: %w", typ, ErrNotFound) + } + + return id, nil +} + +// TypesByName returns all types which have the given essential name. +// +// Returns ErrNotFound if no matching Type exists. +func (d *decoder) TypesByName(name essentialName) ([]Type, error) { + var types []Type + for id := range d.namedTypes.Find(string(name)) { + typ, err := d.TypeByID(id) + if err != nil { + return nil, err + } + + if newEssentialName(typ.TypeName()) == name { + // Deal with hash collisions by checking against the name. + types = append(types, typ) + } + } + + if len(types) == 0 { + // Return an unwrapped error because this is on the hot path + // for CO-RE. + return nil, ErrNotFound + } + + return types, nil +} + +// TypeByID decodes a type and any of its descendants. +func (d *decoder) TypeByID(id TypeID) (Type, error) { + d.mu.Lock() + defer d.mu.Unlock() + + return d.inflateType(id) +} + +func (d *decoder) inflateType(id TypeID) (typ Type, err error) { + defer func() { + if r := recover(); r != nil { + err = r.(error) + } + + // err is the return value of the enclosing function, even if an explicit + // return is used. + // See https://go.dev/ref/spec#Defer_statements + if err != nil { + // Remove partially inflated type so that d.types only contains + // fully inflated ones. + delete(d.types, id) + } else { + // Populate reverse index. + d.typeIDs[typ] = id + } + }() + + if id < d.firstTypeID { + return d.base.inflateType(id) + } + + if id == 0 { + // Void is defined to always be type ID 0, and is thus omitted from BTF. + // Fast-path because it is looked up frequently. + return (*Void)(nil), nil + } + + if typ, ok := d.types[id]; ok { + return typ, nil + } + + fixup := func(id TypeID, typ *Type) { + fixup, err := d.inflateType(id) + if err != nil { + panic(err) + } + *typ = fixup + } + + convertMembers := func(header *btfType, buf []byte) ([]Member, error) { + var bm btfMember + members := make([]Member, 0, header.Vlen()) + for i := range header.Vlen() { + n, err := unmarshalBtfMember(&bm, buf, d.byteOrder) + if err != nil { + return nil, fmt.Errorf("unmarshal member: %w", err) + } + buf = buf[n:] + + name, err := d.strings.Lookup(bm.NameOff) + if err != nil { + return nil, fmt.Errorf("can't get name for member %d: %w", i, err) + } + + members = append(members, Member{ + Name: name, + Offset: Bits(bm.Offset), + }) + + m := &members[i] + fixup(bm.Type, &m.Type) + + if header.Bitfield() { + m.BitfieldSize = Bits(bm.Offset >> 24) + m.Offset &= 0xffffff + // We ignore legacy bitfield definitions if the current composite + // is a new-style bitfield. This is kind of safe since offset and + // size on the type of the member must be zero if kindFlat is set + // according to spec. + continue + } + + // This may be a legacy bitfield, try to fix it up. + data, ok := d.legacyBitfields[bm.Type] + if ok { + // Bingo! + m.Offset += data[0] + m.BitfieldSize = data[1] + continue + } + } + return members, nil + } + + idx := int(id - d.firstTypeID) + if idx >= len(d.offsets) { + return nil, fmt.Errorf("type id %v: %w", id, ErrNotFound) + } + + offset := d.offsets[idx] + if offset >= len(d.raw) { + return nil, fmt.Errorf("offset out of bounds") + } + + var ( + header btfType + bInt btfInt + bArr btfArray + bVariable btfVariable + bDeclTag btfDeclTag + pos = d.raw[offset:] + ) + + { + if n, err := unmarshalBtfType(&header, pos, d.byteOrder); err != nil { + return nil, fmt.Errorf("can't unmarshal type info for id %v: %v", id, err) + } else { + pos = pos[n:] + } + + name, err := d.strings.Lookup(header.NameOff) + if err != nil { + return nil, fmt.Errorf("get name for type id %d: %w", id, err) + } + + switch header.Kind() { + case kindInt: + size := header.Size() + if _, err := unmarshalBtfInt(&bInt, pos, d.byteOrder); err != nil { + return nil, fmt.Errorf("can't unmarshal btfInt, id: %d: %w", id, err) + } + if bInt.Offset() > 0 || bInt.Bits().Bytes() != size { + d.legacyBitfields[id] = [2]Bits{bInt.Offset(), bInt.Bits()} + } + typ = &Int{name, header.Size(), bInt.Encoding()} + d.types[id] = typ + + case kindPointer: + ptr := &Pointer{nil} + d.types[id] = ptr + + fixup(header.Type(), &ptr.Target) + typ = ptr + + case kindArray: + if _, err := unmarshalBtfArray(&bArr, pos, d.byteOrder); err != nil { + return nil, fmt.Errorf("can't unmarshal btfArray, id: %d: %w", id, err) + } + + arr := &Array{nil, nil, bArr.Nelems} + d.types[id] = arr + + fixup(bArr.IndexType, &arr.Index) + fixup(bArr.Type, &arr.Type) + typ = arr + + case kindStruct: + str := &Struct{name, header.Size(), nil, nil} + d.types[id] = str + typ = str + + str.Members, err = convertMembers(&header, pos) + if err != nil { + return nil, fmt.Errorf("struct %s (id %d): %w", name, id, err) + } + + case kindUnion: + uni := &Union{name, header.Size(), nil, nil} + d.types[id] = uni + typ = uni + + uni.Members, err = convertMembers(&header, pos) + if err != nil { + return nil, fmt.Errorf("union %s (id %d): %w", name, id, err) + } + + case kindEnum: + enum := &Enum{name, header.Size(), header.Signed(), nil} + d.types[id] = enum + typ = enum + + var be btfEnum + enum.Values = make([]EnumValue, 0, header.Vlen()) + for i := range header.Vlen() { + n, err := unmarshalBtfEnum(&be, pos, d.byteOrder) + if err != nil { + return nil, fmt.Errorf("unmarshal btfEnum %d, id: %d: %w", i, id, err) + } + pos = pos[n:] + + name, err := d.strings.Lookup(be.NameOff) + if err != nil { + return nil, fmt.Errorf("get name for enum value %d: %s", i, err) + } + + value := uint64(be.Val) + if enum.Signed { + // Sign extend values to 64 bit. + value = uint64(int32(be.Val)) + } + enum.Values = append(enum.Values, EnumValue{name, value}) + } + + case kindForward: + typ = &Fwd{name, header.FwdKind()} + d.types[id] = typ + + case kindTypedef: + typedef := &Typedef{name, nil, nil} + d.types[id] = typedef + + fixup(header.Type(), &typedef.Type) + typ = typedef + + case kindVolatile: + volatile := &Volatile{nil} + d.types[id] = volatile + + fixup(header.Type(), &volatile.Type) + typ = volatile + + case kindConst: + cnst := &Const{nil} + d.types[id] = cnst + + fixup(header.Type(), &cnst.Type) + typ = cnst + + case kindRestrict: + restrict := &Restrict{nil} + d.types[id] = restrict + + fixup(header.Type(), &restrict.Type) + typ = restrict + + case kindFunc: + fn := &Func{name, nil, header.Linkage(), nil, nil} + d.types[id] = fn + + fixup(header.Type(), &fn.Type) + typ = fn + + case kindFuncProto: + fp := &FuncProto{} + d.types[id] = fp + + params := make([]FuncParam, 0, header.Vlen()) + var bParam btfParam + for i := range header.Vlen() { + n, err := unmarshalBtfParam(&bParam, pos, d.byteOrder) + if err != nil { + return nil, fmt.Errorf("can't unmarshal btfParam %d, id: %d: %w", i, id, err) + } + pos = pos[n:] + + name, err := d.strings.Lookup(bParam.NameOff) + if err != nil { + return nil, fmt.Errorf("get name for func proto parameter %d: %s", i, err) + } + + param := FuncParam{Name: name} + fixup(bParam.Type, ¶m.Type) + params = append(params, param) + } + + fixup(header.Type(), &fp.Return) + fp.Params = params + typ = fp + + case kindVar: + if _, err := unmarshalBtfVariable(&bVariable, pos, d.byteOrder); err != nil { + return nil, fmt.Errorf("can't read btfVariable, id: %d: %w", id, err) + } + + v := &Var{name, nil, VarLinkage(bVariable.Linkage), nil} + d.types[id] = v + + fixup(header.Type(), &v.Type) + typ = v + + case kindDatasec: + ds := &Datasec{name, header.Size(), nil} + d.types[id] = ds + + vlen := header.Vlen() + vars := make([]VarSecinfo, 0, vlen) + var bSecInfo btfVarSecinfo + for i := range vlen { + n, err := unmarshalBtfVarSecInfo(&bSecInfo, pos, d.byteOrder) + if err != nil { + return nil, fmt.Errorf("can't unmarshal btfVarSecinfo %d, id: %d: %w", i, id, err) + } + pos = pos[n:] + + vs := VarSecinfo{ + Offset: bSecInfo.Offset, + Size: bSecInfo.Size, + } + fixup(bSecInfo.Type, &vs.Type) + vars = append(vars, vs) + } + ds.Vars = vars + typ = ds + + case kindFloat: + typ = &Float{name, header.Size()} + d.types[id] = typ + + case kindDeclTag: + if _, err := unmarshalBtfDeclTag(&bDeclTag, pos, d.byteOrder); err != nil { + return nil, fmt.Errorf("can't read btfDeclTag, id: %d: %w", id, err) + } + + btfIndex := bDeclTag.ComponentIdx + if uint64(btfIndex) > math.MaxInt { + return nil, fmt.Errorf("type id %d: index exceeds int", id) + } + + dt := &declTag{nil, name, int(int32(btfIndex))} + d.types[id] = dt + + fixup(header.Type(), &dt.Type) + typ = dt + + case kindTypeTag: + tt := &TypeTag{nil, name} + d.types[id] = tt + + fixup(header.Type(), &tt.Type) + typ = tt + + case kindEnum64: + enum := &Enum{name, header.Size(), header.Signed(), nil} + d.types[id] = enum + typ = enum + + enum.Values = make([]EnumValue, 0, header.Vlen()) + var bEnum64 btfEnum64 + for i := range header.Vlen() { + n, err := unmarshalBtfEnum64(&bEnum64, pos, d.byteOrder) + if err != nil { + return nil, fmt.Errorf("can't unmarshal btfEnum64 %d, id: %d: %w", i, id, err) + } + pos = pos[n:] + + name, err := d.strings.Lookup(bEnum64.NameOff) + if err != nil { + return nil, fmt.Errorf("get name for enum64 value %d: %s", i, err) + } + value := (uint64(bEnum64.ValHi32) << 32) | uint64(bEnum64.ValLo32) + enum.Values = append(enum.Values, EnumValue{name, value}) + } + + default: + return nil, fmt.Errorf("type id %d: unknown kind: %v", id, header.Kind()) + } + } + + for _, tagID := range d.declTags[id] { + dtType, err := d.inflateType(tagID) + if err != nil { + return nil, err + } + + dt, ok := dtType.(*declTag) + if !ok { + return nil, fmt.Errorf("type id %v: not a declTag", tagID) + } + + switch t := typ.(type) { + case *Var: + if dt.Index != -1 { + return nil, fmt.Errorf("type %s: component idx %d is not -1", dt, dt.Index) + } + t.Tags = append(t.Tags, dt.Value) + + case *Typedef: + if dt.Index != -1 { + return nil, fmt.Errorf("type %s: component idx %d is not -1", dt, dt.Index) + } + t.Tags = append(t.Tags, dt.Value) + + case composite: + if dt.Index >= 0 { + members := t.members() + if dt.Index >= len(members) { + return nil, fmt.Errorf("type %s: component idx %d exceeds members of %s", dt, dt.Index, t) + } + + members[dt.Index].Tags = append(members[dt.Index].Tags, dt.Value) + } else if dt.Index == -1 { + switch t2 := t.(type) { + case *Struct: + t2.Tags = append(t2.Tags, dt.Value) + case *Union: + t2.Tags = append(t2.Tags, dt.Value) + } + } else { + return nil, fmt.Errorf("type %s: decl tag for type %s has invalid component idx", dt, t) + } + + case *Func: + fp, ok := t.Type.(*FuncProto) + if !ok { + return nil, fmt.Errorf("type %s: %s is not a FuncProto", dt, t.Type) + } + + // Ensure the number of argument tag lists equals the number of arguments + if len(t.ParamTags) == 0 { + t.ParamTags = make([][]string, len(fp.Params)) + } + + if dt.Index >= 0 { + if dt.Index >= len(fp.Params) { + return nil, fmt.Errorf("type %s: component idx %d exceeds params of %s", dt, dt.Index, t) + } + + t.ParamTags[dt.Index] = append(t.ParamTags[dt.Index], dt.Value) + } else if dt.Index == -1 { + t.Tags = append(t.Tags, dt.Value) + } else { + return nil, fmt.Errorf("type %s: decl tag for type %s has invalid component idx", dt, t) + } + + default: + return nil, fmt.Errorf("type %s: decl tag for type %s is not supported", dt, t) + } + } + + return typ, nil +} + +// An index from string to TypeID. +// +// Fuzzy because it may return false positive matches. +type fuzzyStringIndex struct { + seed maphash.Seed + entries []fuzzyStringIndexEntry +} + +func newFuzzyStringIndex(capacity int) *fuzzyStringIndex { + return &fuzzyStringIndex{ + maphash.MakeSeed(), + make([]fuzzyStringIndexEntry, 0, capacity), + } +} + +// Add a string to the index. +// +// Calling the method with identical arguments will create duplicate entries. +func (idx *fuzzyStringIndex) Add(name []byte, id TypeID) { + hash := uint32(maphash.Bytes(idx.seed, name)) + idx.entries = append(idx.entries, newFuzzyStringIndexEntry(hash, id)) +} + +// Build the index. +// +// Must be called after [Add] and before [Match]. +func (idx *fuzzyStringIndex) Build() { + slices.Sort(idx.entries) +} + +// Find TypeIDs which may match the name. +// +// May return false positives, but is guaranteed to not have false negatives. +// +// You must call [Build] at least once before calling this method. +func (idx *fuzzyStringIndex) Find(name string) iter.Seq[TypeID] { + return func(yield func(TypeID) bool) { + hash := uint32(maphash.String(idx.seed, name)) + + // We match only on the first 32 bits here, so ignore found. + i, _ := slices.BinarySearch(idx.entries, fuzzyStringIndexEntry(hash)<<32) + for i := i; i < len(idx.entries); i++ { + if idx.entries[i].hash() != hash { + break + } + + if !yield(idx.entries[i].id()) { + return + } + } + } +} + +// Tuple mapping the hash of an essential name to a type. +// +// Encoded in an uint64 so that it implements cmp.Ordered. +type fuzzyStringIndexEntry uint64 + +func newFuzzyStringIndexEntry(hash uint32, id TypeID) fuzzyStringIndexEntry { + return fuzzyStringIndexEntry(hash)<<32 | fuzzyStringIndexEntry(id) +} + +func (e fuzzyStringIndexEntry) hash() uint32 { + return uint32(e >> 32) +} + +func (e fuzzyStringIndexEntry) id() TypeID { + return TypeID(e) +} diff --git a/vendor/github.com/cilium/ebpf/collection.go b/vendor/github.com/cilium/ebpf/collection.go index a7b6cb31bf..42621e74c3 100644 --- a/vendor/github.com/cilium/ebpf/collection.go +++ b/vendor/github.com/cilium/ebpf/collection.go @@ -4,15 +4,18 @@ import ( "encoding/binary" "errors" "fmt" + "path/filepath" "reflect" + "runtime" + "slices" "strings" - "github.com/cilium/ebpf/asm" "github.com/cilium/ebpf/btf" "github.com/cilium/ebpf/internal" "github.com/cilium/ebpf/internal/kallsyms" "github.com/cilium/ebpf/internal/kconfig" "github.com/cilium/ebpf/internal/linux" + "github.com/cilium/ebpf/internal/platform" "github.com/cilium/ebpf/internal/sys" ) @@ -33,6 +36,12 @@ type CollectionOptions struct { // The given Maps are Clone()d before being used in the Collection, so the // caller can Close() them freely when they are no longer needed. MapReplacements map[string]*Map + + // Cache amortises the cost of decoding kernel BTF across multiple + // Collection loads. Callers loading more than one Collection should + // allocate a Cache via [btf.NewCache] and share it across loads. + // If nil, a fresh cache is allocated per load and discarded. + Cache *btf.Cache } // CollectionSpec describes a collection. @@ -61,117 +70,34 @@ func (cs *CollectionSpec) Copy() *CollectionSpec { } cpy := CollectionSpec{ - Maps: make(map[string]*MapSpec, len(cs.Maps)), - Programs: make(map[string]*ProgramSpec, len(cs.Programs)), + Maps: copyMapOfSpecs(cs.Maps), + Programs: copyMapOfSpecs(cs.Programs), Variables: make(map[string]*VariableSpec, len(cs.Variables)), ByteOrder: cs.ByteOrder, Types: cs.Types.Copy(), } - for name, spec := range cs.Maps { - cpy.Maps[name] = spec.Copy() - } - - for name, spec := range cs.Programs { - cpy.Programs[name] = spec.Copy() - } - for name, spec := range cs.Variables { - cpy.Variables[name] = spec.copy(&cpy) + cpy.Variables[name] = spec.Copy() } - - return &cpy -} - -// RewriteMaps replaces all references to specific maps. -// -// Use this function to use pre-existing maps instead of creating new ones -// when calling NewCollection. Any named maps are removed from CollectionSpec.Maps. -// -// Returns an error if a named map isn't used in at least one program. -// -// Deprecated: Pass CollectionOptions.MapReplacements when loading the Collection -// instead. -func (cs *CollectionSpec) RewriteMaps(maps map[string]*Map) error { - for symbol, m := range maps { - // have we seen a program that uses this symbol / map - seen := false - for progName, progSpec := range cs.Programs { - err := progSpec.Instructions.AssociateMap(symbol, m) - - switch { - case err == nil: - seen = true - - case errors.Is(err, asm.ErrUnreferencedSymbol): - // Not all programs need to use the map - - default: - return fmt.Errorf("program %s: %w", progName, err) - } - } - - if !seen { - return fmt.Errorf("map %s not referenced by any programs", symbol) - } - - // Prevent NewCollection from creating rewritten maps - delete(cs.Maps, symbol) + if cs.Variables == nil { + cpy.Variables = nil } - return nil -} - -// MissingConstantsError is returned by [CollectionSpec.RewriteConstants]. -type MissingConstantsError struct { - // The constants missing from .rodata. - Constants []string -} - -func (m *MissingConstantsError) Error() string { - return fmt.Sprintf("some constants are missing from .rodata: %s", strings.Join(m.Constants, ", ")) + return &cpy } -// RewriteConstants replaces the value of multiple constants. -// -// The constant must be defined like so in the C program: -// -// volatile const type foobar; -// volatile const type foobar = default; -// -// Replacement values must be of the same length as the C sizeof(type). -// If necessary, they are marshalled according to the same rules as -// map values. -// -// From Linux 5.5 the verifier will use constants to eliminate dead code. -// -// Returns an error wrapping [MissingConstantsError] if a constant doesn't exist. -// -// Deprecated: Use [CollectionSpec.Variables] to interact with constants instead. -// RewriteConstants is now a wrapper around the VariableSpec API. -func (cs *CollectionSpec) RewriteConstants(consts map[string]interface{}) error { - var missing []string - for n, c := range consts { - v, ok := cs.Variables[n] - if !ok { - missing = append(missing, n) - continue - } - - if !v.Constant() { - return fmt.Errorf("variable %s is not a constant", n) - } - - if err := v.Set(c); err != nil { - return fmt.Errorf("rewriting constant %s: %w", n, err) - } +func copyMapOfSpecs[T interface{ Copy() T }](m map[string]T) map[string]T { + if m == nil { + return nil } - if len(missing) != 0 { - return fmt.Errorf("rewrite constants: %w", &MissingConstantsError{Constants: missing}) + cpy := make(map[string]T, len(m)) + for k, v := range m { + cpy[k] = v.Copy() } - return nil + return cpy } // Assign the contents of a CollectionSpec to a struct. @@ -195,22 +121,22 @@ func (cs *CollectionSpec) RewriteConstants(consts map[string]interface{}) error // // Returns an error if any of the eBPF objects can't be found, or // if the same Spec is assigned multiple times. -func (cs *CollectionSpec) Assign(to interface{}) error { - getValue := func(typ reflect.Type, name string) (interface{}, error) { +func (cs *CollectionSpec) Assign(to any) error { + getValue := func(typ reflect.Type, name string) (any, error) { switch typ { - case reflect.TypeOf((*ProgramSpec)(nil)): + case reflect.TypeFor[*ProgramSpec](): if p := cs.Programs[name]; p != nil { return p, nil } return nil, fmt.Errorf("missing program %q", name) - case reflect.TypeOf((*MapSpec)(nil)): + case reflect.TypeFor[*MapSpec](): if m := cs.Maps[name]; m != nil { return m, nil } return nil, fmt.Errorf("missing map %q", name) - case reflect.TypeOf((*VariableSpec)(nil)): + case reflect.TypeFor[*VariableSpec](): if v := cs.Variables[name]; v != nil { return v, nil } @@ -251,7 +177,7 @@ func (cs *CollectionSpec) Assign(to interface{}) error { // // Returns an error if any of the fields can't be found, or // if the same Map or Program is assigned multiple times. -func (cs *CollectionSpec) LoadAndAssign(to interface{}, opts *CollectionOptions) error { +func (cs *CollectionSpec) LoadAndAssign(to any, opts *CollectionOptions) error { loader, err := newCollectionLoader(cs, opts) if err != nil { return err @@ -263,18 +189,18 @@ func (cs *CollectionSpec) LoadAndAssign(to interface{}, opts *CollectionOptions) assignedProgs := make(map[string]bool) assignedVars := make(map[string]bool) - getValue := func(typ reflect.Type, name string) (interface{}, error) { + getValue := func(typ reflect.Type, name string) (any, error) { switch typ { - case reflect.TypeOf((*Program)(nil)): + case reflect.TypeFor[*Program](): assignedProgs[name] = true return loader.loadProgram(name) - case reflect.TypeOf((*Map)(nil)): + case reflect.TypeFor[*Map](): assignedMaps[name] = true return loader.loadMap(name) - case reflect.TypeOf((*Variable)(nil)): + case reflect.TypeFor[*Variable](): assignedVars[name] = true return loader.loadVariable(name) @@ -285,18 +211,17 @@ func (cs *CollectionSpec) LoadAndAssign(to interface{}, opts *CollectionOptions) // Load the Maps and Programs requested by the annotated struct. if err := assignValues(to, getValue); err != nil { - return err + return fmt.Errorf("assign values: %w", err) } // Populate the requested maps. Has a chance of lazy-loading other dependent maps. if err := loader.populateDeferredMaps(); err != nil { - return err + return fmt.Errorf("populate deferred maps: %w", err) } // Evaluate the loader's objects after all (lazy)loading has taken place. for n, m := range loader.maps { - switch m.typ { - case ProgramArray: + if m.typ.canStoreProgram() { // Require all lazy-loaded ProgramArrays to be assigned to the given object. // The kernel empties a ProgramArray once the last user space reference // to it closes, which leads to failed tail calls. Combined with the library @@ -403,6 +328,7 @@ type collectionLoader struct { maps map[string]*Map programs map[string]*Program vars map[string]*Variable + types *btf.Cache } func newCollectionLoader(coll *CollectionSpec, opts *CollectionOptions) (*collectionLoader, error) { @@ -421,12 +347,18 @@ func newCollectionLoader(coll *CollectionSpec, opts *CollectionOptions) (*collec return nil, fmt.Errorf("populating kallsyms caches: %w", err) } + cache := opts.Cache + if cache == nil { + cache = btf.NewCache() + } + return &collectionLoader{ coll, opts, make(map[string]*Map), make(map[string]*Program), make(map[string]*Variable), + cache, }, nil } @@ -434,20 +366,6 @@ func newCollectionLoader(coll *CollectionSpec, opts *CollectionOptions) (*collec // during individual program loading. Since we have less context available // at those stages, we batch the lookups here instead to avoid redundant work. func populateKallsyms(progs map[string]*ProgramSpec) error { - // Look up associated kernel modules for all symbols referenced by - // ProgramSpec.AttachTo for program types that support attaching to kmods. - mods := make(map[string]string) - for _, p := range progs { - if p.AttachTo != "" && p.targetsKernelModule() { - mods[p.AttachTo] = "" - } - } - if len(mods) != 0 { - if err := kallsyms.AssignModules(mods); err != nil { - return fmt.Errorf("getting modules from kallsyms: %w", err) - } - } - // Look up addresses of all kernel symbols referenced by all programs. addrs := make(map[string]uint64) for _, p := range progs { @@ -515,7 +433,11 @@ func (cl *collectionLoader) loadMap(mapName string) (*Map, error) { return m, nil } - m, err := newMapWithOptions(mapSpec, cl.opts.Maps) + if err := mapSpec.updateDataSection(cl.coll.Variables, mapName); err != nil { + return nil, fmt.Errorf("assembling contents of map %s: %w", mapName, err) + } + + m, err := newMapWithOptions(mapSpec, cl.opts.Maps, cl.types) if err != nil { return nil, fmt.Errorf("map %s: %w", mapName, err) } @@ -525,6 +447,7 @@ func (cl *collectionLoader) loadMap(mapName string) (*Map, error) { // that need to be finalized before invoking the verifier. if !mapSpec.Type.canStoreMapOrProgram() { if err := m.finalize(mapSpec); err != nil { + _ = m.Close() return nil, fmt.Errorf("finalizing map %s: %w", mapName, err) } } @@ -577,12 +500,13 @@ func (cl *collectionLoader) loadProgram(progName string) (*Program, error) { } } - prog, err := newProgramWithOptions(progSpec, cl.opts.Programs) + prog, err := newProgramWithOptions(progSpec, cl.opts.Programs, cl.types) if err != nil { return nil, fmt.Errorf("program %s: %w", progName, err) } cl.programs[progName] = prog + return prog, nil } @@ -596,19 +520,7 @@ func (cl *collectionLoader) loadVariable(varName string) (*Variable, error) { return nil, fmt.Errorf("unknown variable %s", varName) } - // Get the key of the VariableSpec's MapSpec in the CollectionSpec. - var mapName string - for n, ms := range cl.coll.Maps { - if ms == varSpec.m { - mapName = n - break - } - } - if mapName == "" { - return nil, fmt.Errorf("variable %s: underlying MapSpec %s was removed from CollectionSpec", varName, varSpec.m.Name) - } - - m, err := cl.loadMap(mapName) + m, err := cl.loadMap(varSpec.SectionName) if err != nil { return nil, fmt.Errorf("variable %s: %w", varName, err) } @@ -618,16 +530,21 @@ func (cl *collectionLoader) loadVariable(varName string) (*Variable, error) { // emit a Variable with a nil Memory. This keeps Collection{Spec}.Variables // consistent across systems with different feature sets without breaking // LoadAndAssign. - mm, err := m.Memory() + var mm *Memory + if unsafeMemory { + mm, err = m.unsafeMemory() + } else { + mm, err = m.Memory() + } if err != nil && !errors.Is(err, ErrNotSupported) { - return nil, fmt.Errorf("variable %s: getting memory for map %s: %w", varName, mapName, err) + return nil, fmt.Errorf("variable %s: getting memory for map %s: %w", varName, varSpec.SectionName, err) } v, err := newVariable( - varSpec.name, - varSpec.offset, - varSpec.size, - varSpec.t, + varSpec.Name, + varSpec.Offset, + varSpec.Size(), + varSpec.Type, mm, ) if err != nil { @@ -685,6 +602,13 @@ func (cl *collectionLoader) populateDeferredMaps() error { } } + if mapSpec.Type == StructOpsMap { + // populate StructOps data into `kernVData` + if err := cl.populateStructOps(m, mapSpec); err != nil { + return err + } + } + // Populate and freeze the map if specified. if err := m.finalize(mapSpec); err != nil { return fmt.Errorf("populating map %s: %w", mapName, err) @@ -694,6 +618,97 @@ func (cl *collectionLoader) populateDeferredMaps() error { return nil } +// populateStructOps translates the user struct bytes into the kernel value struct +// layout for a struct_ops map and writes the result back to mapSpec.Contents[0]. +func (cl *collectionLoader) populateStructOps(m *Map, mapSpec *MapSpec) error { + userType, ok := btf.As[*btf.Struct](mapSpec.Value) + if !ok { + return fmt.Errorf("value should be a *Struct") + } + + userData, err := mapSpec.dataSection() + if err != nil { + return fmt.Errorf("getting data section: %w", err) + } + if len(userData) < int(userType.Size) { + return fmt.Errorf("user data too short: have %d, need at least %d", len(userData), userType.Size) + } + + vType, _, module, err := structOpsFindTarget(userType, cl.types) + if err != nil { + return fmt.Errorf("struct_ops value type %q: %w", userType.Name, err) + } + defer module.Close() + + // Find the inner ops struct embedded in the value struct. + kType, kTypeOff, err := structOpsFindInnerType(vType) + if err != nil { + return err + } + + kernVData := make([]byte, int(vType.Size)) + for _, m := range userType.Members { + i := slices.IndexFunc(kType.Members, func(km btf.Member) bool { + return km.Name == m.Name + }) + + // Allow field to not exist in target as long as the source is zero. + if i == -1 { + mSize, err := btf.Sizeof(m.Type) + if err != nil { + return fmt.Errorf("sizeof(user.%s): %w", m.Name, err) + } + srcOff := int(m.Offset.Bytes()) + if srcOff < 0 || srcOff+mSize > len(userData) { + return fmt.Errorf("member %q: userdata is too small", m.Name) + } + + // let fail if the field in type user type is missing in type kern type + if !structOpsIsMemZeroed(userData[srcOff : srcOff+mSize]) { + return fmt.Errorf("%s doesn't exist in %s, but it has non-zero value", m.Name, kType.Name) + } + + continue + } + + km := kType.Members[i] + + switch btf.UnderlyingType(m.Type).(type) { + case *btf.Pointer: + // If this is a pointer → resolve struct_ops program. + psKey := kType.Name + ":" + m.Name + for k, ps := range cl.coll.Programs { + if ps.AttachTo == psKey { + p, ok := cl.programs[k] + if !ok || p == nil { + return nil + } + if err := structOpsPopulateValue(km, kernVData[kTypeOff:], p); err != nil { + return err + } + } + } + + default: + // Otherwise → memcpy the field contents. + if err := structOpsCopyMember(m, km, userData, kernVData[kTypeOff:]); err != nil { + return fmt.Errorf("field %s: %w", kType.Name, err) + } + } + } + + // Populate the map explicitly and keep a reference on cl.programs. + // This is necessary because we may inline fds into kernVData which + // may become invalid if the GC frees them. + if err := m.Put(uint32(0), kernVData); err != nil { + return err + } + mapSpec.Contents = nil + runtime.KeepAlive(cl.programs) + + return nil +} + // resolveKconfig resolves all variables declared in .kconfig and populates // m.Contents. Does nothing if the given m.Contents is non-empty. func resolveKconfig(m *MapSpec) error { @@ -702,6 +717,10 @@ func resolveKconfig(m *MapSpec) error { return errors.New("map value is not a Datasec") } + if platform.IsWindows { + return fmt.Errorf(".kconfig: %w", internal.ErrNotSupportedOnOS) + } + type configInfo struct { offset uint32 size uint32 @@ -794,6 +813,13 @@ func resolveKconfig(m *MapSpec) error { // Omitting Collection.Close() during application shutdown is an error. // See the package documentation for details around Map and Program lifecycle. func LoadCollection(file string) (*Collection, error) { + if platform.IsWindows { + // This mirrors a check in efW. + if ext := filepath.Ext(file); ext == ".sys" { + return loadCollectionFromNativeImage(file) + } + } + spec, err := LoadCollectionSpec(file) if err != nil { return nil, err @@ -823,31 +849,31 @@ func LoadCollection(file string) (*Collection, error) { // // Ownership and Close()ing responsibility is transferred to `to` // for any successful assigns. On error `to` is left in an undefined state. -func (coll *Collection) Assign(to interface{}) error { +func (coll *Collection) Assign(to any) error { assignedMaps := make(map[string]bool) assignedProgs := make(map[string]bool) assignedVars := make(map[string]bool) // Assign() only transfers already-loaded Maps and Programs. No extra // loading is done. - getValue := func(typ reflect.Type, name string) (interface{}, error) { + getValue := func(typ reflect.Type, name string) (any, error) { switch typ { - case reflect.TypeOf((*Program)(nil)): + case reflect.TypeFor[*Program](): if p := coll.Programs[name]; p != nil { assignedProgs[name] = true return p, nil } return nil, fmt.Errorf("missing program %q", name) - case reflect.TypeOf((*Map)(nil)): + case reflect.TypeFor[*Map](): if m := coll.Maps[name]; m != nil { assignedMaps[name] = true return m, nil } return nil, fmt.Errorf("missing map %q", name) - case reflect.TypeOf((*Variable)(nil)): + case reflect.TypeFor[*Variable](): if v := coll.Variables[name]; v != nil { assignedVars[name] = true return v, nil @@ -860,7 +886,7 @@ func (coll *Collection) Assign(to interface{}) error { } if err := assignValues(to, getValue); err != nil { - return err + return fmt.Errorf("assign values: %w", err) } // Finalize ownership transfer @@ -948,7 +974,7 @@ func ebpfFields(structVal reflect.Value, visited map[reflect.Type]bool) ([]struc // to a struct, attempt to gather its fields as well. var v reflect.Value switch field.Type.Kind() { - case reflect.Ptr: + case reflect.Pointer: if field.Type.Elem().Kind() != reflect.Struct { continue } @@ -983,18 +1009,16 @@ func ebpfFields(structVal reflect.Value, visited map[reflect.Type]bool) ([]struc // // getValue is called for every tagged field of 'to' and must return the value // to be assigned to the field with the given typ and name. -func assignValues(to interface{}, - getValue func(typ reflect.Type, name string) (interface{}, error)) error { +func assignValues(to any, getValue func(typ reflect.Type, name string) (any, error)) error { + if err := internal.IsNilPointer(to); err != nil { + return err + } toValue := reflect.ValueOf(to) - if toValue.Type().Kind() != reflect.Ptr { + if toValue.Type().Kind() != reflect.Pointer { return fmt.Errorf("%T is not a pointer to struct", to) } - if toValue.IsNil() { - return fmt.Errorf("nil pointer to %T", to) - } - fields, err := ebpfFields(toValue.Elem(), nil) if err != nil { return err diff --git a/vendor/github.com/cilium/ebpf/collection_other.go b/vendor/github.com/cilium/ebpf/collection_other.go new file mode 100644 index 0000000000..0e69bb83ac --- /dev/null +++ b/vendor/github.com/cilium/ebpf/collection_other.go @@ -0,0 +1,9 @@ +//go:build !windows + +package ebpf + +import "github.com/cilium/ebpf/internal" + +func loadCollectionFromNativeImage(_ string) (*Collection, error) { + return nil, internal.ErrNotSupportedOnOS +} diff --git a/vendor/github.com/cilium/ebpf/collection_windows.go b/vendor/github.com/cilium/ebpf/collection_windows.go new file mode 100644 index 0000000000..38ce73e8b8 --- /dev/null +++ b/vendor/github.com/cilium/ebpf/collection_windows.go @@ -0,0 +1,136 @@ +package ebpf + +import ( + "errors" + "fmt" + "unsafe" + + "github.com/cilium/ebpf/internal/efw" + "github.com/cilium/ebpf/internal/sys" + "github.com/cilium/ebpf/internal/unix" +) + +func loadCollectionFromNativeImage(file string) (_ *Collection, err error) { + mapFds := make([]efw.FD, 32) + programFds := make([]efw.FD, 32) + var maps map[string]*Map + var programs map[string]*Program + + defer func() { + if err == nil { + return + } + + for _, fd := range append(mapFds, programFds...) { + // efW never uses fd 0. + if fd != 0 { + _ = efw.EbpfCloseFd(int(fd)) + } + } + + for _, m := range maps { + _ = m.Close() + } + + for _, p := range programs { + _ = p.Close() + } + }() + + nMaps, nPrograms, err := efw.EbpfObjectLoadNativeFds(file, mapFds, programFds) + if errors.Is(err, efw.EBPF_NO_MEMORY) && (nMaps > len(mapFds) || nPrograms > len(programFds)) { + mapFds = make([]efw.FD, nMaps) + programFds = make([]efw.FD, nPrograms) + + nMaps, nPrograms, err = efw.EbpfObjectLoadNativeFds(file, mapFds, programFds) + } + if err != nil { + return nil, err + } + + mapFds = mapFds[:nMaps] + programFds = programFds[:nPrograms] + + // The maximum length of a name is only 16 bytes on Linux, longer names + // are truncated. This is not a problem when loading from an ELF, since + // we get the full object name from the symbol table. + // When loading a native image we do not have this luxury. Use an efW native + // API to retrieve up to 64 bytes of the object name. + + maps = make(map[string]*Map, len(mapFds)) + for _, raw := range mapFds { + fd, err := sys.NewFD(int(raw)) + if err != nil { + return nil, err + } + + m, mapErr := newMapFromFD(fd) + if mapErr != nil { + _ = fd.Close() + return nil, mapErr + } + + var efwMapInfo efw.BpfMapInfo + size := uint32(unsafe.Sizeof(efwMapInfo)) + _, err = efw.EbpfObjectGetInfoByFd(m.FD(), unsafe.Pointer(&efwMapInfo), &size) + if err != nil { + _ = m.Close() + return nil, err + } + + if size >= uint32(unsafe.Offsetof(efwMapInfo.Name)+unsafe.Sizeof(efwMapInfo.Name)) { + m.name = unix.ByteSliceToString(efwMapInfo.Name[:]) + } + + if m.name == "" { + _ = m.Close() + return nil, fmt.Errorf("unnamed map") + } + + if _, ok := maps[m.name]; ok { + return nil, fmt.Errorf("duplicate map with the same name: %s", m.name) + } + + maps[m.name] = m + } + + programs = make(map[string]*Program, len(programFds)) + for _, raw := range programFds { + fd, err := sys.NewFD(int(raw)) + if err != nil { + return nil, err + } + + program, err := newProgramFromFD(fd) + if err != nil { + _ = fd.Close() + return nil, err + } + + var efwProgInfo efw.BpfProgInfo + size := uint32(unsafe.Sizeof(efwProgInfo)) + _, err = efw.EbpfObjectGetInfoByFd(program.FD(), unsafe.Pointer(&efwProgInfo), &size) + if err != nil { + _ = program.Close() + return nil, err + } + + if size >= uint32(unsafe.Offsetof(efwProgInfo.Name)+unsafe.Sizeof(efwProgInfo.Name)) { + program.name = unix.ByteSliceToString(efwProgInfo.Name[:]) + } + + if program.name == "" { + _ = program.Close() + return nil, fmt.Errorf("unnamed program") + } + + if _, ok := programs[program.name]; ok { + _ = program.Close() + return nil, fmt.Errorf("duplicate program with the same name: %s", program.name) + } + + programs[program.name] = program + } + + return &Collection{programs, maps, nil}, nil +} diff --git a/vendor/github.com/cilium/ebpf/cpu.go b/vendor/github.com/cilium/ebpf/cpu.go index 07e959efdc..3bcdc386db 100644 --- a/vendor/github.com/cilium/ebpf/cpu.go +++ b/vendor/github.com/cilium/ebpf/cpu.go @@ -1,16 +1,5 @@ package ebpf -import ( - "fmt" - "os" - "strings" - "sync" -) - -var possibleCPU = sync.OnceValues(func() (int, error) { - return parseCPUsFromFile("/sys/devices/system/cpu/possible") -}) - // PossibleCPU returns the max number of CPUs a system may possibly have // Logical CPU numbers must be of the form 0-n func PossibleCPU() (int, error) { @@ -26,41 +15,3 @@ func MustPossibleCPU() int { } return cpus } - -func parseCPUsFromFile(path string) (int, error) { - spec, err := os.ReadFile(path) - if err != nil { - return 0, err - } - - n, err := parseCPUs(string(spec)) - if err != nil { - return 0, fmt.Errorf("can't parse %s: %v", path, err) - } - - return n, nil -} - -// parseCPUs parses the number of cpus from a string produced -// by bitmap_list_string() in the Linux kernel. -// Multiple ranges are rejected, since they can't be unified -// into a single number. -// This is the format of /sys/devices/system/cpu/possible, it -// is not suitable for /sys/devices/system/cpu/online, etc. -func parseCPUs(spec string) (int, error) { - if strings.Trim(spec, "\n") == "0" { - return 1, nil - } - - var low, high int - n, err := fmt.Sscanf(spec, "%d-%d\n", &low, &high) - if n != 2 || err != nil { - return 0, fmt.Errorf("invalid format: %s", spec) - } - if low != 0 { - return 0, fmt.Errorf("CPU spec doesn't start at zero: %s", spec) - } - - // cpus is 0 indexed - return high + 1, nil -} diff --git a/vendor/github.com/cilium/ebpf/cpu_other.go b/vendor/github.com/cilium/ebpf/cpu_other.go new file mode 100644 index 0000000000..eca5164c12 --- /dev/null +++ b/vendor/github.com/cilium/ebpf/cpu_other.go @@ -0,0 +1,13 @@ +//go:build !windows + +package ebpf + +import ( + "sync" + + "github.com/cilium/ebpf/internal/linux" +) + +var possibleCPU = sync.OnceValues(func() (int, error) { + return linux.ParseCPUsFromFile("/sys/devices/system/cpu/possible") +}) diff --git a/vendor/github.com/cilium/ebpf/cpu_windows.go b/vendor/github.com/cilium/ebpf/cpu_windows.go new file mode 100644 index 0000000000..9448b09164 --- /dev/null +++ b/vendor/github.com/cilium/ebpf/cpu_windows.go @@ -0,0 +1,11 @@ +package ebpf + +import ( + "sync" + + "golang.org/x/sys/windows" +) + +var possibleCPU = sync.OnceValues(func() (int, error) { + return int(windows.GetMaximumProcessorCount(windows.ALL_PROCESSOR_GROUPS)), nil +}) diff --git a/vendor/github.com/cilium/ebpf/elf_reader.go b/vendor/github.com/cilium/ebpf/elf_reader.go index 9e8dbc7ae5..e2f264912c 100644 --- a/vendor/github.com/cilium/ebpf/elf_reader.go +++ b/vendor/github.com/cilium/ebpf/elf_reader.go @@ -8,13 +8,17 @@ import ( "errors" "fmt" "io" + "iter" + "maps" "math" "os" + "slices" "strings" "github.com/cilium/ebpf/asm" "github.com/cilium/ebpf/btf" "github.com/cilium/ebpf/internal" + "github.com/cilium/ebpf/internal/platform" "github.com/cilium/ebpf/internal/sys" ) @@ -79,8 +83,8 @@ func LoadCollectionSpecFromReader(rd io.ReaderAt) (*CollectionSpec, error) { // Checks if the ELF file is for BPF data. // Old LLVM versions set e_machine to EM_NONE. - if f.File.Machine != elf.EM_NONE && f.File.Machine != elf.EM_BPF { - return nil, fmt.Errorf("unexpected machine type for BPF ELF: %s", f.File.Machine) + if f.Machine != elf.EM_NONE && f.Machine != elf.EM_BPF { + return nil, fmt.Errorf("unexpected machine type for BPF ELF: %s", f.Machine) } var ( @@ -114,8 +118,17 @@ func LoadCollectionSpecFromReader(rd io.ReaderAt) (*CollectionSpec, error) { case sec.Type == elf.SHT_REL: // Store relocations under the section index of the target relSections[elf.SectionIndex(sec.Info)] = sec - case sec.Type == elf.SHT_PROGBITS && (sec.Flags&elf.SHF_EXECINSTR) != 0 && sec.Size > 0: - sections[idx] = newElfSection(sec, programSection) + case sec.Type == elf.SHT_PROGBITS && sec.Size > 0: + if (sec.Flags&elf.SHF_EXECINSTR) != 0 && sec.Size > 0 { + sections[idx] = newElfSection(sec, programSection) + } else if sec.Name == structOpsLinkSec { + // classification based on sec names so that struct_ops-specific + // sections (.struct_ops.link) is correctly recognized + // as non-executable PROGBITS, allowing value placement and link metadata to be loaded. + sections[idx] = newElfSection(sec, structOpsSection) + } else if sec.Name == structOpsSec { + return nil, fmt.Errorf("section %q: got '.struct_ops' section: %w", sec.Name, ErrNotSupported) + } } } @@ -184,7 +197,18 @@ func LoadCollectionSpecFromReader(rd io.ReaderAt) (*CollectionSpec, error) { return nil, fmt.Errorf("load programs: %w", err) } - return &CollectionSpec{ec.maps, progs, ec.vars, btfSpec, ec.ByteOrder}, nil + // assiociate members in structs with ProgramSpecs using relo + if err := ec.associateStructOpsRelocs(progs); err != nil { + return nil, fmt.Errorf("load struct_ops: %w", err) + } + + return &CollectionSpec{ + ec.maps, + progs, + ec.vars, + btfSpec, + ec.ByteOrder, + }, nil } func loadLicense(sec *elf.Section) (string, error) { @@ -231,6 +255,7 @@ const ( btfMapSection programSection dataSection + structOpsSection ) type elfSection struct { @@ -255,6 +280,17 @@ func newElfSection(section *elf.Section, kind elfSectionKind) *elfSection { } } +// symbolsSorted returns the section's symbols sorted by offset. +func (es *elfSection) symbolsSorted() iter.Seq2[uint64, elf.Symbol] { + return func(yield func(uint64, elf.Symbol) bool) { + for _, off := range slices.Sorted(maps.Keys(es.symbols)) { + if !yield(off, es.symbols[off]) { + return + } + } + } +} + // assignSymbols takes a list of symbols and assigns them to their // respective sections, indexed by name. func (ec *elfCode) assignSymbols(symbols []elf.Symbol) { @@ -282,10 +318,11 @@ func (ec *elfCode) assignSymbols(symbols []elf.Symbol) { 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") { + + // Program sections may contain NOTYPE symbols with local scope, these are + // usually labels for jumps. We do not care for these for the purposes of + // linking and they may overlap with function symbols. + if symType == elf.STT_NOTYPE && elf.ST_BIND(symbol.Info) == elf.STB_LOCAL { continue } // Only collect symbols that occur in program/maps/data sections. @@ -345,6 +382,10 @@ func (ec *elfCode) loadProgramSections() (map[string]*ProgramSpec, error) { return nil, fmt.Errorf("section %v: missing symbols", sec.Name) } + if sec.ReaderAt == nil { + return nil, fmt.Errorf("compressed program section is not supported") + } + funcs, err := ec.loadFunctions(sec) if err != nil { return nil, fmt.Errorf("section %v: %w", sec.Name, err) @@ -397,46 +438,106 @@ func (ec *elfCode) loadProgramSections() (map[string]*ProgramSpec, error) { // 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) { - r := bufio.NewReader(section.Open()) - - // Decode the section's instruction stream. - insns := make(asm.Instructions, 0, section.Size/asm.InstructionSize) - if err := insns.Unmarshal(r, ec.ByteOrder); err != nil { - return nil, fmt.Errorf("decoding instructions for section %s: %w", section.Name, err) - } - if len(insns) == 0 { - return nil, fmt.Errorf("no instructions found in section %s", section.Name) - } +func (ec *elfCode) loadFunctions(sec *elfSection) (map[string]asm.Instructions, error) { + progs := make(map[string]asm.Instructions) + + // Pull out ExtInfos once per section to avoid map lookups on every + // instruction. + fo, lo, ro := ec.extInfo.Section(sec.Name) + + // Raw instruction count since start of the section. ExtInfos point at raw + // insn offsets and ignore the gaps between symbols in case of linked objects. + // We need to count them, we can't obtain this info by any other means. + var raw asm.RawInstructionOffset + + // Sort symbols by offset so we can track instructions by their raw offsets. + for _, sym := range sec.symbolsSorted() { + if progs[sym.Name] != nil { + return nil, fmt.Errorf("duplicate symbol %s in section %s", sym.Name, sec.Name) + } - iter := insns.Iterate() - for iter.Next() { - ins := iter.Ins - offset := iter.Offset.Bytes() + if sym.Value > math.MaxUint32 || sym.Size > math.MaxUint32 { + return nil, fmt.Errorf("symbol %s: offset or size exceeds 32 bits in section %s", sym.Name, sec.Name) + } + if sym.Value+sym.Size > sec.Size { + return nil, fmt.Errorf("symbol %s: size goes out of bounds of section %s", sym.Name, sec.Name) + } - // Tag Symbol Instructions. - if sym, ok := section.symbols[offset]; ok { - *ins = ins.WithSymbol(sym.Name) + // Decode the symbol's instruction stream, limited to its size. + sr := internal.NewBufferedSectionReader(sec, int64(sym.Value), int64(sym.Size)) + insns := make(asm.Instructions, 0, sym.Size/asm.InstructionSize) + insns, err := asm.AppendInstructions(insns, sr, ec.ByteOrder, platform.Linux) + if err != nil { + return nil, fmt.Errorf("decoding instructions for symbol %s in section %s: %w", sym.Name, sec.Name, err) + } + if len(insns) == 0 { + return nil, fmt.Errorf("no instructions found for symbol %s in section %s", sym.Name, sec.Name) } - // Apply any relocations for the current instruction. - // If no relocation is present, resolve any section-relative function calls. - if rel, ok := section.relocations[offset]; ok { - if err := ec.relocateInstruction(ins, rel); err != nil { - return nil, fmt.Errorf("offset %d: relocating instruction: %w", offset, err) - } - } else { - if err := referenceRelativeJump(ins, offset, section.symbols); err != nil { - return nil, fmt.Errorf("offset %d: resolving relative jump: %w", offset, err) + // Mark the first instruction as the start of a function. + insns[0] = insns[0].WithSymbol(sym.Name) + + iter := insns.Iterate() + for iter.Next() { + // Global byte offset of the instruction within the ELF section. + offset := sym.Value + iter.Offset.Bytes() + + // Apply any relocations for the current instruction. If no relocation is + // present, resolve any section-relative function calls. + if rel, ok := sec.relocations[offset]; ok { + if err := ec.relocateInstruction(iter.Ins, rel); err != nil { + return nil, fmt.Errorf("offset %d in section %s: relocating instruction: %w", offset, sec.Name, err) + } + } else { + if err := referenceRelativeJump(iter.Ins, offset, sec.symbols); err != nil { + return nil, fmt.Errorf("offset %d in section %s: resolving relative jump: %w", offset, sec.Name, err) + } } + + assignMetadata(iter.Ins, raw, &fo, &lo, &ro) + + raw += iter.Ins.Width() } + + // Emit the program's instructions. + progs[sym.Name] = insns + } + + return progs, nil +} + +// take pops and returns the first item in q if it matches the given predicate +// f. Otherwise, it returns nil. +func take[T any](q *[]T, f func(T) bool) *T { + if q == nil || len(*q) == 0 { + return nil } - if ec.extInfo != nil { - ec.extInfo.Assign(insns, section.Name) + out := (*q)[0] + if f(out) { + *q = (*q)[1:] + return &out } - return splitSymbols(insns) + return nil +} + +// Tag the instruction with any ExtInfo metadata that's pointing at the given +// raw instruction. +func assignMetadata(ins *asm.Instruction, raw asm.RawInstructionOffset, + fo *btf.FuncOffsets, lo *btf.LineOffsets, ro *btf.CORERelocationOffsets) { + + if f := take(fo, func(f btf.FuncOffset) bool { return f.Offset == raw }); f != nil { + *ins = btf.WithFuncMetadata(*ins, f.Func) + } + + if l := take(lo, func(l btf.LineOffset) bool { return l.Offset == raw }); l != nil { + *ins = ins.WithSource(l.Line) + } + + if r := take(ro, func(r btf.CORERelocationOffset) bool { return r.Offset == raw }); r != nil { + *ins = btf.WithCORERelocationMetadata(*ins, r.Relo) + } } // referenceRelativeJump turns a relative jump to another bpf subprogram within @@ -564,7 +665,7 @@ func (ec *elfCode) relocateInstruction(ins *asm.Instruction, rel elf.Symbol) err switch typ { case elf.STT_NOTYPE, elf.STT_FUNC: - if bind != elf.STB_GLOBAL { + if bind != elf.STB_GLOBAL && bind != elf.STB_WEAK { return fmt.Errorf("call: %s: %w: %s", name, errUnsupportedBinding, bind) } @@ -701,69 +802,102 @@ func (ec *elfCode) relocateInstruction(ins *asm.Instruction, rel elf.Symbol) err return nil } +// loadMaps iterates over all ELF sections marked as map sections (like .maps) +// and parses each symbol into a MapSpec. func (ec *elfCode) loadMaps() error { for _, sec := range ec.sections { if sec.kind != mapSection { continue } - nSym := len(sec.symbols) - if nSym == 0 { + if len(sec.symbols) == 0 { return fmt.Errorf("section %v: no symbols", sec.Name) } - if sec.Size%uint64(nSym) != 0 { - return fmt.Errorf("section %v: map descriptors are not of equal size", sec.Name) + if sec.ReaderAt == nil { + return fmt.Errorf("compressed map section is not supported") } - var ( - r = bufio.NewReader(sec.Open()) - size = sec.Size / uint64(nSym) - ) - for i, offset := 0, uint64(0); i < nSym; i, offset = i+1, offset+size { - mapSym, ok := sec.symbols[offset] - if !ok { - return fmt.Errorf("section %s: missing symbol for map at offset %d", sec.Name, offset) + vars, err := ec.sectionVars(ec.btf, sec.Name) + if err != nil { + return fmt.Errorf("section %v: loading map variable BTF: %w", sec.Name, err) + } + + for _, sym := range sec.symbols { + name := sym.Name + if ec.maps[name] != nil { + return fmt.Errorf("duplicate symbol %s in section %s", name, sec.Name) } - mapName := mapSym.Name - if ec.maps[mapName] != nil { - return fmt.Errorf("section %v: map %v already exists", sec.Name, mapSym) + if sym.Value > math.MaxUint32 || sym.Size > math.MaxUint32 { + return fmt.Errorf("symbol %s: offset or size exceeds 32 bits in section %s", sym.Name, sec.Name) + } + if sym.Value+sym.Size > sec.Size { + return fmt.Errorf("symbol %s: size goes out of bounds of section %s", name, sec.Name) } - lr := io.LimitReader(r, int64(size)) + sr := internal.NewBufferedSectionReader(sec, int64(sym.Value), int64(sym.Size)) spec := MapSpec{ - Name: SanitizeName(mapName, -1), + Name: sanitizeName(name, -1), } switch { - case binary.Read(lr, ec.ByteOrder, &spec.Type) != nil: - return fmt.Errorf("map %s: missing type", mapName) - case binary.Read(lr, ec.ByteOrder, &spec.KeySize) != nil: - return fmt.Errorf("map %s: missing key size", mapName) - case binary.Read(lr, ec.ByteOrder, &spec.ValueSize) != nil: - return fmt.Errorf("map %s: missing value size", mapName) - case binary.Read(lr, ec.ByteOrder, &spec.MaxEntries) != nil: - return fmt.Errorf("map %s: missing max entries", mapName) - case binary.Read(lr, ec.ByteOrder, &spec.Flags) != nil: - return fmt.Errorf("map %s: missing flags", mapName) - } - - extra, err := io.ReadAll(lr) + case binary.Read(sr, ec.ByteOrder, &spec.Type) != nil: + return fmt.Errorf("map %s: missing type", name) + case binary.Read(sr, ec.ByteOrder, &spec.KeySize) != nil: + return fmt.Errorf("map %s: missing key size", name) + case binary.Read(sr, ec.ByteOrder, &spec.ValueSize) != nil: + return fmt.Errorf("map %s: missing value size", name) + case binary.Read(sr, ec.ByteOrder, &spec.MaxEntries) != nil: + return fmt.Errorf("map %s: missing max entries", name) + case binary.Read(sr, ec.ByteOrder, &spec.Flags) != nil: + return fmt.Errorf("map %s: missing flags", name) + } + + extra, err := io.ReadAll(sr) if err != nil { - return fmt.Errorf("map %s: reading map tail: %w", mapName, err) + return fmt.Errorf("map %s: reading map tail: %w", name, err) } if len(extra) > 0 { spec.Extra = bytes.NewReader(extra) } - ec.maps[mapName] = &spec + if v, ok := vars[name]; ok { + spec.Tags = slices.Clone(v.Tags) + } + + ec.maps[name] = &spec } } return nil } +// sectionVars looks up the BTF Datasec for the given section name and returns a +// map of variable names to their btf.Var definitions. +func (ec *elfCode) sectionVars(spec *btf.Spec, sec string) (map[string]*btf.Var, error) { + vars := make(map[string]*btf.Var) + + if spec == nil { + return vars, nil + } + + var ds *btf.Datasec + if err := ec.btf.TypeByName(sec, &ds); err != nil { + return vars, nil + } + + for _, vsi := range ds.Vars { + v, ok := btf.As[*btf.Var](vsi.Type) + if !ok { + return nil, fmt.Errorf("btf.VarSecInfo doesn't point to a *btf.Var: %T", vsi.Type) + } + vars[string(v.Name)] = v + } + + return vars, nil +} + // loadBTFMaps iterates over all ELF sections marked as BTF map sections // (like .maps) and parses them into MapSpecs. Dump the .maps section and // any relocations with `readelf -x .maps -r `. @@ -777,32 +911,47 @@ func (ec *elfCode) loadBTFMaps() error { return fmt.Errorf("missing BTF") } - // Each section must appear as a DataSec in the ELF's BTF blob. - var ds *btf.Datasec - if err := ec.btf.TypeByName(sec.Name, &ds); err != nil { - return fmt.Errorf("cannot find section '%s' in BTF: %w", sec.Name, err) + if sec.ReaderAt == nil { + return fmt.Errorf("compressed BTF map section is not supported") } - // Open a Reader to the ELF's raw section bytes so we can assert that all - // of them are zero on a per-map (per-Var) basis. For now, the section's - // sole purpose is to receive relocations, so all must be zero. - rs := sec.Open() + vars, err := ec.sectionVars(ec.btf, sec.Name) + if err != nil { + return fmt.Errorf("section %v: loading map variable BTF: %w", sec.Name, err) + } - for _, vs := range ds.Vars { - // BPF maps are declared as and assigned to global variables, - // so iterate over each Var in the DataSec and validate their types. - v, ok := vs.Type.(*btf.Var) + if len(vars) != len(sec.symbols) { + return fmt.Errorf("section %v: contains %d symbols but %d btf.Vars", sec.Name, len(sec.symbols), len(vars)) + } + + syms := make(map[string]elf.Symbol) + for _, sym := range sec.symbols { + syms[sym.Name] = sym + } + + for _, v := range vars { + name := v.Name + + // Find the ELF symbol corresponding to this Var. + sym, ok := syms[name] if !ok { - return fmt.Errorf("section %v: unexpected type %s", sec.Name, vs.Type) + return fmt.Errorf("section %v: missing symbol for map %s", sec.Name, name) } - name := string(v.Name) + + if sym.Value > math.MaxUint32 || sym.Size > math.MaxUint32 { + return fmt.Errorf("symbol %s: offset or size exceeds 32 bits in section %s", sym.Name, sec.Name) + } + if sym.Value+sym.Size > sec.Size { + return fmt.Errorf("section %v: symbol %s: size goes out of bounds of section", sec.Name, name) + } + + sr := internal.NewBufferedSectionReader(sec, int64(sym.Value), int64(sym.Size)) // The BTF metadata for each Var contains the full length of the map // declaration, so read the corresponding amount of bytes from the ELF. // This way, we can pinpoint which map declaration contains unexpected // (and therefore unsupported) data. - _, err := io.Copy(internal.DiscardZeroes{}, io.LimitReader(rs, int64(vs.Size))) - if err != nil { + if _, err = io.Copy(internal.DiscardZeroes{}, sr); err != nil { return fmt.Errorf("section %v: map %s: initializing BTF map definitions: %w", sec.Name, name, internal.ErrNotSupported) } @@ -816,22 +965,12 @@ func (ec *elfCode) loadBTFMaps() error { return fmt.Errorf("expected struct, got %s", v.Type) } - mapSpec, err := mapSpecFromBTF(sec, &vs, mapStruct, ec.btf, name, false) + spec, err := mapSpecFromBTF(sec, sym, v, mapStruct, ec.btf, name, false) if err != nil { return fmt.Errorf("map %v: %w", name, err) } - ec.maps[name] = mapSpec - } - - // Drain the ELF section reader to make sure all bytes are accounted for - // with BTF metadata. - i, err := io.Copy(io.Discard, rs) - if err != nil { - return fmt.Errorf("section %v: unexpected error reading remainder of ELF section: %w", sec.Name, err) - } - if i > 0 { - return fmt.Errorf("section %v: %d unexpected remaining bytes in ELF section, invalid BTF?", sec.Name, i) + ec.maps[name] = spec } } @@ -841,13 +980,14 @@ func (ec *elfCode) loadBTFMaps() error { // mapSpecFromBTF produces a MapSpec based on a btf.Struct def representing // a BTF map definition. The name and spec arguments will be copied to the // resulting MapSpec, and inner must be true on any recursive invocations. -func mapSpecFromBTF(es *elfSection, vs *btf.VarSecinfo, def *btf.Struct, spec *btf.Spec, name string, inner bool) (*MapSpec, error) { +func mapSpecFromBTF(es *elfSection, sym elf.Symbol, v *btf.Var, def *btf.Struct, spec *btf.Spec, name string, inner bool) (*MapSpec, error) { var ( key, value btf.Type - keySize, valueSize uint32 + keySize, valueSize uint64 mapType MapType - flags, maxEntries uint32 + flags, maxEntries uint64 pinType PinType + mapExtra uint64 innerMapSpec *MapSpec contents []MapKV err error @@ -891,7 +1031,7 @@ func mapSpecFromBTF(es *elfSection, vs *btf.VarSecinfo, def *btf.Struct, spec *b return nil, fmt.Errorf("can't get size of BTF key: %w", err) } - keySize = uint32(size) + keySize = uint64(size) case "value": if valueSize != 0 { @@ -910,7 +1050,7 @@ func mapSpecFromBTF(es *elfSection, vs *btf.VarSecinfo, def *btf.Struct, spec *b return nil, fmt.Errorf("can't get size of BTF value: %w", err) } - valueSize = uint32(size) + valueSize = uint64(size) case "key_size": // Key needs to be nil and keySize needs to be 0 for key_size to be @@ -984,7 +1124,7 @@ func mapSpecFromBTF(es *elfSection, vs *btf.VarSecinfo, def *btf.Struct, spec *b // on kernels 5.2 and up) // Pass the BTF spec from the parent object, since both parent and // child must be created from the same BTF blob (on kernels that support BTF). - innerMapSpec, err = mapSpecFromBTF(es, vs, t, spec, name+"_inner", true) + innerMapSpec, err = mapSpecFromBTF(es, sym, v, t, spec, name+"_inner", true) if err != nil { return nil, fmt.Errorf("can't parse BTF map definition of inner map: %w", err) } @@ -1000,13 +1140,16 @@ func mapSpecFromBTF(es *elfSection, vs *btf.VarSecinfo, def *btf.Struct, spec *b return nil, fmt.Errorf("unsupported value type %q in 'values' field", t) } - contents, err = resolveBTFValuesContents(es, vs, member) + contents, err = resolveBTFValuesContents(es, sym, member) if err != nil { return nil, fmt.Errorf("resolving values contents: %w", err) } case "map_extra": - return nil, fmt.Errorf("BTF map definition: field %s: %w", member.Name, ErrNotSupported) + mapExtra, err = uintFromBTF(member.Type) + if err != nil { + return nil, fmt.Errorf("resolving map_extra: %w", err) + } default: return nil, fmt.Errorf("unrecognized field %s in BTF map definition", member.Name) @@ -1021,34 +1164,47 @@ func mapSpecFromBTF(es *elfSection, vs *btf.VarSecinfo, def *btf.Struct, spec *b } return &MapSpec{ - Name: SanitizeName(name, -1), + Name: sanitizeName(name, -1), Type: MapType(mapType), - KeySize: keySize, - ValueSize: valueSize, - MaxEntries: maxEntries, - Flags: flags, + KeySize: uint32(keySize), + ValueSize: uint32(valueSize), + MaxEntries: uint32(maxEntries), + Flags: uint32(flags), Key: key, Value: value, Pinning: pinType, InnerMap: innerMapSpec, Contents: contents, + Tags: slices.Clone(v.Tags), + MapExtra: mapExtra, }, nil } -// uintFromBTF resolves the __uint macro, which is a pointer to a sized -// array, e.g. for int (*foo)[10], this function will return 10. -func uintFromBTF(typ btf.Type) (uint32, error) { - ptr, ok := typ.(*btf.Pointer) - if !ok { - return 0, fmt.Errorf("not a pointer: %v", typ) - } +// uintFromBTF resolves the __uint and __ulong macros. +// +// __uint emits a pointer to a sized array. For int (*foo)[10], this function +// will return 10. +// +// __ulong emits an enum with a single value that can represent a 64-bit +// integer. The first (and only) enum value is returned. +func uintFromBTF(typ btf.Type) (uint64, error) { + switch t := typ.(type) { + case *btf.Pointer: + arr, ok := t.Target.(*btf.Array) + if !ok { + return 0, fmt.Errorf("not a pointer to array: %v", typ) + } + return uint64(arr.Nelems), nil - arr, ok := ptr.Target.(*btf.Array) - if !ok { - return 0, fmt.Errorf("not a pointer to array: %v", typ) - } + case *btf.Enum: + if len(t.Values) == 0 { + return 0, errors.New("enum has no values") + } + return t.Values[0].Value, nil - return arr.Nelems, nil + default: + return 0, fmt.Errorf("not a pointer or enum: %v", typ) + } } // resolveBTFArrayMacro resolves the __array macro, which declares an array @@ -1068,58 +1224,74 @@ func resolveBTFArrayMacro(typ btf.Type) (btf.Type, error) { return ptr.Target, nil } -// resolveBTFValuesContents resolves relocations into ELF sections belonging -// to btf.VarSecinfo's. This can be used on the 'values' member in BTF map -// definitions to extract static declarations of map contents. -func resolveBTFValuesContents(es *elfSection, vs *btf.VarSecinfo, member btf.Member) ([]MapKV, error) { - // The elements of a .values pointer array are not encoded in BTF. - // Instead, relocations are generated into each array index. - // However, it's possible to leave certain array indices empty, so all - // indices' offsets need to be checked for emitted relocations. - - // The offset of the 'values' member within the _struct_ (in bits) - // is the starting point of the array. Convert to bytes. Add VarSecinfo - // offset to get the absolute position in the ELF blob. - start := member.Offset.Bytes() + vs.Offset - // 'values' is encoded in BTF as a zero (variable) length struct - // member, and its contents run until the end of the VarSecinfo. - // Add VarSecinfo offset to get the absolute position in the ELF blob. - end := vs.Size + vs.Offset - // The size of an address in this section. This determines the width of - // an index in the array. - align := uint32(es.SectionHeader.Addralign) - - // Check if variable-length section is aligned. - if (end-start)%align != 0 { - return nil, errors.New("unaligned static values section") - } +// valuesRelocations returns an iterator over the relocations in the ELF section +// corresponding to the elements of a .values array in a BTF map definition. Each +// iteration yields the array index and the symbol referenced by the relocation +// at that index. Empty indices are skipped. +func valuesRelocations(es *elfSection, sym elf.Symbol, member btf.Member) iter.Seq2[uint32, elf.Symbol] { + // The elements of a .values pointer array are not encoded in BTF itself. + // Instead, each array index receives a relocation pointing at a symbol + // (map/prog) in another section. However, it's possible to leave certain + // array indices empty, so all indices' offsets need to be checked for emitted + // relocations. + + // Absolute offset of the .values member within the section. + start := sym.Value + uint64(member.Offset.Bytes()) + + // .values is a variable-length struct member, so its contents run until the + // end of the symbol. The symbol offset + size is the absolute offset of the + // end of the array in the section. + end := sym.Value + sym.Size + + // The size of an address in this section. This determines the width of an + // index in the array. + align := es.Addralign + + // Amount of elements in the .values array. elems := (end - start) / align - if elems == 0 { - return nil, nil - } + return func(yield func(uint32, elf.Symbol) bool) { + for i := range uint32(elems) { + // off increases by align on each iteration, starting at .values. + off := start + (uint64(i) * align) - contents := make([]MapKV, 0, elems) + r, ok := es.relocations[off] + if !ok { + continue + } - // k is the array index, off is its corresponding ELF section offset. - for k, off := uint32(0), start; k < elems; k, off = k+1, off+align { - r, ok := es.relocations[uint64(off)] - if !ok { - continue + if !yield(i, r) { + return + } } + } - // Relocation exists for the current offset in the ELF section. - // Emit a value stub based on the type of relocation to be replaced by - // a real fd later in the pipeline before populating the map. - // Map keys are encoded in MapKV entries, so empty array indices are - // skipped here. - switch t := elf.ST_TYPE(r.Info); t { +} + +// resolveBTFValuesContents looks up the symbols referenced by the relocations +// in a .values array and returns them as MapKV pairs, where the key is the +// array index and the value is the symbol name. Empty indices are skipped. +func resolveBTFValuesContents(es *elfSection, sym elf.Symbol, member btf.Member) ([]MapKV, error) { + var contents []MapKV + + if member.Offset.Bytes() > uint32(sym.Size) { + return nil, fmt.Errorf("member offset %d exceeds symbol size %d", member.Offset.Bytes(), sym.Size) + } + + if es.Addralign == 0 { + return nil, fmt.Errorf("section has no address alignment, can't resolve .values contents") + } + + for i, sym := range valuesRelocations(es, sym, member) { + // Emit a value stub based on the type of relocation to be replaced by a + // real fd later in the pipeline before populating the Map. + switch t := elf.ST_TYPE(sym.Info); t { case elf.STT_FUNC: - contents = append(contents, MapKV{uint32(k), r.Name}) + contents = append(contents, MapKV{i, sym.Name}) case elf.STT_OBJECT: - contents = append(contents, MapKV{uint32(k), r.Name}) + contents = append(contents, MapKV{i, sym.Name}) default: - return nil, fmt.Errorf("unknown relocation type %v for symbol %s", t, r.Name) + return nil, fmt.Errorf("unknown relocation type %v for symbol %s", t, sym.Name) } } @@ -1148,7 +1320,7 @@ func (ec *elfCode) loadDataSections() error { } mapSpec := &MapSpec{ - Name: SanitizeName(sec.Name, -1), + Name: sanitizeName(sec.Name, -1), Type: Array, KeySize: 4, ValueSize: uint32(sec.Size), @@ -1159,14 +1331,15 @@ func (ec *elfCode) loadDataSections() error { mapSpec.Flags = sys.BPF_F_RDONLY_PROG } + var data []byte switch sec.Type { // Only open the section if we know there's actual data to be read. case elf.SHT_PROGBITS: - data, err := sec.Data() + var err error + data, err = sec.Data() if err != nil { return fmt.Errorf("data section %s: can't get contents: %w", sec.Name, err) } - mapSpec.Contents = []MapKV{{uint32(0), data}} case elf.SHT_NOBITS: // NOBITS sections like .bss contain only zeroes and are not allocated in @@ -1174,12 +1347,14 @@ func (ec *elfCode) loadDataSections() error { // them. Don't attempt reading zeroes from the ELF, instead allocate the // zeroed memory to support getting and setting VariableSpecs for sections // like .bss. - mapSpec.Contents = []MapKV{{uint32(0), make([]byte, sec.Size)}} + data = make([]byte, sec.Size) default: return fmt.Errorf("data section %s: unknown section type %s", sec.Name, sec.Type) } + mapSpec.Contents = []MapKV{{uint32(0), data}} + for off, sym := range sec.symbols { // Skip symbols marked with the 'hidden' attribute. if elf.ST_VISIBILITY(sym.Other) == elf.STV_HIDDEN || @@ -1208,11 +1383,23 @@ func (ec *elfCode) loadDataSections() error { continue } + if off+sym.Size > uint64(len(data)) { + return fmt.Errorf("data section %s: variable %s exceeds section bounds", sec.Name, sym.Name) + } + + if off > math.MaxUint32 { + return fmt.Errorf("data section %s: variable %s offset %d exceeds maximum", sec.Name, sym.Name, off) + } + + if sym.Size > math.MaxUint32 { + return fmt.Errorf("data section %s: variable %s size %d exceeds maximum", sec.Name, sym.Name, sym.Size) + } + ec.vars[sym.Name] = &VariableSpec{ - name: sym.Name, - offset: off, - size: sym.Size, - m: mapSpec, + SectionName: sec.Name, + Name: sym.Name, + Offset: uint32(off), + Value: slices.Clone(data[off : off+sym.Size]), } } @@ -1243,17 +1430,17 @@ func (ec *elfCode) loadDataSections() error { continue } - if uint64(v.Offset) != ev.offset { - return fmt.Errorf("data section %s: variable %s datasec offset (%d) doesn't match ELF symbol offset (%d)", sec.Name, name, v.Offset, ev.offset) + if v.Offset != ev.Offset { + return fmt.Errorf("data section %s: variable %s datasec offset (%d) doesn't match ELF symbol offset (%d)", sec.Name, name, v.Offset, ev.Offset) } - if uint64(v.Size) != ev.size { - return fmt.Errorf("data section %s: variable %s size in datasec (%d) doesn't match ELF symbol size (%d)", sec.Name, name, v.Size, ev.size) + if v.Size != ev.Size() { + return fmt.Errorf("data section %s: variable %s size in datasec (%d) doesn't match ELF symbol size (%d)", sec.Name, name, v.Size, ev.Size()) } // Decouple the Var in the VariableSpec from the underlying DataSec in // the MapSpec to avoid modifications from affecting map loads later on. - ev.t = btf.Copy(vt).(*btf.Var) + ev.Type = btf.Copy(vt).(*btf.Var) } } } @@ -1328,6 +1515,95 @@ func (ec *elfCode) loadKsymsSection() error { return nil } +// associateStructOpsRelocs handles `.struct_ops.link` +// and associates the target function with the correct struct member in the map. +func (ec *elfCode) associateStructOpsRelocs(progs map[string]*ProgramSpec) error { + for _, sec := range ec.sections { + if sec.kind != structOpsSection { + continue + } + + userData, err := sec.Data() + if err != nil { + return fmt.Errorf("failed to read section data: %w", err) + } + + if ec.btf == nil { + return fmt.Errorf("struct_ops section %s: missing BTF", sec.Name) + } + + // Resolve the BTF datasec describing variables in this section. + var ds *btf.Datasec + if err := ec.btf.TypeByName(sec.Name, &ds); err != nil { + return fmt.Errorf("datasec %s: %w", sec.Name, err) + } + + // Set flags for .struct_ops.link (BPF_F_LINK). + flags := uint32(0) + if sec.Name == structOpsLinkSec { + flags = sys.BPF_F_LINK + } + + for _, vsi := range ds.Vars { + userSt, baseOff, err := ec.createStructOpsMap(vsi, userData, flags) + if err != nil { + return err + } + + if err := structOpsSetAttachTo(sec, baseOff, userSt, progs); err != nil { + return err + } + } + } + + return nil +} + +// createStructOpsMap() creates and registers a MapSpec for a struct_ops +func (ec *elfCode) createStructOpsMap(vsi btf.VarSecinfo, userData []byte, flags uint32) (*btf.Struct, uint32, error) { + varType, ok := btf.As[*btf.Var](vsi.Type) + if !ok { + return nil, 0, fmt.Errorf("vsi: expect var, got %T", vsi.Type) + } + + mapName := varType.Name + + userSt, ok := btf.As[*btf.Struct](varType.Type) + if !ok { + return nil, 0, fmt.Errorf("var %s: expect struct, got %T", varType.Name, varType.Type) + } + + userSize := userSt.Size + baseOff := vsi.Offset + if uint64(baseOff)+uint64(userSize) > uint64(len(userData)) { + return nil, 0, fmt.Errorf("%s exceeds section", mapName) + } + + // Register the MapSpec for this struct_ops instance if doesn't exist + if _, exists := ec.maps[mapName]; exists { + return nil, 0, fmt.Errorf("struct_ops map %s: already exists", mapName) + } + + ec.maps[mapName] = &MapSpec{ + Name: mapName, + Type: StructOpsMap, + Key: &btf.Int{Size: 4}, + KeySize: structOpsKeySize, + ValueSize: userSize, // length of the user-struct type + Value: userSt, + Flags: flags, + MaxEntries: 1, + Contents: []MapKV{ + { + Key: uint32(0), + Value: append([]byte(nil), userData[baseOff:baseOff+userSize]...), + }, + }, + } + + return userSt, baseOff, nil +} + type libbpfElfSectionDef struct { pattern string programType sys.ProgType @@ -1348,29 +1624,9 @@ const ( _SEC_XDP_FRAGS _SEC_USDT - // Ignore any present extra in order to preserve backwards compatibility - // with earlier versions of the library. - ignoreExtra - _SEC_ATTACHABLE_OPT = _SEC_ATTACHABLE | _SEC_EXP_ATTACH_OPT ) -func init() { - // Compatibility with older versions of the library. - // We prepend libbpf definitions since they contain a prefix match - // for "xdp". - elfSectionDefs = append([]libbpfElfSectionDef{ - {"xdp.frags/", sys.BPF_PROG_TYPE_XDP, sys.BPF_XDP, _SEC_XDP_FRAGS | ignoreExtra}, - {"xdp.frags_devmap/", sys.BPF_PROG_TYPE_XDP, sys.BPF_XDP_DEVMAP, _SEC_XDP_FRAGS}, - {"xdp_devmap/", sys.BPF_PROG_TYPE_XDP, sys.BPF_XDP_DEVMAP, 0}, - {"xdp.frags_cpumap/", sys.BPF_PROG_TYPE_XDP, sys.BPF_XDP_CPUMAP, _SEC_XDP_FRAGS}, - {"xdp_cpumap/", sys.BPF_PROG_TYPE_XDP, sys.BPF_XDP_CPUMAP, 0}, - // This has been in the library since the beginning of time. Not sure - // where it came from. - {"seccomp", sys.BPF_PROG_TYPE_SOCKET_FILTER, 0, _SEC_NONE}, - }, elfSectionDefs...) -} - func getProgType(sectionName string) (ProgramType, AttachType, uint32, string) { // Skip optional program marking for now. sectionName = strings.TrimPrefix(sectionName, "?") @@ -1391,15 +1647,13 @@ func getProgType(sectionName string) (ProgramType, AttachType, uint32, string) { if t.flags&_SEC_XDP_FRAGS > 0 { flags |= sys.BPF_F_XDP_HAS_FRAGS } - if t.flags&_SEC_EXP_ATTACH_OPT > 0 { - if programType == XDP { - // The library doesn't yet have code to fallback to not specifying - // attach type. Only do this for XDP since we've enforced correct - // attach type for all other program types. - attachType = AttachNone - } - } - if t.flags&ignoreExtra > 0 { + + // The libbpf documentation on program types states: 'The struct_ops attach + // format supports struct_ops[.s]/ convention, but name is ignored and + // it is recommended to just use plain SEC("struct_ops[.s]").' + // + // Ignore any extra for struct_ops to match libbpf behaviour. + if programType == StructOps { extra = "" } diff --git a/vendor/github.com/cilium/ebpf/elf_sections.go b/vendor/github.com/cilium/ebpf/elf_sections.go index 43dcfb103e..1ded4c4d54 100644 --- a/vendor/github.com/cilium/ebpf/elf_sections.go +++ b/vendor/github.com/cilium/ebpf/elf_sections.go @@ -21,8 +21,10 @@ var elfSectionDefs = []libbpfElfSectionDef{ {"kprobe.session+", sys.BPF_PROG_TYPE_KPROBE, sys.BPF_TRACE_KPROBE_SESSION, _SEC_NONE}, {"uprobe.multi+", sys.BPF_PROG_TYPE_KPROBE, sys.BPF_TRACE_UPROBE_MULTI, _SEC_NONE}, {"uretprobe.multi+", sys.BPF_PROG_TYPE_KPROBE, sys.BPF_TRACE_UPROBE_MULTI, _SEC_NONE}, + {"uprobe.session+", sys.BPF_PROG_TYPE_KPROBE, sys.BPF_TRACE_UPROBE_SESSION, _SEC_NONE}, {"uprobe.multi.s+", sys.BPF_PROG_TYPE_KPROBE, sys.BPF_TRACE_UPROBE_MULTI, _SEC_SLEEPABLE}, {"uretprobe.multi.s+", sys.BPF_PROG_TYPE_KPROBE, sys.BPF_TRACE_UPROBE_MULTI, _SEC_SLEEPABLE}, + {"uprobe.session.s+", sys.BPF_PROG_TYPE_KPROBE, sys.BPF_TRACE_UPROBE_SESSION, _SEC_SLEEPABLE}, {"ksyscall+", sys.BPF_PROG_TYPE_KPROBE, 0, _SEC_NONE}, {"kretsyscall+", sys.BPF_PROG_TYPE_KPROBE, 0, _SEC_NONE}, {"usdt+", sys.BPF_PROG_TYPE_KPROBE, 0, _SEC_USDT}, @@ -49,6 +51,8 @@ var elfSectionDefs = []libbpfElfSectionDef{ {"fentry.s+", sys.BPF_PROG_TYPE_TRACING, sys.BPF_TRACE_FENTRY, _SEC_ATTACH_BTF | _SEC_SLEEPABLE}, {"fmod_ret.s+", sys.BPF_PROG_TYPE_TRACING, sys.BPF_MODIFY_RETURN, _SEC_ATTACH_BTF | _SEC_SLEEPABLE}, {"fexit.s+", sys.BPF_PROG_TYPE_TRACING, sys.BPF_TRACE_FEXIT, _SEC_ATTACH_BTF | _SEC_SLEEPABLE}, + {"fsession+", sys.BPF_PROG_TYPE_TRACING, sys.BPF_TRACE_FSESSION, _SEC_ATTACH_BTF}, + {"fsession.s+", sys.BPF_PROG_TYPE_TRACING, sys.BPF_TRACE_FSESSION, _SEC_ATTACH_BTF | _SEC_SLEEPABLE}, {"freplace+", sys.BPF_PROG_TYPE_EXT, 0, _SEC_ATTACH_BTF}, {"lsm+", sys.BPF_PROG_TYPE_LSM, sys.BPF_LSM_MAC, _SEC_ATTACH_BTF}, {"lsm.s+", sys.BPF_PROG_TYPE_LSM, sys.BPF_LSM_MAC, _SEC_ATTACH_BTF | _SEC_SLEEPABLE}, diff --git a/vendor/github.com/cilium/ebpf/features/doc.go b/vendor/github.com/cilium/ebpf/features/doc.go new file mode 100644 index 0000000000..acc57e3b1e --- /dev/null +++ b/vendor/github.com/cilium/ebpf/features/doc.go @@ -0,0 +1,19 @@ +// Package features allows probing for BPF features available to the calling process. +// +// In general, the error return values from feature probes in this package +// all have the following semantics unless otherwise specified: +// +// err == nil: The feature is available. +// errors.Is(err, ebpf.ErrNotSupported): The feature is not available. +// err != nil: Any errors encountered during probe execution, wrapped. +// +// Note that the latter case may include false negatives, and that resource +// creation may succeed despite an error being returned. For example, some +// map and program types cannot reliably be probed and will return an +// inconclusive error. +// +// As a rule, only `nil` and `ebpf.ErrNotSupported` are conclusive. +// +// Probe results are cached by the library and persist throughout any changes +// to the process' environment, like capability changes. +package features diff --git a/vendor/github.com/cilium/ebpf/features/link.go b/vendor/github.com/cilium/ebpf/features/link.go new file mode 100644 index 0000000000..4f440e7bcb --- /dev/null +++ b/vendor/github.com/cilium/ebpf/features/link.go @@ -0,0 +1,157 @@ +package features + +import ( + "errors" + + "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" +) + +// HaveBPFLinkUprobeMulti probes the running kernel if uprobe_multi link is supported. +// +// See the package documentation for the meaning of the error return value. +func HaveBPFLinkUprobeMulti() error { + return haveBPFLinkUprobeMulti() +} + +var haveBPFLinkUprobeMulti = internal.NewFeatureTest("bpf_link_uprobe_multi", func() error { + prog, err := ebpf.NewProgram(&ebpf.ProgramSpec{ + Name: "probe_upm_link", + Type: ebpf.Kprobe, + Instructions: asm.Instructions{ + asm.Mov.Imm(asm.R0, 0), + asm.Return(), + }, + AttachType: ebpf.AttachTraceUprobeMulti, + License: "MIT", + }) + if errors.Is(err, unix.E2BIG) { + // Kernel doesn't support AttachType field. + return ebpf.ErrNotSupported + } + if err != nil { + return err + } + defer prog.Close() + + // We try to create uprobe multi link on '/' path which results in + // error with -EBADF in case uprobe multi link is supported. + fd, err := sys.LinkCreateUprobeMulti(&sys.LinkCreateUprobeMultiAttr{ + ProgFd: uint32(prog.FD()), + AttachType: sys.BPF_TRACE_UPROBE_MULTI, + Path: sys.NewStringPointer("/"), + Offsets: sys.SlicePointer([]uint64{0}), + Count: 1, + }) + switch { + case errors.Is(err, unix.EBADF): + return nil + case errors.Is(err, unix.EINVAL): + return ebpf.ErrNotSupported + case err != nil: + return err + } + + // should not happen + fd.Close() + return errors.New("successfully attached uprobe_multi to /, kernel bug?") +}, "6.6") + +// HaveBPFLinkKprobeMulti probes the running kernel if kprobe_multi link is supported. +// +// See the package documentation for the meaning of the error return value. +func HaveBPFLinkKprobeMulti() error { + return haveBPFLinkKprobeMulti() +} + +var haveBPFLinkKprobeMulti = internal.NewFeatureTest("bpf_link_kprobe_multi", func() error { + prog, err := ebpf.NewProgram(&ebpf.ProgramSpec{ + Name: "probe_kpm_link", + Type: ebpf.Kprobe, + Instructions: asm.Instructions{ + asm.Mov.Imm(asm.R0, 0), + asm.Return(), + }, + AttachType: ebpf.AttachTraceKprobeMulti, + License: "MIT", + }) + if errors.Is(err, unix.E2BIG) { + // Kernel doesn't support AttachType field. + return ebpf.ErrNotSupported + } + if err != nil { + return err + } + defer prog.Close() + + fd, err := sys.LinkCreateKprobeMulti(&sys.LinkCreateKprobeMultiAttr{ + ProgFd: uint32(prog.FD()), + AttachType: sys.BPF_TRACE_KPROBE_MULTI, + Count: 1, + Syms: sys.NewStringSlicePointer([]string{"vprintk"}), + }) + switch { + case errors.Is(err, unix.EINVAL): + return ebpf.ErrNotSupported + // If CONFIG_FPROBE isn't set. + case errors.Is(err, unix.EOPNOTSUPP): + return ebpf.ErrNotSupported + case err != nil: + return err + } + + fd.Close() + + return nil +}, "5.18") + +// HaveBPFLinkKprobeSession probes the running kernel if kprobe_session link is supported. +// +// See the package documentation for the meaning of the error return value. +func HaveBPFLinkKprobeSession() error { + return haveBPFLinkKprobeSession() +} + +var haveBPFLinkKprobeSession = internal.NewFeatureTest("bpf_link_kprobe_session", func() error { + prog, err := ebpf.NewProgram(&ebpf.ProgramSpec{ + Name: "probe_kps_link", + Type: ebpf.Kprobe, + Instructions: asm.Instructions{ + asm.Mov.Imm(asm.R0, 0), + asm.Return(), + }, + AttachType: ebpf.AttachTraceKprobeSession, + License: "MIT", + }) + if errors.Is(err, unix.E2BIG) { + // Kernel doesn't support AttachType field. + return ebpf.ErrNotSupported + } + if err != nil { + return err + } + defer prog.Close() + + fd, err := sys.LinkCreateKprobeMulti(&sys.LinkCreateKprobeMultiAttr{ + ProgFd: uint32(prog.FD()), + AttachType: sys.BPF_TRACE_KPROBE_SESSION, + Count: 1, + Syms: sys.NewStringSlicePointer([]string{"vprintk"}), + }) + switch { + case errors.Is(err, unix.EINVAL): + return ebpf.ErrNotSupported + // If CONFIG_FPROBE isn't set. + case errors.Is(err, unix.EOPNOTSUPP): + return ebpf.ErrNotSupported + case err != nil: + return err + } + + fd.Close() + + return nil +}, "6.10") diff --git a/vendor/github.com/cilium/ebpf/features/map.go b/vendor/github.com/cilium/ebpf/features/map.go new file mode 100644 index 0000000000..75212552c0 --- /dev/null +++ b/vendor/github.com/cilium/ebpf/features/map.go @@ -0,0 +1,362 @@ +package features + +import ( + "errors" + "fmt" + "os" + "unsafe" + + "github.com/cilium/ebpf" + "github.com/cilium/ebpf/internal" + "github.com/cilium/ebpf/internal/sys" + "github.com/cilium/ebpf/internal/unix" +) + +// HaveMapType probes the running kernel for the availability of the specified map type. +// +// See the package documentation for the meaning of the error return value. +func HaveMapType(mt ebpf.MapType) error { + return haveMapTypeMatrix.Result(mt) +} + +func probeCgroupStorageMap(mt sys.MapType) error { + // keySize needs to be sizeof(struct{u32 + u64}) = 12 (+ padding = 16) + // by using unsafe.Sizeof(int) we are making sure that this works on 32bit and 64bit archs + return createMap(&sys.MapCreateAttr{ + MapType: mt, + ValueSize: 4, + KeySize: uint32(8 + unsafe.Sizeof(int(0))), + MaxEntries: 0, + }) +} + +func probeStorageMap(mt sys.MapType) error { + // maxEntries needs to be 0 + // BPF_F_NO_PREALLOC needs to be set + // btf* fields need to be set + // see alloc_check for local_storage map types + err := createMap(&sys.MapCreateAttr{ + MapType: mt, + KeySize: 4, + ValueSize: 4, + MaxEntries: 0, + MapFlags: sys.BPF_F_NO_PREALLOC, + BtfKeyTypeId: 1, + BtfValueTypeId: 1, + BtfFd: ^uint32(0), + }) + if errors.Is(err, unix.EBADF) { + // Triggered by BtfFd. + return nil + } + return err +} + +func probeNestedMap(mt sys.MapType) error { + // assign invalid innerMapFd to pass validation check + // will return EBADF + err := probeMap(&sys.MapCreateAttr{ + MapType: mt, + InnerMapFd: ^uint32(0), + }) + if errors.Is(err, unix.EBADF) { + return nil + } + return err +} + +func probeMap(attr *sys.MapCreateAttr) error { + if attr.KeySize == 0 { + attr.KeySize = 4 + } + if attr.ValueSize == 0 { + attr.ValueSize = 4 + } + attr.MaxEntries = 1 + return createMap(attr) +} + +func createMap(attr *sys.MapCreateAttr) error { + fd, err := sys.MapCreate(attr) + if err == nil { + fd.Close() + return nil + } + + switch { + // EINVAL occurs when attempting to create a map with an unknown type. + // E2BIG occurs when MapCreateAttr contains non-zero bytes past the end + // of the struct known by the running kernel, meaning the kernel is too old + // to support the given map type. + case errors.Is(err, unix.EINVAL), errors.Is(err, unix.E2BIG): + return ebpf.ErrNotSupported + } + + return err +} + +var haveMapTypeMatrix = internal.FeatureMatrix[ebpf.MapType]{ + ebpf.Hash: {Version: "3.19"}, + ebpf.Array: {Version: "3.19"}, + ebpf.ProgramArray: {Version: "4.2"}, + ebpf.PerfEventArray: {Version: "4.3"}, + ebpf.PerCPUHash: {Version: "4.6"}, + ebpf.PerCPUArray: {Version: "4.6"}, + ebpf.StackTrace: { + Version: "4.6", + Fn: func() error { + return probeMap(&sys.MapCreateAttr{ + MapType: sys.BPF_MAP_TYPE_STACK_TRACE, + ValueSize: 8, // sizeof(uint64) + }) + }, + }, + ebpf.CGroupArray: {Version: "4.8"}, + ebpf.LRUHash: {Version: "4.10"}, + ebpf.LRUCPUHash: {Version: "4.10"}, + ebpf.LPMTrie: { + Version: "4.11", + Fn: func() error { + // keySize and valueSize need to be sizeof(struct{u32 + u8}) + 1 + padding = 8 + // BPF_F_NO_PREALLOC needs to be set + return probeMap(&sys.MapCreateAttr{ + MapType: sys.BPF_MAP_TYPE_LPM_TRIE, + KeySize: 8, + ValueSize: 8, + MapFlags: sys.BPF_F_NO_PREALLOC, + }) + }, + }, + ebpf.ArrayOfMaps: { + Version: "4.12", + Fn: func() error { return probeNestedMap(sys.BPF_MAP_TYPE_ARRAY_OF_MAPS) }, + }, + ebpf.HashOfMaps: { + Version: "4.12", + Fn: func() error { return probeNestedMap(sys.BPF_MAP_TYPE_HASH_OF_MAPS) }, + }, + ebpf.DevMap: {Version: "4.14"}, + ebpf.SockMap: {Version: "4.14"}, + ebpf.CPUMap: {Version: "4.15"}, + ebpf.XSKMap: {Version: "4.18"}, + ebpf.SockHash: {Version: "4.18"}, + ebpf.CGroupStorage: { + Version: "4.19", + Fn: func() error { return probeCgroupStorageMap(sys.BPF_MAP_TYPE_CGROUP_STORAGE) }, + }, + ebpf.ReusePortSockArray: {Version: "4.19"}, + ebpf.PerCPUCGroupStorage: { + Version: "4.20", + Fn: func() error { return probeCgroupStorageMap(sys.BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE) }, + }, + ebpf.Queue: { + Version: "4.20", + Fn: func() error { + return createMap(&sys.MapCreateAttr{ + MapType: sys.BPF_MAP_TYPE_QUEUE, + KeySize: 0, + ValueSize: 4, + MaxEntries: 1, + }) + }, + }, + ebpf.Stack: { + Version: "4.20", + Fn: func() error { + return createMap(&sys.MapCreateAttr{ + MapType: sys.BPF_MAP_TYPE_STACK, + KeySize: 0, + ValueSize: 4, + MaxEntries: 1, + }) + }, + }, + ebpf.SkStorage: { + Version: "5.2", + Fn: func() error { return probeStorageMap(sys.BPF_MAP_TYPE_SK_STORAGE) }, + }, + ebpf.DevMapHash: {Version: "5.4"}, + ebpf.StructOpsMap: { + Version: "5.6", + Fn: func() error { + // StructOps requires setting a vmlinux type id, but id 1 will always + // resolve to some type of integer. This will cause ENOTSUPP. + err := probeMap(&sys.MapCreateAttr{ + MapType: sys.BPF_MAP_TYPE_STRUCT_OPS, + BtfVmlinuxValueTypeId: 1, + }) + if errors.Is(err, sys.ENOTSUPP) { + // ENOTSUPP means the map type is at least known to the kernel. + return nil + } + return err + }, + }, + ebpf.RingBuf: { + Version: "5.8", + Fn: func() error { + // keySize and valueSize need to be 0 + // maxEntries needs to be power of 2 and PAGE_ALIGNED + return createMap(&sys.MapCreateAttr{ + MapType: sys.BPF_MAP_TYPE_RINGBUF, + KeySize: 0, + ValueSize: 0, + MaxEntries: uint32(os.Getpagesize()), + }) + }, + }, + ebpf.InodeStorage: { + Version: "5.10", + Fn: func() error { return probeStorageMap(sys.BPF_MAP_TYPE_INODE_STORAGE) }, + }, + ebpf.TaskStorage: { + Version: "5.11", + Fn: func() error { return probeStorageMap(sys.BPF_MAP_TYPE_TASK_STORAGE) }, + }, + ebpf.BloomFilter: { + Version: "5.16", + Fn: func() error { + return createMap(&sys.MapCreateAttr{ + MapType: sys.BPF_MAP_TYPE_BLOOM_FILTER, + KeySize: 0, + ValueSize: 4, + MaxEntries: 1, + }) + }, + }, + ebpf.UserRingbuf: { + Version: "6.1", + Fn: func() error { + // keySize and valueSize need to be 0 + // maxEntries needs to be power of 2 and PAGE_ALIGNED + return createMap(&sys.MapCreateAttr{ + MapType: sys.BPF_MAP_TYPE_USER_RINGBUF, + KeySize: 0, + ValueSize: 0, + MaxEntries: uint32(os.Getpagesize()), + }) + }, + }, + ebpf.CgroupStorage: { + Version: "6.2", + Fn: func() error { return probeStorageMap(sys.BPF_MAP_TYPE_CGRP_STORAGE) }, + }, + ebpf.Arena: { + Version: "6.9", + Fn: func() error { + return createMap(&sys.MapCreateAttr{ + MapType: sys.BPF_MAP_TYPE_ARENA, + KeySize: 0, + ValueSize: 0, + MaxEntries: 1, // one page + MapExtra: 0, // can mmap() at any address + MapFlags: sys.BPF_F_MMAPABLE, + }) + }, + }, +} + +func init() { + for mt, ft := range haveMapTypeMatrix { + ft.Name = mt.String() + if ft.Fn == nil { + // Avoid referring to the loop variable in the closure. + mt := sys.MapType(mt) + ft.Fn = func() error { return probeMap(&sys.MapCreateAttr{MapType: mt}) } + } + } +} + +// MapFlags document which flags may be feature probed. +type MapFlags uint32 + +// Flags which may be feature probed. +const ( + BPF_F_NO_PREALLOC = sys.BPF_F_NO_PREALLOC + BPF_F_RDONLY_PROG = sys.BPF_F_RDONLY_PROG + BPF_F_WRONLY_PROG = sys.BPF_F_WRONLY_PROG + BPF_F_MMAPABLE = sys.BPF_F_MMAPABLE + BPF_F_INNER_MAP = sys.BPF_F_INNER_MAP +) + +// HaveMapFlag probes the running kernel for the availability of the specified map flag. +// +// Returns an error if flag is not one of the flags declared in this package. +// See the package documentation for the meaning of the error return value. +func HaveMapFlag(flag MapFlags) (err error) { + return haveMapFlagsMatrix.Result(flag) +} + +func probeMapFlag(attr *sys.MapCreateAttr) error { + // For now, we do not check if the map type is supported because we only support + // probing for flags defined on arrays and hashes that are always supported. + // In the future, if we allow probing on flags defined on newer types, checking for map type + // support will be required. + if attr.MapType == sys.BPF_MAP_TYPE_UNSPEC { + attr.MapType = sys.BPF_MAP_TYPE_ARRAY + } + + attr.KeySize = 4 + attr.ValueSize = 4 + attr.MaxEntries = 1 + + fd, err := sys.MapCreate(attr) + if err == nil { + fd.Close() + } else if errors.Is(err, unix.EINVAL) { + // EINVAL occurs when attempting to create a map with an unknown type or an unknown flag. + err = ebpf.ErrNotSupported + } + + return err +} + +var haveMapFlagsMatrix = internal.FeatureMatrix[MapFlags]{ + BPF_F_NO_PREALLOC: { + Version: "4.6", + Fn: func() error { + return probeMapFlag(&sys.MapCreateAttr{ + MapType: sys.BPF_MAP_TYPE_HASH, + MapFlags: BPF_F_NO_PREALLOC, + }) + }, + }, + BPF_F_RDONLY_PROG: { + Version: "5.2", + Fn: func() error { + return probeMapFlag(&sys.MapCreateAttr{ + MapFlags: BPF_F_RDONLY_PROG, + }) + }, + }, + BPF_F_WRONLY_PROG: { + Version: "5.2", + Fn: func() error { + return probeMapFlag(&sys.MapCreateAttr{ + MapFlags: BPF_F_WRONLY_PROG, + }) + }, + }, + BPF_F_MMAPABLE: { + Version: "5.5", + Fn: func() error { + return probeMapFlag(&sys.MapCreateAttr{ + MapFlags: BPF_F_MMAPABLE, + }) + }, + }, + BPF_F_INNER_MAP: { + Version: "5.10", + Fn: func() error { + return probeMapFlag(&sys.MapCreateAttr{ + MapFlags: BPF_F_INNER_MAP, + }) + }, + }, +} + +func init() { + for mf, ft := range haveMapFlagsMatrix { + ft.Name = fmt.Sprint(mf) + } +} diff --git a/vendor/github.com/cilium/ebpf/features/misc.go b/vendor/github.com/cilium/ebpf/features/misc.go new file mode 100644 index 0000000000..c039020a95 --- /dev/null +++ b/vendor/github.com/cilium/ebpf/features/misc.go @@ -0,0 +1,135 @@ +package features + +import ( + "errors" + + "github.com/cilium/ebpf" + "github.com/cilium/ebpf/asm" + "github.com/cilium/ebpf/internal" + "github.com/cilium/ebpf/internal/sys" +) + +// HaveLargeInstructions probes the running kernel if more than 4096 instructions +// per program are supported. +// +// Upstream commit c04c0d2b968a ("bpf: increase complexity limit and maximum program size"). +// +// See the package documentation for the meaning of the error return value. +func HaveLargeInstructions() error { + return haveLargeInstructions() +} + +var haveLargeInstructions = internal.NewFeatureTest(">4096 instructions", func() error { + const maxInsns = 4096 + + insns := make(asm.Instructions, maxInsns, maxInsns+1) + for i := range insns { + insns[i] = asm.Mov.Imm(asm.R0, 1) + } + insns = append(insns, asm.Return()) + + return probeProgram(&ebpf.ProgramSpec{ + Type: ebpf.SocketFilter, + Instructions: insns, + }) +}, "5.2") + +// HaveBoundedLoops probes the running kernel if bounded loops are supported. +// +// Upstream commit 2589726d12a1 ("bpf: introduce bounded loops"). +// +// See the package documentation for the meaning of the error return value. +func HaveBoundedLoops() error { + return haveBoundedLoops() +} + +var haveBoundedLoops = internal.NewFeatureTest("bounded loops", func() error { + return probeProgram(&ebpf.ProgramSpec{ + Type: ebpf.SocketFilter, + Instructions: asm.Instructions{ + asm.Mov.Imm(asm.R0, 10), + asm.Sub.Imm(asm.R0, 1).WithSymbol("loop"), + asm.JNE.Imm(asm.R0, 0, "loop"), + asm.Return(), + }, + }) +}, "5.3") + +// HaveV2ISA probes the running kernel if instructions of the v2 ISA are supported. +// +// Upstream commit 92b31a9af73b ("bpf: add BPF_J{LT,LE,SLT,SLE} instructions"). +// +// See the package documentation for the meaning of the error return value. +func HaveV2ISA() error { + return haveV2ISA() +} + +var haveV2ISA = internal.NewFeatureTest("v2 ISA", func() error { + err := probeProgram(&ebpf.ProgramSpec{ + Type: ebpf.SocketFilter, + Instructions: asm.Instructions{ + asm.Mov.Imm(asm.R0, 0), + asm.JLT.Imm(asm.R0, 0, "exit"), + asm.Mov.Imm(asm.R0, 1), + asm.Return().WithSymbol("exit"), + }, + }) + // This sometimes bubbles up from the JIT on aarch64. + if errors.Is(err, sys.ENOTSUPP) { + return ebpf.ErrNotSupported + } + return err +}, "4.14") + +// HaveV3ISA probes the running kernel if instructions of the v3 ISA are supported. +// +// Upstream commit 092ed0968bb6 ("bpf: verifier support JMP32"). +// +// See the package documentation for the meaning of the error return value. +func HaveV3ISA() error { + return haveV3ISA() +} + +var haveV3ISA = internal.NewFeatureTest("v3 ISA", func() error { + err := probeProgram(&ebpf.ProgramSpec{ + Type: ebpf.SocketFilter, + Instructions: asm.Instructions{ + asm.Mov.Imm(asm.R0, 0), + asm.JLT.Imm32(asm.R0, 0, "exit"), + asm.Mov.Imm(asm.R0, 1), + asm.Return().WithSymbol("exit"), + }, + }) + // This sometimes bubbles up from the JIT on aarch64. + if errors.Is(err, sys.ENOTSUPP) { + return ebpf.ErrNotSupported + } + return err +}, "5.1") + +// HaveV4ISA probes the running kernel if instructions of the v4 ISA are supported. +// +// Upstream commit 1f9a1ea821ff ("bpf: Support new sign-extension load insns"). +// +// See the package documentation for the meaning of the error return value. +func HaveV4ISA() error { + return haveV4ISA() +} + +var haveV4ISA = internal.NewFeatureTest("v4 ISA", func() error { + err := probeProgram(&ebpf.ProgramSpec{ + Type: ebpf.SocketFilter, + Instructions: asm.Instructions{ + asm.Mov.Imm(asm.R0, 0), + asm.JEq.Imm(asm.R0, 1, "error"), + asm.LongJump("exit"), + asm.Mov.Imm(asm.R0, 1).WithSymbol("error"), + asm.Return().WithSymbol("exit"), + }, + }) + // This sometimes bubbles up from the JIT on aarch64. + if errors.Is(err, sys.ENOTSUPP) { + return ebpf.ErrNotSupported + } + return err +}, "6.6") diff --git a/vendor/github.com/cilium/ebpf/features/prog.go b/vendor/github.com/cilium/ebpf/features/prog.go new file mode 100644 index 0000000000..6441d5931d --- /dev/null +++ b/vendor/github.com/cilium/ebpf/features/prog.go @@ -0,0 +1,332 @@ +package features + +import ( + "errors" + "fmt" + "slices" + "strings" + + "github.com/cilium/ebpf" + "github.com/cilium/ebpf/asm" + "github.com/cilium/ebpf/btf" + "github.com/cilium/ebpf/internal" + "github.com/cilium/ebpf/internal/sys" + "github.com/cilium/ebpf/internal/unix" +) + +// HaveProgramType probes the running kernel for the availability of the specified program type. +// +// See the package documentation for the meaning of the error return value. +func HaveProgramType(pt ebpf.ProgramType) (err error) { + return haveProgramTypeMatrix.Result(pt) +} + +func probeProgram(spec *ebpf.ProgramSpec) error { + if spec.Instructions == nil { + spec.Instructions = asm.Instructions{ + asm.LoadImm(asm.R0, 0, asm.DWord), + asm.Return(), + } + } + prog, err := ebpf.NewProgramWithOptions(spec, ebpf.ProgramOptions{ + LogDisabled: true, + }) + if err == nil { + prog.Close() + } + + switch { + // EINVAL occurs when attempting to create a program with an unknown type. + // E2BIG occurs when ProgLoadAttr contains non-zero bytes past the end + // of the struct known by the running kernel, meaning the kernel is too old + // to support the given prog type. + case errors.Is(err, unix.EINVAL), errors.Is(err, unix.E2BIG): + err = ebpf.ErrNotSupported + } + + return err +} + +var haveProgramTypeMatrix = internal.FeatureMatrix[ebpf.ProgramType]{ + ebpf.SocketFilter: {Version: "3.19"}, + ebpf.Kprobe: {Version: "4.1"}, + ebpf.SchedCLS: {Version: "4.1"}, + ebpf.SchedACT: {Version: "4.1"}, + ebpf.TracePoint: {Version: "4.7"}, + ebpf.XDP: {Version: "4.8"}, + ebpf.PerfEvent: {Version: "4.9"}, + ebpf.CGroupSKB: {Version: "4.10"}, + ebpf.CGroupSock: {Version: "4.10"}, + ebpf.LWTIn: {Version: "4.10"}, + ebpf.LWTOut: {Version: "4.10"}, + ebpf.LWTXmit: {Version: "4.10"}, + ebpf.SockOps: {Version: "4.13"}, + ebpf.SkSKB: {Version: "4.14"}, + ebpf.CGroupDevice: {Version: "4.15"}, + ebpf.SkMsg: {Version: "4.17"}, + ebpf.RawTracepoint: {Version: "4.17"}, + ebpf.CGroupSockAddr: { + Version: "4.17", + Fn: func() error { + return probeProgram(&ebpf.ProgramSpec{ + Type: ebpf.CGroupSockAddr, + AttachType: ebpf.AttachCGroupInet4Connect, + }) + }, + }, + ebpf.LWTSeg6Local: {Version: "4.18"}, + ebpf.LircMode2: {Version: "4.18"}, + ebpf.SkReuseport: {Version: "4.19"}, + ebpf.FlowDissector: {Version: "4.20"}, + ebpf.CGroupSysctl: {Version: "5.2"}, + ebpf.RawTracepointWritable: {Version: "5.2"}, + ebpf.CGroupSockopt: { + Version: "5.3", + Fn: func() error { + return probeProgram(&ebpf.ProgramSpec{ + Type: ebpf.CGroupSockopt, + AttachType: ebpf.AttachCGroupGetsockopt, + }) + }, + }, + ebpf.Tracing: { + Version: "5.5", + Fn: func() error { + return probeProgram(&ebpf.ProgramSpec{ + Type: ebpf.Tracing, + AttachType: ebpf.AttachTraceFEntry, + AttachTo: "bpf_init", + }) + }, + }, + ebpf.StructOps: { + Version: "5.6", + Fn: func() error { + err := probeProgram(&ebpf.ProgramSpec{ + Type: ebpf.StructOps, + License: "GPL", + }) + if errors.Is(err, sys.ENOTSUPP) { + // ENOTSUPP means the program type is at least known to the kernel. + return nil + } + return err + }, + }, + ebpf.Extension: { + Version: "5.6", + Fn: func() error { + // create btf.Func to add to first ins of target and extension so both progs are btf powered + btfFn := btf.Func{ + Name: "a", + Type: &btf.FuncProto{ + Return: &btf.Int{}, + Params: []btf.FuncParam{ + {Name: "ctx", Type: &btf.Pointer{Target: &btf.Struct{Name: "xdp_md"}}}, + }, + }, + Linkage: btf.GlobalFunc, + } + insns := asm.Instructions{ + btf.WithFuncMetadata(asm.Mov.Imm(asm.R0, 0), &btfFn), + asm.Return(), + } + + // create target prog + prog, err := ebpf.NewProgramWithOptions( + &ebpf.ProgramSpec{ + Type: ebpf.XDP, + Instructions: insns, + }, + ebpf.ProgramOptions{ + LogDisabled: true, + }, + ) + if err != nil { + return err + } + defer prog.Close() + + // probe for Extension prog with target + return probeProgram(&ebpf.ProgramSpec{ + Type: ebpf.Extension, + Instructions: insns, + AttachTarget: prog, + AttachTo: btfFn.Name, + }) + }, + }, + ebpf.LSM: { + Version: "5.7", + Fn: func() error { + return probeProgram(&ebpf.ProgramSpec{ + Type: ebpf.LSM, + AttachType: ebpf.AttachLSMMac, + AttachTo: "file_mprotect", + License: "GPL", + }) + }, + }, + ebpf.SkLookup: { + Version: "5.9", + Fn: func() error { + return probeProgram(&ebpf.ProgramSpec{ + Type: ebpf.SkLookup, + AttachType: ebpf.AttachSkLookup, + }) + }, + }, + ebpf.Syscall: { + Version: "5.14", + Fn: func() error { + return probeProgram(&ebpf.ProgramSpec{ + Type: ebpf.Syscall, + Flags: sys.BPF_F_SLEEPABLE, + }) + }, + }, + ebpf.Netfilter: { + Version: "6.4", + Fn: func() error { + return probeProgram(&ebpf.ProgramSpec{ + Type: ebpf.Netfilter, + AttachType: ebpf.AttachNetfilter, + }) + }, + }, +} + +func init() { + for key, ft := range haveProgramTypeMatrix { + ft.Name = key.String() + if ft.Fn == nil { + key := key // avoid the dreaded loop variable problem + ft.Fn = func() error { return probeProgram(&ebpf.ProgramSpec{Type: key}) } + } + } +} + +type helperKey struct { + typ ebpf.ProgramType + helper asm.BuiltinFunc +} + +var helperCache = internal.NewFeatureCache(func(key helperKey) *internal.FeatureTest { + return &internal.FeatureTest{ + Name: fmt.Sprintf("%s for program type %s", key.helper, key.typ), + Fn: func() error { + return haveProgramHelper(key.typ, key.helper) + }, + } +}) + +// HaveProgramHelper probes the running kernel for the availability of the specified helper +// function to a specified program type. +// Return values have the following semantics: +// +// err == nil: The feature is available. +// errors.Is(err, ebpf.ErrNotSupported): The feature is not available. +// err != nil: Any errors encountered during probe execution, wrapped. +// +// Note that the latter case may include false negatives, and that program creation may +// succeed despite an error being returned. +// Only `nil` and `ebpf.ErrNotSupported` are conclusive. +// +// Probe results are cached and persist throughout any process capability changes. +func HaveProgramHelper(pt ebpf.ProgramType, helper asm.BuiltinFunc) error { + return helperCache.Result(helperKey{pt, helper}) +} + +func haveProgramHelper(pt ebpf.ProgramType, helper asm.BuiltinFunc) error { + if ok := helperProbeNotImplemented(pt); ok { + return fmt.Errorf("no feature probe for %v/%v", pt, helper) + } + + if err := HaveProgramType(pt); err != nil { + return err + } + + spec := &ebpf.ProgramSpec{ + Type: pt, + Instructions: asm.Instructions{ + helper.Call(), + asm.LoadImm(asm.R0, 0, asm.DWord), + asm.Return(), + }, + License: "GPL", + } + + switch pt { + case ebpf.CGroupSockAddr: + spec.AttachType = ebpf.AttachCGroupInet4Connect + case ebpf.CGroupSockopt: + spec.AttachType = ebpf.AttachCGroupGetsockopt + case ebpf.SkLookup: + spec.AttachType = ebpf.AttachSkLookup + case ebpf.Syscall: + spec.Flags = sys.BPF_F_SLEEPABLE + case ebpf.Netfilter: + spec.AttachType = ebpf.AttachNetfilter + } + + prog, err := ebpf.NewProgramWithOptions(spec, ebpf.ProgramOptions{ + LogLevel: 1, + }) + if err == nil { + prog.Close() + } + + var verr *ebpf.VerifierError + if !errors.As(err, &verr) { + return err + } + + helperTag := fmt.Sprintf("#%d", helper) + + switch { + // EACCES occurs when attempting to create a program probe with a helper + // while the register args when calling this helper aren't set up properly. + // We interpret this as the helper being available, because the verifier + // returns EINVAL if the helper is not supported by the running kernel. + case errors.Is(err, unix.EACCES): + err = nil + + // EINVAL occurs when attempting to create a program with an unknown helper. + case errors.Is(err, unix.EINVAL): + // https://github.com/torvalds/linux/blob/09a0fa92e5b45e99cf435b2fbf5ebcf889cf8780/kernel/bpf/verifier.c#L10663 + if logContainsAll(verr.Log, "invalid func", helperTag) { + return ebpf.ErrNotSupported + } + + // https://github.com/torvalds/linux/blob/09a0fa92e5b45e99cf435b2fbf5ebcf889cf8780/kernel/bpf/verifier.c#L10668 + wrongProgramType := logContainsAll(verr.Log, "program of this type cannot use helper", helperTag) + // https://github.com/torvalds/linux/blob/59b418c7063d30e0a3e1f592d47df096db83185c/kernel/bpf/verifier.c#L10204 + // 4.9 doesn't include # in verifier output. + wrongProgramType = wrongProgramType || logContainsAll(verr.Log, "unknown func") + if wrongProgramType { + return fmt.Errorf("program of this type cannot use helper: %w", ebpf.ErrNotSupported) + } + } + + return err +} + +func logContainsAll(log []string, needles ...string) bool { + first := max(len(log)-5, 0) // Check last 5 lines. + return slices.ContainsFunc(log[first:], func(line string) bool { + for _, needle := range needles { + if !strings.Contains(line, needle) { + return false + } + } + return true + }) +} + +func helperProbeNotImplemented(pt ebpf.ProgramType) bool { + switch pt { + case ebpf.Extension, ebpf.LSM, ebpf.StructOps, ebpf.Tracing: + return true + } + return false +} diff --git a/vendor/github.com/cilium/ebpf/features/version.go b/vendor/github.com/cilium/ebpf/features/version.go new file mode 100644 index 0000000000..d54d3ea212 --- /dev/null +++ b/vendor/github.com/cilium/ebpf/features/version.go @@ -0,0 +1,18 @@ +package features + +import "github.com/cilium/ebpf/internal/linux" + +// LinuxVersionCode returns the version of the currently running kernel +// as defined in the LINUX_VERSION_CODE compile-time macro. It is represented +// in the format described by the KERNEL_VERSION macro from linux/version.h. +// +// Do not use the version to make assumptions about the presence of certain +// kernel features, always prefer feature probes in this package. Some +// distributions backport or disable eBPF features. +func LinuxVersionCode() (uint32, error) { + v, err := linux.KernelVersion() + if err != nil { + return 0, err + } + return v.Kernel(), nil +} diff --git a/vendor/github.com/cilium/ebpf/info.go b/vendor/github.com/cilium/ebpf/info.go index 676e8fa6e7..b2d08de4be 100644 --- a/vendor/github.com/cilium/ebpf/info.go +++ b/vendor/github.com/cilium/ebpf/info.go @@ -9,13 +9,12 @@ import ( "io" "os" "reflect" - "strings" - "syscall" "time" "github.com/cilium/ebpf/asm" "github.com/cilium/ebpf/btf" "github.com/cilium/ebpf/internal" + "github.com/cilium/ebpf/internal/platform" "github.com/cilium/ebpf/internal/sys" "github.com/cilium/ebpf/internal/unix" ) @@ -60,6 +59,36 @@ type MapInfo struct { frozen bool } +// minimalMapInfoFromFd queries the minimum information needed to create a Map +// based on a file descriptor. This requires the map type, key/value sizes, +// maxentries and flags. +// +// Does not fall back to fdinfo since the version gap between fdinfo (4.10) and +// [sys.ObjInfo] (4.13) is small and both kernels are EOL since at least Nov +// 2017. +// +// Requires at least Linux 4.13. +func minimalMapInfoFromFd(fd *sys.FD) (*MapInfo, error) { + var info sys.MapInfo + if err := sys.ObjInfo(fd, &info); err != nil { + return nil, fmt.Errorf("getting object info: %w", err) + } + + typ, err := MapTypeForPlatform(platform.Native, info.Type) + if err != nil { + return nil, fmt.Errorf("map type: %w", err) + } + + return &MapInfo{ + Type: typ, + KeySize: info.KeySize, + ValueSize: info.ValueSize, + MaxEntries: info.MaxEntries, + Flags: uint32(info.MapFlags), + Name: unix.ByteSliceToString(info.Name[:]), + }, nil +} + // newMapInfoFromFd queries map information about the given fd. [sys.ObjInfo] is // attempted first, supplementing any missing values with information from // /proc/self/fdinfo. Ignores EINVAL from ObjInfo as well as ErrNotSupported @@ -74,8 +103,13 @@ func newMapInfoFromFd(fd *sys.FD) (*MapInfo, error) { return nil, fmt.Errorf("getting object info: %w", err1) } + typ, err := MapTypeForPlatform(platform.Native, info.Type) + if err != nil { + return nil, fmt.Errorf("map type: %w", err) + } + mi := &MapInfo{ - MapType(info.Type), + typ, info.KeySize, info.ValueSize, info.MaxEntries, @@ -105,8 +139,9 @@ func newMapInfoFromFd(fd *sys.FD) (*MapInfo, error) { // readMapInfoFromProc queries map information about the given fd from // /proc/self/fdinfo. It only writes data into fields that have a zero value. func readMapInfoFromProc(fd *sys.FD, mi *MapInfo) error { - return scanFdInfo(fd, map[string]interface{}{ - "map_type": &mi.Type, + var mapType uint32 + err := scanFdInfo(fd, map[string]any{ + "map_type": &mapType, "map_id": &mi.id, "key_size": &mi.KeySize, "value_size": &mi.ValueSize, @@ -116,6 +151,18 @@ func readMapInfoFromProc(fd *sys.FD, mi *MapInfo) error { "memlock": &mi.memlock, "frozen": &mi.frozen, }) + if err != nil { + return err + } + + if mi.Type == 0 { + mi.Type, err = MapTypeForPlatform(platform.Linux, mapType) + if err != nil { + return fmt.Errorf("map type: %w", err) + } + } + + return nil } // ID returns the map ID. @@ -168,15 +215,41 @@ func (mi *MapInfo) Frozen() bool { return mi.frozen } -// programStats holds statistics of a program. -type programStats struct { - // Total accumulated runtime of the program ins ns. - runtime time.Duration - // Total number of times the program was called. - runCount uint64 - // Total number of times the programm was NOT called. - // Added in commit 9ed9e9ba2337 ("bpf: Count the number of times recursion was prevented"). - recursionMisses uint64 +// ProgramStats contains runtime statistics for a single [Program], returned by +// [Program.Stats]. +// +// Will contain mostly zero values if the collection of statistics is not +// enabled, see [EnableStats]. +type ProgramStats struct { + // Total accumulated runtime of the Program. + // + // Requires at least Linux 5.8. + Runtime time.Duration + + // Total number of times the Program has executed. + // + // Requires at least Linux 5.8. + RunCount uint64 + + // Total number of times the program was not executed due to recursion. This + // can happen when another bpf program is already running on the cpu, when bpf + // program execution is interrupted, for example. + // + // Requires at least Linux 5.12. + RecursionMisses uint64 +} + +func newProgramStatsFromFd(fd *sys.FD) (*ProgramStats, error) { + var info sys.ProgInfo + if err := sys.ObjInfo(fd, &info); err != nil { + return nil, fmt.Errorf("getting program info: %w", err) + } + + return &ProgramStats{ + Runtime: time.Duration(info.RunTimeNs), + RunCount: info.RunCnt, + RecursionMisses: info.RecursionMisses, + }, nil } // programJitedInfo holds information about JITed info of a program. @@ -213,7 +286,8 @@ type programJitedInfo struct { numFuncLens uint32 } -// ProgramInfo describes a program. +// ProgramInfo describes a Program's immutable metadata. For runtime statistics, +// see [ProgramStats]. type ProgramInfo struct { Type ProgramType id ProgramID @@ -225,11 +299,13 @@ type ProgramInfo struct { createdByUID uint32 haveCreatedByUID bool btf btf.ID - stats *programStats loadTime time.Duration + restricted bool + maps []MapID insns []byte + numInsns uint32 jitedSize uint32 verifiedInstructions uint32 @@ -239,32 +315,81 @@ type ProgramInfo struct { numLineInfos uint32 funcInfos []byte numFuncInfos uint32 + + memlock uint64 } +// minimalProgramFromFd queries the minimum information needed to create a +// Program based on a file descriptor, requiring at least the program type. +// +// Does not fall back to fdinfo since the version gap between fdinfo (4.10) and +// [sys.ObjInfo] (4.13) is small and both kernels are EOL since at least Nov +// 2017. +// +// Requires at least Linux 4.13. +func minimalProgramInfoFromFd(fd *sys.FD) (*ProgramInfo, error) { + var info sys.ProgInfo + if err := sys.ObjInfo(fd, &info); err != nil { + return nil, fmt.Errorf("getting object info: %w", err) + } + + typ, err := ProgramTypeForPlatform(platform.Native, info.Type) + if err != nil { + return nil, fmt.Errorf("program type: %w", err) + } + + return &ProgramInfo{ + Type: typ, + Name: unix.ByteSliceToString(info.Name[:]), + }, nil +} + +// newProgramInfoFromFd queries program information about the given fd. +// +// [sys.ObjInfo] is attempted first, supplementing any missing values with +// information from /proc/self/fdinfo. Ignores EINVAL from ObjInfo as well as +// ErrNotSupported from reading fdinfo (indicating the file exists, but no +// fields of interest were found). If both fail, an error is always returned. func newProgramInfoFromFd(fd *sys.FD) (*ProgramInfo, error) { var info sys.ProgInfo - err := sys.ObjInfo(fd, &info) - if errors.Is(err, syscall.EINVAL) { - return newProgramInfoFromProc(fd) + err1 := sys.ObjInfo(fd, &info) + // EINVAL means the kernel doesn't support BPF_OBJ_GET_INFO_BY_FD. Continue + // with fdinfo if that's the case. + if err1 != nil && !errors.Is(err1, unix.EINVAL) { + return nil, fmt.Errorf("getting object info: %w", err1) } + + typ, err := ProgramTypeForPlatform(platform.Native, info.Type) if err != nil { - return nil, err + return nil, fmt.Errorf("program type: %w", err) } 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, - recursionMisses: info.RecursionMisses, - }, + Type: typ, + id: ProgramID(info.Id), + Tag: hex.EncodeToString(info.Tag[:]), + Name: unix.ByteSliceToString(info.Name[:]), + btf: btf.ID(info.BtfId), jitedSize: info.JitedProgLen, loadTime: time.Duration(info.LoadTime), verifiedInstructions: info.VerifiedInsns, + numInsns: info.XlatedProgLen, + } + + // Supplement OBJ_INFO with data from /proc/self/fdinfo. It contains fields + // like memlock that is not present in OBJ_INFO. + err2 := readProgramInfoFromProc(fd, &pi) + if err2 != nil && !errors.Is(err2, ErrNotSupported) { + return nil, fmt.Errorf("getting map info from fdinfo: %w", err2) + } + + if err1 != nil && err2 != nil { + return nil, fmt.Errorf("ObjInfo and fdinfo both failed: objinfo: %w, fdinfo: %w", err1, err2) + } + + if platform.IsWindows && info.Tag == [8]uint8{} { + // Windows doesn't support the tag field, clear it for now. + pi.Tag = "" } // Start with a clean struct for the second call, otherwise we may get EFAULT. @@ -275,7 +400,7 @@ func newProgramInfoFromFd(fd *sys.FD) (*ProgramInfo, error) { if info.NrMapIds > 0 { pi.maps = make([]MapID, info.NrMapIds) info2.NrMapIds = info.NrMapIds - info2.MapIds = sys.NewSlicePointer(pi.maps) + info2.MapIds = sys.SlicePointer(pi.maps) makeSecondCall = true } else if haveProgramInfoMapIDs() == nil { // This program really has no associated maps. @@ -286,21 +411,32 @@ func newProgramInfoFromFd(fd *sys.FD) (*ProgramInfo, error) { } // createdByUID and NrMapIds were introduced in the same kernel version. - if pi.maps != nil { + if pi.maps != nil && platform.IsLinux { pi.createdByUID = info.CreatedByUid pi.haveCreatedByUID = true } if info.XlatedProgLen > 0 { pi.insns = make([]byte, info.XlatedProgLen) - info2.XlatedProgLen = info.XlatedProgLen - info2.XlatedProgInsns = sys.NewSlicePointer(pi.insns) - makeSecondCall = true + var info3 sys.ProgInfo + info3.XlatedProgLen = info.XlatedProgLen + info3.XlatedProgInsns = sys.SlicePointer(pi.insns) + + // When kernel.kptr_restrict and net.core.bpf_jit_harden are both set, it causes the + // syscall to abort when trying to readback xlated instructions, skipping other info + // as well. So request xlated instructions separately. + if err := sys.ObjInfo(fd, &info3); err != nil { + return nil, err + } + if info3.XlatedProgInsns.IsNil() { + pi.restricted = true + pi.insns = nil + } } if info.NrLineInfo > 0 { pi.lineInfos = make([]byte, btf.LineInfoSize*info.NrLineInfo) - info2.LineInfo = sys.NewSlicePointer(pi.lineInfos) + info2.LineInfo = sys.SlicePointer(pi.lineInfos) info2.LineInfoRecSize = btf.LineInfoSize info2.NrLineInfo = info.NrLineInfo pi.numLineInfos = info.NrLineInfo @@ -309,7 +445,7 @@ func newProgramInfoFromFd(fd *sys.FD) (*ProgramInfo, error) { if info.NrFuncInfo > 0 { pi.funcInfos = make([]byte, btf.FuncInfoSize*info.NrFuncInfo) - info2.FuncInfo = sys.NewSlicePointer(pi.funcInfos) + info2.FuncInfo = sys.SlicePointer(pi.funcInfos) info2.FuncInfoRecSize = btf.FuncInfoSize info2.NrFuncInfo = info.NrFuncInfo pi.numFuncInfos = info.NrFuncInfo @@ -321,7 +457,7 @@ func newProgramInfoFromFd(fd *sys.FD) (*ProgramInfo, error) { pi.jitedInfo.numInsns = info.JitedProgLen pi.jitedInfo.insns = make([]byte, info.JitedProgLen) info2.JitedProgLen = info.JitedProgLen - info2.JitedProgInsns = sys.NewSlicePointer(pi.jitedInfo.insns) + info2.JitedProgInsns = sys.SlicePointer(pi.jitedInfo.insns) makeSecondCall = true } @@ -329,7 +465,7 @@ func newProgramInfoFromFd(fd *sys.FD) (*ProgramInfo, error) { pi.jitedInfo.numFuncLens = info.NrJitedFuncLens pi.jitedInfo.funcLens = make([]uint32, info.NrJitedFuncLens) info2.NrJitedFuncLens = info.NrJitedFuncLens - info2.JitedFuncLens = sys.NewSlicePointer(pi.jitedInfo.funcLens) + info2.JitedFuncLens = sys.SlicePointer(pi.jitedInfo.funcLens) makeSecondCall = true } @@ -337,7 +473,7 @@ func newProgramInfoFromFd(fd *sys.FD) (*ProgramInfo, error) { pi.jitedInfo.numLineInfos = info.NrJitedLineInfo pi.jitedInfo.lineInfos = make([]uint64, info.NrJitedLineInfo) info2.NrJitedLineInfo = info.NrJitedLineInfo - info2.JitedLineInfo = sys.NewSlicePointer(pi.jitedInfo.lineInfos) + info2.JitedLineInfo = sys.SlicePointer(pi.jitedInfo.lineInfos) info2.JitedLineInfoRecSize = info.JitedLineInfoRecSize makeSecondCall = true } @@ -345,7 +481,7 @@ func newProgramInfoFromFd(fd *sys.FD) (*ProgramInfo, error) { if info.NrJitedKsyms > 0 { pi.jitedInfo.numKsyms = info.NrJitedKsyms pi.jitedInfo.ksyms = make([]uint64, info.NrJitedKsyms) - info2.JitedKsyms = sys.NewSlicePointer(pi.jitedInfo.ksyms) + info2.JitedKsyms = sys.SlicePointer(pi.jitedInfo.ksyms) info2.NrJitedKsyms = info.NrJitedKsyms makeSecondCall = true } @@ -354,28 +490,76 @@ func newProgramInfoFromFd(fd *sys.FD) (*ProgramInfo, error) { if err := sys.ObjInfo(fd, &info2); err != nil { return nil, err } + if info.JitedProgLen > 0 && info2.JitedProgInsns.IsNil() { + // JIT information is not available due to kernel.kptr_restrict + pi.jitedInfo.lineInfos = nil + pi.jitedInfo.ksyms = nil + pi.jitedInfo.insns = nil + pi.jitedInfo.funcLens = nil + } + } + + if len(pi.Name) == len(info.Name)-1 { // Possibly truncated, check BTF info for full name + name, err := readNameFromFunc(&pi) + if err == nil { + pi.Name = name + } // If an error occurs, keep the truncated name, which is better than none } return &pi, nil } -func newProgramInfoFromProc(fd *sys.FD) (*ProgramInfo, error) { - var info ProgramInfo - err := scanFdInfo(fd, map[string]interface{}{ - "prog_type": &info.Type, - "prog_tag": &info.Tag, +func readNameFromFunc(pi *ProgramInfo) (string, error) { + if pi.numFuncInfos == 0 { + return "", errors.New("no function info") + } + + spec, err := pi.btfSpec() + if err != nil { + return "", err + } + + funcInfos, err := btf.LoadFuncInfos( + bytes.NewReader(pi.funcInfos), + internal.NativeEndian, + pi.numFuncInfos, + spec, + ) + if err != nil { + return "", err + } + + for _, funcInfo := range funcInfos { + if funcInfo.Offset == 0 { // Information about the whole program + return funcInfo.Func.Name, nil + } + } + return "", errors.New("no function info about program") +} + +func readProgramInfoFromProc(fd *sys.FD, pi *ProgramInfo) error { + var progType uint32 + err := scanFdInfo(fd, map[string]any{ + "prog_type": &progType, + "prog_tag": &pi.Tag, + "memlock": &pi.memlock, }) - if errors.Is(err, ErrNotSupported) { - return nil, &internal.UnsupportedFeatureError{ + if errors.Is(err, ErrNotSupported) && !errors.Is(err, internal.ErrNotSupportedOnOS) { + return &internal.UnsupportedFeatureError{ Name: "reading program info from /proc/self/fdinfo", MinimumVersion: internal.Version{4, 10, 0}, } } if err != nil { - return nil, err + return err } - return &info, nil + pi.Type, err = ProgramTypeForPlatform(platform.Linux, progType) + if err != nil { + return fmt.Errorf("program type: %w", err) + } + + return nil } // ID returns the program ID. @@ -408,38 +592,6 @@ func (pi *ProgramInfo) BTFID() (btf.ID, bool) { return pi.btf, pi.btf > 0 } -// RunCount returns the total number of times the program was called. -// -// Can return 0 if the collection of statistics is not enabled. See EnableStats(). -// The bool return value indicates whether this optional field is available. -func (pi *ProgramInfo) RunCount() (uint64, bool) { - if pi.stats != nil { - return pi.stats.runCount, true - } - return 0, false -} - -// Runtime returns the total accumulated runtime of the program. -// -// Can return 0 if the collection of statistics is not enabled. See EnableStats(). -// The bool return value indicates whether this optional field is available. -func (pi *ProgramInfo) Runtime() (time.Duration, bool) { - if pi.stats != nil { - return pi.stats.runtime, true - } - return time.Duration(0), false -} - -// RecursionMisses returns the total number of times the program was NOT called. -// This can happen when another bpf program is already running on the cpu, which -// is likely to happen for example when you interrupt bpf program execution. -func (pi *ProgramInfo) RecursionMisses() (uint64, bool) { - if pi.stats != nil { - return pi.stats.recursionMisses, true - } - return 0, false -} - // btfSpec returns the BTF spec associated with the program. func (pi *ProgramInfo) btfSpec() (*btf.Spec, error) { id, ok := pi.BTFID() @@ -461,10 +613,17 @@ func (pi *ProgramInfo) btfSpec() (*btf.Spec, error) { return spec, nil } +// ErrRestrictedKernel is returned when kernel address information is restricted +// by kernel.kptr_restrict and/or net.core.bpf_jit_harden sysctls. +var ErrRestrictedKernel = internal.ErrRestrictedKernel + // LineInfos returns the BTF line information of the program. // // Available from 5.0. // +// Returns an error wrapping [ErrRestrictedKernel] if line infos are restricted +// by sysctls. +// // Requires CAP_SYS_ADMIN or equivalent for reading BTF information. Returns // ErrNotSupported if the program was created without BTF or if the kernel // doesn't support the field. @@ -504,9 +663,20 @@ func (pi *ProgramInfo) LineInfos() (btf.LineOffsets, error) { // this metadata requires CAP_SYS_ADMIN or equivalent. If capability is // unavailable, the instructions will be returned without metadata. // +// Returns an error wrapping [ErrRestrictedKernel] if instructions are +// restricted by sysctls. +// // Available from 4.13. Requires CAP_BPF or equivalent for plain instructions. // Requires CAP_SYS_ADMIN for instructions with metadata. func (pi *ProgramInfo) Instructions() (asm.Instructions, error) { + if platform.IsWindows && len(pi.insns) == 0 { + return nil, fmt.Errorf("read instructions: %w", internal.ErrNotSupportedOnOS) + } + + if pi.restricted { + return nil, fmt.Errorf("instructions: %w", ErrRestrictedKernel) + } + // 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 { @@ -514,8 +684,8 @@ func (pi *ProgramInfo) Instructions() (asm.Instructions, error) { } r := bytes.NewReader(pi.insns) - var insns asm.Instructions - if err := insns.Unmarshal(r, internal.NativeEndian); err != nil { + insns, err := asm.AppendInstructions(nil, r, internal.NativeEndian, platform.Native) + if err != nil { return nil, fmt.Errorf("unmarshaling instructions: %w", err) } @@ -538,27 +708,20 @@ func (pi *ProgramInfo) Instructions() (asm.Instructions, error) { return nil, fmt.Errorf("unable to get BTF spec: %w", err) } - lineInfos, err := btf.LoadLineInfos( - bytes.NewReader(pi.lineInfos), - internal.NativeEndian, - pi.numLineInfos, - spec, - ) + lineInfos, err := btf.LoadLineInfos(bytes.NewReader(pi.lineInfos), internal.NativeEndian, pi.numLineInfos, spec) if err != nil { return nil, fmt.Errorf("parse line info: %w", err) } - funcInfos, err := btf.LoadFuncInfos( - bytes.NewReader(pi.funcInfos), - internal.NativeEndian, - pi.numFuncInfos, - spec, - ) + funcInfos, err := btf.LoadFuncInfos(bytes.NewReader(pi.funcInfos), internal.NativeEndian, pi.numFuncInfos, spec) if err != nil { return nil, fmt.Errorf("parse func info: %w", err) } - btf.AssignMetadataToInstructions(insns, funcInfos, lineInfos, btf.CORERelocationInfos{}) + iter := insns.Iterate() + for iter.Next() { + assignMetadata(iter.Ins, iter.Offset, &funcInfos, &lineInfos, nil) + } } } @@ -572,8 +735,12 @@ func (pi *ProgramInfo) Instructions() (asm.Instructions, error) { return insns, nil } -// JitedSize returns the size of the program's JIT-compiled machine code in bytes, which is the -// actual code executed on the host's CPU. This field requires the BPF JIT compiler to be enabled. +// JitedSize returns the size of the program's JIT-compiled machine code in +// bytes, which is the actual code executed on the host's CPU. This field +// requires the BPF JIT compiler to be enabled. +// +// Returns an error wrapping [ErrRestrictedKernel] if jited program size is +// restricted by sysctls. // // Available from 4.13. Reading this metadata requires CAP_BPF or equivalent. func (pi *ProgramInfo) JitedSize() (uint32, error) { @@ -583,16 +750,15 @@ func (pi *ProgramInfo) JitedSize() (uint32, error) { return pi.jitedSize, nil } -// TranslatedSize returns the size of the program's translated instructions in bytes, after it has -// been verified and rewritten by the kernel. +// TranslatedSize returns the size of the program's translated instructions in +// bytes, after it has been verified and rewritten by the kernel. // // Available from 4.13. Reading this metadata requires CAP_BPF or equivalent. func (pi *ProgramInfo) TranslatedSize() (int, error) { - insns := len(pi.insns) - if insns == 0 { + if pi.numInsns == 0 { return 0, fmt.Errorf("insufficient permissions or unsupported kernel: %w", ErrNotSupported) } - return insns, nil + return int(pi.numInsns), nil } // MapIDs returns the maps related to the program. @@ -683,6 +849,9 @@ func (pi *ProgramInfo) JitedFuncLens() ([]uint32, bool) { // // Available from 5.0. // +// Returns an error wrapping [ErrRestrictedKernel] if function information is +// restricted by sysctls. +// // Requires CAP_SYS_ADMIN or equivalent for reading BTF information. Returns // ErrNotSupported if the program was created without BTF or if the kernel // doesn't support the field. @@ -704,7 +873,20 @@ func (pi *ProgramInfo) FuncInfos() (btf.FuncOffsets, error) { ) } -func scanFdInfo(fd *sys.FD, fields map[string]interface{}) error { +// ProgramInfo returns an approximate number of bytes allocated to this program. +// +// Available from 4.10. +// +// The bool return value indicates whether this optional field is available. +func (pi *ProgramInfo) Memlock() (uint64, bool) { + return pi.memlock, pi.memlock > 0 +} + +func scanFdInfo(fd *sys.FD, fields map[string]any) error { + if platform.IsWindows { + return fmt.Errorf("read fdinfo: %w", internal.ErrNotSupportedOnOS) + } + fh, err := os.Open(fmt.Sprintf("/proc/self/fdinfo/%d", fd.Int())) if err != nil { return err @@ -717,29 +899,35 @@ func scanFdInfo(fd *sys.FD, fields map[string]interface{}) error { return nil } -func scanFdInfoReader(r io.Reader, fields map[string]interface{}) error { +func scanFdInfoReader(r io.Reader, fields map[string]any) error { var ( scanner = bufio.NewScanner(r) scanned int + reader bytes.Reader ) for scanner.Scan() { - parts := strings.SplitN(scanner.Text(), "\t", 2) - if len(parts) != 2 { + key, rest, found := bytes.Cut(scanner.Bytes(), []byte(":")) + if !found { + // Line doesn't contain a colon, skip. continue } - - name := strings.TrimSuffix(parts[0], ":") - field, ok := fields[string(name)] + field, ok := fields[string(key)] if !ok { continue } - // If field already contains a non-zero value, don't overwrite it with fdinfo. - if zero(field) { - if n, err := fmt.Sscanln(parts[1], field); err != nil || n != 1 { - return fmt.Errorf("can't parse field %s: %v", name, err) - } + if !zero(field) { + scanned++ + continue + } + + // Cut the \t following the : as well as any potential trailing whitespace. + rest = bytes.TrimSpace(rest) + + reader.Reset(rest) + if n, err := fmt.Fscan(&reader, field); err != nil || n != 1 { + return fmt.Errorf("can't parse field %s: %v", key, err) } scanned++ @@ -768,12 +956,16 @@ func zero(arg any) bool { return v.IsZero() } -// EnableStats starts the measuring of the runtime -// and run counts of eBPF programs. +// EnableStats starts collecting runtime statistics of eBPF programs, like the +// amount of program executions and the cumulative runtime. +// +// Specify a BPF_STATS_* constant to select which statistics to collect, like +// [unix.BPF_STATS_RUN_TIME]. Closing the returned [io.Closer] will stop +// collecting statistics. // -// Collecting statistics can have an impact on the performance. +// Collecting statistics may have a performance impact. // -// Requires at least 5.8. +// Requires at least Linux 5.8. func EnableStats(which uint32) (io.Closer, error) { fd, err := sys.EnableStats(&sys.EnableStatsAttr{ Type: which, @@ -785,6 +977,11 @@ func EnableStats(which uint32) (io.Closer, error) { } var haveProgramInfoMapIDs = internal.NewFeatureTest("map IDs in program info", func() error { + if platform.IsWindows { + // We only support efW versions which have this feature, no need to probe. + return nil + } + prog, err := progLoad(asm.Instructions{ asm.LoadImm(asm.R0, 0, asm.DWord), asm.Return(), @@ -809,4 +1006,4 @@ var haveProgramInfoMapIDs = internal.NewFeatureTest("map IDs in program info", f } return err -}, "4.15") +}, "4.15", "windows:0.21.0") diff --git a/vendor/github.com/cilium/ebpf/internal/buffer.go b/vendor/github.com/cilium/ebpf/internal/buffer.go deleted file mode 100644 index 81c6544330..0000000000 --- a/vendor/github.com/cilium/ebpf/internal/buffer.go +++ /dev/null @@ -1,31 +0,0 @@ -package internal - -import ( - "bytes" - "sync" -) - -var bytesBufferPool = sync.Pool{ - New: func() interface{} { - return new(bytes.Buffer) - }, -} - -// NewBuffer retrieves a [bytes.Buffer] from a pool an re-initialises it. -// -// The returned buffer should be passed to [PutBuffer]. -func NewBuffer(buf []byte) *bytes.Buffer { - wr := bytesBufferPool.Get().(*bytes.Buffer) - // Reinitialize the Buffer with a new backing slice since it is returned to - // the caller by wr.Bytes() below. Pooling is faster despite calling - // NewBuffer. The pooled alloc is still reused, it only needs to be zeroed. - *wr = *bytes.NewBuffer(buf) - return wr -} - -// PutBuffer releases a buffer to the pool. -func PutBuffer(buf *bytes.Buffer) { - // Release reference to the backing buffer. - *buf = *bytes.NewBuffer(nil) - bytesBufferPool.Put(buf) -} diff --git a/vendor/github.com/cilium/ebpf/internal/deque.go b/vendor/github.com/cilium/ebpf/internal/deque.go index e3a3050215..ed113ddd7d 100644 --- a/vendor/github.com/cilium/ebpf/internal/deque.go +++ b/vendor/github.com/cilium/ebpf/internal/deque.go @@ -75,10 +75,7 @@ func (dq *Deque[T]) Grow(n int) { // Round up to the new power of two which is at least 8. // See https://jameshfisher.com/2018/03/30/round-up-power-2/ - capacity := 1 << (64 - bits.LeadingZeros64(need-1)) - if capacity < 8 { - capacity = 8 - } + capacity := max(1<<(64-bits.LeadingZeros64(need-1)), 8) elems := make([]T, have, capacity) pivot := dq.read & dq.mask diff --git a/vendor/github.com/cilium/ebpf/internal/efw/enums.go b/vendor/github.com/cilium/ebpf/internal/efw/enums.go new file mode 100644 index 0000000000..71320b6311 --- /dev/null +++ b/vendor/github.com/cilium/ebpf/internal/efw/enums.go @@ -0,0 +1,65 @@ +//go:build windows + +package efw + +import ( + "syscall" + "unsafe" + + "golang.org/x/sys/windows" +) + +/* +Converts an attach type enum into a GUID. + + ebpf_result_t ebpf_get_ebpf_attach_type( + bpf_attach_type_t bpf_attach_type, + _Out_ ebpf_attach_type_t* ebpf_attach_type_t *ebpf_attach_type) +*/ +var ebpfGetEbpfAttachTypeProc = newProc("ebpf_get_ebpf_attach_type") + +func EbpfGetEbpfAttachType(attachType uint32) (windows.GUID, error) { + addr, err := ebpfGetEbpfAttachTypeProc.Find() + if err != nil { + return windows.GUID{}, err + } + + var attachTypeGUID windows.GUID + err = errorResult(syscall.SyscallN(addr, + uintptr(attachType), + uintptr(unsafe.Pointer(&attachTypeGUID)), + )) + return attachTypeGUID, err +} + +/* +Retrieve a program type given a GUID. + + bpf_prog_type_t ebpf_get_bpf_program_type(_In_ const ebpf_program_type_t* program_type) +*/ +var ebpfGetBpfProgramTypeProc = newProc("ebpf_get_bpf_program_type") + +func EbpfGetBpfProgramType(programType windows.GUID) (uint32, error) { + addr, err := ebpfGetBpfProgramTypeProc.Find() + if err != nil { + return 0, err + } + + return uint32Result(syscall.SyscallN(addr, uintptr(unsafe.Pointer(&programType)))), nil +} + +/* +Retrieve an attach type given a GUID. + + bpf_attach_type_t ebpf_get_bpf_attach_type(_In_ const ebpf_attach_type_t* ebpf_attach_type) +*/ +var ebpfGetBpfAttachTypeProc = newProc("ebpf_get_bpf_attach_type") + +func EbpfGetBpfAttachType(attachType windows.GUID) (uint32, error) { + addr, err := ebpfGetBpfAttachTypeProc.Find() + if err != nil { + return 0, err + } + + return uint32Result(syscall.SyscallN(addr, uintptr(unsafe.Pointer(&attachType)))), nil +} diff --git a/vendor/github.com/cilium/ebpf/internal/efw/error_reporting.go b/vendor/github.com/cilium/ebpf/internal/efw/error_reporting.go new file mode 100644 index 0000000000..83b9a265ee --- /dev/null +++ b/vendor/github.com/cilium/ebpf/internal/efw/error_reporting.go @@ -0,0 +1,155 @@ +//go:build windows + +package efw + +import ( + "errors" + "fmt" + "os" + "syscall" + "testing" + + "golang.org/x/sys/windows" +) + +func init() { + if !testing.Testing() { + return + } + + if isDebuggerPresent() { + return + } + + if err := configureCRTErrorReporting(); err != nil { + fmt.Fprintln(os.Stderr, "WARNING: Could not configure CRT error reporting, tests may hang:", err) + } +} + +var errErrorReportingAlreadyConfigured = errors.New("error reporting already configured") + +// Configure built-in error reporting of the C runtime library. +// +// The C runtime emits assertion failures into a graphical message box by default. +// This causes a hang in CI environments. This function configures the CRT to +// log to stderr instead. +func configureCRTErrorReporting() error { + const ucrtDebug = "ucrtbased.dll" + + // Constants from crtdbg.h + // + // See https://doxygen.reactos.org/da/d40/crt_2crtdbg_8h_source.html + const ( + _CRT_ERROR = 1 + _CRT_ASSERT = 2 + _CRTDBG_MODE_FILE = 0x1 + _CRTDBG_MODE_WNDW = 0x4 + _CRTDBG_HFILE_ERROR = -2 + _CRTDBG_FILE_STDERR = -4 + ) + + // Load the efW API to trigger loading the CRT. This may fail, in which case + // we can't figure out which CRT is being used. + // In that case we rely on the error bubbling up via some other path. + _ = module.Load() + + ucrtHandle, err := syscall.UTF16PtrFromString(ucrtDebug) + if err != nil { + return err + } + + var handle windows.Handle + err = windows.GetModuleHandleEx(0, ucrtHandle, &handle) + if errors.Is(err, windows.ERROR_MOD_NOT_FOUND) { + // Loading the ebpf api did not pull in the debug UCRT, so there is + // nothing to configure. + return nil + } else if err != nil { + return err + } + defer windows.FreeLibrary(handle) + + setReportModeAddr, err := windows.GetProcAddress(handle, "_CrtSetReportMode") + if err != nil { + return err + } + + setReportMode := func(reportType int, reportMode int) (int, error) { + // See https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/crtsetreportmode?view=msvc-170 + r1, _, err := syscall.SyscallN(setReportModeAddr, uintptr(reportType), uintptr(reportMode)) + if int(r1) == -1 { + return 0, fmt.Errorf("set report mode for type %d: %w", reportType, err) + } + return int(r1), nil + } + + setReportFileAddr, err := windows.GetProcAddress(handle, "_CrtSetReportFile") + if err != nil { + return err + } + + setReportFile := func(reportType int, reportFile int) (int, error) { + // See https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/crtsetreportfile?view=msvc-170 + r1, _, err := syscall.SyscallN(setReportFileAddr, uintptr(reportType), uintptr(reportFile)) + if int(r1) == _CRTDBG_HFILE_ERROR { + return 0, fmt.Errorf("set report file for type %d: %w", reportType, err) + } + return int(r1), nil + } + + reportToFile := func(reportType, defaultMode int) error { + oldMode, err := setReportMode(reportType, _CRTDBG_MODE_FILE) + if err != nil { + return err + } + + if oldMode != defaultMode { + // Attempt to restore old mode if it was different from the expected default. + _, _ = setReportMode(reportType, oldMode) + return errErrorReportingAlreadyConfigured + } + + oldFile, err := setReportFile(reportType, _CRTDBG_FILE_STDERR) + if err != nil { + return err + } + + if oldFile != -1 { + // Attempt to restore old file if it was different from the expected default. + _, _ = setReportFile(reportType, oldFile) + return errErrorReportingAlreadyConfigured + } + + return nil + } + + // See https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/crtsetreportmode?view=msvc-170#remarks + // for defaults. + if err := reportToFile(_CRT_ASSERT, _CRTDBG_MODE_WNDW); err != nil { + return err + } + + if err := reportToFile(_CRT_ERROR, _CRTDBG_MODE_WNDW); err != nil { + return err + } + + return nil +} + +// isDebuggerPresent returns true if the current process is being debugged. +// +// See https://learn.microsoft.com/en-us/windows/win32/api/debugapi/nf-debugapi-isdebuggerpresent +func isDebuggerPresent() bool { + kernel32Handle, err := windows.LoadLibrary("kernel32.dll") + if err != nil { + return false + } + + isDebuggerPresentAddr, err := windows.GetProcAddress(kernel32Handle, "IsDebuggerPresent") + if err != nil { + return false + } + + r1, _, _ := syscall.SyscallN(isDebuggerPresentAddr) + return r1 != 0 +} diff --git a/vendor/github.com/cilium/ebpf/internal/efw/fd.go b/vendor/github.com/cilium/ebpf/internal/efw/fd.go new file mode 100644 index 0000000000..b0d0bcdd4f --- /dev/null +++ b/vendor/github.com/cilium/ebpf/internal/efw/fd.go @@ -0,0 +1,34 @@ +//go:build windows + +package efw + +import ( + "syscall" + "unsafe" +) + +// ebpf_result_t ebpf_close_fd(fd_t fd) +var ebpfCloseFdProc = newProc("ebpf_close_fd") + +func EbpfCloseFd(fd int) error { + addr, err := ebpfCloseFdProc.Find() + if err != nil { + return err + } + + return errorResult(syscall.SyscallN(addr, uintptr(fd))) +} + +// ebpf_result_t ebpf_duplicate_fd(fd_t fd, _Out_ fd_t* dup) +var ebpfDuplicateFdProc = newProc("ebpf_duplicate_fd") + +func EbpfDuplicateFd(fd int) (int, error) { + addr, err := ebpfDuplicateFdProc.Find() + if err != nil { + return -1, err + } + + var dup FD + err = errorResult(syscall.SyscallN(addr, uintptr(fd), uintptr(unsafe.Pointer(&dup)))) + return int(dup), err +} diff --git a/vendor/github.com/cilium/ebpf/internal/efw/map.go b/vendor/github.com/cilium/ebpf/internal/efw/map.go new file mode 100644 index 0000000000..82f510fef0 --- /dev/null +++ b/vendor/github.com/cilium/ebpf/internal/efw/map.go @@ -0,0 +1,109 @@ +//go:build windows + +package efw + +import ( + "runtime" + "syscall" + "unsafe" + + "golang.org/x/sys/windows" +) + +/* +ebpf_ring_buffer_map_map_buffer( + + fd_t map_fd, + _Outptr_result_maybenull_ void** consumer, + _Outptr_result_maybenull_ const void** producer, + _Outptr_result_buffer_maybenull_(*data_size) const uint8_t** data, + _Out_ size_t* data_size) EBPF_NO_EXCEPT; +*/ +var ebpfRingBufferMapMapBufferProc = newProc("ebpf_ring_buffer_map_map_buffer") + +func EbpfRingBufferMapMapBuffer(mapFd int) (consumer, producer, data *uint8, dataLen Size, _ error) { + addr, err := ebpfRingBufferMapMapBufferProc.Find() + if err != nil { + return nil, nil, nil, 0, err + } + + err = errorResult(syscall.SyscallN(addr, + uintptr(mapFd), + uintptr(unsafe.Pointer(&consumer)), + uintptr(unsafe.Pointer(&producer)), + uintptr(unsafe.Pointer(&data)), + uintptr(unsafe.Pointer(&dataLen)), + )) + if err != nil { + return nil, nil, nil, 0, err + } + + return consumer, producer, data, dataLen, nil +} + +/* +ebpf_ring_buffer_map_unmap_buffer( + + fd_t map_fd, _In_ void* consumer, _In_ const void* producer, _In_ const void* data) EBPF_NO_EXCEPT; +*/ +var ebpfRingBufferMapUnmapBufferProc = newProc("ebpf_ring_buffer_map_unmap_buffer") + +func EbpfRingBufferMapUnmapBuffer(mapFd int, consumer, producer, data *uint8) error { + addr, err := ebpfRingBufferMapUnmapBufferProc.Find() + if err != nil { + return err + } + + return errorResult(syscall.SyscallN(addr, + uintptr(mapFd), + uintptr(unsafe.Pointer(consumer)), + uintptr(unsafe.Pointer(producer)), + uintptr(unsafe.Pointer(data)), + )) +} + +/* +ebpf_result_t ebpf_map_set_wait_handle( + + fd_t map_fd, + uint64_t index, + ebpf_handle_t handle) +*/ +var ebpfMapSetWaitHandleProc = newProc("ebpf_map_set_wait_handle") + +func EbpfMapSetWaitHandle(mapFd int, index uint64, handle windows.Handle) error { + addr, err := ebpfMapSetWaitHandleProc.Find() + if err != nil { + return err + } + + return errorResult(syscall.SyscallN(addr, + uintptr(mapFd), + uintptr(index), + uintptr(handle), + )) +} + +/* +ebpf_result_t ebpf_ring_buffer_map_write( + + fd_t ring_buffer_map_fd, + const void* data, + size_t data_length) +*/ +var ebpfRingBufferMapWriteProc = newProc("ebpf_ring_buffer_map_write") + +func EbpfRingBufferMapWrite(ringBufferMapFd int, data []byte) error { + addr, err := ebpfRingBufferMapWriteProc.Find() + if err != nil { + return err + } + + err = errorResult(syscall.SyscallN(addr, + uintptr(ringBufferMapFd), + uintptr(unsafe.Pointer(&data[0])), + uintptr(len(data)), + )) + runtime.KeepAlive(data) + return err +} diff --git a/vendor/github.com/cilium/ebpf/internal/efw/module.go b/vendor/github.com/cilium/ebpf/internal/efw/module.go new file mode 100644 index 0000000000..606d83930a --- /dev/null +++ b/vendor/github.com/cilium/ebpf/internal/efw/module.go @@ -0,0 +1,36 @@ +//go:build windows + +// Package efw contains support code for eBPF for Windows. +package efw + +import ( + "golang.org/x/sys/windows" +) + +// module is the global handle for the eBPF for Windows user-space API. +var module = windows.NewLazyDLL("ebpfapi.dll") + +// FD is the equivalent of fd_t. +// +// See https://github.com/microsoft/ebpf-for-windows/blob/54632eb360c560ebef2f173be1a4a4625d540744/include/ebpf_api.h#L24 +type FD int32 + +// Size is the equivalent of size_t. +// +// This is correct on amd64 and arm64 according to tests on godbolt.org. +type Size uint64 + +// Int is the equivalent of int on MSVC (am64, arm64) and MinGW (gcc, clang). +type Int int32 + +// ObjectType is the equivalent of ebpf_object_type_t. +// +// See https://github.com/microsoft/ebpf-for-windows/blob/44f5de09ec0f3f7ad176c00a290c1cb7106cdd5e/include/ebpf_core_structs.h#L41 +type ObjectType uint32 + +const ( + EBPF_OBJECT_UNKNOWN ObjectType = iota + EBPF_OBJECT_MAP + EBPF_OBJECT_LINK + EBPF_OBJECT_PROGRAM +) diff --git a/vendor/github.com/cilium/ebpf/internal/efw/native.go b/vendor/github.com/cilium/ebpf/internal/efw/native.go new file mode 100644 index 0000000000..04f796abbf --- /dev/null +++ b/vendor/github.com/cilium/ebpf/internal/efw/native.go @@ -0,0 +1,44 @@ +//go:build windows + +package efw + +import ( + "syscall" + "unsafe" + + "golang.org/x/sys/windows" +) + +/* +ebpf_result_t ebpf_object_load_native_by_fds( + + _In_z_ const char* file_name, + _Inout_ size_t* count_of_maps, + _Out_writes_opt_(count_of_maps) fd_t* map_fds, + _Inout_ size_t* count_of_programs, + _Out_writes_opt_(count_of_programs) fd_t* program_fds) +*/ +var ebpfObjectLoadNativeByFdsProc = newProc("ebpf_object_load_native_by_fds") + +func EbpfObjectLoadNativeFds(fileName string, mapFds []FD, programFds []FD) (int, int, error) { + addr, err := ebpfObjectLoadNativeByFdsProc.Find() + if err != nil { + return 0, 0, err + } + + fileBytes, err := windows.ByteSliceFromString(fileName) + if err != nil { + return 0, 0, err + } + + countOfMaps := Size(len(mapFds)) + countOfPrograms := Size(len(programFds)) + err = errorResult(syscall.SyscallN(addr, + uintptr(unsafe.Pointer(&fileBytes[0])), + uintptr(unsafe.Pointer(&countOfMaps)), + uintptr(unsafe.Pointer(&mapFds[0])), + uintptr(unsafe.Pointer(&countOfPrograms)), + uintptr(unsafe.Pointer(&programFds[0])), + )) + return int(countOfMaps), int(countOfPrograms), err +} diff --git a/vendor/github.com/cilium/ebpf/internal/efw/object.go b/vendor/github.com/cilium/ebpf/internal/efw/object.go new file mode 100644 index 0000000000..560e2f09b3 --- /dev/null +++ b/vendor/github.com/cilium/ebpf/internal/efw/object.go @@ -0,0 +1,117 @@ +//go:build windows + +package efw + +import ( + "syscall" + "unsafe" + + "golang.org/x/sys/windows" +) + +// https://github.com/microsoft/ebpf-for-windows/blob/9d9003c39c3fd75be5225ac0fce30077d6bf0604/include/ebpf_core_structs.h#L15 +const _EBPF_MAX_PIN_PATH_LENGTH = 256 + +/* +Retrieve object info and type from a fd. + + ebpf_result_t ebpf_object_get_info_by_fd( + fd_t bpf_fd, + _Inout_updates_bytes_to_opt_(*info_size, *info_size) void* info, + _Inout_opt_ uint32_t* info_size, + _Out_opt_ ebpf_object_type_t* type) +*/ +var ebpfObjectGetInfoByFdProc = newProc("ebpf_object_get_info_by_fd") + +func EbpfObjectGetInfoByFd(fd int, info unsafe.Pointer, info_size *uint32) (ObjectType, error) { + addr, err := ebpfObjectGetInfoByFdProc.Find() + if err != nil { + return 0, err + } + + var objectType ObjectType + err = errorResult(syscall.SyscallN(addr, + uintptr(fd), + uintptr(info), + uintptr(unsafe.Pointer(info_size)), + uintptr(unsafe.Pointer(&objectType)), + )) + return objectType, err +} + +// ebpf_result_t ebpf_object_unpin(_In_z_ const char* path) +var ebpfObjectUnpinProc = newProc("ebpf_object_unpin") + +func EbpfObjectUnpin(path string) error { + addr, err := ebpfObjectUnpinProc.Find() + if err != nil { + return err + } + + pathBytes, err := windows.ByteSliceFromString(path) + if err != nil { + return err + } + + return errorResult(syscall.SyscallN(addr, uintptr(unsafe.Pointer(&pathBytes[0])))) +} + +/* +Retrieve the next pinned object path. + + ebpf_result_t ebpf_get_next_pinned_object_path( + _In_opt_z_ const char* start_path, + _Out_writes_z_(next_path_len) char* next_path, + size_t next_path_len, + _Inout_opt_ ebpf_object_type_t* type) +*/ +var ebpfGetNextPinnedObjectPath = newProc("ebpf_get_next_pinned_object_path") + +func EbpfGetNextPinnedObjectPath(startPath string, objectType ObjectType) (string, ObjectType, error) { + addr, err := ebpfGetNextPinnedObjectPath.Find() + if err != nil { + return "", 0, err + } + + ptr, err := windows.BytePtrFromString(startPath) + if err != nil { + return "", 0, err + } + + tmp := make([]byte, _EBPF_MAX_PIN_PATH_LENGTH) + err = errorResult(syscall.SyscallN(addr, + uintptr(unsafe.Pointer(ptr)), + uintptr(unsafe.Pointer(&tmp[0])), + uintptr(len(tmp)), + uintptr(unsafe.Pointer(&objectType)), + )) + return windows.ByteSliceToString(tmp), objectType, err +} + +/* +Canonicalize a path using filesystem canonicalization rules. + + _Must_inspect_result_ ebpf_result_t + ebpf_canonicalize_pin_path(_Out_writes_(output_size) char* output, size_t output_size, _In_z_ const char* input) +*/ +var ebpfCanonicalizePinPath = newProc("ebpf_canonicalize_pin_path") + +func EbpfCanonicalizePinPath(input string) (string, error) { + addr, err := ebpfCanonicalizePinPath.Find() + if err != nil { + return "", err + } + + inputBytes, err := windows.ByteSliceFromString(input) + if err != nil { + return "", err + } + + output := make([]byte, _EBPF_MAX_PIN_PATH_LENGTH) + err = errorResult(syscall.SyscallN(addr, + uintptr(unsafe.Pointer(&output[0])), + uintptr(len(output)), + uintptr(unsafe.Pointer(&inputBytes[0])), + )) + return windows.ByteSliceToString(output), err +} diff --git a/vendor/github.com/cilium/ebpf/internal/efw/proc.go b/vendor/github.com/cilium/ebpf/internal/efw/proc.go new file mode 100644 index 0000000000..81329905fd --- /dev/null +++ b/vendor/github.com/cilium/ebpf/internal/efw/proc.go @@ -0,0 +1,50 @@ +//go:build windows + +package efw + +import ( + "errors" + "fmt" + "syscall" + + "golang.org/x/sys/windows" +) + +/* +The BPF syscall wrapper which is ABI compatible with Linux. + + int bpf(int cmd, union bpf_attr* attr, unsigned int size) +*/ +var BPF = newProc("bpf") + +type proc struct { + proc *windows.LazyProc +} + +func newProc(name string) proc { + return proc{module.NewProc(name)} +} + +func (p proc) Find() (uintptr, error) { + if err := p.proc.Find(); err != nil { + if errors.Is(err, windows.ERROR_MOD_NOT_FOUND) { + return 0, fmt.Errorf("load %s: not found", module.Name) + } + return 0, err + } + return p.proc.Addr(), nil +} + +// uint32Result wraps a function which returns a uint32_t. +func uint32Result(r1, _ uintptr, _ syscall.Errno) uint32 { + return uint32(r1) +} + +// errorResult wraps a function which returns ebpf_result_t. +func errorResult(r1, _ uintptr, errNo syscall.Errno) error { + err := resultToError(Result(r1)) + if err != nil && errNo != 0 { + return fmt.Errorf("%w (errno: %v)", err, errNo) + } + return err +} diff --git a/vendor/github.com/cilium/ebpf/internal/efw/program.go b/vendor/github.com/cilium/ebpf/internal/efw/program.go new file mode 100644 index 0000000000..6202acf32c --- /dev/null +++ b/vendor/github.com/cilium/ebpf/internal/efw/program.go @@ -0,0 +1,39 @@ +//go:build windows + +package efw + +import ( + "syscall" + "unsafe" + + "golang.org/x/sys/windows" +) + +/* +Attach a program. + + ebpf_result_t ebpf_program_attach_by_fds( + fd_t program_fd, + _In_opt_ const ebpf_attach_type_t* attach_type, + _In_reads_bytes_opt_(attach_parameters_size) void* attach_parameters, + size_t attach_parameters_size, + _Out_ fd_t* link) +*/ +var ebpfProgramAttachByFdsProc = newProc("ebpf_program_attach_by_fds") + +func EbpfProgramAttachFds(fd int, attachType windows.GUID, params unsafe.Pointer, params_size uintptr) (int, error) { + addr, err := ebpfProgramAttachByFdsProc.Find() + if err != nil { + return 0, err + } + + var link FD + err = errorResult(syscall.SyscallN(addr, + uintptr(fd), + uintptr(unsafe.Pointer(&attachType)), + uintptr(params), + params_size, + uintptr(unsafe.Pointer(&link)), + )) + return int(link), err +} diff --git a/vendor/github.com/cilium/ebpf/internal/efw/result.go b/vendor/github.com/cilium/ebpf/internal/efw/result.go new file mode 100644 index 0000000000..4c68da9315 --- /dev/null +++ b/vendor/github.com/cilium/ebpf/internal/efw/result.go @@ -0,0 +1,57 @@ +//go:build windows + +package efw + +// See https://github.com/microsoft/ebpf-for-windows/blob/main/include/ebpf_result.h +type Result int32 + +//go:generate go tool stringer -tags windows -output result_string_windows.go -type=Result + +const ( + EBPF_SUCCESS Result = iota + EBPF_VERIFICATION_FAILED + EBPF_JIT_COMPILATION_FAILED + EBPF_PROGRAM_LOAD_FAILED + EBPF_INVALID_FD + EBPF_INVALID_OBJECT + EBPF_INVALID_ARGUMENT + EBPF_OBJECT_NOT_FOUND + EBPF_OBJECT_ALREADY_EXISTS + EBPF_FILE_NOT_FOUND + EBPF_ALREADY_PINNED + EBPF_NOT_PINNED + EBPF_NO_MEMORY + EBPF_PROGRAM_TOO_LARGE + EBPF_RPC_EXCEPTION + EBPF_ALREADY_INITIALIZED + EBPF_ELF_PARSING_FAILED + EBPF_FAILED + EBPF_OPERATION_NOT_SUPPORTED + EBPF_KEY_NOT_FOUND + EBPF_ACCESS_DENIED + EBPF_BLOCKED_BY_POLICY + EBPF_ARITHMETIC_OVERFLOW + EBPF_EXTENSION_FAILED_TO_LOAD + EBPF_INSUFFICIENT_BUFFER + EBPF_NO_MORE_KEYS + EBPF_KEY_ALREADY_EXISTS + EBPF_NO_MORE_TAIL_CALLS + EBPF_PENDING + EBPF_OUT_OF_SPACE + EBPF_CANCELED + EBPF_INVALID_POINTER + EBPF_TIMEOUT + EBPF_STALE_ID + EBPF_INVALID_STATE +) + +func (r Result) Error() string { + return r.String() +} + +func resultToError(res Result) error { + if res == EBPF_SUCCESS { + return nil + } + return res +} diff --git a/vendor/github.com/cilium/ebpf/internal/efw/result_string_windows.go b/vendor/github.com/cilium/ebpf/internal/efw/result_string_windows.go new file mode 100644 index 0000000000..1e55b51865 --- /dev/null +++ b/vendor/github.com/cilium/ebpf/internal/efw/result_string_windows.go @@ -0,0 +1,57 @@ +// Code generated by "stringer -tags windows -output result_string_windows.go -type=Result"; DO NOT EDIT. + +package efw + +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[EBPF_SUCCESS-0] + _ = x[EBPF_VERIFICATION_FAILED-1] + _ = x[EBPF_JIT_COMPILATION_FAILED-2] + _ = x[EBPF_PROGRAM_LOAD_FAILED-3] + _ = x[EBPF_INVALID_FD-4] + _ = x[EBPF_INVALID_OBJECT-5] + _ = x[EBPF_INVALID_ARGUMENT-6] + _ = x[EBPF_OBJECT_NOT_FOUND-7] + _ = x[EBPF_OBJECT_ALREADY_EXISTS-8] + _ = x[EBPF_FILE_NOT_FOUND-9] + _ = x[EBPF_ALREADY_PINNED-10] + _ = x[EBPF_NOT_PINNED-11] + _ = x[EBPF_NO_MEMORY-12] + _ = x[EBPF_PROGRAM_TOO_LARGE-13] + _ = x[EBPF_RPC_EXCEPTION-14] + _ = x[EBPF_ALREADY_INITIALIZED-15] + _ = x[EBPF_ELF_PARSING_FAILED-16] + _ = x[EBPF_FAILED-17] + _ = x[EBPF_OPERATION_NOT_SUPPORTED-18] + _ = x[EBPF_KEY_NOT_FOUND-19] + _ = x[EBPF_ACCESS_DENIED-20] + _ = x[EBPF_BLOCKED_BY_POLICY-21] + _ = x[EBPF_ARITHMETIC_OVERFLOW-22] + _ = x[EBPF_EXTENSION_FAILED_TO_LOAD-23] + _ = x[EBPF_INSUFFICIENT_BUFFER-24] + _ = x[EBPF_NO_MORE_KEYS-25] + _ = x[EBPF_KEY_ALREADY_EXISTS-26] + _ = x[EBPF_NO_MORE_TAIL_CALLS-27] + _ = x[EBPF_PENDING-28] + _ = x[EBPF_OUT_OF_SPACE-29] + _ = x[EBPF_CANCELED-30] + _ = x[EBPF_INVALID_POINTER-31] + _ = x[EBPF_TIMEOUT-32] + _ = x[EBPF_STALE_ID-33] + _ = x[EBPF_INVALID_STATE-34] +} + +const _Result_name = "EBPF_SUCCESSEBPF_VERIFICATION_FAILEDEBPF_JIT_COMPILATION_FAILEDEBPF_PROGRAM_LOAD_FAILEDEBPF_INVALID_FDEBPF_INVALID_OBJECTEBPF_INVALID_ARGUMENTEBPF_OBJECT_NOT_FOUNDEBPF_OBJECT_ALREADY_EXISTSEBPF_FILE_NOT_FOUNDEBPF_ALREADY_PINNEDEBPF_NOT_PINNEDEBPF_NO_MEMORYEBPF_PROGRAM_TOO_LARGEEBPF_RPC_EXCEPTIONEBPF_ALREADY_INITIALIZEDEBPF_ELF_PARSING_FAILEDEBPF_FAILEDEBPF_OPERATION_NOT_SUPPORTEDEBPF_KEY_NOT_FOUNDEBPF_ACCESS_DENIEDEBPF_BLOCKED_BY_POLICYEBPF_ARITHMETIC_OVERFLOWEBPF_EXTENSION_FAILED_TO_LOADEBPF_INSUFFICIENT_BUFFEREBPF_NO_MORE_KEYSEBPF_KEY_ALREADY_EXISTSEBPF_NO_MORE_TAIL_CALLSEBPF_PENDINGEBPF_OUT_OF_SPACEEBPF_CANCELEDEBPF_INVALID_POINTEREBPF_TIMEOUTEBPF_STALE_IDEBPF_INVALID_STATE" + +var _Result_index = [...]uint16{0, 12, 36, 63, 87, 102, 121, 142, 163, 189, 208, 227, 242, 256, 278, 296, 320, 343, 354, 382, 400, 418, 440, 464, 493, 517, 534, 557, 580, 592, 609, 622, 642, 654, 667, 685} + +func (i Result) String() string { + if i < 0 || i >= Result(len(_Result_index)-1) { + return "Result(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _Result_name[_Result_index[i]:_Result_index[i+1]] +} diff --git a/vendor/github.com/cilium/ebpf/internal/efw/structs.go b/vendor/github.com/cilium/ebpf/internal/efw/structs.go new file mode 100644 index 0000000000..558dbb8653 --- /dev/null +++ b/vendor/github.com/cilium/ebpf/internal/efw/structs.go @@ -0,0 +1,36 @@ +//go:build windows + +package efw + +import "golang.org/x/sys/windows" + +// https://github.com/microsoft/ebpf-for-windows/blob/95267a53b26c68a94145d1731e2a4c8b546034c3/include/ebpf_structs.h#L366 +const _BPF_OBJ_NAME_LEN = 64 + +// See https://github.com/microsoft/ebpf-for-windows/blob/95267a53b26c68a94145d1731e2a4c8b546034c3/include/ebpf_structs.h#L372-L386 +type BpfMapInfo struct { + _ uint32 ///< Map ID. + _ uint32 ///< Type of map. + _ uint32 ///< Size in bytes of a map key. + _ uint32 ///< Size in bytes of a map value. + _ uint32 ///< Maximum number of entries allowed in the map. + Name [_BPF_OBJ_NAME_LEN]byte ///< Null-terminated map name. + _ uint32 ///< Map flags. + + _ uint32 ///< ID of inner map template. + _ uint32 ///< Number of pinned paths. +} + +// See https://github.com/microsoft/ebpf-for-windows/blob/95267a53b26c68a94145d1731e2a4c8b546034c3/include/ebpf_structs.h#L396-L410 +type BpfProgInfo struct { + _ uint32 ///< Program ID. + _ uint32 ///< Program type, if a cross-platform type. + _ uint32 ///< Number of maps associated with this program. + _ uintptr ///< Pointer to caller-allocated array to fill map IDs into. + Name [_BPF_OBJ_NAME_LEN]byte ///< Null-terminated map name. + + _ windows.GUID ///< Program type UUID. + _ windows.GUID ///< Attach type UUID. + _ uint32 ///< Number of pinned paths. + _ uint32 ///< Number of attached links. +} diff --git a/vendor/github.com/cilium/ebpf/internal/endian_le.go b/vendor/github.com/cilium/ebpf/internal/endian_le.go index 6dcd916d5d..d833ea764f 100644 --- a/vendor/github.com/cilium/ebpf/internal/endian_le.go +++ b/vendor/github.com/cilium/ebpf/internal/endian_le.go @@ -1,4 +1,4 @@ -//go:build 386 || amd64 || amd64p32 || arm || arm64 || loong64 || mipsle || mips64le || mips64p32le || ppc64le || riscv64 +//go:build 386 || amd64 || amd64p32 || arm || arm64 || loong64 || mipsle || mips64le || mips64p32le || ppc64le || riscv64 || wasm package internal diff --git a/vendor/github.com/cilium/ebpf/internal/feature.go b/vendor/github.com/cilium/ebpf/internal/feature.go index 6f64822e11..e27064c23e 100644 --- a/vendor/github.com/cilium/ebpf/internal/feature.go +++ b/vendor/github.com/cilium/ebpf/internal/feature.go @@ -4,8 +4,9 @@ import ( "errors" "fmt" "runtime" - "strings" "sync" + + "github.com/cilium/ebpf/internal/platform" ) // ErrNotSupported indicates that a feature is not supported. @@ -15,6 +16,10 @@ var ErrNotSupported = errors.New("not supported") // operating system. var ErrNotSupportedOnOS = fmt.Errorf("%w on %s", ErrNotSupported, runtime.GOOS) +// ErrRestrictedKernel is returned when kernel address information is restricted +// by kernel.kptr_restrict and/or net.core.bpf_jit_harden sysctls. +var ErrRestrictedKernel = errors.New("restricted by kernel.kptr_restrict and/or net.core.bpf_jit_harden sysctls") + // UnsupportedFeatureError is returned by FeatureTest() functions. type UnsupportedFeatureError struct { // The minimum version required for this feature. @@ -74,33 +79,12 @@ type FeatureTestFn func() error // Linux. Returns [ErrNotSupportedOnOS] if there is no version specified for the // current OS. func NewFeatureTest(name string, fn FeatureTestFn, versions ...string) func() error { - const nativePrefix = runtime.GOOS + ":" - - if len(versions) == 0 { - return func() error { - return fmt.Errorf("feature test %q: no versions specified", name) - } + version, err := platform.SelectVersion(versions) + if err != nil { + return func() error { return err } } - ft := &FeatureTest{ - Name: name, - Fn: fn, - } - - for _, version := range versions { - if strings.HasPrefix(version, nativePrefix) { - ft.Version = strings.TrimPrefix(version, nativePrefix) - break - } - - if OnLinux && !strings.ContainsRune(version, ':') { - // Allow version numbers without a GOOS prefix on Linux. - ft.Version = version - break - } - } - - if ft.Version == "" { + if version == "" { return func() error { // We don't return an UnsupportedFeatureError here, since that will // trigger version checks which don't make sense. @@ -108,6 +92,12 @@ func NewFeatureTest(name string, fn FeatureTestFn, versions ...string) func() er } } + ft := &FeatureTest{ + Name: name, + Version: version, + Fn: fn, + } + return ft.execute } @@ -175,12 +165,18 @@ type FeatureMatrix[K comparable] map[K]*FeatureTest // Result returns the outcome of the feature test for the given key. // // It's safe to call this function concurrently. +// +// Always returns [ErrNotSupportedOnOS] on Windows. func (fm FeatureMatrix[K]) Result(key K) error { ft, ok := fm[key] if !ok { return fmt.Errorf("no feature probe for %v", key) } + if platform.IsWindows { + return fmt.Errorf("%s: %w", ft.Name, ErrNotSupportedOnOS) + } + return ft.execute() } @@ -201,6 +197,10 @@ func NewFeatureCache[K comparable](newTest func(K) *FeatureTest) *FeatureCache[K } func (fc *FeatureCache[K]) Result(key K) error { + if platform.IsWindows { + return fmt.Errorf("feature probe for %v: %w", key, ErrNotSupportedOnOS) + } + // NB: Executing the feature test happens without fc.mu taken. return fc.retrieve(key).execute() } diff --git a/vendor/github.com/cilium/ebpf/internal/goos.go b/vendor/github.com/cilium/ebpf/internal/goos.go deleted file mode 100644 index 0569d5ce0b..0000000000 --- a/vendor/github.com/cilium/ebpf/internal/goos.go +++ /dev/null @@ -1,7 +0,0 @@ -package internal - -import "runtime" - -const ( - OnLinux = runtime.GOOS == "linux" -) diff --git a/vendor/github.com/cilium/ebpf/internal/kallsyms/kallsyms.go b/vendor/github.com/cilium/ebpf/internal/kallsyms/kallsyms.go index 2cdfb56a58..efc64a5030 100644 --- a/vendor/github.com/cilium/ebpf/internal/kallsyms/kallsyms.go +++ b/vendor/github.com/cilium/ebpf/internal/kallsyms/kallsyms.go @@ -1,165 +1,21 @@ package kallsyms import ( + "bytes" "errors" "fmt" "io" "os" "slices" "strconv" - "strings" "github.com/cilium/ebpf/internal" + "github.com/cilium/ebpf/internal/platform" ) var errAmbiguousKsym = errors.New("multiple kernel symbols with the same name") var symAddrs cache[string, uint64] -var symModules cache[string, string] - -// Module returns the kernel module providing the given symbol in the kernel, if -// any. Returns an empty string and no error if the symbol is not present in the -// kernel. Only function symbols are considered. Returns an error if multiple -// symbols with the same name were found. -// -// Consider [AssignModules] if you need to resolve multiple symbols, as it will -// only perform one iteration over /proc/kallsyms. -func Module(name string) (string, error) { - if name == "" { - return "", nil - } - - if mod, ok := symModules.Load(name); ok { - return mod, nil - } - - request := map[string]string{name: ""} - if err := AssignModules(request); err != nil { - return "", err - } - - return request[name], nil -} - -// AssignModules looks up the kernel module providing each given symbol, if any, -// and assigns them to their corresponding values in the symbols map. Only -// function symbols are considered. Results of all lookups are cached, -// successful or otherwise. -// -// Any symbols missing in the kernel are ignored. Returns an error if multiple -// symbols with a given name were found. -func AssignModules(symbols map[string]string) error { - if !internal.OnLinux { - return fmt.Errorf("read /proc/kallsyms: %w", internal.ErrNotSupportedOnOS) - } - - if len(symbols) == 0 { - return nil - } - - // Attempt to fetch symbols from cache. - request := make(map[string]string) - for name := range symbols { - if mod, ok := symModules.Load(name); ok { - symbols[name] = mod - continue - } - - // Mark the symbol to be read from /proc/kallsyms. - request[name] = "" - } - if len(request) == 0 { - // All symbols satisfied from cache. - return nil - } - - f, err := os.Open("/proc/kallsyms") - if err != nil { - return err - } - defer f.Close() - - if err := assignModules(f, request); err != nil { - return fmt.Errorf("assigning symbol modules: %w", err) - } - - // Update the cache with the new symbols. Cache all requested symbols, even if - // they're missing or don't belong to a module. - for name, mod := range request { - symModules.Store(name, mod) - symbols[name] = mod - } - - return nil -} - -// assignModules assigns kernel symbol modules read from f to values requested -// by symbols. Always scans the whole input to make sure the user didn't request -// an ambiguous symbol. -func assignModules(f io.Reader, symbols map[string]string) error { - if len(symbols) == 0 { - return nil - } - - found := make(map[string]struct{}) - r := newReader(f) - for r.Line() { - // Only look for function symbols in the kernel's text section (tT). - s, err, skip := parseSymbol(r, []rune{'t', 'T'}) - if err != nil { - return fmt.Errorf("parsing kallsyms line: %w", err) - } - if skip { - continue - } - - if _, requested := symbols[s.name]; !requested { - continue - } - - if _, ok := found[s.name]; ok { - // We've already seen this symbol. Return an error to avoid silently - // attaching to a symbol in the wrong module. libbpf also rejects - // referring to ambiguous symbols. - // - // We can't simply check if we already have a value for the given symbol, - // since many won't have an associated kernel module. - return fmt.Errorf("symbol %s: duplicate found at address 0x%x (module %q): %w", - s.name, s.addr, s.mod, errAmbiguousKsym) - } - - symbols[s.name] = s.mod - found[s.name] = struct{}{} - } - if err := r.Err(); err != nil { - return fmt.Errorf("reading kallsyms: %w", err) - } - - return nil -} - -// Address returns the address of the given symbol in the kernel. Returns 0 and -// no error if the symbol is not present. Returns an error if multiple addresses -// were found for a symbol. -// -// Consider [AssignAddresses] if you need to resolve multiple symbols, as it -// will only perform one iteration over /proc/kallsyms. -func Address(symbol string) (uint64, error) { - if symbol == "" { - return 0, nil - } - - if addr, ok := symAddrs.Load(symbol); ok { - return addr, nil - } - - request := map[string]uint64{symbol: 0} - if err := AssignAddresses(request); err != nil { - return 0, err - } - - return request[symbol], nil -} // AssignAddresses looks up the addresses of the requested symbols in the kernel // and assigns them to their corresponding values in the symbols map. Results @@ -168,7 +24,7 @@ func Address(symbol string) (uint64, error) { // Any symbols missing in the kernel are ignored. Returns an error if multiple // addresses were found for a symbol. func AssignAddresses(symbols map[string]uint64) error { - if !internal.OnLinux { + if !platform.IsLinux { return fmt.Errorf("read /proc/kallsyms: %w", internal.ErrNotSupportedOnOS) } @@ -229,7 +85,7 @@ func assignAddresses(f io.Reader, symbols map[string]uint64) error { continue } - existing, requested := symbols[s.name] + existing, requested := symbols[string(s.name)] if existing != 0 { // Multiple addresses for a symbol have been found. Return a friendly // error to avoid silently attaching to the wrong symbol. libbpf also @@ -237,7 +93,20 @@ func assignAddresses(f io.Reader, symbols map[string]uint64) error { return fmt.Errorf("symbol %s(0x%x): duplicate found at address 0x%x: %w", s.name, existing, s.addr, errAmbiguousKsym) } if requested { - symbols[s.name] = s.addr + // Reading a symbol with a zero address is a strong indication that + // kptr_restrict is set and the process doesn't have CAP_SYSLOG, or + // kptr_restrict is set to 2 (never show addresses). + // + // When running the kernel with KASLR disabled (like CI kernels running in + // microVMs), kallsyms will display many absolute symbols at address 0. + // This memory is unlikely to contain anything useful, and production + // machines are unlikely to run without KASLR. + // + // Return a helpful error instead of silently returning zero addresses. + if s.addr == 0 { + return fmt.Errorf("symbol %s: %w", s.name, internal.ErrRestrictedKernel) + } + symbols[string(s.name)] = s.addr } } if err := r.Err(); err != nil { @@ -249,15 +118,18 @@ func assignAddresses(f io.Reader, symbols map[string]uint64) error { type ksym struct { addr uint64 - name string - mod string + name []byte + mod []byte } // parseSymbol parses a line from /proc/kallsyms into an address, type, name and // module. Skip will be true if the symbol doesn't match any of the given symbol // types. See `man 1 nm` for all available types. // -// Example line: `ffffffffc1682010 T nf_nat_init [nf_nat]` +// Only yields symbols whose type is contained in types. An empty value for types +// disables this filtering. +// +// Example line: `ffffffffc1682010 T nf_nat_init\t[nf_nat]` func parseSymbol(r *reader, types []rune) (s ksym, err error, skip bool) { for i := 0; r.Word(); i++ { switch i { @@ -275,13 +147,13 @@ func parseSymbol(r *reader, types []rune) (s ksym, err error, skip bool) { } // Name of the symbol. case 2: - s.name = r.Text() + s.name = r.Bytes() // Kernel module the symbol is provided by. case 3: - s.mod = strings.Trim(r.Text(), "[]") + s.mod = bytes.Trim(r.Bytes(), "[]") // Ignore any future fields. default: - break + return } } diff --git a/vendor/github.com/cilium/ebpf/internal/kallsyms/reader.go b/vendor/github.com/cilium/ebpf/internal/kallsyms/reader.go index 2bd4f8eafc..3011e83f68 100644 --- a/vendor/github.com/cilium/ebpf/internal/kallsyms/reader.go +++ b/vendor/github.com/cilium/ebpf/internal/kallsyms/reader.go @@ -2,9 +2,8 @@ package kallsyms import ( "bufio" + "bytes" "io" - "unicode" - "unicode/utf8" ) // reader is a line and word-oriented reader built for reading /proc/kallsyms. @@ -71,46 +70,18 @@ func (r *reader) Line() bool { // Returns true if a word is found and Word should be called again. Returns // false when all words on the line have been read. func (r *reader) Word() bool { - if len(r.line) == 0 { - return false - } - - // Find next word start, skipping leading spaces. - start := 0 - for width := 0; start < len(r.line); start += width { - var c rune - c, width = utf8.DecodeRune(r.line[start:]) - if !unicode.IsSpace(c) { - break - } - } + line := bytes.TrimSpace(r.line) - // Whitespace scanning reached the end of the line due to trailing whitespace, - // meaning there are no more words to read - if start == len(r.line) { + if len(line) == 0 { return false } - // Find next word end. - for width, i := 0, start; i < len(r.line); i += width { - var c rune - c, width = utf8.DecodeRune(r.line[i:]) - if unicode.IsSpace(c) { - r.word = r.line[start:i] - r.line = r.line[i:] - return true - } + var found bool + r.word, r.line, found = bytes.Cut(line, []byte{' '}) + if !found { + r.word, r.line, _ = bytes.Cut(line, []byte{'\t'}) } - - // The line contains data, but no end-of-word boundary was found. This is the - // last, unterminated word in the line. - if len(r.line) > start { - r.word = r.line[start:] - r.line = nil - return true - } - - return false + return true } func (r *reader) Err() error { diff --git a/vendor/github.com/cilium/ebpf/internal/kconfig/kconfig.go b/vendor/github.com/cilium/ebpf/internal/kconfig/kconfig.go index 29c62b6266..de18dc8290 100644 --- a/vendor/github.com/cilium/ebpf/internal/kconfig/kconfig.go +++ b/vendor/github.com/cilium/ebpf/internal/kconfig/kconfig.go @@ -183,8 +183,8 @@ func putValueString(data []byte, typ btf.Type, value string) error { // Any Int, which is not bool, of one byte could be used to store char: // https://github.com/torvalds/linux/blob/1a5304fecee5/tools/lib/bpf/libbpf.c#L3637-L3638 - if contentType.Size != 1 && contentType.Encoding != btf.Bool { - return fmt.Errorf("cannot add string value, expected array of btf.Int of size 1, got array of btf.Int of size: %v", contentType.Size) + if contentType.Size != 1 || contentType.Encoding == btf.Bool { + return fmt.Errorf("cannot add string value, expected array of non-bool btf.Int of size 1, got array of btf.Int with encoding %v and size %v", contentType.Encoding, contentType.Size) } if !strings.HasPrefix(value, `"`) || !strings.HasSuffix(value, `"`) { diff --git a/vendor/github.com/cilium/ebpf/internal/linux/auxv.go b/vendor/github.com/cilium/ebpf/internal/linux/auxv.go index 10508fd8fc..a864d6b4a9 100644 --- a/vendor/github.com/cilium/ebpf/internal/linux/auxv.go +++ b/vendor/github.com/cilium/ebpf/internal/linux/auxv.go @@ -5,6 +5,7 @@ import ( "io" "github.com/cilium/ebpf/internal" + "github.com/cilium/ebpf/internal/platform" "github.com/cilium/ebpf/internal/unix" ) @@ -46,7 +47,7 @@ func (r *auxvRuntimeReader) ReadAuxvPair() (uint64, uint64, error) { } func newAuxvRuntimeReader() (auxvPairReader, error) { - if !internal.OnLinux { + if !platform.IsLinux { return nil, fmt.Errorf("read auxv from runtime: %w", internal.ErrNotSupportedOnOS) } diff --git a/vendor/github.com/cilium/ebpf/internal/linux/cpu.go b/vendor/github.com/cilium/ebpf/internal/linux/cpu.go new file mode 100644 index 0000000000..bd55ac915e --- /dev/null +++ b/vendor/github.com/cilium/ebpf/internal/linux/cpu.go @@ -0,0 +1,45 @@ +package linux + +import ( + "fmt" + "os" + "strings" +) + +func ParseCPUsFromFile(path string) (int, error) { + spec, err := os.ReadFile(path) + if err != nil { + return 0, err + } + + n, err := parseCPUs(string(spec)) + if err != nil { + return 0, fmt.Errorf("can't parse %s: %v", path, err) + } + + return n, nil +} + +// parseCPUs parses the number of cpus from a string produced +// by bitmap_list_string() in the Linux kernel. +// Multiple ranges are rejected, since they can't be unified +// into a single number. +// This is the format of /sys/devices/system/cpu/possible, it +// is not suitable for /sys/devices/system/cpu/online, etc. +func parseCPUs(spec string) (int, error) { + if strings.Trim(spec, "\n") == "0" { + return 1, nil + } + + var low, high int + n, err := fmt.Sscanf(spec, "%d-%d\n", &low, &high) + if n != 2 || err != nil { + return 0, fmt.Errorf("invalid format: %s", spec) + } + if low != 0 { + return 0, fmt.Errorf("CPU spec doesn't start at zero: %s", spec) + } + + // cpus is 0 indexed + return high + 1, nil +} diff --git a/vendor/github.com/cilium/ebpf/internal/mountinfo/mountinfo.go b/vendor/github.com/cilium/ebpf/internal/mountinfo/mountinfo.go new file mode 100644 index 0000000000..a3cb1d19ba --- /dev/null +++ b/vendor/github.com/cilium/ebpf/internal/mountinfo/mountinfo.go @@ -0,0 +1,150 @@ +// Package mountinfo parses /proc/self/mountinfo to discover filesystem +// mounts visible in the calling process's mount namespace. +package mountinfo + +import ( + "bufio" + "errors" + "fmt" + "io" + "os" + "slices" + "strings" + "sync" + + "github.com/cilium/ebpf/internal" + "github.com/cilium/ebpf/internal/platform" +) + +// Entry represents one parsed line from /proc/self/mountinfo. +type Entry struct { + // MountPoint is the absolute path where the filesystem is mounted, + // with octal escapes (\040, \011, \012, \134) decoded. + MountPoint string + + // Root is the path within the source filesystem that's exposed at + // MountPoint, octal-decoded. It is "/" for a full filesystem mount and + // some other path (e.g. "/events") when a subdirectory of the source + // filesystem has been bind-mounted onto MountPoint. Callers that want + // to use MountPoint as the root of a filesystem should reject entries + // where Root != "/". + Root string + + // FSType is the filesystem type, e.g. "bpf", "tracefs", "debugfs". + FSType string +} + +// Read parses /proc/self/mountinfo and returns one Entry per mount line. +// The result is cached for the lifetime of the process; mounts that appear +// or disappear after the first call are not reflected. +// +// On non-Linux, returns an error wrapping [internal.ErrNotSupportedOnOS]. +func Read() ([]Entry, error) { + return readOnce() +} + +// FindByFSType returns the mount points for filesystems of the given type, +// in order of appearance, with duplicates removed. +func FindByFSType(fstype string) ([]string, error) { + entries, err := Read() + if err != nil { + return nil, err + } + return filterByFSType(entries, fstype), nil +} + +func filterByFSType(entries []Entry, fstype string) []string { + var mounts []string + for _, e := range entries { + if e.FSType != fstype { + continue + } + // Number of matching mounts is expected to be very low; linear + // search is faster and more cache-friendly than a map at this + // scale, and avoids the allocations a map would incur. + if !slices.Contains(mounts, e.MountPoint) { + mounts = append(mounts, e.MountPoint) + } + } + return mounts +} + +// parseEntries parses mountinfo data read from r. +func parseEntries(r io.Reader) ([]Entry, error) { + var entries []Entry + // Format of /proc/self/mountinfo: + // + // {id} {parent id} {major:minor} {root} {mount point} {mount options} [optional fields...] - {filesystem type} {source} {superblock options} + br := bufio.NewReader(r) + for { + line, err := br.ReadString('\n') + if line != "" { + line = strings.TrimRight(line, "\n") + if line != "" { + entry, perr := parseLine(line) + if perr != nil { + return nil, perr + } + entries = append(entries, entry) + } + } + if errors.Is(err, io.EOF) { + break + } + if err != nil { + return nil, fmt.Errorf("read mountinfo: %w", err) + } + } + + return entries, nil +} + +func parseLine(line string) (Entry, error) { + firstHalfStr, secondHalfStr, ok := strings.Cut(line, " - ") + if !ok { + return Entry{}, fmt.Errorf("invalid mountinfo line, missing dash: %q", line) + } + + secondHalf := strings.Fields(strings.TrimSpace(secondHalfStr)) + if len(secondHalf) == 0 { + return Entry{}, fmt.Errorf("invalid mountinfo line, too few fields after dash: %q", line) + } + + firstHalf := strings.Fields(strings.TrimSpace(firstHalfStr)) + if len(firstHalf) < 6 { + return Entry{}, fmt.Errorf("invalid mountinfo line, too few fields: %q", line) + } + + return Entry{ + Root: unescape(firstHalf[3]), + MountPoint: unescape(firstHalf[4]), + FSType: secondHalf[0], + }, nil +} + +// show_mountinfo in the kernel has the escape set of ' \t\n\\'. Instead of +// a full octal unescaper, only replace these specific characters. +var unescaper = strings.NewReplacer( + `\040`, " ", + `\011`, "\t", + `\012`, "\n", + `\134`, `\`, +) + +func unescape(s string) string { + return unescaper.Replace(s) +} + +var readOnce = sync.OnceValues(func() ([]Entry, error) { + if !platform.IsLinux { + return nil, fmt.Errorf("mountinfo: %w", internal.ErrNotSupportedOnOS) + } + + f, err := os.Open("/proc/self/mountinfo") + if err != nil { + return nil, fmt.Errorf("open mountinfo: %w", err) + } + defer f.Close() + + return parseEntries(f) +}) diff --git a/vendor/github.com/cilium/ebpf/internal/nil.go b/vendor/github.com/cilium/ebpf/internal/nil.go new file mode 100644 index 0000000000..83ba6c6f22 --- /dev/null +++ b/vendor/github.com/cilium/ebpf/internal/nil.go @@ -0,0 +1,24 @@ +package internal + +import ( + "fmt" + "reflect" +) + +// IsNilPointer returns an error if i is a nil interface or a nil pointer. +// Otherwise, it returns nil. +func IsNilPointer(i any) error { + if i == nil { + return fmt.Errorf("nil interface") + } + v := reflect.ValueOf(i) + if v.Kind() != reflect.Pointer { + return nil + } + + if v.IsNil() { + return fmt.Errorf("nil %T", i) + } + + return nil +} diff --git a/vendor/github.com/cilium/ebpf/internal/output.go b/vendor/github.com/cilium/ebpf/internal/output.go index dd6e6cbafe..bcbb6818df 100644 --- a/vendor/github.com/cilium/ebpf/internal/output.go +++ b/vendor/github.com/cilium/ebpf/internal/output.go @@ -92,6 +92,11 @@ func GoTypeName(t any) string { for rT.Kind() == reflect.Pointer { rT = rT.Elem() } - // Doesn't return the correct Name for generic types due to https://github.com/golang/go/issues/55924 - return rT.Name() + + name := rT.Name() + if pkgPath := rT.PkgPath(); pkgPath != "" { + name = strings.ReplaceAll(name, pkgPath+".", "") + } + + return name } diff --git a/vendor/github.com/cilium/ebpf/internal/platform/constants.go b/vendor/github.com/cilium/ebpf/internal/platform/constants.go new file mode 100644 index 0000000000..b57ae1e59f --- /dev/null +++ b/vendor/github.com/cilium/ebpf/internal/platform/constants.go @@ -0,0 +1,62 @@ +package platform + +import "fmt" + +// Values used to tag platform specific constants. +// +// The value for Linux is zero so that existing constants do not change. +const ( + LinuxTag = uint32(iota) << platformShift + WindowsTag +) + +const ( + platformMax = 1<<3 - 1 // most not exceed 3 bits to avoid setting the high bit + platformShift = 28 + platformMask = platformMax << platformShift +) + +func tagForPlatform(platform string) (uint32, error) { + switch platform { + case Linux: + return LinuxTag, nil + case Windows: + return WindowsTag, nil + default: + return 0, fmt.Errorf("unrecognized platform: %s", platform) + } +} + +func platformForConstant(c uint32) string { + tag := uint32(c & platformMask) + switch tag { + case LinuxTag: + return Linux + case WindowsTag: + return Windows + default: + return "" + } +} + +// Encode a platform and a value into a tagged constant. +// +// Returns an error if platform is unknown or c is out of bounds. +func EncodeConstant[T ~uint32](platform string, c uint32) (T, error) { + if c>>platformShift > 0 { + return 0, fmt.Errorf("invalid constant 0x%x", c) + } + + tag, err := tagForPlatform(platform) + if err != nil { + return 0, err + } + + return T(tag | c), nil +} + +// Decode a platform and a value from a tagged constant. +func DecodeConstant[T ~uint32](c T) (string, uint32) { + v := uint32(c) & ^uint32(platformMask) + return platformForConstant(uint32(c)), v +} diff --git a/vendor/github.com/cilium/ebpf/internal/platform/platform.go b/vendor/github.com/cilium/ebpf/internal/platform/platform.go new file mode 100644 index 0000000000..1c5bad396f --- /dev/null +++ b/vendor/github.com/cilium/ebpf/internal/platform/platform.go @@ -0,0 +1,42 @@ +package platform + +import ( + "errors" + "runtime" + "strings" +) + +const ( + Linux = "linux" + Windows = "windows" +) + +const ( + IsLinux = runtime.GOOS == "linux" + IsWindows = runtime.GOOS == "windows" +) + +// SelectVersion extracts the platform-appropriate version from a list of strings like +// `linux:6.1` or `windows:0.20.0`. +// +// Returns an empty string and nil if no version matched or an error if no strings were passed. +func SelectVersion(versions []string) (string, error) { + const prefix = runtime.GOOS + ":" + + if len(versions) == 0 { + return "", errors.New("no versions specified") + } + + for _, version := range versions { + if after, ok := strings.CutPrefix(version, prefix); ok { + return after, nil + } + + if IsLinux && !strings.ContainsRune(version, ':') { + // Allow version numbers without a GOOS prefix on Linux. + return version, nil + } + } + + return "", nil +} diff --git a/vendor/github.com/cilium/ebpf/internal/platform/platform_linux.go b/vendor/github.com/cilium/ebpf/internal/platform/platform_linux.go new file mode 100644 index 0000000000..f0aa240dc2 --- /dev/null +++ b/vendor/github.com/cilium/ebpf/internal/platform/platform_linux.go @@ -0,0 +1,3 @@ +package platform + +const Native = Linux diff --git a/vendor/github.com/cilium/ebpf/internal/platform/platform_other.go b/vendor/github.com/cilium/ebpf/internal/platform/platform_other.go new file mode 100644 index 0000000000..cd33b3f685 --- /dev/null +++ b/vendor/github.com/cilium/ebpf/internal/platform/platform_other.go @@ -0,0 +1,5 @@ +//go:build !linux && !windows + +package platform + +const Native = "" diff --git a/vendor/github.com/cilium/ebpf/internal/platform/platform_windows.go b/vendor/github.com/cilium/ebpf/internal/platform/platform_windows.go new file mode 100644 index 0000000000..26b4a8ecb3 --- /dev/null +++ b/vendor/github.com/cilium/ebpf/internal/platform/platform_windows.go @@ -0,0 +1,3 @@ +package platform + +const Native = Windows diff --git a/vendor/github.com/cilium/ebpf/internal/sys/doc.go b/vendor/github.com/cilium/ebpf/internal/sys/doc.go index dfe174448e..75d7e4013a 100644 --- a/vendor/github.com/cilium/ebpf/internal/sys/doc.go +++ b/vendor/github.com/cilium/ebpf/internal/sys/doc.go @@ -3,4 +3,4 @@ package sys // Regenerate types.go by invoking go generate in the current directory. -//go:generate go run github.com/cilium/ebpf/internal/cmd/gentypes ../../btf/testdata/vmlinux.btf.gz +//go:generate go tool gentypes ../../btf/testdata/vmlinux.btf.gz diff --git a/vendor/github.com/cilium/ebpf/internal/sys/fd.go b/vendor/github.com/cilium/ebpf/internal/sys/fd.go index e2ba43fd3b..f12d11c206 100644 --- a/vendor/github.com/cilium/ebpf/internal/sys/fd.go +++ b/vendor/github.com/cilium/ebpf/internal/sys/fd.go @@ -1,74 +1,44 @@ package sys import ( - "fmt" "math" - "os" - "path/filepath" "runtime" "strconv" - "strings" - "github.com/cilium/ebpf/internal/testutils/fdtrace" + "github.com/cilium/ebpf/internal/testutils/testmain" "github.com/cilium/ebpf/internal/unix" ) var ErrClosedFd = unix.EBADF -type FD struct { - raw int -} +// A value for an invalid fd. +// +// Luckily this is consistent across Linux and Windows. +// +// See https://github.com/microsoft/ebpf-for-windows/blob/54632eb360c560ebef2f173be1a4a4625d540744/include/ebpf_api.h#L25 +const invalidFd = -1 func newFD(value int) *FD { - fdtrace.TraceFD(value, 1) + testmain.TraceFD(value, 1) - fd := &FD{value} - runtime.SetFinalizer(fd, (*FD).finalize) + fd := &FD{raw: value} + fd.cleanup = runtime.AddCleanup(fd, func(raw int) { + testmain.LeakFD(raw) + _ = unix.Close(raw) + }, fd.raw) return fd } -// finalize is set as the FD's runtime finalizer and -// sends a leak trace before calling FD.Close(). -func (fd *FD) finalize() { - if fd.raw < 0 { - return - } - - fdtrace.LeakFD(fd.raw) - - _ = fd.Close() -} - -// 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 + return int(fd.raw) } func (fd *FD) Uint() uint32 { - if fd.raw < 0 || int64(fd.raw) > math.MaxUint32 { + if fd.raw == invalidFd { // 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 @@ -76,90 +46,14 @@ func (fd *FD) Uint() uint32 { return uint32(fd.raw) } -func (fd *FD) Close() error { - if fd.raw < 0 { - return nil - } - - return unix.Close(fd.Disown()) -} - // Disown destroys the FD and returns its raw file descriptor without closing // it. After this call, the underlying fd is no longer tied to the FD's // lifecycle. func (fd *FD) Disown() int { value := fd.raw - fdtrace.ForgetFD(value) - fd.raw = -1 + testmain.ForgetFD(value) + fd.raw = invalidFd - runtime.SetFinalizer(fd, nil) + fd.cleanup.Stop() return value } - -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 -} - -// File takes ownership of FD and turns it into an [*os.File]. -// -// You must not use the FD after the call returns. -// -// Returns nil if the FD is not valid. -func (fd *FD) File(name string) *os.File { - if fd.raw < 0 { - return nil - } - - return os.NewFile(uintptr(fd.Disown()), name) -} - -// ObjGetTyped wraps [ObjGet] with a readlink call to extract the type of the -// underlying bpf object. -func ObjGetTyped(attr *ObjGetAttr) (*FD, ObjType, error) { - fd, err := ObjGet(attr) - if err != nil { - return nil, 0, err - } - - typ, err := readType(fd) - if err != nil { - _ = fd.Close() - return nil, 0, fmt.Errorf("reading fd type: %w", err) - } - - return fd, typ, nil -} - -// readType returns the bpf object type of the file descriptor by calling -// readlink(3). Returns an error if the file descriptor does not represent a bpf -// object. -func readType(fd *FD) (ObjType, error) { - s, err := os.Readlink(filepath.Join("/proc/self/fd/", fd.String())) - if err != nil { - return 0, fmt.Errorf("readlink fd %d: %w", fd.Int(), err) - } - - s = strings.TrimPrefix(s, "anon_inode:") - - switch s { - case "bpf-map": - return BPF_TYPE_MAP, nil - case "bpf-prog": - return BPF_TYPE_PROG, nil - case "bpf-link": - return BPF_TYPE_LINK, nil - } - - return 0, fmt.Errorf("unknown type %s of fd %d", s, fd.Int()) -} diff --git a/vendor/github.com/cilium/ebpf/internal/sys/fd_other.go b/vendor/github.com/cilium/ebpf/internal/sys/fd_other.go new file mode 100644 index 0000000000..2a6423a591 --- /dev/null +++ b/vendor/github.com/cilium/ebpf/internal/sys/fd_other.go @@ -0,0 +1,72 @@ +//go:build !windows + +package sys + +import ( + "fmt" + "os" + "runtime" + + "github.com/cilium/ebpf/internal/unix" +) + +type FD struct { + raw int + cleanup runtime.Cleanup +} + +// 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) Close() error { + if fd.raw < 0 { + return nil + } + + return unix.Close(fd.Disown()) +} + +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 +} + +// File takes ownership of FD and turns it into an [*os.File]. +// +// You must not use the FD after the call returns. +// +// Returns [ErrClosedFd] if the fd is not valid. +func (fd *FD) File(name string) (*os.File, error) { + if fd.raw == invalidFd { + return nil, ErrClosedFd + } + + return os.NewFile(uintptr(fd.Disown()), name), nil +} diff --git a/vendor/github.com/cilium/ebpf/internal/sys/fd_windows.go b/vendor/github.com/cilium/ebpf/internal/sys/fd_windows.go new file mode 100644 index 0000000000..1291c763f1 --- /dev/null +++ b/vendor/github.com/cilium/ebpf/internal/sys/fd_windows.go @@ -0,0 +1,60 @@ +package sys + +import ( + "fmt" + "os" + "runtime" + + "github.com/cilium/ebpf/internal" + "github.com/cilium/ebpf/internal/efw" +) + +// FD wraps a handle which is managed by the eBPF for Windows runtime. +// +// It is not equivalent to a real file descriptor or handle. +type FD struct { + raw int + cleanup runtime.Cleanup +} + +// NewFD wraps a raw fd with a finalizer. +// +// You must not use the raw fd after calling this function. +func NewFD(value int) (*FD, error) { + if value == invalidFd { + return nil, fmt.Errorf("invalid fd %d", value) + } + + if value == 0 { + // The efW runtime never uses zero fd it seems. No need to dup it. + return nil, fmt.Errorf("invalid zero fd") + } + + return newFD(value), nil +} + +func (fd *FD) Close() error { + if fd.raw == invalidFd { + return nil + } + + return efw.EbpfCloseFd(fd.Disown()) +} + +func (fd *FD) Dup() (*FD, error) { + if fd.raw == invalidFd { + return nil, ErrClosedFd + } + + dup, err := efw.EbpfDuplicateFd(fd.raw) + if err != nil { + return nil, err + } + + return NewFD(int(dup)) +} + +// File is not implemented. +func (fd *FD) File(name string) (*os.File, error) { + return nil, fmt.Errorf("file from fd: %w", internal.ErrNotSupportedOnOS) +} diff --git a/vendor/github.com/cilium/ebpf/internal/sys/pinning.go b/vendor/github.com/cilium/ebpf/internal/sys/pinning_other.go similarity index 98% rename from vendor/github.com/cilium/ebpf/internal/sys/pinning.go rename to vendor/github.com/cilium/ebpf/internal/sys/pinning_other.go index 9a4c6c7a15..96ad43abd3 100644 --- a/vendor/github.com/cilium/ebpf/internal/sys/pinning.go +++ b/vendor/github.com/cilium/ebpf/internal/sys/pinning_other.go @@ -1,3 +1,5 @@ +//go:build !windows + package sys import ( diff --git a/vendor/github.com/cilium/ebpf/internal/sys/pinning_windows.go b/vendor/github.com/cilium/ebpf/internal/sys/pinning_windows.go new file mode 100644 index 0000000000..c8ab685500 --- /dev/null +++ b/vendor/github.com/cilium/ebpf/internal/sys/pinning_windows.go @@ -0,0 +1,44 @@ +package sys + +import ( + "errors" + "runtime" + + "github.com/cilium/ebpf/internal/efw" +) + +func Pin(currentPath, newPath string, fd *FD) error { + defer runtime.KeepAlive(fd) + + if newPath == "" { + return errors.New("given pinning path cannot be empty") + } + if currentPath == newPath { + return nil + } + + if currentPath == "" { + return ObjPin(&ObjPinAttr{ + Pathname: NewStringPointer(newPath), + BpfFd: fd.Uint(), + }) + } + + return ObjPin(&ObjPinAttr{ + Pathname: NewStringPointer(newPath), + BpfFd: fd.Uint(), + }) +} + +func Unpin(pinnedPath string) error { + if pinnedPath == "" { + return nil + } + + err := efw.EbpfObjectUnpin(pinnedPath) + if err != nil && !errors.Is(err, efw.EBPF_KEY_NOT_FOUND) { + return err + } + + return nil +} diff --git a/vendor/github.com/cilium/ebpf/internal/sys/ptr.go b/vendor/github.com/cilium/ebpf/internal/sys/ptr.go index af0c014e3b..aa6c2e91aa 100644 --- a/vendor/github.com/cilium/ebpf/internal/sys/ptr.go +++ b/vendor/github.com/cilium/ebpf/internal/sys/ptr.go @@ -6,13 +6,13 @@ import ( "github.com/cilium/ebpf/internal/unix" ) -// NewPointer creates a 64-bit pointer from an unsafe Pointer. -func NewPointer(ptr unsafe.Pointer) Pointer { +// UnsafePointer creates a 64-bit pointer from an unsafe Pointer. +func UnsafePointer(ptr unsafe.Pointer) Pointer { return Pointer{ptr: ptr} } -// NewSlicePointer creates a 64-bit pointer from a slice. -func NewSlicePointer[T comparable](buf []T) Pointer { +// UnsafeSlicePointer creates an untyped [Pointer] from a slice. +func UnsafeSlicePointer[T comparable](buf []T) Pointer { if len(buf) == 0 { return Pointer{} } @@ -20,21 +20,43 @@ func NewSlicePointer[T comparable](buf []T) Pointer { return Pointer{ptr: unsafe.Pointer(unsafe.SliceData(buf))} } -// NewSlicePointerLen creates a 64-bit pointer from a byte slice. +// TypedPointer points to typed memory. // -// Useful to assign both the pointer and the length in one go. -func NewSlicePointerLen(buf []byte) (Pointer, uint32) { - return NewSlicePointer(buf), uint32(len(buf)) +// It is like a *T except that it accounts for the BPF syscall interface. +type TypedPointer[T any] struct { + _ [0]*T // prevent TypedPointer[a] to be convertible to TypedPointer[b] + ptr Pointer } -// NewStringPointer creates a 64-bit pointer from a string. -func NewStringPointer(str string) Pointer { - p, err := unix.BytePtrFromString(str) +func (p TypedPointer[T]) IsNil() bool { + return p.ptr.ptr == nil +} + +// SlicePointer creates a [TypedPointer] from a slice. +func SlicePointer[T comparable](s []T) TypedPointer[T] { + return TypedPointer[T]{ptr: UnsafeSlicePointer(s)} +} + +// StringPointer points to a null-terminated string. +type StringPointer struct { + _ [0]string + ptr Pointer +} + +// NewStringPointer creates a [StringPointer] from a string. +func NewStringPointer(str string) StringPointer { + slice, err := unix.ByteSliceFromString(str) if err != nil { - return Pointer{} + return StringPointer{} } - return Pointer{ptr: unsafe.Pointer(p)} + return StringPointer{ptr: Pointer{ptr: unsafe.Pointer(&slice[0])}} +} + +// StringSlicePointer points to a slice of [StringPointer]. +type StringSlicePointer struct { + _ [0][]string + ptr Pointer } // NewStringSlicePointer allocates an array of Pointers to each string in the @@ -42,11 +64,11 @@ func NewStringPointer(str string) Pointer { // resulting array. // // Use this function to pass arrays of strings as syscall arguments. -func NewStringSlicePointer(strings []string) Pointer { - sp := make([]Pointer, 0, len(strings)) +func NewStringSlicePointer(strings []string) StringSlicePointer { + sp := make([]StringPointer, 0, len(strings)) for _, s := range strings { sp = append(sp, NewStringPointer(s)) } - return Pointer{ptr: unsafe.Pointer(&sp[0])} + return StringSlicePointer{ptr: Pointer{ptr: unsafe.Pointer(&sp[0])}} } diff --git a/vendor/github.com/cilium/ebpf/internal/sys/ptr_32_be.go b/vendor/github.com/cilium/ebpf/internal/sys/ptr_32_be.go index 6278c79c9e..0b0feeb7a6 100644 --- a/vendor/github.com/cilium/ebpf/internal/sys/ptr_32_be.go +++ b/vendor/github.com/cilium/ebpf/internal/sys/ptr_32_be.go @@ -3,12 +3,14 @@ package sys import ( + "structs" "unsafe" ) // Pointer wraps an unsafe.Pointer to be 64bit to // conform to the syscall specification. type Pointer struct { + structs.HostLayout pad uint32 ptr unsafe.Pointer } diff --git a/vendor/github.com/cilium/ebpf/internal/sys/ptr_32_le.go b/vendor/github.com/cilium/ebpf/internal/sys/ptr_32_le.go index c27b537e8e..f9007fe84b 100644 --- a/vendor/github.com/cilium/ebpf/internal/sys/ptr_32_le.go +++ b/vendor/github.com/cilium/ebpf/internal/sys/ptr_32_le.go @@ -3,12 +3,14 @@ package sys import ( + "structs" "unsafe" ) // Pointer wraps an unsafe.Pointer to be 64bit to // conform to the syscall specification. type Pointer struct { + structs.HostLayout ptr unsafe.Pointer pad uint32 } diff --git a/vendor/github.com/cilium/ebpf/internal/sys/ptr_64.go b/vendor/github.com/cilium/ebpf/internal/sys/ptr_64.go index 2d7828230a..05196cca74 100644 --- a/vendor/github.com/cilium/ebpf/internal/sys/ptr_64.go +++ b/vendor/github.com/cilium/ebpf/internal/sys/ptr_64.go @@ -3,11 +3,13 @@ package sys import ( + "structs" "unsafe" ) // Pointer wraps an unsafe.Pointer to be 64bit to // conform to the syscall specification. type Pointer struct { + structs.HostLayout ptr unsafe.Pointer } diff --git a/vendor/github.com/cilium/ebpf/internal/sys/signals.go b/vendor/github.com/cilium/ebpf/internal/sys/signals.go index e5337191d6..e75e960520 100644 --- a/vendor/github.com/cilium/ebpf/internal/sys/signals.go +++ b/vendor/github.com/cilium/ebpf/internal/sys/signals.go @@ -1,3 +1,5 @@ +//go:build !windows + package sys import ( diff --git a/vendor/github.com/cilium/ebpf/internal/sys/syscall.go b/vendor/github.com/cilium/ebpf/internal/sys/syscall.go index 4fdb74c57a..3a2ae9c5ab 100644 --- a/vendor/github.com/cilium/ebpf/internal/sys/syscall.go +++ b/vendor/github.com/cilium/ebpf/internal/sys/syscall.go @@ -12,42 +12,13 @@ import ( // It is not the same as ENOTSUP or EOPNOTSUPP. const ENOTSUPP = unix.Errno(524) -// 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) { - // Prevent the Go profiler from repeatedly interrupting the verifier, - // which could otherwise lead to a livelock due to receiving EAGAIN. - if cmd == BPF_PROG_LOAD || cmd == BPF_PROG_RUN { - maskProfilerSignal() - defer unmaskProfilerSignal() - } - - 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 +// TokenInfo type Info interface { info() (unsafe.Pointer, uint32) } @@ -102,10 +73,30 @@ func (i *KprobeMultiLinkInfo) info() (unsafe.Pointer, uint32) { return unsafe.Pointer(i), uint32(unsafe.Sizeof(*i)) } +func (i *UprobeMultiLinkInfo) info() (unsafe.Pointer, uint32) { + return unsafe.Pointer(i), uint32(unsafe.Sizeof(*i)) +} + +func (i *RawTracepointLinkInfo) info() (unsafe.Pointer, uint32) { + return unsafe.Pointer(i), uint32(unsafe.Sizeof(*i)) +} + func (i *KprobeLinkInfo) info() (unsafe.Pointer, uint32) { return unsafe.Pointer(i), uint32(unsafe.Sizeof(*i)) } +func (i *UprobeLinkInfo) info() (unsafe.Pointer, uint32) { + return unsafe.Pointer(i), uint32(unsafe.Sizeof(*i)) +} + +func (i *TracepointLinkInfo) info() (unsafe.Pointer, uint32) { + return unsafe.Pointer(i), uint32(unsafe.Sizeof(*i)) +} + +func (i *EventLinkInfo) info() (unsafe.Pointer, uint32) { + return unsafe.Pointer(i), uint32(unsafe.Sizeof(*i)) +} + var _ Info = (*BtfInfo)(nil) func (i *BtfInfo) info() (unsafe.Pointer, uint32) { @@ -116,15 +107,21 @@ func (i *PerfEventLinkInfo) info() (unsafe.Pointer, uint32) { return unsafe.Pointer(i), uint32(unsafe.Sizeof(*i)) } +var _ Info = (*TokenInfo)(nil) + +func (i *TokenInfo) 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. +// info may be one of MapInfo, ProgInfo, LinkInfo, BtfInfo and TokenInfo. func ObjInfo(fd *FD, info Info) error { ptr, len := info.info() err := ObjGetInfoByFd(&ObjGetInfoByFdAttr{ BpfFd: fd.Uint(), InfoLen: len, - Info: NewPointer(ptr), + Info: UnsafePointer(ptr), }) runtime.KeepAlive(fd) return err @@ -150,6 +147,12 @@ const ( BPF_LOG_STATS ) +// MapID uniquely identifies a bpf_map. +type MapID uint32 + +// ProgramID uniquely identifies a bpf_map. +type ProgramID uint32 + // LinkID uniquely identifies a bpf_link. type LinkID uint32 diff --git a/vendor/github.com/cilium/ebpf/internal/sys/syscall_other.go b/vendor/github.com/cilium/ebpf/internal/sys/syscall_other.go new file mode 100644 index 0000000000..969eaa4106 --- /dev/null +++ b/vendor/github.com/cilium/ebpf/internal/sys/syscall_other.go @@ -0,0 +1,108 @@ +//go:build !windows + +package sys + +import ( + "errors" + "fmt" + "os" + "path/filepath" + "runtime" + "strings" + "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) { + // Prevent the Go profiler from repeatedly interrupting the verifier, + // which could otherwise lead to a livelock due to receiving EAGAIN. + if cmd == BPF_PROG_LOAD || cmd == BPF_PROG_RUN { + maskProfilerSignal() + defer unmaskProfilerSignal() + } + + tok, err := tokenAttr(cmd, attr) + if err != nil { + return 0, fmt.Errorf("apply token attributes: %w", err) + } + + 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} + } + + // Tokens are in use, attempt to enrich EPERM with capability information. + if tok != nil && errors.Is(err, unix.EPERM) { + // Kernels before 6.17 don't have token info, so we can't return accurate + // errors. Make the token error a hint by adding a question mark. + // + // Returning ErrTokenCapabilities here isn't ideal since it's not + // conclusive, but since this behaviour is limited to one LTS release + // (6.12), and only when tokens are enabled, it's better than having to + // account for it in all existing feature probes and tests. + if tok.info == nil { + return r1, fmt.Errorf("%w (%w?)", err, ErrTokenCapabilities) + } + + if terr := tok.Capable(cmd, attr); terr != nil { + return r1, fmt.Errorf("%w: %w", terr, err) + } + } + + return r1, err + } +} + +// ObjGetTyped wraps [ObjGet] with a readlink call to extract the type of the +// underlying bpf object. +func ObjGetTyped(attr *ObjGetAttr) (*FD, ObjType, error) { + fd, err := ObjGet(attr) + if err != nil { + return nil, 0, err + } + + typ, err := readType(fd) + if err != nil { + _ = fd.Close() + return nil, 0, fmt.Errorf("reading fd type: %w", err) + } + + return fd, typ, nil +} + +// readType returns the bpf object type of the file descriptor by calling +// readlink(3). Returns an error if the file descriptor does not represent a bpf +// object. +func readType(fd *FD) (ObjType, error) { + s, err := os.Readlink(filepath.Join("/proc/self/fd/", fd.String())) + if err != nil { + return 0, fmt.Errorf("readlink fd %d: %w", fd.Int(), err) + } + + s = strings.TrimPrefix(s, "anon_inode:") + + switch s { + case "bpf-map": + return BPF_TYPE_MAP, nil + case "bpf-prog": + return BPF_TYPE_PROG, nil + case "bpf-link": + return BPF_TYPE_LINK, nil + } + + return 0, fmt.Errorf("unknown type %s of fd %d", s, fd.Int()) +} diff --git a/vendor/github.com/cilium/ebpf/internal/sys/syscall_windows.go b/vendor/github.com/cilium/ebpf/internal/sys/syscall_windows.go new file mode 100644 index 0000000000..08f73805c6 --- /dev/null +++ b/vendor/github.com/cilium/ebpf/internal/sys/syscall_windows.go @@ -0,0 +1,69 @@ +package sys + +import ( + "fmt" + "syscall" + "unsafe" + + "golang.org/x/sys/windows" + + "github.com/cilium/ebpf/internal" + "github.com/cilium/ebpf/internal/efw" + "github.com/cilium/ebpf/internal/unix" +) + +// BPF calls the BPF syscall wrapper in ebpfapi.dll. +// +// Any pointers contained in attr must use the Pointer type from this package. +// +// The implementation lives in https://github.com/microsoft/ebpf-for-windows/blob/main/libs/api/bpf_syscall.cpp +func BPF(cmd Cmd, attr unsafe.Pointer, size uintptr) (uintptr, error) { + // On Linux we need to guard against preemption by the profiler here. On + // Windows it seems like a cgocall may not be preempted: + // https://github.com/golang/go/blob/8b51146c698bcfcc2c2b73fa9390db5230f2ce0a/src/runtime/os_windows.go#L1240-L1246 + + addr, err := efw.BPF.Find() + if err != nil { + return 0, err + } + + // Using [LazyProc.Call] forces attr to escape, which isn't the case when using syscall.Syscall directly. + r1, _, lastError := syscall.SyscallN(addr, uintptr(cmd), uintptr(attr), size) + + if ret := int(efw.Int(r1)); ret < 0 { + errNo := unix.Errno(-ret) + if errNo == unix.EINVAL && lastError == windows.ERROR_CALL_NOT_IMPLEMENTED { + return 0, internal.ErrNotSupportedOnOS + } + return 0, wrappedErrno{errNo} + } + + return r1, nil +} + +// ObjGetTyped retrieves an pinned object and its type. +func ObjGetTyped(attr *ObjGetAttr) (*FD, ObjType, error) { + fd, err := ObjGet(attr) + if err != nil { + return nil, 0, err + } + + efwType, err := efw.EbpfObjectGetInfoByFd(fd.Int(), nil, nil) + if err != nil { + _ = fd.Close() + return nil, 0, err + } + + switch efwType { + case efw.EBPF_OBJECT_UNKNOWN: + return fd, BPF_TYPE_UNSPEC, nil + case efw.EBPF_OBJECT_MAP: + return fd, BPF_TYPE_MAP, nil + case efw.EBPF_OBJECT_LINK: + return fd, BPF_TYPE_LINK, nil + case efw.EBPF_OBJECT_PROGRAM: + return fd, BPF_TYPE_PROG, nil + default: + return nil, 0, fmt.Errorf("unrecognized object type %v", efwType) + } +} diff --git a/vendor/github.com/cilium/ebpf/internal/sys/token.go b/vendor/github.com/cilium/ebpf/internal/sys/token.go new file mode 100644 index 0000000000..9a5df39221 --- /dev/null +++ b/vendor/github.com/cilium/ebpf/internal/sys/token.go @@ -0,0 +1,226 @@ +package sys + +import ( + "errors" + "fmt" + "sync" + "unsafe" + + "github.com/cilium/ebpf/internal" + "github.com/cilium/ebpf/internal/mountinfo" + "github.com/cilium/ebpf/internal/platform" + "github.com/cilium/ebpf/internal/testutils/testmain" + "github.com/cilium/ebpf/internal/unix" +) + +var ( + // ErrTokenCapabilities indicates that the operation failed due to + // insufficient capabilities being delegated to the token. + ErrTokenCapabilities = errors.New("token insufficiently privileged") +) + +// Token is a BPF token that can be used to create BPF objects that normally +// require CAP_SYS_ADMIN or CAP_NET_ADMIN. +// +// With a valid token, equivalent BPF objects can be created while only being +// granted CAP_BPF. +type Token struct { + fd *FD + + // Token info is available as of Linux 6.17, may be nil. + info *TokenInfo +} + +// FD returns the raw file descriptor of the token. +func (t *Token) FD() int { + return t.fd.Int() +} + +// Capable returns an error if the token does not allow using cmd or some of its +// provided attributes. +// +// Always returns nil on kernels before 6.17, as they don't provide token info. +func (t *Token) Capable(cmd Cmd, attr unsafe.Pointer) error { + if t.info == nil { + return nil + } + + if !t.cmd(cmd) { + return fmt.Errorf("command %s: %w", cmd, ErrTokenCapabilities) + } + + switch cmd { + case BPF_MAP_CREATE: + attr := (*MapCreateAttr)(attr) + if !t.mapType(attr.MapType) { + return fmt.Errorf("map type %s: %w", attr.MapType, ErrTokenCapabilities) + } + + case BPF_PROG_LOAD: + attr := (*ProgLoadAttr)(attr) + if !t.progType(attr.ProgType) { + return fmt.Errorf("program type %s: %w", attr.ProgType, ErrTokenCapabilities) + } + + if !t.attachType(attr.ExpectedAttachType) { + return fmt.Errorf("attach type %s: %w", attr.ExpectedAttachType, ErrTokenCapabilities) + } + } + + return nil +} + +// cmd returns true if cmd is allowed by the Token. +func (t *Token) cmd(cmd Cmd) bool { + if t.info == nil { + return true + } + return t.info.AllowedCmds&(uint64(1)<= len(_Cmd_index)-1 { + return "Cmd(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _Cmd_name[_Cmd_index[idx]:_Cmd_index[idx+1]] +} +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_TYPE_UNSPEC-0] + _ = x[BPF_MAP_TYPE_HASH-1] + _ = x[BPF_MAP_TYPE_ARRAY-2] + _ = x[BPF_MAP_TYPE_PROG_ARRAY-3] + _ = x[BPF_MAP_TYPE_PERF_EVENT_ARRAY-4] + _ = x[BPF_MAP_TYPE_PERCPU_HASH-5] + _ = x[BPF_MAP_TYPE_PERCPU_ARRAY-6] + _ = x[BPF_MAP_TYPE_STACK_TRACE-7] + _ = x[BPF_MAP_TYPE_CGROUP_ARRAY-8] + _ = x[BPF_MAP_TYPE_LRU_HASH-9] + _ = x[BPF_MAP_TYPE_LRU_PERCPU_HASH-10] + _ = x[BPF_MAP_TYPE_LPM_TRIE-11] + _ = x[BPF_MAP_TYPE_ARRAY_OF_MAPS-12] + _ = x[BPF_MAP_TYPE_HASH_OF_MAPS-13] + _ = x[BPF_MAP_TYPE_DEVMAP-14] + _ = x[BPF_MAP_TYPE_SOCKMAP-15] + _ = x[BPF_MAP_TYPE_CPUMAP-16] + _ = x[BPF_MAP_TYPE_XSKMAP-17] + _ = x[BPF_MAP_TYPE_SOCKHASH-18] + _ = x[BPF_MAP_TYPE_CGROUP_STORAGE_DEPRECATED-19] + _ = x[BPF_MAP_TYPE_CGROUP_STORAGE-19] + _ = x[BPF_MAP_TYPE_REUSEPORT_SOCKARRAY-20] + _ = x[BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE_DEPRECATED-21] + _ = x[BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE-21] + _ = x[BPF_MAP_TYPE_QUEUE-22] + _ = x[BPF_MAP_TYPE_STACK-23] + _ = x[BPF_MAP_TYPE_SK_STORAGE-24] + _ = x[BPF_MAP_TYPE_DEVMAP_HASH-25] + _ = x[BPF_MAP_TYPE_STRUCT_OPS-26] + _ = x[BPF_MAP_TYPE_RINGBUF-27] + _ = x[BPF_MAP_TYPE_INODE_STORAGE-28] + _ = x[BPF_MAP_TYPE_TASK_STORAGE-29] + _ = x[BPF_MAP_TYPE_BLOOM_FILTER-30] + _ = x[BPF_MAP_TYPE_USER_RINGBUF-31] + _ = x[BPF_MAP_TYPE_CGRP_STORAGE-32] + _ = x[BPF_MAP_TYPE_ARENA-33] + _ = x[BPF_MAP_TYPE_INSN_ARRAY-34] + _ = x[__MAX_BPF_MAP_TYPE-35] +} + +const _MapType_name = "BPF_MAP_TYPE_UNSPECBPF_MAP_TYPE_HASHBPF_MAP_TYPE_ARRAYBPF_MAP_TYPE_PROG_ARRAYBPF_MAP_TYPE_PERF_EVENT_ARRAYBPF_MAP_TYPE_PERCPU_HASHBPF_MAP_TYPE_PERCPU_ARRAYBPF_MAP_TYPE_STACK_TRACEBPF_MAP_TYPE_CGROUP_ARRAYBPF_MAP_TYPE_LRU_HASHBPF_MAP_TYPE_LRU_PERCPU_HASHBPF_MAP_TYPE_LPM_TRIEBPF_MAP_TYPE_ARRAY_OF_MAPSBPF_MAP_TYPE_HASH_OF_MAPSBPF_MAP_TYPE_DEVMAPBPF_MAP_TYPE_SOCKMAPBPF_MAP_TYPE_CPUMAPBPF_MAP_TYPE_XSKMAPBPF_MAP_TYPE_SOCKHASHBPF_MAP_TYPE_CGROUP_STORAGE_DEPRECATEDBPF_MAP_TYPE_REUSEPORT_SOCKARRAYBPF_MAP_TYPE_PERCPU_CGROUP_STORAGE_DEPRECATEDBPF_MAP_TYPE_QUEUEBPF_MAP_TYPE_STACKBPF_MAP_TYPE_SK_STORAGEBPF_MAP_TYPE_DEVMAP_HASHBPF_MAP_TYPE_STRUCT_OPSBPF_MAP_TYPE_RINGBUFBPF_MAP_TYPE_INODE_STORAGEBPF_MAP_TYPE_TASK_STORAGEBPF_MAP_TYPE_BLOOM_FILTERBPF_MAP_TYPE_USER_RINGBUFBPF_MAP_TYPE_CGRP_STORAGEBPF_MAP_TYPE_ARENABPF_MAP_TYPE_INSN_ARRAY__MAX_BPF_MAP_TYPE" + +var _MapType_index = [...]uint16{0, 19, 36, 54, 77, 106, 130, 155, 179, 204, 225, 253, 274, 300, 325, 344, 364, 383, 402, 423, 461, 493, 538, 556, 574, 597, 621, 644, 664, 690, 715, 740, 765, 790, 808, 831, 849} + +func (i MapType) String() string { + idx := int(i) - 0 + if i < 0 || idx >= len(_MapType_index)-1 { + return "MapType(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _MapType_name[_MapType_index[idx]:_MapType_index[idx+1]] +} +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_PROG_TYPE_UNSPEC-0] + _ = x[BPF_PROG_TYPE_SOCKET_FILTER-1] + _ = x[BPF_PROG_TYPE_KPROBE-2] + _ = x[BPF_PROG_TYPE_SCHED_CLS-3] + _ = x[BPF_PROG_TYPE_SCHED_ACT-4] + _ = x[BPF_PROG_TYPE_TRACEPOINT-5] + _ = x[BPF_PROG_TYPE_XDP-6] + _ = x[BPF_PROG_TYPE_PERF_EVENT-7] + _ = x[BPF_PROG_TYPE_CGROUP_SKB-8] + _ = x[BPF_PROG_TYPE_CGROUP_SOCK-9] + _ = x[BPF_PROG_TYPE_LWT_IN-10] + _ = x[BPF_PROG_TYPE_LWT_OUT-11] + _ = x[BPF_PROG_TYPE_LWT_XMIT-12] + _ = x[BPF_PROG_TYPE_SOCK_OPS-13] + _ = x[BPF_PROG_TYPE_SK_SKB-14] + _ = x[BPF_PROG_TYPE_CGROUP_DEVICE-15] + _ = x[BPF_PROG_TYPE_SK_MSG-16] + _ = x[BPF_PROG_TYPE_RAW_TRACEPOINT-17] + _ = x[BPF_PROG_TYPE_CGROUP_SOCK_ADDR-18] + _ = x[BPF_PROG_TYPE_LWT_SEG6LOCAL-19] + _ = x[BPF_PROG_TYPE_LIRC_MODE2-20] + _ = x[BPF_PROG_TYPE_SK_REUSEPORT-21] + _ = x[BPF_PROG_TYPE_FLOW_DISSECTOR-22] + _ = x[BPF_PROG_TYPE_CGROUP_SYSCTL-23] + _ = x[BPF_PROG_TYPE_RAW_TRACEPOINT_WRITABLE-24] + _ = x[BPF_PROG_TYPE_CGROUP_SOCKOPT-25] + _ = x[BPF_PROG_TYPE_TRACING-26] + _ = x[BPF_PROG_TYPE_STRUCT_OPS-27] + _ = x[BPF_PROG_TYPE_EXT-28] + _ = x[BPF_PROG_TYPE_LSM-29] + _ = x[BPF_PROG_TYPE_SK_LOOKUP-30] + _ = x[BPF_PROG_TYPE_SYSCALL-31] + _ = x[BPF_PROG_TYPE_NETFILTER-32] + _ = x[__MAX_BPF_PROG_TYPE-33] +} + +const _ProgType_name = "BPF_PROG_TYPE_UNSPECBPF_PROG_TYPE_SOCKET_FILTERBPF_PROG_TYPE_KPROBEBPF_PROG_TYPE_SCHED_CLSBPF_PROG_TYPE_SCHED_ACTBPF_PROG_TYPE_TRACEPOINTBPF_PROG_TYPE_XDPBPF_PROG_TYPE_PERF_EVENTBPF_PROG_TYPE_CGROUP_SKBBPF_PROG_TYPE_CGROUP_SOCKBPF_PROG_TYPE_LWT_INBPF_PROG_TYPE_LWT_OUTBPF_PROG_TYPE_LWT_XMITBPF_PROG_TYPE_SOCK_OPSBPF_PROG_TYPE_SK_SKBBPF_PROG_TYPE_CGROUP_DEVICEBPF_PROG_TYPE_SK_MSGBPF_PROG_TYPE_RAW_TRACEPOINTBPF_PROG_TYPE_CGROUP_SOCK_ADDRBPF_PROG_TYPE_LWT_SEG6LOCALBPF_PROG_TYPE_LIRC_MODE2BPF_PROG_TYPE_SK_REUSEPORTBPF_PROG_TYPE_FLOW_DISSECTORBPF_PROG_TYPE_CGROUP_SYSCTLBPF_PROG_TYPE_RAW_TRACEPOINT_WRITABLEBPF_PROG_TYPE_CGROUP_SOCKOPTBPF_PROG_TYPE_TRACINGBPF_PROG_TYPE_STRUCT_OPSBPF_PROG_TYPE_EXTBPF_PROG_TYPE_LSMBPF_PROG_TYPE_SK_LOOKUPBPF_PROG_TYPE_SYSCALLBPF_PROG_TYPE_NETFILTER__MAX_BPF_PROG_TYPE" + +var _ProgType_index = [...]uint16{0, 20, 47, 67, 90, 113, 137, 154, 178, 202, 227, 247, 268, 290, 312, 332, 359, 379, 407, 437, 464, 488, 514, 542, 569, 606, 634, 655, 679, 696, 713, 736, 757, 780, 799} + +func (i ProgType) String() string { + idx := int(i) - 0 + if i < 0 || idx >= len(_ProgType_index)-1 { + return "ProgType(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _ProgType_name[_ProgType_index[idx]:_ProgType_index[idx+1]] +} +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_CGROUP_INET_INGRESS-0] + _ = x[BPF_CGROUP_INET_EGRESS-1] + _ = x[BPF_CGROUP_INET_SOCK_CREATE-2] + _ = x[BPF_CGROUP_SOCK_OPS-3] + _ = x[BPF_SK_SKB_STREAM_PARSER-4] + _ = x[BPF_SK_SKB_STREAM_VERDICT-5] + _ = x[BPF_CGROUP_DEVICE-6] + _ = x[BPF_SK_MSG_VERDICT-7] + _ = x[BPF_CGROUP_INET4_BIND-8] + _ = x[BPF_CGROUP_INET6_BIND-9] + _ = x[BPF_CGROUP_INET4_CONNECT-10] + _ = x[BPF_CGROUP_INET6_CONNECT-11] + _ = x[BPF_CGROUP_INET4_POST_BIND-12] + _ = x[BPF_CGROUP_INET6_POST_BIND-13] + _ = x[BPF_CGROUP_UDP4_SENDMSG-14] + _ = x[BPF_CGROUP_UDP6_SENDMSG-15] + _ = x[BPF_LIRC_MODE2-16] + _ = x[BPF_FLOW_DISSECTOR-17] + _ = x[BPF_CGROUP_SYSCTL-18] + _ = x[BPF_CGROUP_UDP4_RECVMSG-19] + _ = x[BPF_CGROUP_UDP6_RECVMSG-20] + _ = x[BPF_CGROUP_GETSOCKOPT-21] + _ = x[BPF_CGROUP_SETSOCKOPT-22] + _ = x[BPF_TRACE_RAW_TP-23] + _ = x[BPF_TRACE_FENTRY-24] + _ = x[BPF_TRACE_FEXIT-25] + _ = x[BPF_MODIFY_RETURN-26] + _ = x[BPF_LSM_MAC-27] + _ = x[BPF_TRACE_ITER-28] + _ = x[BPF_CGROUP_INET4_GETPEERNAME-29] + _ = x[BPF_CGROUP_INET6_GETPEERNAME-30] + _ = x[BPF_CGROUP_INET4_GETSOCKNAME-31] + _ = x[BPF_CGROUP_INET6_GETSOCKNAME-32] + _ = x[BPF_XDP_DEVMAP-33] + _ = x[BPF_CGROUP_INET_SOCK_RELEASE-34] + _ = x[BPF_XDP_CPUMAP-35] + _ = x[BPF_SK_LOOKUP-36] + _ = x[BPF_XDP-37] + _ = x[BPF_SK_SKB_VERDICT-38] + _ = x[BPF_SK_REUSEPORT_SELECT-39] + _ = x[BPF_SK_REUSEPORT_SELECT_OR_MIGRATE-40] + _ = x[BPF_PERF_EVENT-41] + _ = x[BPF_TRACE_KPROBE_MULTI-42] + _ = x[BPF_LSM_CGROUP-43] + _ = x[BPF_STRUCT_OPS-44] + _ = x[BPF_NETFILTER-45] + _ = x[BPF_TCX_INGRESS-46] + _ = x[BPF_TCX_EGRESS-47] + _ = x[BPF_TRACE_UPROBE_MULTI-48] + _ = x[BPF_CGROUP_UNIX_CONNECT-49] + _ = x[BPF_CGROUP_UNIX_SENDMSG-50] + _ = x[BPF_CGROUP_UNIX_RECVMSG-51] + _ = x[BPF_CGROUP_UNIX_GETPEERNAME-52] + _ = x[BPF_CGROUP_UNIX_GETSOCKNAME-53] + _ = x[BPF_NETKIT_PRIMARY-54] + _ = x[BPF_NETKIT_PEER-55] + _ = x[BPF_TRACE_KPROBE_SESSION-56] + _ = x[BPF_TRACE_UPROBE_SESSION-57] + _ = x[BPF_TRACE_FSESSION-58] + _ = x[__MAX_BPF_ATTACH_TYPE-59] +} + +const _AttachType_name = "BPF_CGROUP_INET_INGRESSBPF_CGROUP_INET_EGRESSBPF_CGROUP_INET_SOCK_CREATEBPF_CGROUP_SOCK_OPSBPF_SK_SKB_STREAM_PARSERBPF_SK_SKB_STREAM_VERDICTBPF_CGROUP_DEVICEBPF_SK_MSG_VERDICTBPF_CGROUP_INET4_BINDBPF_CGROUP_INET6_BINDBPF_CGROUP_INET4_CONNECTBPF_CGROUP_INET6_CONNECTBPF_CGROUP_INET4_POST_BINDBPF_CGROUP_INET6_POST_BINDBPF_CGROUP_UDP4_SENDMSGBPF_CGROUP_UDP6_SENDMSGBPF_LIRC_MODE2BPF_FLOW_DISSECTORBPF_CGROUP_SYSCTLBPF_CGROUP_UDP4_RECVMSGBPF_CGROUP_UDP6_RECVMSGBPF_CGROUP_GETSOCKOPTBPF_CGROUP_SETSOCKOPTBPF_TRACE_RAW_TPBPF_TRACE_FENTRYBPF_TRACE_FEXITBPF_MODIFY_RETURNBPF_LSM_MACBPF_TRACE_ITERBPF_CGROUP_INET4_GETPEERNAMEBPF_CGROUP_INET6_GETPEERNAMEBPF_CGROUP_INET4_GETSOCKNAMEBPF_CGROUP_INET6_GETSOCKNAMEBPF_XDP_DEVMAPBPF_CGROUP_INET_SOCK_RELEASEBPF_XDP_CPUMAPBPF_SK_LOOKUPBPF_XDPBPF_SK_SKB_VERDICTBPF_SK_REUSEPORT_SELECTBPF_SK_REUSEPORT_SELECT_OR_MIGRATEBPF_PERF_EVENTBPF_TRACE_KPROBE_MULTIBPF_LSM_CGROUPBPF_STRUCT_OPSBPF_NETFILTERBPF_TCX_INGRESSBPF_TCX_EGRESSBPF_TRACE_UPROBE_MULTIBPF_CGROUP_UNIX_CONNECTBPF_CGROUP_UNIX_SENDMSGBPF_CGROUP_UNIX_RECVMSGBPF_CGROUP_UNIX_GETPEERNAMEBPF_CGROUP_UNIX_GETSOCKNAMEBPF_NETKIT_PRIMARYBPF_NETKIT_PEERBPF_TRACE_KPROBE_SESSIONBPF_TRACE_UPROBE_SESSIONBPF_TRACE_FSESSION__MAX_BPF_ATTACH_TYPE" + +var _AttachType_index = [...]uint16{0, 23, 45, 72, 91, 115, 140, 157, 175, 196, 217, 241, 265, 291, 317, 340, 363, 377, 395, 412, 435, 458, 479, 500, 516, 532, 547, 564, 575, 589, 617, 645, 673, 701, 715, 743, 757, 770, 777, 795, 818, 852, 866, 888, 902, 916, 929, 944, 958, 980, 1003, 1026, 1049, 1076, 1103, 1121, 1136, 1160, 1184, 1202, 1223} + +func (i AttachType) String() string { + idx := int(i) - 0 + if i < 0 || idx >= len(_AttachType_index)-1 { + return "AttachType(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _AttachType_name[_AttachType_index[idx]:_AttachType_index[idx+1]] +} diff --git a/vendor/github.com/cilium/ebpf/internal/sysenc/buffer.go b/vendor/github.com/cilium/ebpf/internal/sysenc/buffer.go index 1b4f052ee2..62e483a1c4 100644 --- a/vendor/github.com/cilium/ebpf/internal/sysenc/buffer.go +++ b/vendor/github.com/cilium/ebpf/internal/sysenc/buffer.go @@ -63,7 +63,7 @@ func (b Buffer) AppendTo(dst []byte) []byte { func (b Buffer) Pointer() sys.Pointer { // NB: This deliberately ignores b.length to support zero-copy // marshaling / unmarshaling using unsafe.Pointer. - return sys.NewPointer(b.ptr) + return sys.UnsafePointer(b.ptr) } // Unmarshal the buffer into the provided value. diff --git a/vendor/github.com/cilium/ebpf/internal/sysenc/marshal.go b/vendor/github.com/cilium/ebpf/internal/sysenc/marshal.go index 0026af8f24..fdd81ac126 100644 --- a/vendor/github.com/cilium/ebpf/internal/sysenc/marshal.go +++ b/vendor/github.com/cilium/ebpf/internal/sysenc/marshal.go @@ -1,14 +1,12 @@ package sysenc import ( - "bytes" "encoding" "encoding/binary" "errors" "fmt" "reflect" "slices" - "sync" "unsafe" "github.com/cilium/ebpf/internal" @@ -53,11 +51,7 @@ func Marshal(data any, size int) (Buffer, error) { return newBuffer(buf), nil } - wr := internal.NewBuffer(make([]byte, 0, size)) - defer internal.PutBuffer(wr) - - err = binary.Write(wr, internal.NativeEndian, value) - buf = wr.Bytes() + buf, err = binary.Append(nil, internal.NativeEndian, value) } if err != nil { return Buffer{}, err @@ -70,17 +64,11 @@ func Marshal(data any, size int) (Buffer, error) { return newBuffer(buf), nil } -var bytesReaderPool = sync.Pool{ - New: func() interface{} { - return new(bytes.Reader) - }, -} - // Unmarshal a byte slice in the system's native endianness into data. // // Returns an error if buf can't be unmarshalled according to the behaviour -// of [binary.Read]. -func Unmarshal(data interface{}, buf []byte) error { +// of [binary.Decode]. +func Unmarshal(data any, buf []byte) error { switch value := data.(type) { case encoding.BinaryUnmarshaler: return value.UnmarshalBinary(buf) @@ -100,16 +88,12 @@ func Unmarshal(data interface{}, buf []byte) error { return nil } - rd := bytesReaderPool.Get().(*bytes.Reader) - defer bytesReaderPool.Put(rd) - - rd.Reset(buf) - - if err := binary.Read(rd, internal.NativeEndian, value); err != nil { + n, err := binary.Decode(buf, internal.NativeEndian, value) + if err != nil { return err } - if rd.Len() != 0 { + if n != len(buf) { return fmt.Errorf("unmarshaling %T doesn't consume all data", data) } diff --git a/vendor/github.com/cilium/ebpf/internal/testutils/fdtrace/main.go b/vendor/github.com/cilium/ebpf/internal/testutils/fdtrace/main.go deleted file mode 100644 index c1f7b42d91..0000000000 --- a/vendor/github.com/cilium/ebpf/internal/testutils/fdtrace/main.go +++ /dev/null @@ -1,31 +0,0 @@ -package fdtrace - -import ( - "os" - "sync" -) - -type testingM interface { - Run() int -} - -// TestMain runs m with fd tracing enabled. -// -// The function calls [os.Exit] and does not return. -func TestMain(m testingM) { - fds = new(sync.Map) - - ret := m.Run() - - if fs := flushFrames(); len(fs) != 0 { - for _, f := range fs { - onLeakFD(f) - } - } - - if foundLeak.Load() { - ret = 99 - } - - os.Exit(ret) -} diff --git a/vendor/github.com/cilium/ebpf/internal/testutils/fdtrace/fd_trace.go b/vendor/github.com/cilium/ebpf/internal/testutils/testmain/fd_trace.go similarity index 99% rename from vendor/github.com/cilium/ebpf/internal/testutils/fdtrace/fd_trace.go rename to vendor/github.com/cilium/ebpf/internal/testutils/testmain/fd_trace.go index 562df2cc0c..c47acf89ce 100644 --- a/vendor/github.com/cilium/ebpf/internal/testutils/fdtrace/fd_trace.go +++ b/vendor/github.com/cilium/ebpf/internal/testutils/testmain/fd_trace.go @@ -1,4 +1,4 @@ -package fdtrace +package testmain import ( "bytes" diff --git a/vendor/github.com/cilium/ebpf/internal/testutils/testmain/main.go b/vendor/github.com/cilium/ebpf/internal/testutils/testmain/main.go new file mode 100644 index 0000000000..53de97c863 --- /dev/null +++ b/vendor/github.com/cilium/ebpf/internal/testutils/testmain/main.go @@ -0,0 +1,58 @@ +package testmain + +import ( + "flag" + "fmt" + "os" + "sync" + + "github.com/cilium/ebpf/internal/platform" +) + +type testingM interface { + Run() int +} + +// Run m with various debug aids enabled. +// +// The function calls [os.Exit] and does not return. +func Run(m testingM) { + const traceLogFlag = "trace-log" + + var ts *traceSession + if platform.IsWindows { + traceLog := flag.Bool(traceLogFlag, false, "Output a trace of eBPF runtime activity") + flag.Parse() + + if *traceLog { + var err error + ts, err = newTraceSession() + if err != nil { + fmt.Fprintln(os.Stderr, "Disabling trace logging:", err) + } + } + } + defer ts.Close() + + fds = new(sync.Map) + ret := m.Run() + + for _, f := range flushFrames() { + onLeakFD(f) + } + + if foundLeak.Load() { + ret = 99 + } + + if err := ts.Dump(os.Stderr); err != nil { + fmt.Fprintln(os.Stderr, "Error while dumping trace log:", err) + ret = 99 + } + + if platform.IsWindows && ret != 0 && ts == nil { + fmt.Fprintf(os.Stderr, "Consider enabling trace logging with -%s\n", traceLogFlag) + } + + os.Exit(ret) +} diff --git a/vendor/github.com/cilium/ebpf/internal/testutils/testmain/windows.go b/vendor/github.com/cilium/ebpf/internal/testutils/testmain/windows.go new file mode 100644 index 0000000000..533af9dbb2 --- /dev/null +++ b/vendor/github.com/cilium/ebpf/internal/testutils/testmain/windows.go @@ -0,0 +1,219 @@ +package testmain + +import ( + "encoding/xml" + "fmt" + "io" + "os" + "os/exec" + "path/filepath" + "slices" + "strconv" + "strings" + "text/tabwriter" +) + +type tracelogKeywords uint64 + +// Know tracelog keywords. +// +// See https://github.com/microsoft/ebpf-for-windows/blob/main/libs/shared/ebpf_tracelog.h +var allKeywords = []string{ + "entry-exit", + "base", + "error", + "epoch", + "core", + "link", + "map", + "program", + "api", + "printk", + "native", +} + +func (kw *tracelogKeywords) UnmarshalText(text []byte) error { + decoded, err := strconv.ParseUint(string(text), 0, 64) + if err != nil { + return fmt.Errorf("foo: %w", err) + } + *kw = tracelogKeywords(decoded) + return nil +} + +func (kw tracelogKeywords) decode() []string { + var keywords []string + for _, keyword := range allKeywords { + if kw&1 > 0 { + keywords = append(keywords, keyword) + } + kw >>= 1 + } + if kw > 0 { + keywords = append(keywords, fmt.Sprintf("0x%x", kw)) + } + return keywords +} + +type traceSession struct { + session string +} + +// newTraceSession starts a trace log for eBPF for Windows related events. +// +// * https://github.com/microsoft/ebpf-for-windows/blob/main/docs/GettingStarted.md#using-tracing +// * https://devblogs.microsoft.com/performance-diagnostics/controlling-the-event-session-name-with-the-instance-name/ and +func newTraceSession() (*traceSession, error) { + def := filepath.Join(os.Getenv("ProgramFiles"), "ebpf-for-windows\\ebpfforwindows.wprp") + if _, err := os.Stat(def); err != nil { + return nil, err + } + + session := fmt.Sprintf("epbf-go-%d", os.Getpid()) + wpr := exec.Command("wpr.exe", "-start", def, "-filemode", "-instancename", session) + wpr.Stderr = os.Stderr + if err := wpr.Run(); err != nil { + return nil, err + } + + return &traceSession{session}, nil +} + +func (ts *traceSession) Close() error { + if ts == nil { + return nil + } + + return ts.stop(os.DevNull) +} + +func (ts *traceSession) stop(file string) error { + if ts.session == "" { + return nil + } + + wpr := exec.Command("wpr.exe", "-stop", file, "-instancename", ts.session) + if err := wpr.Run(); err != nil { + return err + } + + ts.session = "" + return nil +} + +func (ts *traceSession) Dump(w io.Writer) error { + if ts == nil { + return nil + } + + path, err := os.MkdirTemp("", "ebpf-go-trace") + if err != nil { + return err + } + defer os.RemoveAll(path) + + trace := filepath.Join(path, "trace.etl") + if err := ts.stop(trace); err != nil { + return fmt.Errorf("write trace: %w", err) + } + + netsh := exec.Command("netsh.exe", "trace", "convert", trace, "dump=XML") + if err := netsh.Run(); err != nil { + return err + } + + f, err := os.Open(filepath.Join(path, "trace.xml")) + if err != nil { + return err + } + defer f.Close() + + return summariseWPRTrace(f, w) +} + +func summariseWPRTrace(r io.Reader, w io.Writer) error { + type nameValue struct { + Name string `xml:"Name,attr"` + Value string `xml:",chardata"` + } + + type event struct { + XMLName xml.Name `xml:"Event"` + System struct { + Provider struct { + Name string `xml:"Name,attr"` + } `xml:"Provider"` + TimeCreated struct { + SystemTime string `xml:"SystemTime,attr"` + } `xml:"TimeCreated"` + Keywords tracelogKeywords `xml:"Keywords"` + Level uint64 `xml:"Level"` + } `xml:"System"` + EventData struct { + Data []nameValue `xml:"Data"` + } `xml:"EventData"` + RenderingInfo struct { + Task string `xml:"Task"` + } `xml:"RenderingInfo"` + } + + var events struct { + Events []event `xml:"Event"` + } + + err := xml.NewDecoder(r).Decode(&events) + if err != nil { + return fmt.Errorf("unmarshal trace XML: %w", err) + } + + tw := tabwriter.NewWriter(w, 0, 0, 1, ' ', 0) + for _, event := range events.Events { + if !strings.Contains(event.System.Provider.Name, "Ebpf") { + continue + } + + flag := " " + // See https://learn.microsoft.com/en-us/windows/win32/api/traceloggingprovider/nf-traceloggingprovider-tracelogginglevel#remarks + if event.System.Level > 0 && event.System.Level <= 3 { + flag = "!" + } + + kw := event.System.Keywords.decode() + fmt.Fprintf(tw, "%s\t%s\t", flag, strings.Join(kw, ",")) + + data := event.EventData.Data + slices.SortFunc(data, func(a, b nameValue) int { + return strings.Compare(a.Name, b.Name) + }) + + var first string + for _, name := range []string{ + "Entry", + "Message", + "ErrorMessage", + } { + i := slices.IndexFunc(data, func(kv nameValue) bool { + return kv.Name == name + }) + + if i == -1 { + continue + } + + first = data[i].Value + data = slices.Delete(data, i, i+1) + break + } + + // NB: This may be empty. + fmt.Fprintf(tw, "%s\t", first) + + for _, data := range data { + fmt.Fprintf(tw, "%s=%s\t", data.Name, data.Value) + } + + fmt.Fprintln(tw) + } + + return tw.Flush() +} diff --git a/vendor/github.com/cilium/ebpf/internal/tracefs/kprobe.go b/vendor/github.com/cilium/ebpf/internal/tracefs/kprobe.go index bdadf4d8ea..c42725b4d7 100644 --- a/vendor/github.com/cilium/ebpf/internal/tracefs/kprobe.go +++ b/vendor/github.com/cilium/ebpf/internal/tracefs/kprobe.go @@ -13,6 +13,8 @@ import ( "github.com/cilium/ebpf/internal" "github.com/cilium/ebpf/internal/linux" + "github.com/cilium/ebpf/internal/mountinfo" + "github.com/cilium/ebpf/internal/platform" "github.com/cilium/ebpf/internal/unix" ) @@ -22,7 +24,7 @@ var ( ErrInvalidMaxActive = errors.New("can only set maxactive on kretprobes") ) -//go:generate go run golang.org/x/tools/cmd/stringer@latest -type=ProbeType -linecomment +//go:generate go tool stringer -type=ProbeType -linecomment type ProbeType uint8 @@ -71,11 +73,11 @@ func RandomGroup(prefix string) (string, error) { } // validIdentifier implements the equivalent of a regex match -// against "^[a-zA-Z_][0-9a-zA-Z_]*$". +// against "^[a-zA-Z_][0-9a-zA-Z_-]*$". // -// Trace event groups, names and kernel symbols must adhere to this set -// of characters. Non-empty, first character must not be a number, all -// characters must be alphanumeric or underscore. +// Trace event groups, names and kernel symbols must adhere to this set of +// characters. Non-empty, first character must not be a number or hyphen, all +// characters must be alphanumeric, underscore or hyphen. func validIdentifier(s string) bool { if len(s) < 1 { return false @@ -85,7 +87,7 @@ func validIdentifier(s string) bool { case c >= 'a' && c <= 'z': case c >= 'A' && c <= 'Z': case c == '_': - case i > 0 && c >= '0' && c <= '9': + case i > 0 && (c == '-' || c >= '0' && c <= '9'): default: return false @@ -108,15 +110,31 @@ func sanitizeTracefsPath(path ...string) (string, error) { return p, nil } -// getTracefsPath will return a correct path to the tracefs mount point. -// Since kernel 4.1 tracefs should be mounted by default at /sys/kernel/tracing, -// but may be also be available at /sys/kernel/debug/tracing if debugfs is mounted. -// The available tracefs paths will depends on distribution choices. +// getTracefsPath returns a correct path to the tracefs mount point. +// +// The discovery order is: +// +// 1. Any tracefs mount listed in /proc/self/mountinfo (kernel 4.1+). +// This works regardless of where the mount sits in the filesystem, +// so containers that bind-mount tracefs at a non-canonical path are +// supported automatically. +// 2. A debugfs mount with a tracing/ subdirectory, for older systems +// where tracefs has not been lifted out of debugfs. +// 3. As a final fallback, probe the canonical kernel paths +// (/sys/kernel/tracing, /sys/kernel/debug/tracing) directly with +// statfs. This catches edge cases where /proc/self/mountinfo is +// unavailable or doesn't report the mount. var getTracefsPath = sync.OnceValues(func() (string, error) { - if !internal.OnLinux { + if !platform.IsLinux { return "", fmt.Errorf("tracefs: %w", internal.ErrNotSupportedOnOS) } + if entries, err := mountinfo.Read(); err == nil { + if path := findTracefsInEntries(entries); path != "" { + return path, nil + } + } + for _, p := range []struct { path string fsType int64 @@ -134,6 +152,28 @@ var getTracefsPath = sync.OnceValues(func() (string, error) { return "", errors.New("neither debugfs nor tracefs are mounted") }) +// findTracefsInEntries returns the first occurrence of a tracefs in the +// given entries. +// +// Returns an empty string when no usable mount is found. +func findTracefsInEntries(entries []mountinfo.Entry) string { + for _, e := range entries { + if e.FSType == "tracefs" && e.Root == "/" { + return e.MountPoint + } + } + for _, e := range entries { + if e.FSType != "debugfs" || e.Root != "/" { + continue + } + tracing := filepath.Join(e.MountPoint, "tracing") + if info, err := os.Stat(tracing); err == nil && info.IsDir() { + return tracing + } + } + return "" +} + // sanitizeIdentifier replaces every invalid character for the tracefs api with an underscore. // // It is equivalent to calling regexp.MustCompile("[^a-zA-Z0-9]+").ReplaceAllString("_"). @@ -199,6 +239,8 @@ type Event struct { group, name string // event id allocated by the kernel. 0 if the event has already been removed. id uint64 + + cleanup runtime.Cleanup } // NewEvent creates a new ephemeral trace event. @@ -305,14 +347,21 @@ func NewEvent(args ProbeArgs) (*Event, error) { if err := removeEvent(args.Type, event); err != nil { return nil, fmt.Errorf("failed to remove spurious maxactive event: %s", err) } - return nil, fmt.Errorf("create trace event with non-default maxactive: %w", internal.ErrNotSupported) + + return nil, &internal.UnsupportedFeatureError{ + MinimumVersion: internal.Version{4, 12}, + Name: "trace event with non-default maxactive", + } } if err != nil { return nil, fmt.Errorf("get trace event id: %w", err) } - evt := &Event{args.Type, args.Group, eventName, tid} - runtime.SetFinalizer(evt, (*Event).Close) + evt := &Event{typ: args.Type, group: args.Group, name: eventName, id: tid} + evt.cleanup = runtime.AddCleanup(evt, func(*byte) { + _ = removeEvent(args.Type, fmt.Sprintf("%s/%s", args.Group, eventName)) + }, nil) + return evt, nil } @@ -325,7 +374,7 @@ func (evt *Event) Close() error { } evt.id = 0 - runtime.SetFinalizer(evt, nil) + evt.cleanup.Stop() pe := fmt.Sprintf("%s/%s", evt.group, evt.name) return removeEvent(evt.typ, pe) } diff --git a/vendor/github.com/cilium/ebpf/internal/tracefs/probetype_string.go b/vendor/github.com/cilium/ebpf/internal/tracefs/probetype_string.go index 87cb0a059b..ed8471a893 100644 --- a/vendor/github.com/cilium/ebpf/internal/tracefs/probetype_string.go +++ b/vendor/github.com/cilium/ebpf/internal/tracefs/probetype_string.go @@ -17,8 +17,9 @@ const _ProbeType_name = "kprobeuprobe" var _ProbeType_index = [...]uint8{0, 6, 12} func (i ProbeType) String() string { - if i >= ProbeType(len(_ProbeType_index)-1) { + idx := int(i) - 0 + if i < 0 || idx >= len(_ProbeType_index)-1 { return "ProbeType(" + strconv.FormatInt(int64(i), 10) + ")" } - return _ProbeType_name[_ProbeType_index[i]:_ProbeType_index[i+1]] + return _ProbeType_name[_ProbeType_index[idx]:_ProbeType_index[idx+1]] } diff --git a/vendor/github.com/cilium/ebpf/internal/unix/errno_windows.go b/vendor/github.com/cilium/ebpf/internal/unix/errno_windows.go index 7500cd6d4e..266e43daa0 100644 --- a/vendor/github.com/cilium/ebpf/internal/unix/errno_windows.go +++ b/vendor/github.com/cilium/ebpf/internal/unix/errno_windows.go @@ -9,7 +9,7 @@ import ( "syscall" ) -//go:generate go run golang.org/x/tools/cmd/stringer@latest -type=Errno -tags=windows -output=errno_string_windows.go +//go:generate go tool stringer -type=Errno -tags=windows -output=errno_string_windows.go // Windows specific constants for Unix errnos. // @@ -37,6 +37,7 @@ const ( EILSEQ Errno = 42 ENOTSUP Errno = 129 EOPNOTSUPP Errno = 130 + EOTHER Errno = 131 ETIMEDOUT Errno = 138 EWOULDBLOCK Errno = 140 ) diff --git a/vendor/github.com/cilium/ebpf/internal/unix/strings_other.go b/vendor/github.com/cilium/ebpf/internal/unix/strings_other.go index 49e1fe2172..76f367aa85 100644 --- a/vendor/github.com/cilium/ebpf/internal/unix/strings_other.go +++ b/vendor/github.com/cilium/ebpf/internal/unix/strings_other.go @@ -9,3 +9,7 @@ func BytePtrFromString(s string) (*byte, error) { func ByteSliceToString(s []byte) string { return "" } + +func ByteSliceFromString(s string) ([]byte, error) { + return nil, errNonLinux() +} diff --git a/vendor/github.com/cilium/ebpf/internal/unix/strings_windows.go b/vendor/github.com/cilium/ebpf/internal/unix/strings_windows.go index 7e3d4da777..00af5a9686 100644 --- a/vendor/github.com/cilium/ebpf/internal/unix/strings_windows.go +++ b/vendor/github.com/cilium/ebpf/internal/unix/strings_windows.go @@ -17,3 +17,7 @@ func BytePtrFromString(s string) (*byte, error) { func ByteSliceToString(s []byte) string { return windows.ByteSliceToString(s) } + +func ByteSliceFromString(s string) ([]byte, error) { + return windows.ByteSliceFromString(s) +} diff --git a/vendor/github.com/cilium/ebpf/internal/unix/types_linux.go b/vendor/github.com/cilium/ebpf/internal/unix/types_linux.go index 385adf08b8..7cb13372b3 100644 --- a/vendor/github.com/cilium/ebpf/internal/unix/types_linux.go +++ b/vendor/github.com/cilium/ebpf/internal/unix/types_linux.go @@ -4,73 +4,89 @@ package unix import ( "syscall" + "unsafe" linux "golang.org/x/sys/unix" ) const ( - BPF_F_NO_PREALLOC = linux.BPF_F_NO_PREALLOC - BPF_F_NUMA_NODE = linux.BPF_F_NUMA_NODE - BPF_F_RDONLY = linux.BPF_F_RDONLY - BPF_F_WRONLY = linux.BPF_F_WRONLY - BPF_F_RDONLY_PROG = linux.BPF_F_RDONLY_PROG - BPF_F_WRONLY_PROG = linux.BPF_F_WRONLY_PROG - BPF_F_SLEEPABLE = linux.BPF_F_SLEEPABLE - BPF_F_XDP_HAS_FRAGS = linux.BPF_F_XDP_HAS_FRAGS - BPF_F_MMAPABLE = linux.BPF_F_MMAPABLE - BPF_F_INNER_MAP = linux.BPF_F_INNER_MAP - BPF_F_KPROBE_MULTI_RETURN = linux.BPF_F_KPROBE_MULTI_RETURN - BPF_F_UPROBE_MULTI_RETURN = linux.BPF_F_UPROBE_MULTI_RETURN - BPF_F_LOCK = linux.BPF_F_LOCK - BPF_OBJ_NAME_LEN = linux.BPF_OBJ_NAME_LEN - BPF_TAG_SIZE = linux.BPF_TAG_SIZE - BPF_RINGBUF_BUSY_BIT = linux.BPF_RINGBUF_BUSY_BIT - BPF_RINGBUF_DISCARD_BIT = linux.BPF_RINGBUF_DISCARD_BIT - BPF_RINGBUF_HDR_SZ = linux.BPF_RINGBUF_HDR_SZ - SYS_BPF = linux.SYS_BPF - F_DUPFD_CLOEXEC = linux.F_DUPFD_CLOEXEC - EPOLL_CTL_ADD = linux.EPOLL_CTL_ADD - EPOLL_CLOEXEC = linux.EPOLL_CLOEXEC - O_CLOEXEC = linux.O_CLOEXEC - O_NONBLOCK = linux.O_NONBLOCK - PROT_NONE = linux.PROT_NONE - PROT_READ = linux.PROT_READ - PROT_WRITE = linux.PROT_WRITE - MAP_ANON = linux.MAP_ANON - MAP_SHARED = linux.MAP_SHARED - MAP_PRIVATE = linux.MAP_PRIVATE - PERF_ATTR_SIZE_VER1 = linux.PERF_ATTR_SIZE_VER1 - PERF_TYPE_SOFTWARE = linux.PERF_TYPE_SOFTWARE - PERF_TYPE_TRACEPOINT = linux.PERF_TYPE_TRACEPOINT - PERF_COUNT_SW_BPF_OUTPUT = linux.PERF_COUNT_SW_BPF_OUTPUT - PERF_EVENT_IOC_DISABLE = linux.PERF_EVENT_IOC_DISABLE - PERF_EVENT_IOC_ENABLE = linux.PERF_EVENT_IOC_ENABLE - PERF_EVENT_IOC_SET_BPF = linux.PERF_EVENT_IOC_SET_BPF - PerfBitWatermark = linux.PerfBitWatermark - PerfBitWriteBackward = linux.PerfBitWriteBackward - PERF_SAMPLE_RAW = linux.PERF_SAMPLE_RAW - PERF_FLAG_FD_CLOEXEC = linux.PERF_FLAG_FD_CLOEXEC - RLIM_INFINITY = linux.RLIM_INFINITY - RLIMIT_MEMLOCK = linux.RLIMIT_MEMLOCK - BPF_STATS_RUN_TIME = linux.BPF_STATS_RUN_TIME - PERF_RECORD_LOST = linux.PERF_RECORD_LOST - 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 - SIGPROF = linux.SIGPROF - SIGUSR1 = linux.SIGUSR1 - SIG_BLOCK = linux.SIG_BLOCK - SIG_UNBLOCK = linux.SIG_UNBLOCK - BPF_FS_MAGIC = linux.BPF_FS_MAGIC - TRACEFS_MAGIC = linux.TRACEFS_MAGIC - DEBUGFS_MAGIC = linux.DEBUGFS_MAGIC - BPF_RB_NO_WAKEUP = linux.BPF_RB_NO_WAKEUP - BPF_RB_FORCE_WAKEUP = linux.BPF_RB_FORCE_WAKEUP - AF_UNSPEC = linux.AF_UNSPEC - IFF_UP = linux.IFF_UP + BPF_F_NO_PREALLOC = linux.BPF_F_NO_PREALLOC + BPF_F_NUMA_NODE = linux.BPF_F_NUMA_NODE + BPF_F_RDONLY = linux.BPF_F_RDONLY + BPF_F_WRONLY = linux.BPF_F_WRONLY + BPF_F_RDONLY_PROG = linux.BPF_F_RDONLY_PROG + BPF_F_WRONLY_PROG = linux.BPF_F_WRONLY_PROG + BPF_F_SLEEPABLE = linux.BPF_F_SLEEPABLE + BPF_F_XDP_HAS_FRAGS = linux.BPF_F_XDP_HAS_FRAGS + BPF_F_MMAPABLE = linux.BPF_F_MMAPABLE + BPF_F_INNER_MAP = linux.BPF_F_INNER_MAP + BPF_F_KPROBE_MULTI_RETURN = linux.BPF_F_KPROBE_MULTI_RETURN + BPF_F_UPROBE_MULTI_RETURN = linux.BPF_F_UPROBE_MULTI_RETURN + BPF_F_LOCK = linux.BPF_F_LOCK + BPF_OBJ_NAME_LEN = linux.BPF_OBJ_NAME_LEN + BPF_TAG_SIZE = linux.BPF_TAG_SIZE + BPF_RINGBUF_BUSY_BIT = linux.BPF_RINGBUF_BUSY_BIT + BPF_RINGBUF_DISCARD_BIT = linux.BPF_RINGBUF_DISCARD_BIT + BPF_RINGBUF_HDR_SZ = linux.BPF_RINGBUF_HDR_SZ + SYS_BPF = linux.SYS_BPF + F_DUPFD_CLOEXEC = linux.F_DUPFD_CLOEXEC + EPOLL_CTL_ADD = linux.EPOLL_CTL_ADD + EPOLL_CLOEXEC = linux.EPOLL_CLOEXEC + O_RDONLY = linux.O_RDONLY + O_DIRECTORY = linux.O_DIRECTORY + O_CLOEXEC = linux.O_CLOEXEC + O_NONBLOCK = linux.O_NONBLOCK + PROT_NONE = linux.PROT_NONE + PROT_READ = linux.PROT_READ + PROT_WRITE = linux.PROT_WRITE + MAP_ANON = linux.MAP_ANON + MAP_SHARED = linux.MAP_SHARED + MAP_FIXED = linux.MAP_FIXED + MAP_PRIVATE = linux.MAP_PRIVATE + PERF_ATTR_SIZE_VER1 = linux.PERF_ATTR_SIZE_VER1 + PERF_TYPE_SOFTWARE = linux.PERF_TYPE_SOFTWARE + PERF_TYPE_TRACEPOINT = linux.PERF_TYPE_TRACEPOINT + PERF_COUNT_SW_BPF_OUTPUT = linux.PERF_COUNT_SW_BPF_OUTPUT + PERF_EVENT_IOC_DISABLE = linux.PERF_EVENT_IOC_DISABLE + PERF_EVENT_IOC_ENABLE = linux.PERF_EVENT_IOC_ENABLE + PERF_EVENT_IOC_SET_BPF = linux.PERF_EVENT_IOC_SET_BPF + PerfBitWatermark = linux.PerfBitWatermark + PerfBitWriteBackward = linux.PerfBitWriteBackward + PERF_SAMPLE_RAW = linux.PERF_SAMPLE_RAW + PERF_FLAG_FD_CLOEXEC = linux.PERF_FLAG_FD_CLOEXEC + RLIM_INFINITY = linux.RLIM_INFINITY + RLIMIT_MEMLOCK = linux.RLIMIT_MEMLOCK + BPF_STATS_RUN_TIME = linux.BPF_STATS_RUN_TIME + PERF_RECORD_LOST = linux.PERF_RECORD_LOST + 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 + SIGPROF = linux.SIGPROF + SIGUSR1 = linux.SIGUSR1 + SIG_BLOCK = linux.SIG_BLOCK + SIG_UNBLOCK = linux.SIG_UNBLOCK + BPF_FS_MAGIC = linux.BPF_FS_MAGIC + TRACEFS_MAGIC = linux.TRACEFS_MAGIC + DEBUGFS_MAGIC = linux.DEBUGFS_MAGIC + BPF_RB_NO_WAKEUP = linux.BPF_RB_NO_WAKEUP + BPF_RB_FORCE_WAKEUP = linux.BPF_RB_FORCE_WAKEUP + AF_UNSPEC = linux.AF_UNSPEC + IFF_UP = linux.IFF_UP + LINUX_CAPABILITY_VERSION_3 = linux.LINUX_CAPABILITY_VERSION_3 + CLONE_NEWNET = linux.CLONE_NEWNET + CLONE_NEWUSER = linux.CLONE_NEWUSER + CLONE_NEWNS = linux.CLONE_NEWNS + MOVE_MOUNT_F_EMPTY_PATH = linux.MOVE_MOUNT_F_EMPTY_PATH + AF_UNIX = linux.AF_UNIX + SOCK_STREAM = linux.SOCK_STREAM + SOCK_CLOEXEC = linux.SOCK_CLOEXEC + FSOPEN_CLOEXEC = linux.FSOPEN_CLOEXEC + FSMOUNT_CLOEXEC = linux.FSMOUNT_CLOEXEC + MSG_CMSG_CLOEXEC = linux.MSG_CMSG_CLOEXEC + SizeofInt = linux.SizeofInt ) type Statfs_t = linux.Statfs_t @@ -83,6 +99,9 @@ type EpollEvent = linux.EpollEvent type PerfEventAttr = linux.PerfEventAttr type Utsname = linux.Utsname type CPUSet = linux.CPUSet +type CapUserData = linux.CapUserData +type CapUserHeader = linux.CapUserHeader +type SysProcAttr = linux.SysProcAttr func Syscall(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err syscall.Errno) { return linux.Syscall(trap, a1, a2, a3) @@ -136,6 +155,11 @@ func Mmap(fd int, offset int64, length int, prot int, flags int) (data []byte, e return linux.Mmap(fd, offset, length, prot, flags) } +//go:nocheckptr +func MmapPtr(fd int, offset int64, addr unsafe.Pointer, length uintptr, prot int, flags int) (ret unsafe.Pointer, err error) { + return linux.MmapPtr(fd, offset, addr, length, prot, flags) +} + func Munmap(b []byte) (err error) { return linux.Munmap(b) } @@ -168,6 +192,10 @@ func ByteSliceToString(s []byte) string { return linux.ByteSliceToString(s) } +func ByteSliceFromString(s string) ([]byte, error) { + return linux.ByteSliceFromString(s) +} + func Renameat2(olddirfd int, oldpath string, newdirfd int, newpath string, flags uint) error { return linux.Renameat2(olddirfd, oldpath, newdirfd, newpath, flags) } @@ -199,3 +227,67 @@ func SchedGetaffinity(pid int, set *CPUSet) error { func Auxv() ([][2]uintptr, error) { return linux.Auxv() } + +func Unshare(flag int) error { + return linux.Unshare(flag) +} + +func Setns(fd int, nstype int) error { + return linux.Setns(fd, nstype) +} + +func Capget(hdr *CapUserHeader, data *CapUserData) (err error) { + return linux.Capget(hdr, data) +} + +func Capset(hdr *CapUserHeader, data *CapUserData) (err error) { + return linux.Capset(hdr, data) +} + +func Sendmsg(fd int, p []byte, oob []byte, to linux.Sockaddr, flags int) (err error) { + return linux.Sendmsg(fd, p, oob, to, flags) +} + +func Fsopen(fsname string, flags int) (fd int, err error) { + return linux.Fsopen(fsname, flags) +} + +func FsconfigSetString(fd int, key string, value string) error { + return linux.FsconfigSetString(fd, key, value) +} + +func FsconfigCreate(fd int) (err error) { + return linux.FsconfigCreate(fd) +} + +func Fsmount(fd int, flags int, mountAttrs int) (fsfd int, err error) { + return linux.Fsmount(fd, flags, mountAttrs) +} + +func MoveMount(fromDirfd int, fromPathName string, toDirfd int, toPathName string, flags int) (err error) { + return linux.MoveMount(fromDirfd, fromPathName, toDirfd, toPathName, flags) +} + +func UnixRights(fds ...int) []byte { + return linux.UnixRights(fds...) +} + +func Recvmsg(fd int, p []byte, oob []byte, flags int) (n int, oobn int, recvflags int, from linux.Sockaddr, err error) { + return linux.Recvmsg(fd, p, oob, flags) +} + +func Socketpair(domain int, typ int, proto int) (fd [2]int, err error) { + return linux.Socketpair(domain, typ, proto) +} + +func CmsgSpace(datalen int) int { + return linux.CmsgSpace(datalen) +} + +func ParseSocketControlMessage(b []byte) ([]linux.SocketControlMessage, error) { + return linux.ParseSocketControlMessage(b) +} + +func ParseUnixRights(m *linux.SocketControlMessage) ([]int, error) { + return linux.ParseUnixRights(m) +} diff --git a/vendor/github.com/cilium/ebpf/internal/unix/types_other.go b/vendor/github.com/cilium/ebpf/internal/unix/types_other.go index 323f7ff34e..5d75582e2d 100644 --- a/vendor/github.com/cilium/ebpf/internal/unix/types_other.go +++ b/vendor/github.com/cilium/ebpf/internal/unix/types_other.go @@ -4,6 +4,7 @@ package unix import ( "syscall" + "unsafe" ) // Constants are distinct to avoid breaking switch statements. @@ -32,11 +33,14 @@ const ( EPOLL_CLOEXEC O_CLOEXEC O_NONBLOCK + O_DIRECTORY + O_RDONLY PROT_NONE PROT_READ PROT_WRITE MAP_ANON MAP_SHARED + MAP_FIXED MAP_PRIVATE PERF_ATTR_SIZE_VER1 PERF_TYPE_SOFTWARE @@ -71,6 +75,12 @@ const ( BPF_F_LOCK AF_UNSPEC IFF_UP + LINUX_CAPABILITY_VERSION_3 + SOCK_CLOEXEC + FSOPEN_CLOEXEC + FSMOUNT_CLOEXEC + MSG_CMSG_CLOEXEC + SizeofInt ) type Statfs_t struct { @@ -113,6 +123,17 @@ type Sigset_t struct { Val [4]uint64 } +type CapUserHeader struct { + Version uint32 + Pid int32 +} + +type CapUserData struct { + Effective uint32 + Permitted uint32 + Inheritable uint32 +} + func Syscall(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err Errno) { return 0, 0, ENOTSUP } @@ -197,6 +218,10 @@ func Mmap(fd int, offset int64, length int, prot int, flags int) (data []byte, e return []byte{}, errNonLinux() } +func MmapPtr(fd int, offset int64, addr unsafe.Pointer, length uintptr, prot int, flags int) (ret unsafe.Pointer, err error) { + return nil, errNonLinux() +} + func Munmap(b []byte) (err error) { return errNonLinux() } @@ -282,3 +307,11 @@ func SchedGetaffinity(pid int, set *CPUSet) error { func Auxv() ([][2]uintptr, error) { return nil, errNonLinux() } + +func Capget(hdr *CapUserHeader, data *CapUserData) (err error) { + return errNonLinux() +} + +func Capset(hdr *CapUserHeader, data *CapUserData) (err error) { + return errNonLinux() +} diff --git a/vendor/github.com/cilium/ebpf/internal/version.go b/vendor/github.com/cilium/ebpf/internal/version.go index a230830b01..3123dc9f06 100644 --- a/vendor/github.com/cilium/ebpf/internal/version.go +++ b/vendor/github.com/cilium/ebpf/internal/version.go @@ -66,10 +66,7 @@ func (v Version) Kernel() uint32 { // Kernels 4.4 and 4.9 have their SUBLEVEL clamped to 255 to avoid // overflowing into PATCHLEVEL. // See kernel commit 9b82f13e7ef3 ("kbuild: clamp SUBLEVEL to 255"). - s := v[2] - if s > 255 { - s = 255 - } + s := min(v[2], 255) // Truncate members to uint8 to prevent them from spilling over into // each other when overflowing 8 bits. diff --git a/vendor/github.com/cilium/ebpf/link/anchor.go b/vendor/github.com/cilium/ebpf/link/anchor.go index 1a3b5f7681..10fbba079c 100644 --- a/vendor/github.com/cilium/ebpf/link/anchor.go +++ b/vendor/github.com/cilium/ebpf/link/anchor.go @@ -1,3 +1,5 @@ +//go:build !windows + package link import ( diff --git a/vendor/github.com/cilium/ebpf/link/cgroup.go b/vendor/github.com/cilium/ebpf/link/cgroup.go index f17d34f03c..64aea1ae3c 100644 --- a/vendor/github.com/cilium/ebpf/link/cgroup.go +++ b/vendor/github.com/cilium/ebpf/link/cgroup.go @@ -1,3 +1,5 @@ +//go:build !windows + package link import ( @@ -165,6 +167,10 @@ func (cg *progAttachCgroup) Unpin() error { return fmt.Errorf("can't unpin cgroup: %w", ErrNotSupported) } +func (cg *progAttachCgroup) Detach() error { + return fmt.Errorf("can't detach cgroup: %w", ErrNotSupported) +} + func (cg *progAttachCgroup) Info() (*Info, error) { return nil, fmt.Errorf("can't get cgroup info: %w", ErrNotSupported) } diff --git a/vendor/github.com/cilium/ebpf/link/iter.go b/vendor/github.com/cilium/ebpf/link/iter.go index 0a39faef88..40bb69c708 100644 --- a/vendor/github.com/cilium/ebpf/link/iter.go +++ b/vendor/github.com/cilium/ebpf/link/iter.go @@ -1,3 +1,5 @@ +//go:build !windows + package link import ( @@ -42,7 +44,7 @@ func AttachIter(opts IterOptions) (*Iter, error) { attr := sys.LinkCreateIterAttr{ ProgFd: uint32(progFd), AttachType: sys.AttachType(ebpf.AttachTraceIter), - IterInfo: sys.NewPointer(unsafe.Pointer(&info)), + IterInfo: sys.UnsafePointer(unsafe.Pointer(&info)), IterInfoLen: uint32(unsafe.Sizeof(info)), } @@ -75,7 +77,7 @@ func (it *Iter) Open() (io.ReadCloser, error) { return nil, fmt.Errorf("can't create iterator: %w", err) } - return fd.File("bpf_iter"), nil + return fd.File("bpf_iter") } // union bpf_iter_link_info.map diff --git a/vendor/github.com/cilium/ebpf/link/kprobe.go b/vendor/github.com/cilium/ebpf/link/kprobe.go index 6f93a27a25..8b27677f90 100644 --- a/vendor/github.com/cilium/ebpf/link/kprobe.go +++ b/vendor/github.com/cilium/ebpf/link/kprobe.go @@ -1,3 +1,5 @@ +//go:build !windows + package link import ( @@ -31,8 +33,9 @@ type KprobeOptions struct { // Increase the maximum number of concurrent invocations of a kretprobe. // Required when tracing some long running functions in the kernel. // - // Deprecated: this setting forces the use of an outdated kernel API and is not portable - // across kernel versions. + // Warning: this setting forces the use of an outdated kernel API and is + // not portable across kernel versions. On supported kernels, consider using + // fexit programs instead, as they don't have this MaxActive limitation. RetprobeMaxActive int // Prefix used for the event name if the kprobe must be attached using tracefs. // The group name will be formatted as `_`. diff --git a/vendor/github.com/cilium/ebpf/link/kprobe_multi.go b/vendor/github.com/cilium/ebpf/link/kprobe_multi.go index f19f9f4c73..b72ea95878 100644 --- a/vendor/github.com/cilium/ebpf/link/kprobe_multi.go +++ b/vendor/github.com/cilium/ebpf/link/kprobe_multi.go @@ -1,14 +1,14 @@ +//go:build !windows + package link import ( "errors" "fmt" "os" - "unsafe" "github.com/cilium/ebpf" - "github.com/cilium/ebpf/asm" - "github.com/cilium/ebpf/internal" + "github.com/cilium/ebpf/features" "github.com/cilium/ebpf/internal/sys" "github.com/cilium/ebpf/internal/unix" ) @@ -84,10 +84,10 @@ func kprobeMulti(prog *ebpf.Program, opts KprobeMultiOptions, flags uint32) (Lin return nil, fmt.Errorf("one of Symbols or Addresses is required: %w", errInvalidInput) } if syms != 0 && addrs != 0 { - return nil, fmt.Errorf("Symbols and Addresses are mutually exclusive: %w", errInvalidInput) + return nil, fmt.Errorf("fields Symbols and Addresses are mutually exclusive: %w", errInvalidInput) } if cookies > 0 && cookies != syms && cookies != addrs { - return nil, fmt.Errorf("Cookies must be exactly Symbols or Addresses in length: %w", errInvalidInput) + return nil, fmt.Errorf("field Cookies must be exactly Symbols or Addresses in length: %w", errInvalidInput) } attachType := sys.BPF_TRACE_KPROBE_MULTI @@ -108,11 +108,11 @@ func kprobeMulti(prog *ebpf.Program, opts KprobeMultiOptions, flags uint32) (Lin case addrs != 0: attr.Count = addrs - attr.Addrs = sys.NewPointer(unsafe.Pointer(&opts.Addresses[0])) + attr.Addrs = sys.SlicePointer(opts.Addresses) } if cookies != 0 { - attr.Cookies = sys.NewPointer(unsafe.Pointer(&opts.Cookies[0])) + attr.Cookies = sys.SlicePointer(opts.Cookies) } fd, err := sys.LinkCreateKprobeMulti(attr) @@ -125,11 +125,11 @@ func kprobeMulti(prog *ebpf.Program, opts KprobeMultiOptions, flags uint32) (Lin } if opts.Session { - if haveFeatErr := haveBPFLinkKprobeSession(); haveFeatErr != nil { + if haveFeatErr := features.HaveBPFLinkKprobeSession(); haveFeatErr != nil { return nil, haveFeatErr } } else { - if haveFeatErr := haveBPFLinkKprobeMulti(); haveFeatErr != nil { + if haveFeatErr := features.HaveBPFLinkKprobeMulti(); haveFeatErr != nil { return nil, haveFeatErr } } @@ -156,12 +156,30 @@ func (kml *kprobeMultiLink) Update(_ *ebpf.Program) error { func (kml *kprobeMultiLink) Info() (*Info, error) { var info sys.KprobeMultiLinkInfo if err := sys.ObjInfo(kml.fd, &info); err != nil { - return nil, fmt.Errorf("kprobe multi link info: %s", err) + return nil, fmt.Errorf("kprobe multi link info: %w", err) + } + var addrs = make([]uint64, info.Count) + var cookies = make([]uint64, info.Count) + info = sys.KprobeMultiLinkInfo{ + Addrs: sys.SlicePointer(addrs), + Cookies: sys.SlicePointer(cookies), + Count: uint32(len(addrs)), + } + if err := sys.ObjInfo(kml.fd, &info); err != nil { + return nil, fmt.Errorf("kprobe multi link info: %w", err) + } + if info.Addrs.IsNil() { + addrs = nil + } + if info.Cookies.IsNil() { + cookies = nil } extra := &KprobeMultiInfo{ - count: info.Count, - flags: info.Flags, - missed: info.Missed, + Count: info.Count, + Flags: info.Flags, + Missed: info.Missed, + addrs: addrs, + cookies: cookies, } return &Info{ @@ -171,85 +189,3 @@ func (kml *kprobeMultiLink) Info() (*Info, error) { extra, }, nil } - -var haveBPFLinkKprobeMulti = internal.NewFeatureTest("bpf_link_kprobe_multi", func() error { - prog, err := ebpf.NewProgram(&ebpf.ProgramSpec{ - Name: "probe_kpm_link", - Type: ebpf.Kprobe, - Instructions: asm.Instructions{ - asm.Mov.Imm(asm.R0, 0), - asm.Return(), - }, - AttachType: ebpf.AttachTraceKprobeMulti, - License: "MIT", - }) - if errors.Is(err, unix.E2BIG) { - // Kernel doesn't support AttachType field. - return internal.ErrNotSupported - } - if err != nil { - return err - } - defer prog.Close() - - fd, err := sys.LinkCreateKprobeMulti(&sys.LinkCreateKprobeMultiAttr{ - ProgFd: uint32(prog.FD()), - AttachType: sys.BPF_TRACE_KPROBE_MULTI, - Count: 1, - Syms: sys.NewStringSlicePointer([]string{"vprintk"}), - }) - switch { - case errors.Is(err, unix.EINVAL): - return internal.ErrNotSupported - // If CONFIG_FPROBE isn't set. - case errors.Is(err, unix.EOPNOTSUPP): - return internal.ErrNotSupported - case err != nil: - return err - } - - fd.Close() - - return nil -}, "5.18") - -var haveBPFLinkKprobeSession = internal.NewFeatureTest("bpf_link_kprobe_session", func() error { - prog, err := ebpf.NewProgram(&ebpf.ProgramSpec{ - Name: "probe_kps_link", - Type: ebpf.Kprobe, - Instructions: asm.Instructions{ - asm.Mov.Imm(asm.R0, 0), - asm.Return(), - }, - AttachType: ebpf.AttachTraceKprobeSession, - License: "MIT", - }) - if errors.Is(err, unix.E2BIG) { - // Kernel doesn't support AttachType field. - return internal.ErrNotSupported - } - if err != nil { - return err - } - defer prog.Close() - - fd, err := sys.LinkCreateKprobeMulti(&sys.LinkCreateKprobeMultiAttr{ - ProgFd: uint32(prog.FD()), - AttachType: sys.BPF_TRACE_KPROBE_SESSION, - Count: 1, - Syms: sys.NewStringSlicePointer([]string{"vprintk"}), - }) - switch { - case errors.Is(err, unix.EINVAL): - return internal.ErrNotSupported - // If CONFIG_FPROBE isn't set. - case errors.Is(err, unix.EOPNOTSUPP): - return internal.ErrNotSupported - case err != nil: - return err - } - - fd.Close() - - return nil -}, "6.10") diff --git a/vendor/github.com/cilium/ebpf/link/link.go b/vendor/github.com/cilium/ebpf/link/link.go index 796769f8ea..57b328c2eb 100644 --- a/vendor/github.com/cilium/ebpf/link/link.go +++ b/vendor/github.com/cilium/ebpf/link/link.go @@ -9,8 +9,12 @@ import ( "github.com/cilium/ebpf/btf" "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 = sys.LinkType + var ErrNotSupported = internal.ErrNotSupported // Link represents a Program attached to a BPF hook. @@ -37,6 +41,11 @@ type Link interface { // not called. Close() error + // Detach the link from its corresponding attachment point. + // + // May return an error wrapping ErrNotSupported. + Detach() error + // Info returns metadata on a link. // // May return an error wrapping ErrNotSupported. @@ -46,13 +55,6 @@ type Link interface { isLink() } -// NewLinkFromFD creates a link from a raw fd. -// -// Deprecated: use [NewFromFD] instead. -func NewLinkFromFD(fd int) (Link, error) { - return NewFromFD(fd) -} - // NewFromFD creates a link from a raw fd. // // You should not use fd after calling this function. @@ -90,51 +92,6 @@ func LoadPinnedLink(fileName string, opts *ebpf.LoadPinOptions) (Link, error) { 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, err error) { - defer func() { - if err != nil { - raw.Close() - } - }() - - info, err := raw.Info() - if err != nil { - 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 - case KprobeMultiType: - return &kprobeMultiLink{*raw}, nil - case UprobeMultiType: - return &uprobeMultiLink{*raw}, nil - case PerfEventType: - return &perfEventLink{*raw, nil}, nil - case TCXType: - return &tcxLink{*raw}, nil - case NetfilterType: - return &netfilterLink{*raw}, nil - case NetkitType: - return &netkitLink{*raw}, nil - case XDPType: - return &xdpLink{*raw}, nil - default: - return raw, nil - } -} - // ID uniquely identifies a BPF link. type ID = sys.LinkID @@ -157,159 +114,7 @@ type Info struct { Type Type ID ID Program ebpf.ProgramID - extra interface{} -} - -type TracingInfo struct { - AttachType sys.AttachType - TargetObjId uint32 - TargetBtfId sys.TypeID -} - -type CgroupInfo struct { - CgroupId uint64 - AttachType sys.AttachType - _ [4]byte -} - -type NetNsInfo struct { - NetnsIno uint32 - AttachType sys.AttachType -} - -type TCXInfo struct { - Ifindex uint32 - AttachType sys.AttachType -} - -type XDPInfo struct { - Ifindex uint32 -} - -type NetfilterInfo struct { - Pf uint32 - Hooknum uint32 - Priority int32 - Flags uint32 -} - -type NetkitInfo struct { - Ifindex uint32 - AttachType sys.AttachType -} - -type KprobeMultiInfo struct { - count uint32 - flags uint32 - missed uint64 -} - -// AddressCount is the number of addresses hooked by the kprobe. -func (kpm *KprobeMultiInfo) AddressCount() (uint32, bool) { - return kpm.count, kpm.count > 0 -} - -func (kpm *KprobeMultiInfo) Flags() (uint32, bool) { - return kpm.flags, kpm.count > 0 -} - -func (kpm *KprobeMultiInfo) Missed() (uint64, bool) { - return kpm.missed, kpm.count > 0 -} - -type PerfEventInfo struct { - Type sys.PerfEventType - extra interface{} -} - -func (r *PerfEventInfo) Kprobe() *KprobeInfo { - e, _ := r.extra.(*KprobeInfo) - return e -} - -type KprobeInfo struct { - address uint64 - missed uint64 -} - -func (kp *KprobeInfo) Address() (uint64, bool) { - return kp.address, kp.address > 0 -} - -func (kp *KprobeInfo) Missed() (uint64, bool) { - return kp.missed, kp.address > 0 -} - -// 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 -} - -// XDP 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 -} - -// TCX returns TCX type-specific link info. -// -// Returns nil if the type-specific link info isn't available. -func (r Info) TCX() *TCXInfo { - e, _ := r.extra.(*TCXInfo) - return e -} - -// Netfilter returns netfilter type-specific link info. -// -// Returns nil if the type-specific link info isn't available. -func (r Info) Netfilter() *NetfilterInfo { - e, _ := r.extra.(*NetfilterInfo) - return e -} - -// Netkit returns netkit type-specific link info. -// -// Returns nil if the type-specific link info isn't available. -func (r Info) Netkit() *NetkitInfo { - e, _ := r.extra.(*NetkitInfo) - return e -} - -// KprobeMulti returns kprobe-multi type-specific link info. -// -// Returns nil if the type-specific link info isn't available. -func (r Info) KprobeMulti() *KprobeMultiInfo { - e, _ := r.extra.(*KprobeMultiInfo) - return e -} - -// PerfEvent returns perf-event type-specific link info. -// -// Returns nil if the type-specific link info isn't available. -func (r Info) PerfEvent() *PerfEventInfo { - e, _ := r.extra.(*PerfEventInfo) - return e + extra any } // RawLink is the low-level API to bpf_link. @@ -321,36 +126,6 @@ type RawLink struct { pinnedPath string } -// AttachRawLink creates a raw link. -func AttachRawLink(opts RawLinkOptions) (*RawLink, error) { - if err := haveBPFLink(); err != nil { - return nil, err - } - - if opts.Target < 0 { - return nil, fmt.Errorf("invalid target: %s", sys.ErrClosedFd) - } - - progFd := opts.Program.FD() - if progFd < 0 { - return nil, fmt.Errorf("invalid program: %s", sys.ErrClosedFd) - } - - attr := sys.LinkCreateAttr{ - TargetFd: uint32(opts.Target), - ProgFd: uint32(progFd), - AttachType: sys.AttachType(opts.Attach), - TargetBtfId: opts.BTF, - Flags: opts.Flags, - } - fd, err := sys.LinkCreate(&attr) - if err != nil { - return nil, fmt.Errorf("create link: %w", err) - } - - return &RawLink{fd, ""}, nil -} - func loadPinnedRawLink(fileName string, opts *ebpf.LoadPinOptions) (*RawLink, error) { fd, typ, err := sys.ObjGetTyped(&sys.ObjGetAttr{ Pathname: sys.NewStringPointer(fileName), @@ -443,7 +218,28 @@ func (l *RawLink) UpdateArgs(opts RawLinkUpdateOptions) error { OldProgFd: uint32(oldFd), Flags: opts.Flags, } - return sys.LinkUpdate(&attr) + if err := sys.LinkUpdate(&attr); err != nil { + return fmt.Errorf("update link: %w", err) + } + return nil +} + +// Detach the link from its corresponding attachment point. +func (l *RawLink) Detach() error { + attr := sys.LinkDetachAttr{ + LinkFd: l.fd.Uint(), + } + + err := sys.LinkDetach(&attr) + + switch { + case errors.Is(err, unix.EOPNOTSUPP): + return internal.ErrNotSupported + case err != nil: + return fmt.Errorf("detach link: %w", err) + default: + return nil + } } // Info returns metadata about the link. diff --git a/vendor/github.com/cilium/ebpf/link/link_other.go b/vendor/github.com/cilium/ebpf/link/link_other.go new file mode 100644 index 0000000000..31904aaf5d --- /dev/null +++ b/vendor/github.com/cilium/ebpf/link/link_other.go @@ -0,0 +1,382 @@ +//go:build !windows + +package link + +import ( + "fmt" + + "github.com/cilium/ebpf/internal" + "github.com/cilium/ebpf/internal/platform" + "github.com/cilium/ebpf/internal/sys" +) + +// Valid link types. +const ( + 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 + PerfEventType = sys.BPF_LINK_TYPE_PERF_EVENT + KprobeMultiType = sys.BPF_LINK_TYPE_KPROBE_MULTI + TCXType = sys.BPF_LINK_TYPE_TCX + UprobeMultiType = sys.BPF_LINK_TYPE_UPROBE_MULTI + NetfilterType = sys.BPF_LINK_TYPE_NETFILTER + NetkitType = sys.BPF_LINK_TYPE_NETKIT + StructOpsType = sys.BPF_LINK_TYPE_STRUCT_OPS +) + +// AttachRawLink creates a raw link. +func AttachRawLink(opts RawLinkOptions) (*RawLink, error) { + if err := haveBPFLink(); err != nil { + return nil, err + } + + if opts.Target < 0 { + return nil, fmt.Errorf("invalid target: %s", sys.ErrClosedFd) + } + + progFd := opts.Program.FD() + if progFd < 0 { + return nil, fmt.Errorf("invalid program: %s", sys.ErrClosedFd) + } + + p, attachType := platform.DecodeConstant(opts.Attach) + if p != platform.Linux { + return nil, fmt.Errorf("attach type %s: %w", opts.Attach, internal.ErrNotSupportedOnOS) + } + + attr := sys.LinkCreateAttr{ + TargetFd: uint32(opts.Target), + ProgFd: uint32(progFd), + AttachType: sys.AttachType(attachType), + TargetBtfId: opts.BTF, + Flags: opts.Flags, + } + fd, err := sys.LinkCreate(&attr) + if err != nil { + return nil, fmt.Errorf("create link: %w", err) + } + + return &RawLink{fd, ""}, nil +} + +// 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, err error) { + defer func() { + if err != nil { + raw.Close() + } + }() + + info, err := raw.Info() + if err != nil { + 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 + case KprobeMultiType: + return &kprobeMultiLink{*raw}, nil + case UprobeMultiType: + return &uprobeMultiLink{*raw}, nil + case PerfEventType: + return &perfEventLink{*raw, nil}, nil + case TCXType: + return &tcxLink{*raw}, nil + case NetfilterType: + return &netfilterLink{*raw}, nil + case NetkitType: + return &netkitLink{*raw}, nil + case XDPType: + return &xdpLink{*raw}, nil + case StructOpsType: + return &structOpsLink{*raw}, nil + default: + return raw, nil + } +} + +type TracingInfo struct { + AttachType sys.AttachType + TargetObjectId uint32 + TargetBtfId sys.TypeID +} + +type CgroupInfo struct { + CgroupId uint64 + AttachType sys.AttachType + _ [4]byte +} + +type NetNsInfo struct { + NetnsInode uint32 + AttachType sys.AttachType +} + +type TCXInfo struct { + Ifindex uint32 + AttachType sys.AttachType +} + +type XDPInfo struct { + Ifindex uint32 +} + +type NetfilterInfo struct { + ProtocolFamily NetfilterProtocolFamily + Hook NetfilterInetHook + Priority int32 + Flags uint32 +} + +type NetkitInfo struct { + Ifindex uint32 + AttachType sys.AttachType +} + +type RawTracepointInfo struct { + Name string +} + +type KprobeMultiInfo struct { + // Count is the number of addresses hooked by the kprobe. + Count uint32 + Flags uint32 + Missed uint64 + addrs []uint64 + cookies []uint64 +} + +type KprobeMultiAddress struct { + Address uint64 + Cookie uint64 +} + +// Addresses are the addresses hooked by the kprobe. +func (kpm *KprobeMultiInfo) Addresses() ([]KprobeMultiAddress, bool) { + if kpm.addrs == nil || len(kpm.addrs) != len(kpm.cookies) { + return nil, false + } + addrs := make([]KprobeMultiAddress, len(kpm.addrs)) + for i := range kpm.addrs { + addrs[i] = KprobeMultiAddress{ + Address: kpm.addrs[i], + Cookie: kpm.cookies[i], + } + } + return addrs, true +} + +type UprobeMultiInfo struct { + Count uint32 + Flags uint32 + Missed uint64 + offsets []uint64 + cookies []uint64 + refCtrOffsets []uint64 + // File is the path that the file the uprobe was attached to + // had at creation time. + // + // However, due to various circumstances (differing mount namespaces, + // file replacement, ...), this path may not point to the same binary + // the uprobe was originally attached to. + File string + pid uint32 +} + +type UprobeMultiOffset struct { + Offset uint64 + Cookie uint64 + ReferenceCount uint64 +} + +// Offsets returns the offsets that the uprobe was attached to along with the related cookies and ref counters. +func (umi *UprobeMultiInfo) Offsets() ([]UprobeMultiOffset, bool) { + if umi.offsets == nil || len(umi.cookies) != len(umi.offsets) || len(umi.refCtrOffsets) != len(umi.offsets) { + return nil, false + } + var adresses = make([]UprobeMultiOffset, len(umi.offsets)) + for i := range umi.offsets { + adresses[i] = UprobeMultiOffset{ + Offset: umi.offsets[i], + Cookie: umi.cookies[i], + ReferenceCount: umi.refCtrOffsets[i], + } + } + return adresses, true +} + +// Pid returns the process ID that this uprobe is attached to. +// +// If it does not exist, the uprobe will trigger for all processes. +func (umi *UprobeMultiInfo) Pid() (uint32, bool) { + return umi.pid, umi.pid > 0 +} + +const ( + PerfEventUnspecified = sys.BPF_PERF_EVENT_UNSPEC + PerfEventUprobe = sys.BPF_PERF_EVENT_UPROBE + PerfEventUretprobe = sys.BPF_PERF_EVENT_URETPROBE + PerfEventKprobe = sys.BPF_PERF_EVENT_KPROBE + PerfEventKretprobe = sys.BPF_PERF_EVENT_KRETPROBE + PerfEventTracepoint = sys.BPF_PERF_EVENT_TRACEPOINT + PerfEventEvent = sys.BPF_PERF_EVENT_EVENT +) + +type PerfEventInfo struct { + Type sys.PerfEventType + extra any +} + +func (r *PerfEventInfo) Kprobe() *KprobeInfo { + e, _ := r.extra.(*KprobeInfo) + return e +} + +func (r *PerfEventInfo) Uprobe() *UprobeInfo { + e, _ := r.extra.(*UprobeInfo) + return e +} + +func (r *PerfEventInfo) Tracepoint() *TracepointInfo { + e, _ := r.extra.(*TracepointInfo) + return e +} + +func (r *PerfEventInfo) Event() *EventInfo { + e, _ := r.extra.(*EventInfo) + return e +} + +type KprobeInfo struct { + Address uint64 + Missed uint64 + Function string + Offset uint32 +} + +type UprobeInfo struct { + // File is the path that the file the uprobe was attached to + // had at creation time. + // + // However, due to various circumstances (differing mount namespaces, + // file replacement, ...), this path may not point to the same binary + // the uprobe was originally attached to. + File string + Offset uint32 + Cookie uint64 + OffsetReferenceCount uint64 +} + +type TracepointInfo struct { + Tracepoint string + Cookie uint64 +} + +type EventInfo struct { + Config uint64 + Type uint32 + Cookie uint64 +} + +// 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 +} + +// XDP 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 +} + +// TCX returns TCX type-specific link info. +// +// Returns nil if the type-specific link info isn't available. +func (r Info) TCX() *TCXInfo { + e, _ := r.extra.(*TCXInfo) + return e +} + +// Netfilter returns netfilter type-specific link info. +// +// Returns nil if the type-specific link info isn't available. +func (r Info) Netfilter() *NetfilterInfo { + e, _ := r.extra.(*NetfilterInfo) + return e +} + +// Netkit returns netkit type-specific link info. +// +// Returns nil if the type-specific link info isn't available. +func (r Info) Netkit() *NetkitInfo { + e, _ := r.extra.(*NetkitInfo) + return e +} + +// KprobeMulti returns kprobe-multi type-specific link info. +// +// Returns nil if the type-specific link info isn't available. +func (r Info) KprobeMulti() *KprobeMultiInfo { + e, _ := r.extra.(*KprobeMultiInfo) + return e +} + +// UprobeMulti returns uprobe-multi type-specific link info. +// +// Returns nil if the type-specific link info isn't available. +func (r Info) UprobeMulti() *UprobeMultiInfo { + e, _ := r.extra.(*UprobeMultiInfo) + return e +} + +// PerfEvent returns perf-event type-specific link info. +// +// Returns nil if the type-specific link info isn't available. +func (r Info) PerfEvent() *PerfEventInfo { + e, _ := r.extra.(*PerfEventInfo) + return e +} + +// RawTracepoint returns raw-tracepoint type-specific link info. +// +// Returns nil if the type-specific link info isn't available. +func (r Info) RawTracepoint() *RawTracepointInfo { + e, _ := r.extra.(*RawTracepointInfo) + return e +} diff --git a/vendor/github.com/cilium/ebpf/link/link_windows.go b/vendor/github.com/cilium/ebpf/link/link_windows.go new file mode 100644 index 0000000000..d9c6f88905 --- /dev/null +++ b/vendor/github.com/cilium/ebpf/link/link_windows.go @@ -0,0 +1,48 @@ +package link + +import ( + "fmt" + + "github.com/cilium/ebpf/internal" + "github.com/cilium/ebpf/internal/efw" + "github.com/cilium/ebpf/internal/platform" + "github.com/cilium/ebpf/internal/sys" +) + +// AttachRawLink creates a raw link. +func AttachRawLink(opts RawLinkOptions) (*RawLink, error) { + if opts.Target != 0 || opts.BTF != 0 || opts.Flags != 0 { + return nil, fmt.Errorf("specified option(s) %w", internal.ErrNotSupportedOnOS) + } + + plat, attachType := platform.DecodeConstant(opts.Attach) + if plat != platform.Windows { + return nil, fmt.Errorf("attach type %s: %w", opts.Attach, internal.ErrNotSupportedOnOS) + } + + attachTypeGUID, err := efw.EbpfGetEbpfAttachType(attachType) + if err != nil { + return nil, fmt.Errorf("get attach type: %w", err) + } + + progFd := opts.Program.FD() + if progFd < 0 { + return nil, fmt.Errorf("invalid program: %s", sys.ErrClosedFd) + } + + raw, err := efw.EbpfProgramAttachFds(progFd, attachTypeGUID, nil, 0) + if err != nil { + return nil, fmt.Errorf("attach link: %w", err) + } + + fd, err := sys.NewFD(int(raw)) + if err != nil { + return nil, err + } + + return &RawLink{fd: fd}, nil +} + +func wrapRawLink(raw *RawLink) (Link, error) { + return raw, nil +} diff --git a/vendor/github.com/cilium/ebpf/link/netfilter.go b/vendor/github.com/cilium/ebpf/link/netfilter.go index 9436d11df9..6b5177794e 100644 --- a/vendor/github.com/cilium/ebpf/link/netfilter.go +++ b/vendor/github.com/cilium/ebpf/link/netfilter.go @@ -1,3 +1,5 @@ +//go:build !windows + package link import ( @@ -11,13 +13,35 @@ const NetfilterIPDefrag NetfilterAttachFlags = 0 // Enable IP packet defragmenta type NetfilterAttachFlags uint32 +type NetfilterInetHook = sys.NetfilterInetHook + +const ( + NetfilterInetPreRouting = sys.NF_INET_PRE_ROUTING + NetfilterInetLocalIn = sys.NF_INET_LOCAL_IN + NetfilterInetForward = sys.NF_INET_FORWARD + NetfilterInetLocalOut = sys.NF_INET_LOCAL_OUT + NetfilterInetPostRouting = sys.NF_INET_POST_ROUTING +) + +type NetfilterProtocolFamily = sys.NetfilterProtocolFamily + +const ( + NetfilterProtoUnspec = sys.NFPROTO_UNSPEC + NetfilterProtoInet = sys.NFPROTO_INET // Inet applies to both IPv4 and IPv6 + NetfilterProtoIPv4 = sys.NFPROTO_IPV4 + NetfilterProtoARP = sys.NFPROTO_ARP + NetfilterProtoNetdev = sys.NFPROTO_NETDEV + NetfilterProtoBridge = sys.NFPROTO_BRIDGE + NetfilterProtoIPv6 = sys.NFPROTO_IPV6 +) + type NetfilterOptions struct { // Program must be a netfilter BPF program. Program *ebpf.Program // The protocol family. - ProtocolFamily uint32 - // The number of the hook you are interested in. - HookNumber uint32 + ProtocolFamily NetfilterProtocolFamily + // The netfilter hook to attach to. + Hook NetfilterInetHook // Priority within hook Priority int32 // Extra link flags @@ -49,8 +73,8 @@ func AttachNetfilter(opts NetfilterOptions) (Link, error) { ProgFd: uint32(opts.Program.FD()), AttachType: sys.BPF_NETFILTER, Flags: opts.Flags, - Pf: uint32(opts.ProtocolFamily), - Hooknum: uint32(opts.HookNumber), + Pf: opts.ProtocolFamily, + Hooknum: opts.Hook, Priority: opts.Priority, NetfilterFlags: uint32(opts.NetfilterFlags), } @@ -73,10 +97,10 @@ func (nf *netfilterLink) Info() (*Info, error) { return nil, fmt.Errorf("netfilter link info: %s", err) } extra := &NetfilterInfo{ - Pf: info.Pf, - Hooknum: info.Hooknum, - Priority: info.Priority, - Flags: info.Flags, + ProtocolFamily: info.Pf, + Hook: info.Hooknum, + Priority: info.Priority, + Flags: info.Flags, } return &Info{ diff --git a/vendor/github.com/cilium/ebpf/link/netkit.go b/vendor/github.com/cilium/ebpf/link/netkit.go index 5eee3b023a..5e6a321af1 100644 --- a/vendor/github.com/cilium/ebpf/link/netkit.go +++ b/vendor/github.com/cilium/ebpf/link/netkit.go @@ -1,3 +1,5 @@ +//go:build !windows + package link import ( diff --git a/vendor/github.com/cilium/ebpf/link/netns.go b/vendor/github.com/cilium/ebpf/link/netns.go index b1edd340a3..fd7d6c6c23 100644 --- a/vendor/github.com/cilium/ebpf/link/netns.go +++ b/vendor/github.com/cilium/ebpf/link/netns.go @@ -1,3 +1,5 @@ +//go:build !windows + package link import ( @@ -42,7 +44,7 @@ func (ns *NetNsLink) Info() (*Info, error) { return nil, fmt.Errorf("netns link info: %s", err) } extra := &NetNsInfo{ - NetnsIno: info.NetnsIno, + NetnsInode: info.NetnsIno, AttachType: info.AttachType, } diff --git a/vendor/github.com/cilium/ebpf/link/perf_event.go b/vendor/github.com/cilium/ebpf/link/perf_event.go index 7440e8b292..7938b9d9d8 100644 --- a/vendor/github.com/cilium/ebpf/link/perf_event.go +++ b/vendor/github.com/cilium/ebpf/link/perf_event.go @@ -1,10 +1,11 @@ +//go:build !windows + package link import ( "errors" "fmt" "os" - "runtime" "unsafe" "github.com/cilium/ebpf" @@ -59,15 +60,11 @@ type perfEvent struct { func newPerfEvent(fd *sys.FD, event *tracefs.Event) *perfEvent { pe := &perfEvent{event, fd} - // Both event and fd have their own finalizer, but we want to - // guarantee that they are closed in a certain order. - runtime.SetFinalizer(pe, (*perfEvent).Close) return pe } func (pe *perfEvent) Close() error { - runtime.SetFinalizer(pe, nil) - + // We close the perf event before attempting to remove the tracefs event. if err := pe.fd.Close(); err != nil { return fmt.Errorf("closing perf event fd: %w", err) } @@ -132,7 +129,33 @@ func (pl *perfEventLink) PerfEvent() (*os.File, error) { return nil, err } - return fd.File("perf-event"), nil + return fd.File("perf-event") +} + +// queryInfoWithString queries object info that contains a string field. +// +// The passed stringField and stringLengthField must point to the string field +// and its length field inside the info struct respectively. +// +// It returns the queried string and fills in the passed info struct. +func queryInfoWithString(fd *sys.FD, info sys.Info, stringField *sys.TypedPointer[byte], stringLengthField *uint32) (string, error) { + // Query info to get the length + if err := sys.ObjInfo(fd, info); err != nil { + return "", err + } + + // The stringLengthField pointer points to a field inside info, so it is now populated. + var stringData = make([]byte, *stringLengthField) + *stringField = sys.SlicePointer(stringData) + + // Query info again to fill in the string. + // Since the stringField pointer points to a field inside info, + // the info now contains the pointer to our allocated stringData. + if err := sys.ObjInfo(fd, info); err != nil { + return "", fmt.Errorf("object info with string: %s", err) + } + + return unix.ByteSliceToString(stringData), nil } func (pl *perfEventLink) Info() (*Info, error) { @@ -141,16 +164,52 @@ func (pl *perfEventLink) Info() (*Info, error) { return nil, fmt.Errorf("perf event link info: %s", err) } - var extra2 interface{} + var extra2 any switch info.PerfEventType { - case sys.BPF_PERF_EVENT_KPROBE, sys.BPF_PERF_EVENT_KRETPROBE: + case PerfEventKprobe, PerfEventKretprobe: var kprobeInfo sys.KprobeLinkInfo - if err := sys.ObjInfo(pl.fd, &kprobeInfo); err != nil { + funcName, err := queryInfoWithString(pl.fd, &kprobeInfo, &kprobeInfo.FuncName, &kprobeInfo.NameLen) + if err != nil { return nil, fmt.Errorf("kprobe link info: %s", err) } extra2 = &KprobeInfo{ - address: kprobeInfo.Addr, - missed: kprobeInfo.Missed, + Address: kprobeInfo.Addr, + Missed: kprobeInfo.Missed, + Function: funcName, + Offset: kprobeInfo.Offset, + } + case PerfEventUprobe, PerfEventUretprobe: + var uprobeInfo sys.UprobeLinkInfo + fileName, err := queryInfoWithString(pl.fd, &uprobeInfo, &uprobeInfo.FileName, &uprobeInfo.NameLen) + if err != nil { + return nil, fmt.Errorf("uprobe link info: %s", err) + } + extra2 = &UprobeInfo{ + Offset: uprobeInfo.Offset, + Cookie: uprobeInfo.Cookie, + OffsetReferenceCount: uprobeInfo.RefCtrOffset, + File: fileName, + } + case PerfEventTracepoint: + var tracepointInfo sys.TracepointLinkInfo + tpName, err := queryInfoWithString(pl.fd, &tracepointInfo, &tracepointInfo.TpName, &tracepointInfo.NameLen) + if err != nil { + return nil, fmt.Errorf("perf event link info: %w", err) + } + extra2 = &TracepointInfo{ + Tracepoint: tpName, + Cookie: tracepointInfo.Cookie, + } + case PerfEventEvent: + var eventInfo sys.EventLinkInfo + err := sys.ObjInfo(pl.fd, &eventInfo) + if err != nil { + return nil, fmt.Errorf("trace point link info: %s", err) + } + extra2 = &EventInfo{ + Config: eventInfo.Config, + Type: eventInfo.EventType, + Cookie: eventInfo.Cookie, } } @@ -197,6 +256,10 @@ func (pi *perfEventIoctl) Unpin() error { return fmt.Errorf("perf event ioctl unpin: %w", ErrNotSupported) } +func (pi *perfEventIoctl) Detach() error { + return fmt.Errorf("perf event ioctl detach: %w", ErrNotSupported) +} + func (pi *perfEventIoctl) Info() (*Info, error) { return nil, fmt.Errorf("perf event ioctl info: %w", ErrNotSupported) } @@ -209,7 +272,7 @@ func (pi *perfEventIoctl) PerfEvent() (*os.File, error) { return nil, err } - return fd.File("perf-event"), nil + return fd.File("perf-event") } // attach the given eBPF prog to the perf event stored in pe. diff --git a/vendor/github.com/cilium/ebpf/link/program.go b/vendor/github.com/cilium/ebpf/link/program.go index d8a2a15f93..4928800bf9 100644 --- a/vendor/github.com/cilium/ebpf/link/program.go +++ b/vendor/github.com/cilium/ebpf/link/program.go @@ -1,3 +1,5 @@ +//go:build !windows + package link import ( @@ -48,12 +50,11 @@ func RawAttachProgram(opts RawAttachProgramOptions) error { return fmt.Errorf("attach program: %w", err) } - if flags == sys.BPF_F_REPLACE { - // Ensure that replacing a program works on old kernels. + attr.AttachFlags |= flags + if flags&sys.BPF_F_REPLACE != 0 { attr.ReplaceBpfFd = fdOrID } else { attr.RelativeFdOrId = fdOrID - attr.AttachFlags |= flags } } diff --git a/vendor/github.com/cilium/ebpf/link/query.go b/vendor/github.com/cilium/ebpf/link/query.go index fe534f8efa..c722ab27b3 100644 --- a/vendor/github.com/cilium/ebpf/link/query.go +++ b/vendor/github.com/cilium/ebpf/link/query.go @@ -1,8 +1,10 @@ +//go:build !windows + package link import ( "fmt" - "unsafe" + "slices" "github.com/cilium/ebpf" "github.com/cilium/ebpf/internal/sys" @@ -32,7 +34,12 @@ type QueryResult struct { // HaveLinkInfo returns true if the kernel supports querying link information // for a particular [ebpf.AttachType]. func (qr *QueryResult) HaveLinkInfo() bool { - return qr.Revision > 0 + return slices.ContainsFunc(qr.Programs, + func(ap AttachedProgram) bool { + _, ok := ap.LinkID() + return ok + }, + ) } type AttachedProgram struct { @@ -43,8 +50,7 @@ type AttachedProgram struct { // LinkID returns the ID associated with the program. // // Returns 0, false if the kernel doesn't support retrieving the ID or if the -// program wasn't attached via a link. See [QueryResult.HaveLinkInfo] if you -// need to tell the two apart. +// program wasn't attached via a link. func (ap *AttachedProgram) LinkID() (ID, bool) { return ap.linkID, ap.linkID != 0 } @@ -84,13 +90,13 @@ func QueryPrograms(opts QueryOptions) (*QueryResult, error) { AttachType: sys.AttachType(opts.Attach), QueryFlags: opts.QueryFlags, Count: count, - ProgIds: sys.NewPointer(unsafe.Pointer(&progIds[0])), + ProgIds: sys.SlicePointer(progIds), } var linkIds []ID if haveLinkIDs { linkIds = make([]ID, count) - attr.LinkIds = sys.NewPointer(unsafe.Pointer(&linkIds[0])) + attr.LinkIds = sys.SlicePointer(linkIds) } if err := sys.ProgQuery(&attr); err != nil { diff --git a/vendor/github.com/cilium/ebpf/link/raw_tracepoint.go b/vendor/github.com/cilium/ebpf/link/raw_tracepoint.go index 925e621cbb..5e974e3de6 100644 --- a/vendor/github.com/cilium/ebpf/link/raw_tracepoint.go +++ b/vendor/github.com/cilium/ebpf/link/raw_tracepoint.go @@ -1,3 +1,5 @@ +//go:build !windows + package link import ( @@ -72,6 +74,10 @@ func (frt *simpleRawTracepoint) Unpin() error { return fmt.Errorf("unpin raw_tracepoint: %w", ErrNotSupported) } +func (frt *simpleRawTracepoint) Detach() error { + return fmt.Errorf("detach raw_tracepoint: %w", ErrNotSupported) +} + func (frt *simpleRawTracepoint) Info() (*Info, error) { return nil, fmt.Errorf("can't get raw_tracepoint info: %w", ErrNotSupported) } @@ -85,3 +91,19 @@ var _ Link = (*rawTracepoint)(nil) func (rt *rawTracepoint) Update(_ *ebpf.Program) error { return fmt.Errorf("update raw_tracepoint: %w", ErrNotSupported) } + +func (rt *rawTracepoint) Info() (*Info, error) { + var info sys.RawTracepointLinkInfo + name, err := queryInfoWithString(rt.fd, &info, &info.TpName, &info.TpNameLen) + if err != nil { + return nil, err + } + return &Info{ + info.Type, + info.Id, + ebpf.ProgramID(info.ProgId), + &RawTracepointInfo{ + Name: name, + }, + }, nil +} diff --git a/vendor/github.com/cilium/ebpf/link/socket_filter.go b/vendor/github.com/cilium/ebpf/link/socket_filter.go index 84f0b656f8..8399f02315 100644 --- a/vendor/github.com/cilium/ebpf/link/socket_filter.go +++ b/vendor/github.com/cilium/ebpf/link/socket_filter.go @@ -1,3 +1,5 @@ +//go:build !windows + package link import ( diff --git a/vendor/github.com/cilium/ebpf/link/struct_ops.go b/vendor/github.com/cilium/ebpf/link/struct_ops.go new file mode 100644 index 0000000000..d1b7f25174 --- /dev/null +++ b/vendor/github.com/cilium/ebpf/link/struct_ops.go @@ -0,0 +1,51 @@ +package link + +import ( + "fmt" + + "github.com/cilium/ebpf" + "github.com/cilium/ebpf/internal/sys" +) + +type structOpsLink struct { + RawLink +} + +func (*structOpsLink) Update(*ebpf.Program) error { + return fmt.Errorf("update struct_ops link: %w", ErrNotSupported) +} + +type StructOpsOptions struct { + Map *ebpf.Map +} + +// AttachStructOps attaches a struct_ops map (created from a ".struct_ops.link" +// section) to its kernel subsystem via a BPF link. +func AttachStructOps(opts StructOpsOptions) (Link, error) { + m := opts.Map + + if m == nil { + return nil, fmt.Errorf("map cannot be nil") + } + + if t := m.Type(); t != ebpf.StructOpsMap { + return nil, fmt.Errorf("can't attach non-struct_ops map") + } + + mapFD := m.FD() + if mapFD <= 0 { + return nil, fmt.Errorf("invalid map: %s", sys.ErrClosedFd) + } + + fd, err := sys.LinkCreate(&sys.LinkCreateAttr{ + // For struct_ops links, the mapFD must be passed as ProgFd. + ProgFd: uint32(mapFD), + AttachType: sys.AttachType(ebpf.AttachStructOps), + TargetFd: 0, + }) + if err != nil { + return nil, fmt.Errorf("attach StructOps: create link: %w", err) + } + + return &structOpsLink{RawLink{fd: fd}}, nil +} diff --git a/vendor/github.com/cilium/ebpf/link/syscalls.go b/vendor/github.com/cilium/ebpf/link/syscalls.go index 25951b017d..9948dead41 100644 --- a/vendor/github.com/cilium/ebpf/link/syscalls.go +++ b/vendor/github.com/cilium/ebpf/link/syscalls.go @@ -1,3 +1,5 @@ +//go:build !windows + package link import ( @@ -10,26 +12,6 @@ import ( "github.com/cilium/ebpf/internal/unix" ) -// Type is the kind of link. -type Type = sys.LinkType - -// Valid link types. -const ( - 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 - PerfEventType = sys.BPF_LINK_TYPE_PERF_EVENT - KprobeMultiType = sys.BPF_LINK_TYPE_KPROBE_MULTI - TCXType = sys.BPF_LINK_TYPE_TCX - UprobeMultiType = sys.BPF_LINK_TYPE_UPROBE_MULTI - NetfilterType = sys.BPF_LINK_TYPE_NETFILTER - NetkitType = sys.BPF_LINK_TYPE_NETKIT -) - var haveProgAttach = internal.NewFeatureTest("BPF_PROG_ATTACH", func() error { prog, err := ebpf.NewProgram(&ebpf.ProgramSpec{ Type: ebpf.CGroupSKB, diff --git a/vendor/github.com/cilium/ebpf/link/tcx.go b/vendor/github.com/cilium/ebpf/link/tcx.go index ac045b71da..8661018ec1 100644 --- a/vendor/github.com/cilium/ebpf/link/tcx.go +++ b/vendor/github.com/cilium/ebpf/link/tcx.go @@ -1,3 +1,5 @@ +//go:build !windows + package link import ( diff --git a/vendor/github.com/cilium/ebpf/link/tracepoint.go b/vendor/github.com/cilium/ebpf/link/tracepoint.go index 6fc78b9828..514961ebec 100644 --- a/vendor/github.com/cilium/ebpf/link/tracepoint.go +++ b/vendor/github.com/cilium/ebpf/link/tracepoint.go @@ -1,3 +1,5 @@ +//go:build !windows + package link import ( diff --git a/vendor/github.com/cilium/ebpf/link/tracing.go b/vendor/github.com/cilium/ebpf/link/tracing.go index a461007f9f..d0e595e6f9 100644 --- a/vendor/github.com/cilium/ebpf/link/tracing.go +++ b/vendor/github.com/cilium/ebpf/link/tracing.go @@ -1,3 +1,5 @@ +//go:build !windows + package link import ( @@ -24,9 +26,9 @@ func (f *tracing) Info() (*Info, error) { return nil, fmt.Errorf("tracing link info: %s", err) } extra := &TracingInfo{ - TargetObjId: info.TargetObjId, - TargetBtfId: info.TargetBtfId, - AttachType: info.AttachType, + TargetObjectId: info.TargetObjId, + TargetBtfId: info.TargetBtfId, + AttachType: info.AttachType, } return &Info{ diff --git a/vendor/github.com/cilium/ebpf/link/uprobe.go b/vendor/github.com/cilium/ebpf/link/uprobe.go index 1852a3fadd..83333c4ec7 100644 --- a/vendor/github.com/cilium/ebpf/link/uprobe.go +++ b/vendor/github.com/cilium/ebpf/link/uprobe.go @@ -1,3 +1,5 @@ +//go:build !windows + package link import ( @@ -36,10 +38,22 @@ var ( type Executable struct { // Path of the executable on the filesystem. path string - // Parsed ELF and dynamic symbols' cachedAddresses. - cachedAddresses map[string]uint64 - // Keep track of symbol table lazy load. - cacheAddressesOnce sync.Once + // Cached symbol values for all ELF and dynamic symbols. + // Before using this, lazyLoadSymbols() must be called. + cachedSymbols map[string]symbol + // cachedSymbolsOnce tracks the lazy load of cachedSymbols. + cachedSymbolsOnce sync.Once +} + +type symbol struct { + addr uint64 + size uint64 +} + +// contains returns true if the given address falls within the range +// covered by the symbol. +func (s symbol) contains(address uint64) bool { + return s.addr <= address && address < s.addr+s.size } // UprobeOptions defines additional parameters that will be used @@ -96,20 +110,13 @@ func OpenExecutable(path string) (*Executable, error) { return nil, fmt.Errorf("path cannot be empty") } - f, err := internal.OpenSafeELFFile(path) - if err != nil { - return nil, fmt.Errorf("parse ELF file: %w", err) - } - defer f.Close() - - if f.Type != elf.ET_EXEC && f.Type != elf.ET_DYN { - // ELF is not an executable or a shared object. - return nil, errors.New("the given file is not an executable or a shared object") + if _, err := os.Stat(path); err != nil { + return nil, fmt.Errorf("stat executable: %w", err) } return &Executable{ - path: path, - cachedAddresses: make(map[string]uint64), + path: path, + cachedSymbols: make(map[string]symbol), }, nil } @@ -153,22 +160,18 @@ func (ex *Executable) load(f *internal.SafeELFFile) error { } } - ex.cachedAddresses[s.Name] = address + ex.cachedSymbols[s.Name] = symbol{ + addr: address, + size: s.Size, + } } return nil } -// address calculates the address of a symbol in the executable. -// -// opts must not be nil. -func (ex *Executable) address(symbol string, address, offset uint64) (uint64, error) { - if address > 0 { - return address + offset, nil - } - +func (ex *Executable) lazyLoadSymbols() error { var err error - ex.cacheAddressesOnce.Do(func() { + ex.cachedSymbolsOnce.Do(func() { var f *internal.SafeELFFile f, err = internal.OpenSafeELFFile(ex.path) if err != nil { @@ -177,13 +180,30 @@ func (ex *Executable) address(symbol string, address, offset uint64) (uint64, er } defer f.Close() + if f.Type != elf.ET_EXEC && f.Type != elf.ET_DYN { + // ELF is not an executable or a shared object. + err = errors.New("the given file is not an executable or a shared object") + return + } err = ex.load(f) }) + return err +} + +// address calculates the address of a symbol in the executable. +// +// opts must not be nil. +func (ex *Executable) address(symbol string, address, offset uint64) (uint64, error) { + if address > 0 { + return address + offset, nil + } + + err := ex.lazyLoadSymbols() if err != nil { return 0, fmt.Errorf("lazy load symbols: %w", err) } - address, ok := ex.cachedAddresses[symbol] + sym, ok := ex.cachedSymbols[symbol] if !ok { return 0, fmt.Errorf("symbol %s: %w", symbol, ErrNoSymbol) } @@ -194,12 +214,39 @@ func (ex *Executable) address(symbol string, address, offset uint64) (uint64, er // // Since only offset values are stored and not elf.Symbol, if the value is 0, // assume it's an external symbol. - if address == 0 { + if sym.addr == 0 { return 0, fmt.Errorf("cannot resolve %s library call '%s': %w "+ "(consider providing UprobeOptions.Address)", ex.path, symbol, ErrNotSupported) } - return address + offset, nil + if offset >= sym.size { + return 0, fmt.Errorf("offset %d is out of range of symbol %s", offset, symbol) + } + + return sym.addr + offset, nil +} + +// SymbolOffset represents an offset within a symbol within a binary. +type SymbolOffset struct { + Symbol string + Offset uint64 +} + +// Symbol returns the SymbolOffset that the given address points to. +// This includes the symbol name and the offset within that symbol. +// +// If no symbol is found for the given address, ErrNoSymbol is returned. +func (ex *Executable) Symbol(address uint64) (SymbolOffset, error) { + if err := ex.lazyLoadSymbols(); err != nil { + return SymbolOffset{}, fmt.Errorf("lazy load symbols: %w", err) + } + + for name, symbol := range ex.cachedSymbols { + if symbol.contains(address) { + return SymbolOffset{name, address - symbol.addr}, nil + } + } + return SymbolOffset{}, ErrNoSymbol } // Uprobe attaches the given eBPF program to a perf event that fires when the @@ -209,12 +256,15 @@ func (ex *Executable) address(symbol string, address, offset uint64) (uint64, er // ex, _ = OpenExecutable("/bin/bash") // ex.Uprobe("main", prog, nil) // -// When using symbols which belongs to shared libraries, -// an offset must be provided via options: +// When tracing a location for which no ELF symbol is available, or a shared +// library symbol whose static address is zero, set UprobeOptions.Address to +// the absolute file offset and pass an empty symbol: // -// up, err := ex.Uprobe("main", prog, &UprobeOptions{Offset: 0x123}) +// up, err := ex.Uprobe("", prog, &UprobeOptions{Address: 0x123}) // -// Note: Setting the Offset field in the options supersedes the symbol's offset. +// Address bypasses symbol lookup. Offset, if set, is added to the resolved +// address (whether from symbol resolution or from Address); on its own it +// does not replace symbol resolution. // // Losing the reference to the resulting Link (up) will close the Uprobe // and prevent further execution of prog. The Link must be Closed during @@ -245,12 +295,15 @@ func (ex *Executable) Uprobe(symbol string, prog *ebpf.Program, opts *UprobeOpti // ex, _ = OpenExecutable("/bin/bash") // ex.Uretprobe("main", prog, nil) // -// When using symbols which belongs to shared libraries, -// an offset must be provided via options: +// When tracing a location for which no ELF symbol is available, or a shared +// library symbol whose static address is zero, set UprobeOptions.Address to +// the absolute file offset and pass an empty symbol: // -// up, err := ex.Uretprobe("main", prog, &UprobeOptions{Offset: 0x123}) +// up, err := ex.Uretprobe("", prog, &UprobeOptions{Address: 0x123}) // -// Note: Setting the Offset field in the options supersedes the symbol's offset. +// Address bypasses symbol lookup. Offset, if set, is added to the resolved +// address (whether from symbol resolution or from Address); on its own it +// does not replace symbol resolution. // // Losing the reference to the resulting Link (up) will close the Uprobe // and prevent further execution of prog. The Link must be Closed during diff --git a/vendor/github.com/cilium/ebpf/link/uprobe_multi.go b/vendor/github.com/cilium/ebpf/link/uprobe_multi.go index 49dc18b449..f017405aab 100644 --- a/vendor/github.com/cilium/ebpf/link/uprobe_multi.go +++ b/vendor/github.com/cilium/ebpf/link/uprobe_multi.go @@ -1,14 +1,14 @@ +//go:build !windows + package link import ( "errors" "fmt" "os" - "unsafe" "github.com/cilium/ebpf" - "github.com/cilium/ebpf/asm" - "github.com/cilium/ebpf/internal" + "github.com/cilium/ebpf/features" "github.com/cilium/ebpf/internal/sys" "github.com/cilium/ebpf/internal/unix" ) @@ -69,13 +69,13 @@ func (ex *Executable) uprobeMulti(symbols []string, prog *ebpf.Program, opts *Up refCtrOffsets := len(opts.RefCtrOffsets) if addrs == 0 { - return nil, fmt.Errorf("Addresses are required: %w", errInvalidInput) + return nil, fmt.Errorf("field Addresses is required: %w", errInvalidInput) } if refCtrOffsets > 0 && refCtrOffsets != addrs { - return nil, fmt.Errorf("RefCtrOffsets must be exactly Addresses in length: %w", errInvalidInput) + return nil, fmt.Errorf("field RefCtrOffsets must be exactly Addresses in length: %w", errInvalidInput) } if cookies > 0 && cookies != addrs { - return nil, fmt.Errorf("Cookies must be exactly Addresses in length: %w", errInvalidInput) + return nil, fmt.Errorf("field Cookies must be exactly Addresses in length: %w", errInvalidInput) } attr := &sys.LinkCreateUprobeMultiAttr{ @@ -84,15 +84,15 @@ func (ex *Executable) uprobeMulti(symbols []string, prog *ebpf.Program, opts *Up AttachType: sys.BPF_TRACE_UPROBE_MULTI, UprobeMultiFlags: flags, Count: uint32(addrs), - Offsets: sys.NewPointer(unsafe.Pointer(&addresses[0])), + Offsets: sys.SlicePointer(addresses), Pid: opts.PID, } if refCtrOffsets != 0 { - attr.RefCtrOffsets = sys.NewPointer(unsafe.Pointer(&opts.RefCtrOffsets[0])) + attr.RefCtrOffsets = sys.SlicePointer(opts.RefCtrOffsets) } if cookies != 0 { - attr.Cookies = sys.NewPointer(unsafe.Pointer(&opts.Cookies[0])) + attr.Cookies = sys.SlicePointer(opts.Cookies) } fd, err := sys.LinkCreateUprobeMulti(attr) @@ -107,7 +107,7 @@ func (ex *Executable) uprobeMulti(symbols []string, prog *ebpf.Program, opts *Up } if err != nil { - if haveFeatErr := haveBPFLinkUprobeMulti(); haveFeatErr != nil { + if haveFeatErr := features.HaveBPFLinkUprobeMulti(); haveFeatErr != nil { return nil, haveFeatErr } return nil, err @@ -175,45 +175,54 @@ func (kml *uprobeMultiLink) Update(_ *ebpf.Program) error { return fmt.Errorf("update uprobe_multi: %w", ErrNotSupported) } -var haveBPFLinkUprobeMulti = internal.NewFeatureTest("bpf_link_uprobe_multi", func() error { - prog, err := ebpf.NewProgram(&ebpf.ProgramSpec{ - Name: "probe_upm_link", - Type: ebpf.Kprobe, - Instructions: asm.Instructions{ - asm.Mov.Imm(asm.R0, 0), - asm.Return(), - }, - AttachType: ebpf.AttachTraceUprobeMulti, - License: "MIT", - }) - if errors.Is(err, unix.E2BIG) { - // Kernel doesn't support AttachType field. - return internal.ErrNotSupported - } - if err != nil { - return err - } - defer prog.Close() - - // We try to create uprobe multi link on '/' path which results in - // error with -EBADF in case uprobe multi link is supported. - fd, err := sys.LinkCreateUprobeMulti(&sys.LinkCreateUprobeMultiAttr{ - ProgFd: uint32(prog.FD()), - AttachType: sys.BPF_TRACE_UPROBE_MULTI, - Path: sys.NewStringPointer("/"), - Offsets: sys.NewPointer(unsafe.Pointer(&[]uint64{0})), - Count: 1, - }) - switch { - case errors.Is(err, unix.EBADF): - return nil - case errors.Is(err, unix.EINVAL): - return internal.ErrNotSupported - case err != nil: - return err - } - - // should not happen - fd.Close() - return errors.New("successfully attached uprobe_multi to /, kernel bug?") -}, "6.6") +func (kml *uprobeMultiLink) Info() (*Info, error) { + var info sys.UprobeMultiLinkInfo + if err := sys.ObjInfo(kml.fd, &info); err != nil { + return nil, fmt.Errorf("uprobe multi link info: %s", err) + } + var ( + path = make([]byte, info.PathSize) + refCtrOffsets = make([]uint64, info.Count) + addrs = make([]uint64, info.Count) + cookies = make([]uint64, info.Count) + ) + info = sys.UprobeMultiLinkInfo{ + Path: sys.SlicePointer(path), + PathSize: uint32(len(path)), + Offsets: sys.SlicePointer(addrs), + RefCtrOffsets: sys.SlicePointer(refCtrOffsets), + Cookies: sys.SlicePointer(cookies), + Count: uint32(len(addrs)), + } + if err := sys.ObjInfo(kml.fd, &info); err != nil { + return nil, fmt.Errorf("uprobe multi link info: %s", err) + } + if info.Path.IsNil() { + path = nil + } + if info.Cookies.IsNil() { + cookies = nil + } + if info.Offsets.IsNil() { + addrs = nil + } + if info.RefCtrOffsets.IsNil() { + refCtrOffsets = nil + } + extra := &UprobeMultiInfo{ + Count: info.Count, + Flags: info.Flags, + pid: info.Pid, + offsets: addrs, + cookies: cookies, + refCtrOffsets: refCtrOffsets, + File: unix.ByteSliceToString(path), + } + + return &Info{ + info.Type, + info.Id, + ebpf.ProgramID(info.ProgId), + extra, + }, nil +} diff --git a/vendor/github.com/cilium/ebpf/link/xdp.go b/vendor/github.com/cilium/ebpf/link/xdp.go index 2ec441229a..2daf0c4a22 100644 --- a/vendor/github.com/cilium/ebpf/link/xdp.go +++ b/vendor/github.com/cilium/ebpf/link/xdp.go @@ -1,3 +1,5 @@ +//go:build !windows + package link import ( diff --git a/vendor/github.com/cilium/ebpf/linker.go b/vendor/github.com/cilium/ebpf/linker.go index 024b72bbc8..519c0aed0b 100644 --- a/vendor/github.com/cilium/ebpf/linker.go +++ b/vendor/github.com/cilium/ebpf/linker.go @@ -15,6 +15,7 @@ import ( "github.com/cilium/ebpf/btf" "github.com/cilium/ebpf/internal" "github.com/cilium/ebpf/internal/kallsyms" + "github.com/cilium/ebpf/internal/platform" ) // handles stores handle objects to avoid gc cleanup @@ -53,47 +54,6 @@ func (hs *handles) Close() error { return errors.Join(errs...) } -// splitSymbols splits insns into subsections delimited by Symbol Instructions. -// insns cannot be empty and must start with a Symbol Instruction. -// -// The resulting map is indexed by Symbol name. -func splitSymbols(insns asm.Instructions) (map[string]asm.Instructions, error) { - if len(insns) == 0 { - return nil, errors.New("insns is empty") - } - - currentSym := insns[0].Symbol() - if currentSym == "" { - return nil, errors.New("insns must start with a Symbol") - } - - start := 0 - progs := make(map[string]asm.Instructions) - for i, ins := range insns[1:] { - i := i + 1 - - sym := ins.Symbol() - if sym == "" { - continue - } - - // New symbol, flush the old one out. - progs[currentSym] = slices.Clone(insns[start:i]) - - if progs[sym] != nil { - return nil, fmt.Errorf("insns contains duplicate Symbol %s", sym) - } - currentSym = sym - start = i - } - - if tail := insns[start:]; len(tail) > 0 { - progs[currentSym] = slices.Clone(tail) - } - - return progs, nil -} - // 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 @@ -121,9 +81,8 @@ func hasFunctionReferences(insns asm.Instructions) bool { // applyRelocations collects and applies any CO-RE relocations in insns. // -// Passing a nil target will relocate against the running kernel. insns are -// modified in place. -func applyRelocations(insns asm.Instructions, targets []*btf.Spec, kmodName string, bo binary.ByteOrder, b *btf.Builder) error { +// insns are modified in place. +func applyRelocations(insns asm.Instructions, bo binary.ByteOrder, b *btf.Builder, c *btf.Cache, kernelOverride *btf.Spec, extraTargets []*btf.Spec) error { var relos []*btf.CORERelocation var reloInsns []*asm.Instruction iter := insns.Iterate() @@ -142,25 +101,39 @@ func applyRelocations(insns asm.Instructions, targets []*btf.Spec, kmodName stri bo = internal.NativeEndian } - if len(targets) == 0 { - kernelTarget, err := btf.LoadKernelSpec() + var targets []*btf.Spec + if kernelOverride == nil { + kernel, err := c.Kernel() if err != nil { return fmt.Errorf("load kernel spec: %w", err) } - targets = append(targets, kernelTarget) - if kmodName != "" { - kmodTarget, err := btf.LoadKernelModuleSpec(kmodName) - // Ignore ErrNotExists to cater to kernels which have CONFIG_DEBUG_INFO_BTF_MODULES disabled. - if err != nil && !errors.Is(err, fs.ErrNotExist) { - return fmt.Errorf("load kernel module spec: %w", err) - } - if err == nil { - targets = append(targets, kmodTarget) + modules, err := c.Modules() + // Ignore ErrNotExists to cater to kernels which have CONFIG_DEBUG_INFO_BTF_MODULES + // or CONFIG_DEBUG_INFO_BTF disabled. + if err != nil && !errors.Is(err, fs.ErrNotExist) { + return err + } + + targets = make([]*btf.Spec, 0, 1+len(modules)+len(extraTargets)) + targets = append(targets, kernel) + + for _, kmod := range modules { + spec, err := c.Module(kmod) + if err != nil { + return fmt.Errorf("load BTF for kmod %s: %w", kmod, err) } + + targets = append(targets, spec) } + } else { + // We expect kernelOverride to contain the merged types + // of vmlinux and kernel modules, as distributed by btfhub. + targets = []*btf.Spec{kernelOverride} } + targets = append(targets, extraTargets...) + fixups, err := btf.CORERelocate(relos, targets, bo, b.Add) if err != nil { return err @@ -272,14 +245,16 @@ func fixupAndValidate(insns asm.Instructions) error { return nil } -// POISON_CALL_KFUNC_BASE in libbpf. -// https://github.com/libbpf/libbpf/blob/2778cbce609aa1e2747a69349f7f46a2f94f0522/src/libbpf.c#L5767 -const kfuncCallPoisonBase = 2002000000 +// A constant used to poison calls to non-existent kfuncs. +// +// Similar POISON_CALL_KFUNC_BASE in libbpf, except that we use a value lower +// than 2^28 to fit into a tagged constant. +const kfuncCallPoisonBase = 0xdedc0de // fixupKfuncs loops over all instructions in search for kfunc calls. // If at least one is found, the current kernels BTF and module BTFis are searched to set Instruction.Constant // and Instruction.Offset to the correct values. -func fixupKfuncs(insns asm.Instructions) (_ handles, err error) { +func fixupKfuncs(insns asm.Instructions, cache *btf.Cache) (_ handles, err error) { closeOnError := func(c io.Closer) { if err != nil { c.Close() @@ -297,9 +272,15 @@ func fixupKfuncs(insns asm.Instructions) (_ handles, err error) { return nil, nil fixups: - // only load the kernel spec if we found at least one kfunc call - kernelSpec, err := btf.LoadKernelSpec() - if err != nil { + // Only load kernel BTF if we found at least one kfunc call. kernelSpec can be + // nil if the kernel does not have BTF, in which case we poison all kfunc + // calls. + _, err = cache.Kernel() + // ErrNotSupportedOnOS wraps ErrNotSupported, check for it first. + if errors.Is(err, internal.ErrNotSupportedOnOS) { + return nil, fmt.Errorf("kfuncs are not supported on this platform: %w", err) + } + if err != nil && !errors.Is(err, ErrNotSupported) { return nil, err } @@ -324,32 +305,37 @@ fixups: return nil, fmt.Errorf("kfuncMetaKey doesn't contain kfuncMeta") } + // findTargetInKernel returns [btf.ErrNotFound] if the target can't be found + // or if BTF is not enabled. target := btf.Type((*btf.Func)(nil)) - spec, module, err := findTargetInKernel(kernelSpec, kfm.Func.Name, &target) - if kfm.Binding == elf.STB_WEAK && errors.Is(err, btf.ErrNotFound) { - if ins.IsKfuncCall() { - // If the kfunc call is weak and not found, poison the call. Use a recognizable constant - // to make it easier to debug. And set src to zero so the verifier doesn't complain - // about the invalid imm/offset values before dead-code elimination. - ins.Constant = kfuncCallPoisonBase - ins.Src = 0 - } else if ins.OpCode.IsDWordLoad() { - // If the kfunc DWordLoad is weak and not found, set its address to 0. - ins.Constant = 0 - ins.Src = 0 - } else { - return nil, fmt.Errorf("only kfunc calls and dword loads may have kfunc metadata") + spec, module, err := findTargetInKernel(kfm.Func.Name, &target, cache) + if errors.Is(err, btf.ErrNotFound) { + if kfm.Binding == elf.STB_WEAK { + if ins.IsKfuncCall() { + // If the kfunc call is weak and not found, poison the call. Use a + // recognizable constant to make it easier to debug. + fn, err := asm.BuiltinFuncForPlatform(platform.Native, kfuncCallPoisonBase) + if err != nil { + return nil, err + } + *ins = fn.Call() + } else if ins.OpCode.IsDWordLoad() { + // If the kfunc DWordLoad is weak and not found, set its address to 0. + ins.Constant = 0 + ins.Src = 0 + } else { + return nil, fmt.Errorf("only kfunc calls and dword loads may have kfunc metadata") + } + + iter.Next() + continue } - iter.Next() - continue - } - // Error on non-weak kfunc not found. - if errors.Is(err, btf.ErrNotFound) { + // Error on non-weak kfunc not found. return nil, fmt.Errorf("kfunc %q: %w", kfm.Func.Name, ErrNotSupported) } if err != nil { - return nil, err + return nil, fmt.Errorf("finding kfunc in kernel: %w", err) } idx, err := fdArray.add(module) @@ -467,7 +453,13 @@ func resolveKconfigReferences(insns asm.Instructions) (_ *Map, err error) { } func resolveKsymReferences(insns asm.Instructions) error { - var missing []string + type fixup struct { + *asm.Instruction + *ksymMeta + } + + var symbols map[string]uint64 + var fixups []fixup iter := insns.Iterate() for iter.Next() { @@ -477,28 +469,47 @@ func resolveKsymReferences(insns asm.Instructions) error { continue } - addr, err := kallsyms.Address(meta.Name) - if err != nil { - return fmt.Errorf("resolve ksym %s: %w", meta.Name, err) - } - if addr != 0 { - ins.Constant = int64(addr) - continue + if symbols == nil { + symbols = make(map[string]uint64) } - if meta.Binding == elf.STB_WEAK { - // A weak ksym variable in eBPF C means its resolution is optional. - // Set a zero constant explicitly for clarity. - ins.Constant = 0 + symbols[meta.Name] = 0 + fixups = append(fixups, fixup{ + iter.Ins, meta, + }) + } + + if len(symbols) == 0 { + return nil + } + + err := kallsyms.AssignAddresses(symbols) + // Tolerate ErrRestrictedKernel during initial lookup, user may have all weak + // ksyms and a fallback path. + if err != nil && !errors.Is(err, ErrRestrictedKernel) { + return fmt.Errorf("resolve ksyms: %w", err) + } + + var missing []string + for _, fixup := range fixups { + addr := symbols[fixup.Name] + // A weak ksym variable in eBPF C means its resolution is optional. + if addr == 0 && fixup.Binding != elf.STB_WEAK { + if !slices.Contains(missing, fixup.Name) { + missing = append(missing, fixup.Name) + } continue } - if !slices.Contains(missing, meta.Name) { - missing = append(missing, meta.Name) - } + fixup.Constant = int64(addr) } if len(missing) > 0 { + if err != nil { + // Program contains required ksyms, return the error from above. + return fmt.Errorf("resolve required ksyms: %s: %w", strings.Join(missing, ","), err) + } + return fmt.Errorf("kernel is missing symbol: %s", strings.Join(missing, ",")) } diff --git a/vendor/github.com/cilium/ebpf/map.go b/vendor/github.com/cilium/ebpf/map.go index e5d8bd7809..884fbebf0b 100644 --- a/vendor/github.com/cilium/ebpf/map.go +++ b/vendor/github.com/cilium/ebpf/map.go @@ -17,6 +17,7 @@ import ( "github.com/cilium/ebpf/btf" "github.com/cilium/ebpf/internal" + "github.com/cilium/ebpf/internal/platform" "github.com/cilium/ebpf/internal/sys" "github.com/cilium/ebpf/internal/sysenc" "github.com/cilium/ebpf/internal/unix" @@ -28,7 +29,6 @@ var ( ErrKeyExist = errors.New("key already exists") ErrIterationAborted = errors.New("iteration aborted") ErrMapIncompatible = errors.New("map spec is incompatible with existing map") - errMapNoBTFValue = errors.New("map spec does not contain a BTF Value") // pre-allocating these errors here since they may get called in hot code paths // and cause unnecessary memory allocations @@ -45,12 +45,13 @@ type MapOptions struct { } // MapID represents the unique ID of an eBPF map -type MapID uint32 +type MapID = sys.MapID // MapSpec defines a Map. type MapSpec struct { - // Name is passed to the kernel as a debug aid. Must only contain - // alpha numeric and '_' characters. + // Name is passed to the kernel as a debug aid. + // + // Unsupported characters will be stripped. Name string Type MapType KeySize uint32 @@ -76,6 +77,11 @@ type MapSpec struct { // InnerMap is used as a template for ArrayOfMaps and HashOfMaps InnerMap *MapSpec + // MapExtra is an opaque field whose meaning is map-specific. + // + // Available from 5.16. + MapExtra uint64 + // Extra trailing bytes found in the ELF map definition when using structs // 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. @@ -83,6 +89,11 @@ type MapSpec struct { // The key and value type of this map. May be nil. Key, Value btf.Type + + // Tags is a list of btf_decl_tag attributes set on the map definition. + // + // Decorate a map definition with `__attribute__((btf_decl_tag("foo")))`. + Tags []string } func (ms *MapSpec) String() string { @@ -101,6 +112,7 @@ func (ms *MapSpec) Copy() *MapSpec { cpy.Contents = slices.Clone(cpy.Contents) cpy.Key = btf.Copy(cpy.Key) cpy.Value = btf.Copy(cpy.Value) + cpy.Tags = slices.Clone(cpy.Tags) if cpy.InnerMap == ms { cpy.InnerMap = &cpy @@ -123,8 +135,8 @@ func (ms *MapSpec) Copy() *MapSpec { // The copy is only performed if fixups are necessary, so callers mustn't mutate // the returned spec. func (spec *MapSpec) fixupMagicFields() (*MapSpec, error) { - switch spec.Type { - case ArrayOfMaps, HashOfMaps: + switch { + case spec.Type.canStoreMap(): if spec.ValueSize != 0 && spec.ValueSize != 4 { return nil, errors.New("ValueSize must be zero or four for map of map") } @@ -132,7 +144,7 @@ func (spec *MapSpec) fixupMagicFields() (*MapSpec, error) { spec = spec.Copy() spec.ValueSize = 4 - case PerfEventArray: + case spec.Type == PerfEventArray: if spec.KeySize != 0 && spec.KeySize != 4 { return nil, errors.New("KeySize must be zero or four for perf event array") } @@ -159,7 +171,7 @@ func (spec *MapSpec) fixupMagicFields() (*MapSpec, error) { spec.MaxEntries = n } - case CPUMap: + case spec.Type == CPUMap: n, err := PossibleCPU() if err != nil { return nil, fmt.Errorf("fixup cpu map: %w", err) @@ -174,28 +186,71 @@ func (spec *MapSpec) fixupMagicFields() (*MapSpec, error) { return spec, nil } -// dataSection returns the contents and BTF Datasec descriptor of the spec. -func (ms *MapSpec) dataSection() ([]byte, *btf.Datasec, error) { - if ms.Value == nil { - return nil, nil, errMapNoBTFValue +// dataSection returns the contents of a datasec if the MapSpec represents one. +func (ms *MapSpec) dataSection() ([]byte, error) { + if n := len(ms.Contents); n != 1 { + return nil, fmt.Errorf("expected one key, found %d", n) } - ds, ok := ms.Value.(*btf.Datasec) + kv := ms.Contents[0] + if key, ok := ms.Contents[0].Key.(uint32); !ok || key != 0 { + return nil, fmt.Errorf("expected contents to have key 0") + } + + value, ok := kv.Value.([]byte) if !ok { - return nil, nil, fmt.Errorf("map value BTF is a %T, not a *btf.Datasec", ms.Value) + return nil, fmt.Errorf("value at first map key is %T, not []byte", kv.Value) } - if n := len(ms.Contents); n != 1 { - return nil, nil, fmt.Errorf("expected one key, found %d", n) + return value, nil +} + +// updateDataSection copies the values of variables into MapSpec.Contents[0].Value. +// +// Only variables declared in sectionName will be updated. +func (ms *MapSpec) updateDataSection(vars map[string]*VariableSpec, sectionName string) error { + var specs []*VariableSpec + for _, vs := range vars { + if vs.SectionName != sectionName { + continue + } + + specs = append(specs, vs) } - kv := ms.Contents[0] - value, ok := kv.Value.([]byte) - if !ok { - return nil, nil, fmt.Errorf("value at first map key is %T, not []byte", kv.Value) + if len(specs) == 0 { + return nil + } + + data, err := ms.dataSection() + if err != nil { + return err } - return value, ds, nil + // Do not modify the original data slice, ms.Contents is a shallow copy. + data = slices.Clone(data) + + slices.SortFunc(specs, func(a, b *VariableSpec) int { + return int(int64(a.Offset) - int64(b.Offset)) + }) + + offset := uint32(0) + for _, v := range specs { + if v.Offset < offset { + return fmt.Errorf("variable %s (offset %d) overlaps with previous variable (offset %d)", v.Name, v.Offset, offset) + } + + end := v.Offset + v.Size() + if int(end) > len(data) { + return fmt.Errorf("variable %s exceeds map size", v.Name) + } + + copy(data[v.Offset:end], v.Value) + offset = end + } + + ms.Contents = []MapKV{{Key: uint32(0), Value: data}} + return nil } func (ms *MapSpec) readOnly() bool { @@ -208,8 +263,8 @@ func (ms *MapSpec) writeOnly() bool { // MapKV is used to initialize the contents of a Map. type MapKV struct { - Key interface{} - Value interface{} + Key any + Value any } // Compatible returns nil if an existing map may be used instead of creating @@ -236,11 +291,16 @@ func (ms *MapSpec) Compatible(m *Map) error { diffs = append(diffs, fmt.Sprintf("MaxEntries: %d changed to %d", m.maxEntries, ms.MaxEntries)) } - // BPF_F_RDONLY_PROG is set unconditionally for devmaps. Explicitly allow this - // mismatch. - if !((ms.Type == DevMap || ms.Type == DevMapHash) && m.flags^ms.Flags == sys.BPF_F_RDONLY_PROG) && - m.flags != ms.Flags { - diffs = append(diffs, fmt.Sprintf("Flags: %d changed to %d", m.flags, ms.Flags)) + flags := ms.Flags + if ms.Type == DevMap || ms.Type == DevMapHash { + // As of 0cdbb4b09a06 ("devmap: Allow map lookups from eBPF") + // BPF_F_RDONLY_PROG is set unconditionally for devmaps. Explicitly + // allow this mismatch. + flags |= (m.flags & sys.BPF_F_RDONLY_PROG) + } + + if m.flags != flags { + diffs = append(diffs, fmt.Sprintf("Flags: %d changed to %d", m.flags, flags)) } if len(diffs) == 0 { @@ -252,13 +312,15 @@ func (ms *MapSpec) Compatible(m *Map) error { // Map represents a Map file descriptor. // -// It is not safe to close a map which is used by other goroutines. +// It is not safe to close a Map which is used by other goroutines. // -// Methods which take interface{} arguments by default encode -// them using binary.Read/Write in the machine's native endianness. +// Map operations taking `any` arguments are encoded using [sysenc.Marshal], +// which is zero-copy for fixed-size types without padding as well as slices of +// bytes, but may involve allocations for other types. See its documentation for +// details. // -// Implement encoding.BinaryMarshaler or encoding.BinaryUnmarshaler -// if you require custom encoding. +// Implement encoding.BinaryMarshaler or encoding.BinaryUnmarshaler if you +// require custom encoding or decoding. type Map struct { name string fd *sys.FD @@ -274,9 +336,11 @@ type Map struct { memory *Memory } -// NewMapFromFD creates a map from a raw fd. +// NewMapFromFD creates a [Map] around a raw fd. // // You should not use fd after calling this function. +// +// Requires at least Linux 4.13. func NewMapFromFD(fd int) (*Map, error) { f, err := sys.NewFD(fd) if err != nil { @@ -287,13 +351,13 @@ func NewMapFromFD(fd int) (*Map, error) { } func newMapFromFD(fd *sys.FD) (*Map, error) { - info, err := newMapInfoFromFd(fd) + info, err := minimalMapInfoFromFd(fd) if err != nil { fd.Close() return nil, fmt.Errorf("get map info: %w", err) } - return newMap(fd, info.Name, info.Type, info.KeySize, info.ValueSize, info.MaxEntries, info.Flags) + return newMapFromParts(fd, info.Name, info.Type, info.KeySize, info.ValueSize, info.MaxEntries, info.Flags) } // NewMap creates a new Map. @@ -314,7 +378,7 @@ func NewMap(spec *MapSpec) (*Map, error) { // // May return an error wrapping ErrMapIncompatible. func NewMapWithOptions(spec *MapSpec, opts MapOptions) (*Map, error) { - m, err := newMapWithOptions(spec, opts) + m, err := newMapWithOptions(spec, opts, btf.NewCache()) if err != nil { return nil, fmt.Errorf("creating map: %w", err) } @@ -327,7 +391,7 @@ func NewMapWithOptions(spec *MapSpec, opts MapOptions) (*Map, error) { return m, nil } -func newMapWithOptions(spec *MapSpec, opts MapOptions) (_ *Map, err error) { +func newMapWithOptions(spec *MapSpec, opts MapOptions, c *btf.Cache) (_ *Map, err error) { closeOnError := func(c io.Closer) { if err != nil { c.Close() @@ -368,7 +432,7 @@ func newMapWithOptions(spec *MapSpec, opts MapOptions) (_ *Map, err error) { } var innerFd *sys.FD - if spec.Type == ArrayOfMaps || spec.Type == HashOfMaps { + if spec.Type.canStoreMap() { if spec.InnerMap == nil { return nil, fmt.Errorf("%s requires InnerMap", spec.Type) } @@ -377,7 +441,7 @@ func newMapWithOptions(spec *MapSpec, opts MapOptions) (_ *Map, err error) { return nil, errors.New("inner maps cannot be pinned") } - template, err := spec.InnerMap.createMap(nil) + template, err := spec.InnerMap.createMap(nil, c) if err != nil { return nil, fmt.Errorf("inner map: %w", err) } @@ -389,7 +453,7 @@ func newMapWithOptions(spec *MapSpec, opts MapOptions) (_ *Map, err error) { innerFd = template.fd } - m, err := spec.createMap(innerFd) + m, err := spec.createMap(innerFd, c) if err != nil { return nil, err } @@ -432,6 +496,38 @@ func (m *Map) Memory() (*Memory, error) { return mm, nil } +// unsafeMemory returns a heap-mapped memory region for the Map. The Map must +// have been created with the BPF_F_MMAPABLE flag. Repeated calls to Memory +// return the same mapping. Callers are responsible for coordinating access to +// Memory. +func (m *Map) unsafeMemory() (*Memory, error) { + if m.memory != nil { + if !m.memory.heap { + return nil, errors.New("unsafeMemory would return existing non-heap memory") + } + + return m.memory, nil + } + + if m.flags&sys.BPF_F_MMAPABLE == 0 { + return nil, fmt.Errorf("Map was not created with the BPF_F_MMAPABLE flag: %w", ErrNotSupported) + } + + size, err := m.memorySize() + if err != nil { + return nil, err + } + + mm, err := newUnsafeMemory(m.FD(), size) + if err != nil { + return nil, fmt.Errorf("creating new Memory: %w", err) + } + + m.memory = mm + + return mm, nil +} + func (m *Map) memorySize() (int, error) { switch m.Type() { case Array: @@ -452,7 +548,7 @@ func (m *Map) memorySize() (int, error) { // createMap validates the spec's properties and creates the map in the kernel // using the given opts. It does not populate or freeze the map. -func (spec *MapSpec) createMap(inner *sys.FD) (_ *Map, err error) { +func (spec *MapSpec) createMap(inner *sys.FD, c *btf.Cache) (_ *Map, err error) { closeOnError := func(closer io.Closer) { if err != nil { closer.Close() @@ -474,23 +570,26 @@ func (spec *MapSpec) createMap(inner *sys.FD) (_ *Map, err error) { return nil, err } + p, sysMapType := platform.DecodeConstant(spec.Type) + if p != platform.Native { + return nil, fmt.Errorf("map type %s (%s): %w", spec.Type, p, internal.ErrNotSupportedOnOS) + } + attr := sys.MapCreateAttr{ - MapType: sys.MapType(spec.Type), + MapName: maybeFillObjName(spec.Name), + MapType: sys.MapType(sysMapType), KeySize: spec.KeySize, ValueSize: spec.ValueSize, MaxEntries: spec.MaxEntries, MapFlags: spec.Flags, NumaNode: spec.NumaNode, + MapExtra: spec.MapExtra, } if inner != nil { attr.InnerMapFd = inner.Uint() } - if haveObjName() == nil { - attr.MapName = sys.NewObjName(spec.Name) - } - if spec.Key != nil || spec.Value != nil { handle, keyTypeID, valueTypeID, err := btf.MarshalMapKV(spec.Key, spec.Value) if err != nil && !errors.Is(err, btf.ErrNotSupported) { @@ -505,6 +604,42 @@ func (spec *MapSpec) createMap(inner *sys.FD) (_ *Map, err error) { attr.BtfKeyTypeId = keyTypeID attr.BtfValueTypeId = valueTypeID } + + if spec.Type == StructOpsMap { + if handle == nil { + return nil, fmt.Errorf("struct_ops requires BTF") + } + + localValue, ok := btf.As[*btf.Struct](spec.Value) + if !ok { + return nil, fmt.Errorf("struct_ops: value must be struct") + } + + targetValue, targetID, module, err := structOpsFindTarget(localValue, c) + if err != nil { + return nil, fmt.Errorf("struct_ops: %w", err) + } + defer module.Close() + + spec = spec.Copy() + spec.ValueSize = targetValue.Size + + attr.ValueSize = targetValue.Size + attr.BtfVmlinuxValueTypeId = targetID + + if module != nil { + // BPF_F_VTYPE_BTF_OBJ_FD is required if the type comes from a module + attr.MapFlags |= sys.BPF_F_VTYPE_BTF_OBJ_FD + // set FD for the kernel module + attr.ValueTypeBtfObjFd = int32(module.FD()) + } + + // StructOpsMap forbids passing BtfKeyTypeId or BtfValueTypeId, but + // requires BtfFd. Do the simple thing and just zero out the fields. + // See https://github.com/torvalds/linux/blob/9b332cece987ee1790b2ed4c989e28162fa47860/kernel/bpf/syscall.c#L1382-L1384 + attr.BtfKeyTypeId = 0 + attr.BtfValueTypeId = 0 + } } fd, err := sys.MapCreate(&attr) @@ -520,7 +655,7 @@ func (spec *MapSpec) createMap(inner *sys.FD) (_ *Map, err error) { } defer closeOnError(fd) - m, err := newMap(fd, spec.Name, spec.Type, spec.KeySize, spec.ValueSize, spec.MaxEntries, spec.Flags) + m, err := newMapFromParts(fd, spec.Name, spec.Type, spec.KeySize, spec.ValueSize, spec.MaxEntries, spec.Flags) if err != nil { return nil, fmt.Errorf("map create: %w", err) } @@ -528,17 +663,35 @@ func (spec *MapSpec) createMap(inner *sys.FD) (_ *Map, err error) { } func handleMapCreateError(attr sys.MapCreateAttr, spec *MapSpec, err error) error { - if errors.Is(err, unix.EPERM) { - return fmt.Errorf("map create: %w (MEMLOCK may be too low, consider rlimit.RemoveMemlock)", err) + if platform.IsWindows { + if errors.Is(err, unix.EINVAL) && attr.MapFlags != 0 { + return fmt.Errorf("map create: flags: %w", internal.ErrNotSupportedOnOS) + } + + return err } - if errors.Is(err, unix.EINVAL) && spec.MaxEntries == 0 { - return fmt.Errorf("map create: %w (MaxEntries may be incorrectly set to zero)", err) + + if errors.Is(err, sys.ErrTokenCapabilities) { + return fmt.Errorf("map create: %w", err) } - if errors.Is(err, unix.EINVAL) && spec.Type == UnspecifiedMap { - return fmt.Errorf("map create: cannot use type %s", UnspecifiedMap) + + if errors.Is(err, unix.EPERM) { + return fmt.Errorf("map create: %w (MEMLOCK may be too low, consider rlimit.RemoveMemlock)", err) } - if errors.Is(err, unix.EINVAL) && spec.Flags&sys.BPF_F_NO_PREALLOC > 0 { - return fmt.Errorf("map create: %w (noPrealloc flag may be incompatible with map type %s)", err, spec.Type) + + if errors.Is(err, unix.EINVAL) { + if spec.MaxEntries == 0 && !spec.Type.mustHaveZeroMaxEntries() { + return fmt.Errorf("map create: %w (MaxEntries may be incorrectly set to zero)", err) + } + if spec.Type == UnspecifiedMap { + return fmt.Errorf("map create: cannot use type %s", UnspecifiedMap) + } + if spec.Flags&sys.BPF_F_NO_PREALLOC != 0 && !spec.Type.mustHaveNoPrealloc() { + return fmt.Errorf("map create: %w (BPF_F_NO_PREALLOC flag may be incompatible with map type %s)", err, spec.Type) + } + if spec.Flags&sys.BPF_F_NO_PREALLOC == 0 && spec.Type.mustHaveNoPrealloc() { + return fmt.Errorf("map create: %w (BPF_F_NO_PREALLOC flag may need to be set for map type %s)", err, spec.Type) + } } if spec.Type.canStoreMap() { @@ -580,9 +733,9 @@ func handleMapCreateError(attr sys.MapCreateAttr, spec *MapSpec, err error) erro return fmt.Errorf("map create: %w", err) } -// newMap allocates and returns a new Map structure. +// newMapFromParts allocates and returns a new Map structure. // Sets the fullValueSize on per-CPU maps. -func newMap(fd *sys.FD, name string, typ MapType, keySize, valueSize, maxEntries, flags uint32) (*Map, error) { +func newMapFromParts(fd *sys.FD, name string, typ MapType, keySize, valueSize, maxEntries, flags uint32) (*Map, error) { m := &Map{ name, fd, @@ -645,7 +798,7 @@ func (m *Map) Flags() uint32 { // but newer kernels support more MapInfo fields with the introduction of more // features. See [MapInfo] and its methods for more details. // -// Returns an error wrapping ErrNotSupported if the kernel supports neither +// Returns an error wrapping [ErrNotSupported] if the kernel supports neither // BPF_OBJ_GET_INFO_BY_FD nor reading map information from /proc/self/fdinfo. func (m *Map) Info() (*MapInfo, error) { return newMapInfoFromFd(m.fd) @@ -653,7 +806,7 @@ func (m *Map) Info() (*MapInfo, error) { // Handle returns a reference to the Map's type information in the kernel. // -// Returns ErrNotSupported if the kernel has no BTF support, or if there is no +// Returns [ErrNotSupported] if the kernel has no BTF support, or if there is no // BTF associated with the Map. func (m *Map) Handle() (*btf.Handle, error) { info, err := m.Info() @@ -681,7 +834,7 @@ const LookupLock MapLookupFlags = sys.BPF_F_LOCK // and *valueOut is not nil. // // Returns an error if the key doesn't exist, see ErrKeyNotExist. -func (m *Map) Lookup(key, valueOut interface{}) error { +func (m *Map) Lookup(key, valueOut any) error { return m.LookupWithFlags(key, valueOut, 0) } @@ -695,7 +848,7 @@ func (m *Map) Lookup(key, valueOut interface{}) error { // 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 { +func (m *Map) LookupWithFlags(key, valueOut any, flags MapLookupFlags) error { if m.typ.hasPerCPUValue() { return m.lookupPerCPU(key, valueOut, flags) } @@ -711,7 +864,7 @@ func (m *Map) LookupWithFlags(key, valueOut interface{}, flags MapLookupFlags) e // LookupAndDelete retrieves and deletes a value from a Map. // // Returns ErrKeyNotExist if the key doesn't exist. -func (m *Map) LookupAndDelete(key, valueOut interface{}) error { +func (m *Map) LookupAndDelete(key, valueOut any) error { return m.LookupAndDeleteWithFlags(key, valueOut, 0) } @@ -722,7 +875,7 @@ func (m *Map) LookupAndDelete(key, valueOut interface{}) error { // contain a spinlock. // // Returns ErrKeyNotExist if the key doesn't exist. -func (m *Map) LookupAndDeleteWithFlags(key, valueOut interface{}, flags MapLookupFlags) error { +func (m *Map) LookupAndDeleteWithFlags(key, valueOut any, flags MapLookupFlags) error { if m.typ.hasPerCPUValue() { return m.lookupAndDeletePerCPU(key, valueOut, flags) } @@ -737,9 +890,9 @@ func (m *Map) LookupAndDeleteWithFlags(key, valueOut interface{}, flags MapLooku // LookupBytes gets a value from Map. // // Returns a nil value if a key doesn't exist. -func (m *Map) LookupBytes(key interface{}) ([]byte, error) { +func (m *Map) LookupBytes(key any) ([]byte, error) { valueBytes := make([]byte, m.fullValueSize) - valuePtr := sys.NewSlicePointer(valueBytes) + valuePtr := sys.UnsafeSlicePointer(valueBytes) err := m.lookup(key, valuePtr, 0) if errors.Is(err, ErrKeyNotExist) { @@ -755,13 +908,13 @@ func (m *Map) lookupPerCPU(key, valueOut any, flags MapLookupFlags) error { return err } valueBytes := make([]byte, m.fullValueSize) - if err := m.lookup(key, sys.NewSlicePointer(valueBytes), flags); err != nil { + if err := m.lookup(key, sys.UnsafeSlicePointer(valueBytes), flags); err != nil { return err } return unmarshalPerCPUValue(slice, int(m.valueSize), valueBytes) } -func (m *Map) lookup(key interface{}, valueOut sys.Pointer, flags MapLookupFlags) error { +func (m *Map) lookup(key any, valueOut sys.Pointer, flags MapLookupFlags) error { keyPtr, err := m.marshalKey(key) if err != nil { return fmt.Errorf("can't marshal key: %w", err) @@ -789,7 +942,7 @@ func (m *Map) lookupAndDeletePerCPU(key, valueOut any, flags MapLookupFlags) err return err } valueBytes := make([]byte, m.fullValueSize) - if err := m.lookupAndDelete(key, sys.NewSlicePointer(valueBytes), flags); err != nil { + if err := m.lookupAndDelete(key, sys.UnsafeSlicePointer(valueBytes), flags); err != nil { return err } return unmarshalPerCPUValue(slice, int(m.valueSize), valueBytes) @@ -797,6 +950,10 @@ func (m *Map) lookupAndDeletePerCPU(key, valueOut any, flags MapLookupFlags) err // ensurePerCPUSlice allocates a slice for a per-CPU value if necessary. func ensurePerCPUSlice(sliceOrPtr any) (any, error) { + if err := internal.IsNilPointer(sliceOrPtr); err != nil { + return nil, fmt.Errorf("per-cpu value: %w", err) + } + sliceOrPtrType := reflect.TypeOf(sliceOrPtr) if sliceOrPtrType.Kind() == reflect.Slice { // The target is a slice, the caller is responsible for ensuring that @@ -805,7 +962,7 @@ func ensurePerCPUSlice(sliceOrPtr any) (any, error) { } slicePtrType := sliceOrPtrType - if slicePtrType.Kind() != reflect.Ptr || slicePtrType.Elem().Kind() != reflect.Slice { + if slicePtrType.Kind() != reflect.Pointer || slicePtrType.Elem().Kind() != reflect.Slice { return nil, fmt.Errorf("per-cpu value requires a slice or a pointer to slice") } @@ -818,14 +975,14 @@ func ensurePerCPUSlice(sliceOrPtr any) (any, error) { slice := reflect.MakeSlice(sliceType, possibleCPUs, possibleCPUs) sliceElemType := sliceType.Elem() - sliceElemIsPointer := sliceElemType.Kind() == reflect.Ptr + sliceElemIsPointer := sliceElemType.Kind() == reflect.Pointer reflect.ValueOf(sliceOrPtr).Elem().Set(slice) if !sliceElemIsPointer { return slice.Interface(), nil } sliceElemType = sliceElemType.Elem() - for i := 0; i < possibleCPUs; i++ { + for i := range possibleCPUs { newElem := reflect.New(sliceElemType) slice.Index(i).Set(newElem) } @@ -872,7 +1029,7 @@ const ( // Put replaces or creates a value in map. // // It is equivalent to calling Update with UpdateAny. -func (m *Map) Put(key, value interface{}) error { +func (m *Map) Put(key, value any) error { return m.Update(key, value, UpdateAny) } @@ -922,7 +1079,7 @@ func (m *Map) update(key any, valuePtr sys.Pointer, flags MapUpdateFlags) error // Delete removes a value. // // Returns ErrKeyNotExist if the key does not exist. -func (m *Map) Delete(key interface{}) error { +func (m *Map) Delete(key any) error { keyPtr, err := m.marshalKey(key) if err != nil { return fmt.Errorf("can't marshal key: %w", err) @@ -944,7 +1101,7 @@ func (m *Map) Delete(key interface{}) error { // See NextKeyBytes for details. // // Returns ErrKeyNotExist if there is no next key. -func (m *Map) NextKey(key, nextKeyOut interface{}) error { +func (m *Map) NextKey(key, nextKeyOut any) error { nextKeyBytes := makeMapSyscallOutput(nextKeyOut, int(m.keySize)) if err := m.nextKey(key, nextKeyBytes.Pointer()); err != nil { @@ -964,9 +1121,9 @@ func (m *Map) NextKey(key, nextKeyOut interface{}) error { // Use Iterate if you want to traverse all entries in the map. // // Returns nil if there are no more keys. -func (m *Map) NextKeyBytes(key interface{}) ([]byte, error) { +func (m *Map) NextKeyBytes(key any) ([]byte, error) { nextKey := make([]byte, m.keySize) - nextKeyPtr := sys.NewSlicePointer(nextKey) + nextKeyPtr := sys.UnsafeSlicePointer(nextKey) err := m.nextKey(key, nextKeyPtr) if errors.Is(err, ErrKeyNotExist) { @@ -976,7 +1133,7 @@ func (m *Map) NextKeyBytes(key interface{}) ([]byte, error) { return nextKey, err } -func (m *Map) nextKey(key interface{}, nextKeyOut sys.Pointer) error { +func (m *Map) nextKey(key any, nextKeyOut sys.Pointer) error { var ( keyPtr sys.Pointer err error @@ -998,7 +1155,7 @@ func (m *Map) nextKey(key interface{}, nextKeyOut sys.Pointer) error { 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) { + if platform.IsLinux && key == nil && errors.Is(err, unix.EFAULT) { var guessKey []byte guessKey, err = m.guessNonExistentKey() if err != nil { @@ -1006,7 +1163,7 @@ func (m *Map) nextKey(key interface{}, nextKeyOut sys.Pointer) error { } // Retry the syscall with a valid non-existing key. - attr.Key = sys.NewSlicePointer(guessKey) + attr.Key = sys.UnsafeSlicePointer(guessKey) if err = sys.MapGetNextKey(&attr); err == nil { return nil } @@ -1032,11 +1189,11 @@ func (m *Map) guessNonExistentKey() ([]byte, error) { if err != nil { return nil, err } - valuePtr := sys.NewSlicePointer(page) + valuePtr := sys.UnsafeSlicePointer(page) randKey := make([]byte, int(m.keySize)) - for i := 0; i < 4; i++ { + for i := range 4 { 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 @@ -1085,7 +1242,7 @@ func (m *Map) guessNonExistentKey() ([]byte, error) { // ErrKeyNotExist is returned when the batch lookup has reached // 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(cursor *MapBatchCursor, keysOut, valuesOut interface{}, opts *BatchOptions) (int, error) { +func (m *Map) BatchLookup(cursor *MapBatchCursor, keysOut, valuesOut any, opts *BatchOptions) (int, error) { n, err := m.batchLookup(sys.BPF_MAP_LOOKUP_BATCH, cursor, keysOut, valuesOut, opts) if err != nil { return n, fmt.Errorf("map batch lookup: %w", err) @@ -1109,7 +1266,7 @@ func (m *Map) BatchLookup(cursor *MapBatchCursor, keysOut, valuesOut interface{} // ErrKeyNotExist is returned when the batch lookup has reached // 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(cursor *MapBatchCursor, keysOut, valuesOut interface{}, opts *BatchOptions) (int, error) { +func (m *Map) BatchLookupAndDelete(cursor *MapBatchCursor, keysOut, valuesOut any, opts *BatchOptions) (int, error) { n, err := m.batchLookup(sys.BPF_MAP_LOOKUP_AND_DELETE_BATCH, cursor, keysOut, valuesOut, opts) if err != nil { return n, fmt.Errorf("map batch lookup and delete: %w", err) @@ -1123,7 +1280,7 @@ type MapBatchCursor struct { opaque []byte } -func (m *Map) batchLookup(cmd sys.Cmd, cursor *MapBatchCursor, keysOut, valuesOut interface{}, opts *BatchOptions) (int, error) { +func (m *Map) batchLookup(cmd sys.Cmd, cursor *MapBatchCursor, keysOut, valuesOut any, opts *BatchOptions) (int, error) { if m.typ.hasPerCPUValue() { return m.batchLookupPerCPU(cmd, cursor, keysOut, valuesOut, opts) } @@ -1135,13 +1292,13 @@ func (m *Map) batchLookup(cmd sys.Cmd, cursor *MapBatchCursor, keysOut, valuesOu valueBuf := sysenc.SyscallOutput(valuesOut, count*int(m.fullValueSize)) - n, err := m.batchLookupCmd(cmd, cursor, count, keysOut, valueBuf.Pointer(), opts) - if errors.Is(err, unix.ENOSPC) { + n, sysErr := m.batchLookupCmd(cmd, cursor, count, keysOut, valueBuf.Pointer(), opts) + if errors.Is(sysErr, unix.ENOSPC) { // Hash tables return ENOSPC when the size of the batch is smaller than // any bucket. - return n, fmt.Errorf("%w (batch size too small?)", err) - } else if err != nil { - return n, err + return n, fmt.Errorf("%w (batch size too small?)", sysErr) + } else if sysErr != nil && !errors.Is(sysErr, unix.ENOENT) { + return 0, sysErr } err = valueBuf.Unmarshal(valuesOut) @@ -1149,43 +1306,41 @@ func (m *Map) batchLookup(cmd sys.Cmd, cursor *MapBatchCursor, keysOut, valuesOu return 0, err } - return n, nil + return n, sysErr } -func (m *Map) batchLookupPerCPU(cmd sys.Cmd, cursor *MapBatchCursor, keysOut, valuesOut interface{}, opts *BatchOptions) (int, error) { +func (m *Map) batchLookupPerCPU(cmd sys.Cmd, cursor *MapBatchCursor, keysOut, valuesOut any, opts *BatchOptions) (int, error) { count, err := sliceLen(keysOut) if err != nil { return 0, fmt.Errorf("keys: %w", err) } - valueBuf := make([]byte, count*int(m.fullValueSize)) - valuePtr := sys.NewSlicePointer(valueBuf) + valueBuf := sysenc.SyscallOutput(valuesOut, count*int(m.fullValueSize)) - n, sysErr := m.batchLookupCmd(cmd, cursor, count, keysOut, valuePtr, opts) + n, sysErr := m.batchLookupCmd(cmd, cursor, count, keysOut, valueBuf.Pointer(), opts) if sysErr != nil && !errors.Is(sysErr, unix.ENOENT) { - return 0, err + return 0, sysErr } - err = unmarshalBatchPerCPUValue(valuesOut, count, int(m.valueSize), valueBuf) - if err != nil { - return 0, err + if bytesBuf := valueBuf.Bytes(); bytesBuf != nil { + err = unmarshalBatchPerCPUValue(valuesOut, count, int(m.valueSize), bytesBuf) + if err != nil { + return 0, err + } } return n, sysErr } func (m *Map) batchLookupCmd(cmd sys.Cmd, cursor *MapBatchCursor, count int, keysOut any, valuePtr sys.Pointer, opts *BatchOptions) (int, error) { - cursorLen := int(m.keySize) - if cursorLen < 4 { - // * generic_map_lookup_batch requires that batch_out is key_size bytes. - // This is used by array and LPM maps. - // - // * __htab_map_lookup_and_delete_batch requires u32. This is used by the - // various hash maps. - // - // Use a minimum of 4 bytes to avoid having to distinguish between the two. - cursorLen = 4 - } + // * generic_map_lookup_batch requires that batch_out is key_size bytes. + // This is used by array and LPM maps. + // + // * __htab_map_lookup_and_delete_batch requires u32. This is used by the + // various hash maps. + // + // Use a minimum of 4 bytes to avoid having to distinguish between the two. + cursorLen := max(int(m.keySize), 4) inBatch := cursor.opaque if inBatch == nil { @@ -1200,10 +1355,6 @@ func (m *Map) batchLookupCmd(cmd sys.Cmd, cursor *MapBatchCursor, count int, key return 0, errors.New("a cursor may not be reused across maps") } - if err := haveBatchAPI(); err != nil { - return 0, err - } - keyBuf := sysenc.SyscallOutput(keysOut, count*int(m.keySize)) attr := sys.MapLookupBatchAttr{ @@ -1211,8 +1362,8 @@ func (m *Map) batchLookupCmd(cmd sys.Cmd, cursor *MapBatchCursor, count int, key Keys: keyBuf.Pointer(), Values: valuePtr, Count: uint32(count), - InBatch: sys.NewSlicePointer(inBatch), - OutBatch: sys.NewSlicePointer(cursor.opaque), + InBatch: sys.UnsafeSlicePointer(inBatch), + OutBatch: sys.UnsafeSlicePointer(cursor.opaque), } if opts != nil { @@ -1223,6 +1374,9 @@ func (m *Map) batchLookupCmd(cmd sys.Cmd, cursor *MapBatchCursor, count int, key _, sysErr := sys.BPF(cmd, unsafe.Pointer(&attr), unsafe.Sizeof(attr)) sysErr = wrapMapError(sysErr) if sysErr != nil && !errors.Is(sysErr, unix.ENOENT) { + if haveFeatErr := haveBatchAPI(); haveFeatErr != nil { + return 0, haveFeatErr + } return 0, sysErr } @@ -1237,7 +1391,7 @@ func (m *Map) batchLookupCmd(cmd sys.Cmd, cursor *MapBatchCursor, count int, key // simultaneously. // "keys" and "values" must be of type slice, a pointer // to a slice or buffer will not work. -func (m *Map) BatchUpdate(keys, values interface{}, opts *BatchOptions) (int, error) { +func (m *Map) BatchUpdate(keys, values any, opts *BatchOptions) (int, error) { if m.typ.hasPerCPUValue() { return m.batchUpdatePerCPU(keys, values, opts) } @@ -1294,12 +1448,12 @@ func (m *Map) batchUpdatePerCPU(keys, values any, opts *BatchOptions) (int, erro return 0, err } - return m.batchUpdate(count, keys, sys.NewSlicePointer(valueBuf), opts) + return m.batchUpdate(count, keys, sys.UnsafeSlicePointer(valueBuf), opts) } // BatchDelete batch deletes entries in the map by keys. // "keys" must be of type slice, a pointer to a slice or buffer will not work. -func (m *Map) BatchDelete(keys interface{}, opts *BatchOptions) (int, error) { +func (m *Map) BatchDelete(keys any, opts *BatchOptions) (int, error) { count, err := sliceLen(keys) if err != nil { return 0, fmt.Errorf("keys: %w", err) @@ -1479,11 +1633,11 @@ func (m *Map) finalize(spec *MapSpec) error { return nil } -func (m *Map) marshalKey(data interface{}) (sys.Pointer, error) { +func (m *Map) marshalKey(data any) (sys.Pointer, error) { if data == nil { if m.keySize == 0 { // Queues have a key length of zero, so passing nil here is valid. - return sys.NewPointer(nil), nil + return sys.UnsafePointer(nil), nil } return sys.Pointer{}, errors.New("can't use nil as key of map") } @@ -1491,7 +1645,7 @@ func (m *Map) marshalKey(data interface{}) (sys.Pointer, error) { return marshalMapSyscallInput(data, int(m.keySize)) } -func (m *Map) marshalValue(data interface{}) (sys.Pointer, error) { +func (m *Map) marshalValue(data any) (sys.Pointer, error) { var ( buf []byte err error @@ -1518,7 +1672,7 @@ func (m *Map) marshalValue(data interface{}) (sys.Pointer, error) { return sys.Pointer{}, err } - return sys.NewSlicePointer(buf), nil + return sys.UnsafeSlicePointer(buf), nil } func (m *Map) unmarshalValue(value any, buf sysenc.Buffer) error { @@ -1650,7 +1804,7 @@ func newMapIterator(target *Map) *MapIterator { // the result of Err afterwards. // // See Map.Get for further caveats around valueOut. -func (mi *MapIterator) Next(keyOut, valueOut interface{}) bool { +func (mi *MapIterator) Next(keyOut, valueOut any) bool { if mi.err != nil || mi.done { return false } @@ -1726,9 +1880,10 @@ func MapGetNextID(startID MapID) (MapID, error) { return MapID(attr.NextId), sys.MapGetNextId(attr) } -// NewMapFromID returns the map for a given id. +// NewMapFromID returns the [Map] for a given map id. Returns [ErrNotExist] if +// there is no eBPF map with the given id. // -// Returns ErrNotExist, if there is no eBPF map with the given id. +// Requires at least Linux 4.13. func NewMapFromID(id MapID) (*Map, error) { fd, err := sys.MapGetFdById(&sys.MapGetFdByIdAttr{ Id: uint32(id), diff --git a/vendor/github.com/cilium/ebpf/marshalers.go b/vendor/github.com/cilium/ebpf/marshalers.go index 57a0a8e88a..2542f2d0a3 100644 --- a/vendor/github.com/cilium/ebpf/marshalers.go +++ b/vendor/github.com/cilium/ebpf/marshalers.go @@ -20,7 +20,7 @@ import ( // unsafe.Pointer. func marshalMapSyscallInput(data any, length int) (sys.Pointer, error) { if ptr, ok := data.(unsafe.Pointer); ok { - return sys.NewPointer(ptr), nil + return sys.UnsafePointer(ptr), nil } buf, err := sysenc.Marshal(data, length) @@ -62,7 +62,7 @@ func appendPerCPUSlice(buf []byte, slice any, possibleCPUs, elemLength, alignedE // Grow increases the slice's capacity, _if_necessary_ buf = slices.Grow(buf, alignedElemLength*possibleCPUs) - for i := 0; i < sliceLen; i++ { + for i := range sliceLen { elem := sliceValue.Index(i).Interface() elemBytes, err := sysenc.Marshal(elem, elemLength) if err != nil { @@ -96,7 +96,7 @@ func marshalPerCPUValue(slice any, elemLength int) (sys.Pointer, error) { return sys.Pointer{}, err } - return sys.NewSlicePointer(buf), nil + return sys.UnsafeSlicePointer(buf), nil } // marshalBatchPerCPUValue encodes a batch-sized slice of slices containing @@ -118,7 +118,7 @@ func marshalBatchPerCPUValue(slice any, batchLen, elemLength int) ([]byte, error } alignedElemLength := internal.Align(elemLength, 8) buf := make([]byte, 0, batchLen*alignedElemLength*possibleCPUs) - for i := 0; i < batchLen; i++ { + for i := range batchLen { batch := sliceValue.Slice(i*possibleCPUs, (i+1)*possibleCPUs).Interface() buf, err = appendPerCPUSlice(buf, batch, possibleCPUs, elemLength, alignedElemLength) if err != nil { @@ -150,9 +150,9 @@ func unmarshalPerCPUValue(slice any, elemLength int, buf []byte) error { } sliceElemType := sliceType.Elem() - sliceElemIsPointer := sliceElemType.Kind() == reflect.Ptr + sliceElemIsPointer := sliceElemType.Kind() == reflect.Pointer stride := internal.Align(elemLength, 8) - for i := 0; i < possibleCPUs; i++ { + for i := range possibleCPUs { var elem any v := sliceValue.Index(i) if sliceElemIsPointer { @@ -199,7 +199,7 @@ func unmarshalBatchPerCPUValue(slice any, batchLen, elemLength int, buf []byte) len(buf), batchLen*fullValueSize) } - for i := 0; i < batchLen; i++ { + for i := range batchLen { elem := sliceValue.Slice(i*possibleCPUs, (i+1)*possibleCPUs).Interface() if err := unmarshalPerCPUValue(elem, elemLength, buf[:fullValueSize]); err != nil { return fmt.Errorf("batch %d: %w", i, err) diff --git a/vendor/github.com/cilium/ebpf/memory.go b/vendor/github.com/cilium/ebpf/memory.go index 312c967131..a31fae3463 100644 --- a/vendor/github.com/cilium/ebpf/memory.go +++ b/vendor/github.com/cilium/ebpf/memory.go @@ -35,8 +35,11 @@ var ErrReadOnly = errors.New("resource is read-only") // for individual values. For accesses beyond a single value, the usual // concurrent programming rules apply. type Memory struct { - b []byte - ro bool + b []byte + ro bool + heap bool + + cleanup runtime.Cleanup } func newMemory(fd, size int) (*Memory, error) { @@ -61,25 +64,29 @@ func newMemory(fd, size int) (*Memory, error) { return nil, fmt.Errorf("setting up memory-mapped region: %w", err) } - mm := &Memory{ - b, - ro, - } - runtime.SetFinalizer(mm, (*Memory).close) + mm := &Memory{b: b, ro: ro, heap: false} + mm.cleanup = runtime.AddCleanup(mm, memoryCleanupFunc(), b) return mm, nil } -func (mm *Memory) close() { - if err := unix.Munmap(mm.b); err != nil { - panic(fmt.Errorf("unmapping memory: %w", err)) +func memoryCleanupFunc() func([]byte) { + return func(b []byte) { + if err := unix.Munmap(b); err != nil { + panic(fmt.Errorf("unmapping memory: %w", err)) + } } +} + +func (mm *Memory) close() { + mm.cleanup.Stop() + memoryCleanupFunc()(mm.b) mm.b = nil } // Size returns the size of the memory-mapped region in bytes. -func (mm *Memory) Size() int { - return len(mm.b) +func (mm *Memory) Size() uint32 { + return uint32(len(mm.b)) } // ReadOnly returns true if the memory-mapped region is read-only. @@ -88,8 +95,11 @@ func (mm *Memory) ReadOnly() bool { } // bounds returns true if an access at off of the given size is within bounds. -func (mm *Memory) bounds(off uint64, size uint64) bool { - return off+size < uint64(len(mm.b)) +func (mm *Memory) bounds(off, size uint32) bool { + if off+size < off { + return false + } + return off+size <= uint32(len(mm.b)) } // ReadAt implements [io.ReaderAt]. Useful for creating a new [io.OffsetWriter]. diff --git a/vendor/github.com/cilium/ebpf/memory_unsafe.go b/vendor/github.com/cilium/ebpf/memory_unsafe.go new file mode 100644 index 0000000000..7e00b9e680 --- /dev/null +++ b/vendor/github.com/cilium/ebpf/memory_unsafe.go @@ -0,0 +1,340 @@ +package ebpf + +import ( + "errors" + "fmt" + "os" + "reflect" + "runtime" + "unsafe" + + "github.com/cilium/ebpf/internal" + "github.com/cilium/ebpf/internal/unix" +) + +// This file contains an experimental, unsafe implementation of Memory that +// allows taking a Go pointer to a memory-mapped region. This currently does not +// have first-class support from the Go runtime, so it may break in future Go +// versions. The Go proposal for the runtime to track off-heap pointers is here: +// https://github.com/golang/go/issues/70224. +// +// In Go, the programmer should not have to worry about freeing memory. Since +// this API synthesizes Go variables around global variables declared in a BPF +// C program, we want to lean on the runtime for making sure accessing them is +// safe at all times. Unfortunately, Go (as of 1.24) does not have the ability +// of automatically managing memory that was not allocated by the runtime. +// +// This led to a solution that requests regular Go heap memory by allocating a +// slice (making the runtime track pointers into the slice's backing array) and +// memory-mapping the bpf map's memory over it. Then, before returning the +// Memory to the caller, a finalizer is set on the backing array, making sure +// the bpf map's memory is unmapped from the heap before releasing the backing +// array to the runtime for reallocation. +// +// This obviates the need to maintain a reference to the *Memory at all times, +// which is difficult for the caller to achieve if the variable access is done +// through another object (like a sync.Atomic) that can potentially be passed +// around the Go application. Accidentally losing the reference to the *Memory +// would result in hard-to-debug segfaults, which are always unexpected in Go. + +//go:linkname heapObjectsCanMove runtime.heapObjectsCanMove +func heapObjectsCanMove() bool + +// Set from a file behind the ebpf_unsafe_memory_experiment build tag to enable +// features that require mapping bpf map memory over the Go heap. +var unsafeMemory = false + +// ErrInvalidType is returned when the given type cannot be used as a Memory or +// Variable pointer. +var ErrInvalidType = errors.New("invalid type") + +func newUnsafeMemory(fd, size int) (*Memory, error) { + // Some architectures need the size to be page-aligned to work with MAP_FIXED. + if size%os.Getpagesize() != 0 { + return nil, fmt.Errorf("memory: must be a multiple of page size (requested %d bytes)", size) + } + + // Allocate a page-aligned span of memory on the Go heap. + alloc, err := allocate(size) + if err != nil { + return nil, fmt.Errorf("allocating memory: %w", err) + } + + // Typically, maps created with BPF_F_RDONLY_PROG remain writable from user + // space until frozen. As a security precaution, the kernel doesn't allow + // mapping bpf map memory as read-write into user space if the bpf map was + // frozen, or if it was created using the RDONLY_PROG flag. + // + // The user would be able to write to the map after freezing (since the kernel + // can't change the protection mode of an already-mapped page), while the + // verifier assumes the contents to be immutable. + // + // Map the bpf map memory over a page-aligned allocation on the Go heap. + err = mapmap(fd, alloc, size, unix.PROT_READ|unix.PROT_WRITE) + + // If the map is frozen when an rw mapping is requested, expect EPERM. If the + // map was created with BPF_F_RDONLY_PROG, expect EACCES. + var ro bool + if errors.Is(err, unix.EPERM) || errors.Is(err, unix.EACCES) { + ro = true + err = mapmap(fd, alloc, size, unix.PROT_READ) + } + if err != nil { + return nil, fmt.Errorf("setting up memory-mapped region: %w", err) + } + + mm := &Memory{ + unsafe.Slice((*byte)(alloc), size), + ro, + true, + runtime.Cleanup{}, + } + + return mm, nil +} + +// allocate returns a pointer to a page-aligned section of memory on the Go +// heap, managed by the runtime. +// +//go:nocheckptr +func allocate(size int) (unsafe.Pointer, error) { + // Memory-mapping over a piece of the Go heap is unsafe when the GC can + // randomly decide to move objects around, in which case the mapped region + // will not move along with it. + if heapObjectsCanMove() { + return nil, errors.New("this Go runtime has a moving garbage collector") + } + + if size == 0 { + return nil, errors.New("size must be greater than 0") + } + + // Request at least two pages of memory from the runtime to ensure we can + // align the requested allocation to a page boundary. This is needed for + // MAP_FIXED and makes sure we don't mmap over some other allocation on the Go + // heap. + size = internal.Align(size+os.Getpagesize(), os.Getpagesize()) + + // Allocate a new slice and store a pointer to its backing array. + alloc := unsafe.Pointer(unsafe.SliceData(make([]byte, size))) + + // Align the pointer to a page boundary within the allocation. This may alias + // the initial pointer if it was already page-aligned. + aligned := internal.Align(uintptr(alloc), uintptr(os.Getpagesize())) + alloc = unsafe.Add(alloc, aligned-uintptr(alloc)) + + // Return an aligned pointer into the backing array, losing the original + // reference. The runtime.SetFinalizer docs specify that its argument 'must be + // a pointer to an object, complit or local var', but this is still somewhat + // vague and not enforced by the current implementation. + // + // Currently, finalizers can be set and triggered from any address within a + // heap allocation, even individual struct fields or arbitrary offsets within + // a slice. In this case, finalizers set on struct fields or slice offsets + // will only run when the whole struct or backing array are collected. The + // accepted runtime.AddCleanup proposal makes this behaviour more explicit and + // is set to deprecate runtime.SetFinalizer. + // + // Alternatively, we'd have to track the original allocation and the aligned + // pointer separately, which severely complicates finalizer setup and makes it + // prone to human error. For now, just bump the pointer and treat it as the + // new and only reference to the backing array. + return alloc, nil +} + +// mapmap memory-maps the given file descriptor at the given address and sets a +// finalizer on addr to unmap it when it's no longer reachable. +func mapmap(fd int, addr unsafe.Pointer, size, flags int) error { + // Map the bpf map memory over the Go heap. This will result in the following + // mmap layout in the process' address space (0xc000000000 is a span of Go + // heap), visualized using pmap: + // + // Address Kbytes RSS Dirty Mode Mapping + // 000000c000000000 1824 864 864 rw--- [ anon ] + // 000000c0001c8000 4 4 4 rw-s- [ anon ] + // 000000c0001c9000 2268 16 16 rw--- [ anon ] + // + // This will break up the Go heap, but as long as the runtime doesn't try to + // move our allocation around, this is safe for as long as we hold a reference + // to our allocated object. + // + // Use MAP_SHARED to make sure the kernel sees any writes we do, and MAP_FIXED + // to ensure the mapping starts exactly at the address we requested. If alloc + // isn't page-aligned, the mapping operation will fail. + if _, err := unix.MmapPtr(fd, 0, addr, uintptr(size), + flags, unix.MAP_SHARED|unix.MAP_FIXED); err != nil { + return fmt.Errorf("setting up memory-mapped region: %w", err) + } + + // Set a finalizer on the heap allocation to undo the mapping before the span + // is collected and reused by the runtime. This has a few reasons: + // + // - Avoid leaking memory/mappings. + // - Future writes to this memory should never clobber a bpf map's contents. + // - Some bpf maps are mapped read-only, causing a segfault if the runtime + // reallocates and zeroes the span later. + runtime.SetFinalizer((*byte)(addr), unmap(size)) + + return nil +} + +// unmap returns a function that takes a pointer to a memory-mapped region on +// the Go heap. The function undoes any mappings and discards the span's +// contents. +// +// Used as a finalizer in [newMemory], split off into a separate function for +// testing and to avoid accidentally closing over the unsafe.Pointer to the +// memory region, which would cause a cyclical reference. +// +// The resulting function panics if the mmap operation returns an error, since +// it would mean the integrity of the Go heap is compromised. +func unmap(size int) func(*byte) { + return func(a *byte) { + // Create another mapping at the same address to undo the original mapping. + // This will cause the kernel to repair the slab since we're using the same + // protection mode and flags as the original mapping for the Go heap. + // + // Address Kbytes RSS Dirty Mode Mapping + // 000000c000000000 4096 884 884 rw--- [ anon ] + // + // Using munmap here would leave an unmapped hole in the heap, compromising + // its integrity. + // + // MmapPtr allocates another unsafe.Pointer at the same address. Even though + // we discard it here, it may temporarily resurrect the backing array and + // delay its collection to the next GC cycle. + _, err := unix.MmapPtr(-1, 0, unsafe.Pointer(a), uintptr(size), + unix.PROT_READ|unix.PROT_WRITE, + unix.MAP_PRIVATE|unix.MAP_FIXED|unix.MAP_ANON) + if err != nil { + panic(fmt.Errorf("undoing bpf map memory mapping: %w", err)) + } + } +} + +// checkUnsafeMemory ensures value T can be accessed in mm at offset off. +// +// The comparable constraint narrows down the set of eligible types to exclude +// slices, maps and functions. These complex types cannot be mapped to memory +// directly. +func checkUnsafeMemory[T comparable](mm *Memory, off uint32) error { + if mm.b == nil { + return fmt.Errorf("memory-mapped region is nil") + } + if mm.ro { + return ErrReadOnly + } + if !mm.heap { + return fmt.Errorf("memory region is not heap-mapped, build with '-tags ebpf_unsafe_memory_experiment' to enable: %w", ErrNotSupported) + } + + t := reflect.TypeFor[T]() + if err := checkType(t.String(), t); err != nil { + return err + } + + size := t.Size() + if size == 0 { + return fmt.Errorf("zero-sized type %s: %w", t, ErrInvalidType) + } + + if off%uint32(t.Align()) != 0 { + return fmt.Errorf("unaligned access of memory-mapped region: %d-byte aligned read at offset %d", t.Align(), off) + } + + vs, bs := uint32(size), uint32(len(mm.b)) + if !mm.bounds(off, vs) { + return fmt.Errorf("%d-byte value at offset %d exceeds mmap size of %d bytes", vs, off, bs) + } + + return nil +} + +// checkType recursively checks if the given type is supported for memory +// mapping. Only fixed-size, non-Go-pointer types are supported: bools, floats, +// (u)int[8-64], arrays, and structs containing them. As an exception, uintptr +// is allowed since the backing memory is expected to contain 32-bit pointers on +// 32-bit systems despite BPF always allocating 64 bits for pointers in a data +// section. +// +// Doesn't check for loops since it rejects pointers. Should that ever change, a +// visited set would be needed. +func checkType(name string, t reflect.Type) error { + // Special-case atomic types to allow them to be used as root types as well as + // struct fields. Notably, omit atomic.Value and atomic.Pointer since those + // are pointer types. Also, atomic.Value embeds an interface value, which + // doesn't make sense to share with C land. + if t.PkgPath() == "sync/atomic" { + switch t.Name() { + case "Bool", "Int32", "Int64", "Uint32", "Uint64", "Uintptr": + return nil + } + } + + switch t.Kind() { + case reflect.Uintptr, reflect.Bool, reflect.Float32, reflect.Float64, + reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, + reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return nil + + case reflect.Array: + at := t.Elem() + if err := checkType(fmt.Sprintf("%s.%s", name, at.String()), at); err != nil { + return err + } + + case reflect.Struct: + var hasHostLayout bool + for i := range t.NumField() { + at := t.Field(i).Type + + // Require [structs.HostLayout] to be embedded in all structs. Check the + // full package path to reject a user-defined HostLayout type. + if at.PkgPath() == "structs" && at.Name() == "HostLayout" { + hasHostLayout = true + continue + } + + if err := checkType(fmt.Sprintf("%s.%s", name, at.String()), at); err != nil { + return err + } + } + + if !hasHostLayout { + return fmt.Errorf("struct %s must embed structs.HostLayout: %w", name, ErrInvalidType) + } + + default: + // For basic types like int and bool, the kind name is the same as the type + // name, so the fallthrough case would print 'int type int not supported'. + // Omit the kind name if it matches the type name. + if t.String() == t.Kind().String() { + // Output: type int not supported + return fmt.Errorf("type %s not supported: %w", name, ErrInvalidType) + } + + // Output: interface value io.Reader not supported + return fmt.Errorf("%s type %s not supported: %w", t.Kind(), name, ErrInvalidType) + } + + return nil +} + +// memoryPointer returns a pointer to a value of type T at offset off in mm. +// Taking a pointer to a read-only Memory or to a Memory that is not heap-mapped +// is not supported. +// +// T must contain only fixed-size, non-Go-pointer types: bools, floats, +// (u)int[8-64], arrays, and structs containing them. Structs must embed +// [structs.HostLayout]. [ErrInvalidType] is returned if T is not a valid type. +// +// Memory must be writable, off must be aligned to the size of T, and the value +// must be within bounds of the Memory. +// +// To access read-only memory, use [Memory.ReadAt]. +func memoryPointer[T comparable](mm *Memory, off uint32) (*T, error) { + if err := checkUnsafeMemory[T](mm, off); err != nil { + return nil, fmt.Errorf("memory pointer: %w", err) + } + return (*T)(unsafe.Pointer(&mm.b[off])), nil +} diff --git a/vendor/github.com/cilium/ebpf/memory_unsafe_tag.go b/vendor/github.com/cilium/ebpf/memory_unsafe_tag.go new file mode 100644 index 0000000000..e662065edf --- /dev/null +++ b/vendor/github.com/cilium/ebpf/memory_unsafe_tag.go @@ -0,0 +1,7 @@ +//go:build ebpf_unsafe_memory_experiment + +package ebpf + +func init() { + unsafeMemory = true +} diff --git a/vendor/github.com/cilium/ebpf/prog.go b/vendor/github.com/cilium/ebpf/prog.go index 8fcae6a3fb..9d120ab6f5 100644 --- a/vendor/github.com/cilium/ebpf/prog.go +++ b/vendor/github.com/cilium/ebpf/prog.go @@ -8,15 +8,15 @@ import ( "math" "path/filepath" "runtime" + "slices" "strings" "time" - "unsafe" "github.com/cilium/ebpf/asm" "github.com/cilium/ebpf/btf" "github.com/cilium/ebpf/internal" - "github.com/cilium/ebpf/internal/kallsyms" "github.com/cilium/ebpf/internal/linux" + "github.com/cilium/ebpf/internal/platform" "github.com/cilium/ebpf/internal/sys" "github.com/cilium/ebpf/internal/sysenc" "github.com/cilium/ebpf/internal/unix" @@ -25,6 +25,10 @@ import ( // ErrNotSupported is returned whenever the kernel doesn't support a feature. var ErrNotSupported = internal.ErrNotSupported +// ErrProgIncompatible is returned when a loaded Program is incompatible with a +// given spec. +var ErrProgIncompatible = errors.New("program is incompatible") + // errBadRelocation is returned when the verifier rejects a program due to a // bad CO-RE relocation. // @@ -38,7 +42,7 @@ var errBadRelocation = errors.New("bad CO-RE relocation") var errUnknownKfunc = errors.New("unknown kfunc") // ProgramID represents the unique ID of an eBPF program. -type ProgramID uint32 +type ProgramID = sys.ProgramID const ( // Number of bytes to pad the output buffer for BPF_PROG_TEST_RUN. @@ -56,6 +60,12 @@ const minVerifierLogSize = 64 * 1024 // future, but avoid the unnecessary EINVAL for now. const maxVerifierLogSize = math.MaxUint32 >> 2 +// maxVerifierAttempts is the maximum number of times the verifier will retry +// loading a program with a growing log buffer before giving up. Since we double +// the log size on every attempt, this is the absolute maximum number of +// attempts before the buffer reaches [maxVerifierLogSize]. +const maxVerifierAttempts = 30 + // ProgramOptions control loading a program into the kernel. type ProgramOptions struct { // Bitmap controlling the detail emitted by the kernel's eBPF verifier log. @@ -90,24 +100,29 @@ type ProgramOptions struct { // use the kernel BTF from a well-known location if nil. KernelTypes *btf.Spec - // Type information used for CO-RE relocations of kernel modules, - // indexed by module name. - // - // This is useful in environments where the kernel BTF is not available - // (containers) or where it is in a non-standard location. Defaults to - // use the kernel module BTF from a well-known location if nil. - KernelModuleTypes map[string]*btf.Spec + // Additional targets to consider for CO-RE relocations. This can be used to + // pass BTF information for kernel modules when it's not present on + // KernelTypes. + ExtraRelocationTargets []*btf.Spec } // ProgramSpec defines a Program. type ProgramSpec struct { - // Name is passed to the kernel as a debug aid. Must only contain - // alpha numeric and '_' characters. + // Name is passed to the kernel as a debug aid. + // + // Unsupported characters will be stripped. Name string // Type determines at which hook in the kernel a program will run. Type ProgramType + // Network interface index the user intends to attach this program to after + // loading. Only valid for some program types. + // + // Provides driver-specific context about the target interface to the + // verifier, required when using certain BPF helpers. + Ifindex uint32 + // AttachType of the program, needed to differentiate allowed context // accesses in some newer program types like CGroupSockAddr. // @@ -161,19 +176,32 @@ func (ps *ProgramSpec) Copy() *ProgramSpec { // Tag calculates the kernel tag for a series of instructions. // // Use asm.Instructions.Tag if you need to calculate for non-native endianness. +// +// Deprecated: The value produced by this method no longer matches tags produced +// by the kernel since Linux 6.18. Use [ProgramSpec.Compatible] instead. func (ps *ProgramSpec) Tag() (string, error) { return ps.Instructions.Tag(internal.NativeEndian) } -// kernelModule returns the kernel module providing the symbol in -// ProgramSpec.AttachTo, if any. Returns an empty string if the symbol is not -// present or not part of a kernel module. -func (ps *ProgramSpec) kernelModule() (string, error) { - if ps.targetsKernelModule() { - return kallsyms.Module(ps.AttachTo) +// Compatible returns nil if a loaded Program's kernel tag matches the one of +// the ProgramSpec. +// +// Returns [ErrProgIncompatible] if the tags do not match. +func (ps *ProgramSpec) Compatible(info *ProgramInfo) error { + if platform.IsWindows { + return fmt.Errorf("%w: Windows does not support tag readback from kernel", internal.ErrNotSupportedOnOS) } - return "", nil + ok, err := ps.Instructions.HasTag(info.Tag, internal.NativeEndian) + if err != nil { + return err + } + + if !ok { + return fmt.Errorf("%w: ProgramSpec and Program tags do not match", ErrProgIncompatible) + } + + return nil } // targetsKernelModule returns true if the program supports being attached to a @@ -214,6 +242,8 @@ type Program struct { name string pinnedPath string typ ProgramType + + btf *btf.Handle } // NewProgram creates a new Program. @@ -238,7 +268,7 @@ func NewProgramWithOptions(spec *ProgramSpec, opts ProgramOptions) (*Program, er return nil, errors.New("can't load a program from a nil spec") } - prog, err := newProgramWithOptions(spec, opts) + prog, err := newProgramWithOptions(spec, opts, btf.NewCache()) if errors.Is(err, asm.ErrUnsatisfiedMapReference) { return nil, fmt.Errorf("cannot load program without loading its whole collection: %w", err) } @@ -246,15 +276,15 @@ func NewProgramWithOptions(spec *ProgramSpec, opts ProgramOptions) (*Program, er } var ( - coreBadLoad = []byte(fmt.Sprintf("(18) r10 = 0x%x\n", btf.COREBadRelocationSentinel)) + coreBadLoad = fmt.Appendf(nil, "(18) r10 = 0x%x\n", btf.COREBadRelocationSentinel) // This log message was introduced by ebb676daa1a3 ("bpf: Print function name in // addition to function id") which first appeared in v4.10 and has remained // unchanged since. - coreBadCall = []byte(fmt.Sprintf("invalid func unknown#%d\n", btf.COREBadRelocationSentinel)) - kfuncBadCall = []byte(fmt.Sprintf("invalid func unknown#%d\n", kfuncCallPoisonBase)) + coreBadCall = fmt.Appendf(nil, "invalid func unknown#%d\n", btf.COREBadRelocationSentinel) + kfuncBadCall = fmt.Appendf(nil, "invalid func unknown#%d\n", kfuncCallPoisonBase) ) -func newProgramWithOptions(spec *ProgramSpec, opts ProgramOptions) (*Program, error) { +func newProgramWithOptions(spec *ProgramSpec, opts ProgramOptions, c *btf.Cache) (result *Program, _ error) { if len(spec.Instructions) == 0 { return nil, errors.New("instructions cannot be empty") } @@ -280,38 +310,26 @@ func newProgramWithOptions(spec *ProgramSpec, opts ProgramOptions) (*Program, er kv = v.Kernel() } + p, progType := platform.DecodeConstant(spec.Type) + if p != platform.Native { + return nil, fmt.Errorf("program type %s (%s): %w", spec.Type, p, internal.ErrNotSupportedOnOS) + } + attr := &sys.ProgLoadAttr{ - ProgType: sys.ProgType(spec.Type), + ProgName: maybeFillObjName(spec.Name), + ProgType: sys.ProgType(progType), ProgFlags: spec.Flags, + ProgIfindex: spec.Ifindex, ExpectedAttachType: sys.AttachType(spec.AttachType), License: sys.NewStringPointer(spec.License), KernVersion: kv, } - if haveObjName() == nil { - attr.ProgName = sys.NewObjName(spec.Name) - } - insns := make(asm.Instructions, len(spec.Instructions)) copy(insns, spec.Instructions) - kmodName, err := spec.kernelModule() - if err != nil { - return nil, fmt.Errorf("kernel module search: %w", err) - } - - var targets []*btf.Spec - if opts.KernelTypes != nil { - targets = append(targets, opts.KernelTypes) - } - if kmodName != "" && opts.KernelModuleTypes != nil { - if modBTF, ok := opts.KernelModuleTypes[kmodName]; ok { - targets = append(targets, modBTF) - } - } - var b btf.Builder - if err := applyRelocations(insns, targets, kmodName, spec.ByteOrder, &b); err != nil { + if err := applyRelocations(insns, spec.ByteOrder, &b, c, opts.KernelTypes, opts.ExtraRelocationTargets); err != nil { return nil, fmt.Errorf("apply CO-RE relocations: %w", err) } @@ -333,19 +351,25 @@ func newProgramWithOptions(spec *ProgramSpec, opts ProgramOptions) (*Program, er attr.FuncInfoRecSize = btf.FuncInfoSize attr.FuncInfoCnt = uint32(len(fib)) / btf.FuncInfoSize - attr.FuncInfo = sys.NewSlicePointer(fib) + attr.FuncInfo = sys.SlicePointer(fib) attr.LineInfoRecSize = btf.LineInfoSize attr.LineInfoCnt = uint32(len(lib)) / btf.LineInfoSize - attr.LineInfo = sys.NewSlicePointer(lib) + attr.LineInfo = sys.SlicePointer(lib) } + var handle *btf.Handle if !b.Empty() { - handle, err := btf.NewHandle(&b) + var err error + handle, err = btf.NewHandle(&b) if err != nil { return nil, fmt.Errorf("load BTF: %w", err) } - defer handle.Close() + defer func() { + if result == nil || result.btf != handle { + handle.Close() + } + }() attr.ProgBtfFd = uint32(handle.FD()) } @@ -364,7 +388,7 @@ func newProgramWithOptions(spec *ProgramSpec, opts ProgramOptions) (*Program, er return nil, err } - handles, err := fixupKfuncs(insns) + handles, err := fixupKfuncs(insns, c) if err != nil { return nil, fmt.Errorf("fixing up kfuncs: %w", err) } @@ -372,7 +396,7 @@ func newProgramWithOptions(spec *ProgramSpec, opts ProgramOptions) (*Program, er if len(handles) > 0 { fdArray := handles.fdArray() - attr.FdArray = sys.NewPointer(unsafe.Pointer(&fdArray[0])) + attr.FdArray = sys.SlicePointer(fdArray) } buf := bytes.NewBuffer(make([]byte, 0, insns.Size())) @@ -382,7 +406,7 @@ func newProgramWithOptions(spec *ProgramSpec, opts ProgramOptions) (*Program, er } bytecode := buf.Bytes() - attr.Insns = sys.NewSlicePointer(bytecode) + attr.Insns = sys.SlicePointer(bytecode) attr.InsnCnt = uint32(len(bytecode) / asm.InstructionSize) if spec.AttachTarget != nil { @@ -395,73 +419,102 @@ func newProgramWithOptions(spec *ProgramSpec, opts ProgramOptions) (*Program, er attr.AttachBtfObjFd = uint32(spec.AttachTarget.FD()) defer runtime.KeepAlive(spec.AttachTarget) } else if spec.AttachTo != "" { - module, targetID, err := findProgramTargetInKernel(spec.AttachTo, spec.Type, spec.AttachType) + var targetMember string + attachTo := spec.AttachTo + + if spec.Type == StructOps { + attachTo, targetMember, _ = strings.Cut(attachTo, ":") + if targetMember == "" { + return nil, fmt.Errorf("struct_ops: AttachTo must be ':' (got %s)", spec.AttachTo) + } + } + + module, targetID, err := findProgramTargetInKernel(attachTo, spec.Type, spec.AttachType, c) if err != nil && !errors.Is(err, errUnrecognizedAttachType) { // We ignore errUnrecognizedAttachType since AttachTo may be non-empty // for programs that don't attach anywhere. return nil, fmt.Errorf("attach %s/%s: %w", spec.Type, spec.AttachType, err) } + if spec.Type == StructOps { + var s *btf.Spec + + target := btf.Type((*btf.Struct)(nil)) + s, module, err = findTargetInKernel(attachTo, &target, c) + if err != nil { + return nil, fmt.Errorf("lookup struct_ops kern type %q: %w", attachTo, err) + } + kType := target.(*btf.Struct) + + targetID, err = s.TypeID(kType) + if err != nil { + return nil, fmt.Errorf("type id for %s: %w", kType.TypeName(), err) + } + + idx := slices.IndexFunc(kType.Members, func(m btf.Member) bool { + return m.Name == targetMember + }) + if idx < 0 { + return nil, fmt.Errorf("member %q not found in %s", targetMember, kType.Name) + } + + // ExpectedAttachType: index of the target member in the struct + attr.ExpectedAttachType = sys.AttachType(idx) + } + attr.AttachBtfId = targetID - if module != nil { + if module != nil && attr.AttachBtfObjFd == 0 { attr.AttachBtfObjFd = uint32(module.FD()) defer module.Close() } } - // The caller requested a specific verifier log level. Set up the log buffer - // so that there is a chance of loading the program in a single shot. - logSize := internal.Between(opts.LogSizeStart, minVerifierLogSize, maxVerifierLogSize) - var logBuf []byte - if !opts.LogDisabled && opts.LogLevel != 0 { - logBuf = make([]byte, logSize) - attr.LogLevel = opts.LogLevel - attr.LogSize = uint32(len(logBuf)) - attr.LogBuf = sys.NewSlicePointer(logBuf) + if platform.IsWindows && opts.LogLevel != 0 { + return nil, fmt.Errorf("log level: %w", internal.ErrNotSupportedOnOS) } - for { - var fd *sys.FD + var logBuf []byte + var fd *sys.FD + if opts.LogDisabled { + // Loading with logging disabled should never retry. fd, err = sys.ProgLoad(attr) if err == nil { - return &Program{unix.ByteSliceToString(logBuf), fd, spec.Name, "", spec.Type}, nil + return &Program{"", fd, spec.Name, "", spec.Type, handle}, nil } - - if opts.LogDisabled { - break - } - - if attr.LogTrueSize != 0 && attr.LogSize >= attr.LogTrueSize { - // The log buffer already has the correct size. - break + } else { + // Only specify log size if log level is also specified. Setting size + // without level results in EINVAL. Level will be bumped to LogLevelBranch + // if the first load fails. + if opts.LogLevel != 0 { + attr.LogLevel = opts.LogLevel + attr.LogSize = internal.Between(opts.LogSizeStart, minVerifierLogSize, maxVerifierLogSize) } - if attr.LogSize != 0 && !errors.Is(err, unix.ENOSPC) { - // Logging is enabled and the error is not ENOSPC, so we can infer - // that the log buffer is large enough. - break - } + attempts := 1 + for { + if attr.LogLevel != 0 { + logBuf = make([]byte, attr.LogSize) + attr.LogBuf = sys.SlicePointer(logBuf) + } - if attr.LogLevel == 0 { - // Logging is not enabled but loading the program failed. Enable - // basic logging. - attr.LogLevel = LogLevelBranch - } + fd, err = sys.ProgLoad(attr) + if err == nil { + return &Program{unix.ByteSliceToString(logBuf), fd, spec.Name, "", spec.Type, handle}, nil + } - // Make an educated guess how large the buffer should be by multiplying. - // Ensure the size doesn't overflow. - const factor = 2 - logSize = internal.Between(logSize, minVerifierLogSize, maxVerifierLogSize/factor) - logSize *= factor + if !retryLogAttrs(attr, opts.LogSizeStart, err) { + break + } - if attr.LogTrueSize != 0 { - // The kernel has given us a hint how large the log buffer has to be. - logSize = attr.LogTrueSize + if attempts >= maxVerifierAttempts { + return nil, fmt.Errorf("load program: %w (bug: hit %d verifier attempts)", err, maxVerifierAttempts) + } + attempts++ } + } - logBuf = make([]byte, logSize) - attr.LogSize = logSize - attr.LogBuf = sys.NewSlicePointer(logBuf) + if errors.Is(err, sys.ErrTokenCapabilities) { + return nil, fmt.Errorf("load program: %w", err) } end := bytes.IndexByte(logBuf, 0) @@ -511,11 +564,55 @@ func newProgramWithOptions(spec *ProgramSpec, opts ProgramOptions) (*Program, er return nil, internal.ErrorWithLog("load program", err, logBuf) } -// NewProgramFromFD creates a program from a raw fd. +func retryLogAttrs(attr *sys.ProgLoadAttr, startSize uint32, err error) bool { + if attr.LogSize == maxVerifierLogSize { + // Maximum buffer size reached, don't grow or retry. + return false + } + + // ENOSPC means the log was enabled on the previous iteration, so we only + // need to grow the buffer. + if errors.Is(err, unix.ENOSPC) { + if attr.LogTrueSize != 0 { + // Kernel supports LogTrueSize and previous iteration undershot the buffer + // size. Try again with the given true size. + attr.LogSize = attr.LogTrueSize + return true + } + + // Ensure the size doesn't overflow. + const factor = 2 + if attr.LogSize >= maxVerifierLogSize/factor { + attr.LogSize = maxVerifierLogSize + return true + } + + // Make an educated guess how large the buffer should be by multiplying. Due + // to int division, this rounds down odd sizes. + attr.LogSize = internal.Between(attr.LogSize, minVerifierLogSize, maxVerifierLogSize/factor) + attr.LogSize *= factor + + return true + } + + if attr.LogLevel == 0 { + // Loading the program failed, it wasn't a buffer-related error, and the log + // was disabled the previous iteration. Enable basic logging and retry. + attr.LogLevel = LogLevelBranch + attr.LogSize = internal.Between(startSize, minVerifierLogSize, maxVerifierLogSize) + return true + } + + // Loading the program failed for a reason other than buffer size and the log + // was already enabled the previous iteration. Don't retry. + return false +} + +// NewProgramFromFD creates a [Program] around a raw fd. // // You should not use fd after calling this function. // -// Requires at least Linux 4.10. +// Requires at least Linux 4.13. Returns an error on Windows. func NewProgramFromFD(fd int) (*Program, error) { f, err := sys.NewFD(fd) if err != nil { @@ -525,9 +622,10 @@ func NewProgramFromFD(fd int) (*Program, error) { return newProgramFromFD(f) } -// NewProgramFromID returns the program for a given id. +// NewProgramFromID returns the [Program] for a given program id. Returns +// [ErrNotExist] if there is no eBPF program with the given id. // -// Returns ErrNotExist, if there is no eBPF program with the given id. +// Requires at least Linux 4.13. func NewProgramFromID(id ProgramID) (*Program, error) { fd, err := sys.ProgGetFdById(&sys.ProgGetFdByIdAttr{ Id: uint32(id), @@ -540,13 +638,13 @@ func NewProgramFromID(id ProgramID) (*Program, error) { } func newProgramFromFD(fd *sys.FD) (*Program, error) { - info, err := newProgramInfoFromFd(fd) + info, err := minimalProgramInfoFromFd(fd) if err != nil { fd.Close() return nil, fmt.Errorf("discover program type: %w", err) } - return &Program{"", fd, info.Name, "", info.Type}, nil + return &Program{"", fd, info.Name, "", info.Type, nil}, nil } func (p *Program) String() string { @@ -568,11 +666,23 @@ func (p *Program) Info() (*ProgramInfo, error) { return newProgramInfoFromFd(p.fd) } +// Stats returns runtime statistics about the Program. Requires BPF statistics +// collection to be enabled, see [EnableStats]. +// +// Requires at least Linux 5.8. +func (p *Program) Stats() (*ProgramStats, error) { + return newProgramStatsFromFd(p.fd) +} + // Handle returns a reference to the program's type information in the kernel. // // Returns ErrNotSupported if the kernel has no BTF support, or if there is no // BTF associated with the program. func (p *Program) Handle() (*btf.Handle, error) { + if p.btf != nil { + return p.btf.Clone() + } + info, err := p.Info() if err != nil { return nil, err @@ -586,6 +696,48 @@ func (p *Program) Handle() (*btf.Handle, error) { return btf.NewHandleFromID(id) } +// SetHandle caches program's type information so that subsequent +// Handle() call doesn't need to query it from the kernel (requires +// CAP_SYS_ADMIN). +// +// The caller remains responsible for closing the btf.Handle; SetHandle +// makes a private copy. +// +// Type info is auto-cached for Programs loaded by the library itself. +// Use SetHandle() with program references obtained via NewProgramFromFD +// and LoadPinnedProgram. +func (p *Program) SetHandle(h *btf.Handle) error { + if h == nil { + return fmt.Errorf("nil BTF handle") + } + + info, err := p.Info() + if err != nil { + return err + } + + handleInfo, err := h.Info() + if err != nil { + return err + } + + if id, ok := info.BTFID(); !ok || id != handleInfo.ID { + return fmt.Errorf("program/BTF mismatch") + } + + if p.btf != nil { + return nil + } + + dup, err := h.Clone() + if err != nil { + return err + } + + p.btf = dup + return nil +} + // FD gets the file descriptor of the Program. // // It is invalid to call this function after Close has been called. @@ -603,12 +755,18 @@ func (p *Program) Clone() (*Program, error) { return nil, nil } + btfClone, err := p.btf.Clone() + if err != nil { + return nil, fmt.Errorf("can't clone program: %w", err) + } + dup, err := p.fd.Dup() if err != nil { + btfClone.Close() return nil, fmt.Errorf("can't clone program: %w", err) } - return &Program{p.VerifierLog, dup, p.name, "", p.typ}, nil + return &Program{p.VerifierLog, dup, p.name, "", p.typ, btfClone}, nil } // Pin persists the Program on the BPF virtual file system past the lifetime of @@ -653,6 +811,10 @@ func (p *Program) Close() error { return nil } + if err := p.btf.Close(); err != nil { + return err + } + return p.fd.Close() } @@ -666,9 +828,9 @@ type RunOptions struct { // Program's data after Program has run. Caller must allocate. Optional field. DataOut []byte // Program's context input. Optional field. - Context interface{} + Context any // Program's context after Program has run. Must be a pointer or slice. Optional field. - ContextOut interface{} + ContextOut any // Minimum number of times to run Program. Optional field. Defaults to 1. // // The program may be executed more often than this due to interruptions, e.g. @@ -679,6 +841,11 @@ type RunOptions struct { // CPU to run Program on. Optional field. // Note not all program types support this field. CPU uint32 + // BatchSize (default 64) affects the kernel's packet buffer allocation behaviour when running + // programs with BPF_F_TEST_XDP_LIVE_FRAMES and a non-zero [RunOptions.Repeat] value. + // For more details, see the kernel documentation on BPF_PROG_RUN: + // https://docs.kernel.org/bpf/bpf_prog_run.html#running-xdp-programs-in-live-frame-mode + BatchSize uint32 // Called whenever the syscall is interrupted, and should be set to testing.B.ResetTimer // or similar. Typically used during benchmarking. Optional field. // @@ -759,6 +926,10 @@ func (p *Program) Benchmark(in []byte, repeat int, reset func()) (uint32, time.D } var haveProgRun = internal.NewFeatureTest("BPF_PROG_RUN", func() error { + if platform.IsWindows { + return nil + } + prog, err := NewProgram(&ProgramSpec{ // SocketFilter does not require privileges on newer kernels. Type: SocketFilter, @@ -778,7 +949,7 @@ var haveProgRun = internal.NewFeatureTest("BPF_PROG_RUN", func() error { attr := sys.ProgRunAttr{ ProgFd: uint32(prog.FD()), DataSizeIn: uint32(len(in)), - DataIn: sys.NewSlicePointer(in), + DataIn: sys.SlicePointer(in), } err = sys.ProgRun(&attr) @@ -800,7 +971,7 @@ var haveProgRun = internal.NewFeatureTest("BPF_PROG_RUN", func() error { } return err -}, "4.12") +}, "4.12", "windows:0.20") func (p *Program) run(opts *RunOptions) (uint32, time.Duration, error) { if uint(len(opts.Data)) > math.MaxUint32 { @@ -811,33 +982,47 @@ func (p *Program) run(opts *RunOptions) (uint32, time.Duration, error) { return 0, 0, err } - var ctxBytes []byte + var ctxIn []byte if opts.Context != nil { - ctx := new(bytes.Buffer) - if err := binary.Write(ctx, internal.NativeEndian, opts.Context); err != nil { + var err error + ctxIn, err = binary.Append(nil, internal.NativeEndian, opts.Context) + if err != nil { return 0, 0, fmt.Errorf("cannot serialize context: %v", err) } - ctxBytes = ctx.Bytes() } var ctxOut []byte if opts.ContextOut != nil { ctxOut = make([]byte, binary.Size(opts.ContextOut)) + } else if platform.IsWindows && len(ctxIn) > 0 { + // Windows rejects a non-zero ctxIn with a nil ctxOut. + ctxOut = make([]byte, len(ctxIn)) } attr := sys.ProgRunAttr{ ProgFd: p.fd.Uint(), DataSizeIn: uint32(len(opts.Data)), DataSizeOut: uint32(len(opts.DataOut)), - DataIn: sys.NewSlicePointer(opts.Data), - DataOut: sys.NewSlicePointer(opts.DataOut), + DataIn: sys.SlicePointer(opts.Data), + DataOut: sys.SlicePointer(opts.DataOut), Repeat: uint32(opts.Repeat), - CtxSizeIn: uint32(len(ctxBytes)), + CtxSizeIn: uint32(len(ctxIn)), CtxSizeOut: uint32(len(ctxOut)), - CtxIn: sys.NewSlicePointer(ctxBytes), - CtxOut: sys.NewSlicePointer(ctxOut), + CtxIn: sys.SlicePointer(ctxIn), + CtxOut: sys.SlicePointer(ctxOut), Flags: opts.Flags, Cpu: opts.CPU, + BatchSize: opts.BatchSize, + } + + if p.Type() == Syscall && ctxIn != nil && ctxOut != nil { + // Linux syscall program errors on non-nil ctxOut, uses ctxIn + // for both input and output. Shield the user from this wart. + if len(ctxIn) != len(ctxOut) { + return 0, 0, errors.New("length mismatch: Context and ContextOut") + } + attr.CtxOut, attr.CtxSizeOut = sys.TypedPointer[uint8]{}, 0 + ctxOut = ctxIn } retry: @@ -886,7 +1071,7 @@ retry: opts.DataOut = opts.DataOut[:int(attr.DataSizeOut)] } - if len(ctxOut) != 0 { + if opts.ContextOut != nil { b := bytes.NewReader(ctxOut) if err := binary.Read(b, internal.NativeEndian, opts.ContextOut); err != nil { return 0, 0, fmt.Errorf("failed to decode ContextOut: %v", err) @@ -940,36 +1125,16 @@ func LoadPinnedProgram(fileName string, opts *LoadPinOptions) (*Program, error) return nil, fmt.Errorf("%s is not a Program", fileName) } - info, err := newProgramInfoFromFd(fd) - if err != nil { - _ = fd.Close() - return nil, fmt.Errorf("info for %s: %w", fileName, err) - } + p, err := newProgramFromFD(fd) + if err == nil { + p.pinnedPath = fileName - var progName string - if haveObjName() == nil { - progName = info.Name - } else { - progName = filepath.Base(fileName) + if haveObjName() != nil { + p.name = filepath.Base(fileName) + } } - return &Program{"", fd, progName, fileName, info.Type}, nil -} - -// SanitizeName replaces all invalid characters in name with replacement. -// Passing a negative value for replacement will delete characters instead -// of replacing them. Use this to automatically generate valid names for maps -// and programs at runtime. -// -// The set of allowed characters depends on the running kernel version. -// Dots are only allowed as of kernel 5.2. -func SanitizeName(name string, replacement rune) string { - return strings.Map(func(char rune) rune { - if invalidBPFObjNameChar(char) { - return replacement - } - return char - }, name) + return p, err } // ProgramGetNextID returns the ID of the next eBPF program. @@ -1005,7 +1170,7 @@ var errUnrecognizedAttachType = errors.New("unrecognized attach type") // // Returns errUnrecognizedAttachType if the combination of progType and attachType // is not recognised. -func findProgramTargetInKernel(name string, progType ProgramType, attachType AttachType) (*btf.Handle, btf.TypeID, error) { +func findProgramTargetInKernel(name string, progType ProgramType, attachType AttachType, cache *btf.Cache) (*btf.Handle, btf.TypeID, error) { type match struct { p ProgramType a AttachType @@ -1017,6 +1182,10 @@ func findProgramTargetInKernel(name string, progType ProgramType, attachType Att ) switch (match{progType, attachType}) { + case match{StructOps, AttachStructOps}: + typeName = name + featureName = "struct_ops " + name + target = (*btf.Struct)(nil) case match{LSM, AttachLSMMac}: typeName = "bpf_lsm_" + name featureName = name + " LSM hook" @@ -1045,12 +1214,7 @@ func findProgramTargetInKernel(name string, progType ProgramType, attachType Att return nil, 0, errUnrecognizedAttachType } - spec, err := btf.LoadKernelSpec() - if err != nil { - return nil, 0, fmt.Errorf("load kernel spec: %w", err) - } - - spec, module, err := findTargetInKernel(spec, typeName, &target) + spec, module, err := findTargetInKernel(typeName, &target, cache) if errors.Is(err, btf.ErrNotFound) { return nil, 0, &internal.UnsupportedFeatureError{Name: featureName} } @@ -1078,13 +1242,24 @@ func findProgramTargetInKernel(name string, progType ProgramType, attachType Att // target will point at the found type after a successful call. Searches both // vmlinux and any loaded modules. // -// Returns a non-nil handle if the type was found in a module, or btf.ErrNotFound -// if the type wasn't found at all. -func findTargetInKernel(kernelSpec *btf.Spec, typeName string, target *btf.Type) (*btf.Spec, *btf.Handle, error) { - err := kernelSpec.TypeByName(typeName, target) +// Returns a non-nil handle if the type was found in a module, [btf.ErrNotFound] +// if the type wasn't found or if BTF is not enabled. +func findTargetInKernel(typeName string, target *btf.Type, cache *btf.Cache) (*btf.Spec, *btf.Handle, error) { + kernelSpec, err := cache.Kernel() + if err != nil { + return nil, nil, fmt.Errorf("load kernel spec: %w (%w)", btf.ErrNotFound, err) + } + + err = kernelSpec.TypeByName(typeName, target) if errors.Is(err, btf.ErrNotFound) { - spec, module, err := findTargetInModule(kernelSpec, typeName, target) + spec, module, err := findTargetInModule(typeName, target, cache) if err != nil { + // EPERM may be returned when we do not have CAP_SYS_ADMIN. + // Wrap error with btf.ErrNotFound so callers can handle it accordingly. + if errors.Is(err, unix.EPERM) { + return spec, nil, fmt.Errorf("find target in modules: %w (%w)", btf.ErrNotFound, err) + } + return nil, nil, fmt.Errorf("find target in modules: %w", err) } return spec, module, nil @@ -1101,7 +1276,7 @@ func findTargetInKernel(kernelSpec *btf.Spec, typeName string, target *btf.Type) // are searched in the order they were loaded. // // Returns btf.ErrNotFound if the target can't be found in any module. -func findTargetInModule(base *btf.Spec, typeName string, target *btf.Type) (*btf.Spec, *btf.Handle, error) { +func findTargetInModule(typeName string, target *btf.Type, cache *btf.Cache) (*btf.Spec, *btf.Handle, error) { it := new(btf.HandleIterator) defer it.Handle.Close() @@ -1115,7 +1290,7 @@ func findTargetInModule(base *btf.Spec, typeName string, target *btf.Type) (*btf continue } - spec, err := it.Handle.Spec(base) + spec, err := cache.Module(info.Name) if err != nil { return nil, nil, fmt.Errorf("parse types for module %s: %w", info.Name, err) } diff --git a/vendor/github.com/cilium/ebpf/struct_ops.go b/vendor/github.com/cilium/ebpf/struct_ops.go new file mode 100644 index 0000000000..78e4d35b20 --- /dev/null +++ b/vendor/github.com/cilium/ebpf/struct_ops.go @@ -0,0 +1,250 @@ +package ebpf + +import ( + "errors" + "fmt" + "reflect" + "strings" + + "github.com/cilium/ebpf/btf" + "github.com/cilium/ebpf/internal" +) + +const structOpsValuePrefix = "bpf_struct_ops_" +const structOpsLinkSec = ".struct_ops.link" +const structOpsSec = ".struct_ops" +const structOpsKeySize = 4 + +// structOpsMemberLayout describes the location and type of a struct_ops member. +type structOpsMemberLayout struct { + member btf.Member + off int + size int + typ btf.Type +} + +// newStructOpsMemberLayout returns a layout information from a struct_ops member. +func newStructOpsMemberLayout(m btf.Member) (*structOpsMemberLayout, error) { + if m.BitfieldSize > 0 { + return nil, fmt.Errorf("bitfield %s not supported", m.Name) + } + + size, err := btf.Sizeof(m.Type) + if err != nil { + return nil, fmt.Errorf("sizeof(%s): %w", m.Name, err) + } + + off := int(m.Offset.Bytes()) + if off < 0 { + return nil, fmt.Errorf("member %q: invalid offset", m.Name) + } + + return &structOpsMemberLayout{ + member: m, + off: off, + size: size, + typ: btf.UnderlyingType(m.Type), + }, nil +} + +// bytes returns the portion of `buf` corresponding to the member. +func (ml *structOpsMemberLayout) bytes(buf []byte) ([]byte, error) { + if ml.off < 0 || ml.off+ml.size > len(buf) { + return nil, fmt.Errorf("member %q: value buffer too small", ml.member.Name) + } + return buf[ml.off : ml.off+ml.size], nil +} + +// structOpsFuncPtrMember returns an error unless m is a func pointer member. +func structOpsFuncPtrMember(m btf.Member) error { + kmPtr, ok := btf.As[*btf.Pointer](m.Type) + if !ok { + return fmt.Errorf("member %s is not a func pointer", m.Name) + } + if _, isFuncProto := btf.As[*btf.FuncProto](kmPtr.Target); !isFuncProto { + return fmt.Errorf("member %s is not a func pointer", m.Name) + } + return nil +} + +// structOpsFindInnerType returns the "inner" struct inside a value struct_ops type. +// +// Given a value like: +// +// struct bpf_struct_ops_bpf_testmod_ops { +// struct bpf_struct_ops_common common; +// struct bpf_testmod_ops data; +// }; +// +// this function returns the *btf.Struct for "bpf_testmod_ops" along with the +// byte offset of the "data" member inside the value type. +// +// The inner struct name is derived by trimming the "bpf_struct_ops_" prefix +// from the value's name. +func structOpsFindInnerType(vType *btf.Struct) (*btf.Struct, uint32, error) { + innerName := strings.TrimPrefix(vType.Name, structOpsValuePrefix) + + for _, m := range vType.Members { + if st, ok := btf.As[*btf.Struct](m.Type); ok && st.Name == innerName { + return st, m.Offset.Bytes(), nil + } + } + + return nil, 0, fmt.Errorf("inner struct %q not found in %s", innerName, vType.Name) +} + +// structOpsFindTarget resolves the kernel-side "value struct" for a struct_ops map. +func structOpsFindTarget(userType *btf.Struct, cache *btf.Cache) (vType *btf.Struct, id btf.TypeID, module *btf.Handle, err error) { + // the kernel value type name, e.g. "bpf_struct_ops_" + vTypeName := structOpsValuePrefix + userType.Name + + target := btf.Type((*btf.Struct)(nil)) + spec, module, err := findTargetInKernel(vTypeName, &target, cache) + if errors.Is(err, btf.ErrNotFound) { + return nil, 0, nil, fmt.Errorf("%q doesn't exist in kernel: %w", vTypeName, ErrNotSupported) + } + if err != nil { + return nil, 0, nil, fmt.Errorf("lookup value type %q: %w", vTypeName, err) + } + + id, err = spec.TypeID(target) + if err != nil { + return nil, 0, nil, err + } + + return target.(*btf.Struct), id, module, nil +} + +// structOpsPopulateValue writes a `prog FD` which references to `p` into the +// struct_ops value buffer `kernVData` at byte offset `dstOff` corresponding to +// the member `km`. +func structOpsPopulateValue(km btf.Member, kernVData []byte, p *Program) error { + if err := structOpsFuncPtrMember(km); err != nil { + return err + } + + layout, err := newStructOpsMemberLayout(km) + if err != nil { + return err + } + + dst, err := layout.bytes(kernVData) + if err != nil || len(dst) != 8 { + return fmt.Errorf("member %q: value buffer too small for func ptr", km.Name) + } + + internal.NativeEndian.PutUint64(dst, uint64(p.FD())) + return nil +} + +// structOpsValidateMemberPair checks whether `m` can be copied into `km`. +func structOpsValidateMemberPair(m, km btf.Member) (int, error) { + mLayout, err := newStructOpsMemberLayout(m) + if err != nil { + return 0, fmt.Errorf("user member %s: %w", m.Name, err) + } + + kLayout, err := newStructOpsMemberLayout(km) + if err != nil { + return 0, fmt.Errorf("kernel member %s: %w", km.Name, err) + } + + if mLayout.size != kLayout.size { + return 0, fmt.Errorf("size mismatch for %s: user=%d kernel=%d", m.Name, mLayout.size, kLayout.size) + } + + if reflect.TypeOf(mLayout.typ) != reflect.TypeOf(kLayout.typ) { + return 0, fmt.Errorf("unmatched member type %s != %s (kernel)", m.Name, km.Name) + } + + return mLayout.size, nil +} + +// structOpsCopyMemberBytes copies the bytes of `m` into `km`. +func structOpsCopyMemberBytes(m, km btf.Member, data, kernVData []byte, size int) error { + mLayout, err := newStructOpsMemberLayout(m) + if err != nil { + return fmt.Errorf("user member %s: %w", m.Name, err) + } + + kLayout, err := newStructOpsMemberLayout(km) + if err != nil { + return fmt.Errorf("kernel member %s: %w", km.Name, err) + } + + if mLayout.size != size { + return fmt.Errorf("member %q: unexpected validated size %d, got %d", m.Name, size, mLayout.size) + } + if kLayout.size != size { + return fmt.Errorf("member %q: unexpected validated size %d, got %d", km.Name, size, kLayout.size) + } + + src, err := mLayout.bytes(data) + if err != nil { + return fmt.Errorf("member %q: userdata is too small", m.Name) + } + + dst, err := kLayout.bytes(kernVData) + if err != nil { + return fmt.Errorf("member %q: value type is too small", km.Name) + } + + switch mLayout.typ.(type) { + case *btf.Struct, *btf.Union: + if !structOpsIsMemZeroed(src) { + return fmt.Errorf("non-zero nested struct %s: %w", m.Name, ErrNotSupported) + } + return nil + } + + copy(dst, src) + return nil +} + +// structOpsCopyMember copies `m` into `km`. +func structOpsCopyMember(m, km btf.Member, data []byte, kernVData []byte) error { + size, err := structOpsValidateMemberPair(m, km) + if err != nil { + return err + } + return structOpsCopyMemberBytes(m, km, data, kernVData, size) +} + +// structOpsIsMemZeroed() checks whether all bytes in data are zero. +func structOpsIsMemZeroed(data []byte) bool { + for _, b := range data { + if b != 0 { + return false + } + } + return true +} + +// structOpsSetAttachTo sets p.AttachTo in the expected "struct_name:memberName" format +// based on the struct definition. +// +// this relies on the assumption that each member in the +// `.struct_ops` section has a relocation at its starting byte offset. +func structOpsSetAttachTo( + sec *elfSection, + baseOff uint32, + userSt *btf.Struct, + progs map[string]*ProgramSpec) error { + for _, m := range userSt.Members { + memberOff := m.Offset + sym, ok := sec.relocations[uint64(baseOff+memberOff.Bytes())] + if !ok { + continue + } + p, ok := progs[sym.Name] + if !ok || p == nil { + return fmt.Errorf("program %s not found", sym.Name) + } + + if p.Type != StructOps { + return fmt.Errorf("program %s is not StructOps", sym.Name) + } + p.AttachTo = userSt.Name + ":" + m.Name + } + return nil +} diff --git a/vendor/github.com/cilium/ebpf/syscalls.go b/vendor/github.com/cilium/ebpf/syscalls.go index 25c84c3c5c..f0f42b77d3 100644 --- a/vendor/github.com/cilium/ebpf/syscalls.go +++ b/vendor/github.com/cilium/ebpf/syscalls.go @@ -7,10 +7,12 @@ import ( "math" "os" "runtime" + "strings" "github.com/cilium/ebpf/asm" "github.com/cilium/ebpf/internal" "github.com/cilium/ebpf/internal/linux" + "github.com/cilium/ebpf/internal/platform" "github.com/cilium/ebpf/internal/sys" "github.com/cilium/ebpf/internal/tracefs" "github.com/cilium/ebpf/internal/unix" @@ -25,25 +27,41 @@ var ( sysErrNotSupported = sys.Error(ErrNotSupported, sys.ENOTSUPP) ) -// invalidBPFObjNameChar returns true if char may not appear in -// a BPF object name. -func invalidBPFObjNameChar(char rune) bool { - dotAllowed := objNameAllowsDot() == nil - - switch { - case char >= 'A' && char <= 'Z': - return false - case char >= 'a' && char <= 'z': - return false - case char >= '0' && char <= '9': - return false - case dotAllowed && char == '.': - return false - case char == '_': - return false - default: - return true +// sanitizeName replaces all invalid characters in name with replacement. +// Passing a negative value for replacement will delete characters instead +// of replacing them. +// +// The set of allowed characters may change over time. +func sanitizeName(name string, replacement rune) string { + return strings.Map(func(char rune) rune { + switch { + case char >= 'A' && char <= 'Z': + return char + case char >= 'a' && char <= 'z': + return char + case char >= '0' && char <= '9': + return char + case char == '.': + return char + case char == '_': + return char + default: + return replacement + } + }, name) +} + +func maybeFillObjName(name string) sys.ObjName { + if errors.Is(haveObjName(), ErrNotSupported) { + return sys.ObjName{} + } + + name = sanitizeName(name, -1) + if errors.Is(objNameAllowsDot(), ErrNotSupported) { + name = strings.ReplaceAll(name, ".", "") } + + return sys.NewObjName(name) } func progLoad(insns asm.Instructions, typ ProgramType, license string) (*sys.FD, error) { @@ -56,12 +74,17 @@ func progLoad(insns asm.Instructions, typ ProgramType, license string) (*sys.FD, return sys.ProgLoad(&sys.ProgLoadAttr{ ProgType: sys.ProgType(typ), License: sys.NewStringPointer(license), - Insns: sys.NewSlicePointer(bytecode), + Insns: sys.SlicePointer(bytecode), InsnCnt: uint32(len(bytecode) / asm.InstructionSize), }) } var haveNestedMaps = internal.NewFeatureTest("nested maps", func() error { + if platform.IsWindows { + // We only support efW versions which have this feature, no need to probe. + return nil + } + _, err := sys.MapCreate(&sys.MapCreateAttr{ MapType: sys.MapType(ArrayOfMaps), KeySize: 4, @@ -77,7 +100,7 @@ var haveNestedMaps = internal.NewFeatureTest("nested maps", func() error { return nil } return err -}, "4.12") +}, "4.12", "windows:0.21.0") var haveMapMutabilityModifiers = internal.NewFeatureTest("read- and write-only maps", func() error { // This checks BPF_F_RDONLY_PROG and BPF_F_WRONLY_PROG. Since @@ -171,6 +194,11 @@ func wrapMapError(err error) error { } var haveObjName = internal.NewFeatureTest("object names", func() error { + if platform.IsWindows { + // We only support efW versions which have this feature, no need to probe. + return nil + } + attr := sys.MapCreateAttr{ MapType: sys.MapType(Array), KeySize: 4, @@ -179,24 +207,21 @@ var haveObjName = internal.NewFeatureTest("object names", func() error { MapName: sys.NewObjName("feature_test"), } - // Tolerate EPERM as this runs during ELF loading which is potentially - // unprivileged. Only EINVAL is conclusive, thrown from CHECK_ATTR. fd, err := sys.MapCreate(&attr) - if errors.Is(err, unix.EPERM) { - return nil - } - if errors.Is(err, unix.EINVAL) { - return internal.ErrNotSupported - } if err != nil { - return err + return internal.ErrNotSupported } _ = fd.Close() return nil -}, "4.15") +}, "4.15", "windows:0.21.0") var objNameAllowsDot = internal.NewFeatureTest("dot in object names", func() error { + if platform.IsWindows { + // We only support efW versions which have this feature, no need to probe. + return nil + } + if err := haveObjName(); err != nil { return err } @@ -209,23 +234,14 @@ var objNameAllowsDot = internal.NewFeatureTest("dot in object names", func() err MapName: sys.NewObjName(".test"), } - // Tolerate EPERM, otherwise MapSpec.Name has its dots removed when run by - // unprivileged tools. (bpf2go, other code gen). Only EINVAL is conclusive, - // thrown from bpf_obj_name_cpy(). fd, err := sys.MapCreate(&attr) - if errors.Is(err, unix.EPERM) { - return nil - } - if errors.Is(err, unix.EINVAL) { - return internal.ErrNotSupported - } if err != nil { - return err + return internal.ErrNotSupported } _ = fd.Close() return nil -}, "5.2") +}, "5.2", "windows:0.21.0") var haveBatchAPI = internal.NewFeatureTest("map batch api", func() error { var maxEntries uint32 = 2 @@ -337,7 +353,7 @@ var haveProgramExtInfos = internal.NewFeatureTest("program ext_infos", func() er _, err := sys.ProgLoad(&sys.ProgLoadAttr{ ProgType: sys.ProgType(SocketFilter), License: sys.NewStringPointer("MIT"), - Insns: sys.NewSlicePointer(bytecode), + Insns: sys.SlicePointer(bytecode), InsnCnt: uint32(len(bytecode) / asm.InstructionSize), FuncInfoCnt: 1, ProgBtfFd: math.MaxUint32, diff --git a/vendor/github.com/cilium/ebpf/types.go b/vendor/github.com/cilium/ebpf/types.go index d7fb00509d..f87715aaec 100644 --- a/vendor/github.com/cilium/ebpf/types.go +++ b/vendor/github.com/cilium/ebpf/types.go @@ -1,10 +1,11 @@ package ebpf import ( + "github.com/cilium/ebpf/internal/platform" "github.com/cilium/ebpf/internal/sys" ) -//go:generate go run golang.org/x/tools/cmd/stringer@latest -output types_string.go -type=MapType,ProgramType,PinType +//go:generate go tool stringer -output types_string.go -type=MapType,ProgramType,PinType // MapType indicates the type map structure // that will be initialized in the kernel. @@ -12,7 +13,7 @@ type MapType uint32 // All the various map types that can be created const ( - UnspecifiedMap MapType = iota + UnspecifiedMap MapType = MapType(platform.LinuxTag | iota) // Hash is a hash map Hash // Array is an array map @@ -104,27 +105,58 @@ const ( Arena ) +// Map types (Windows). +const ( + WindowsHash MapType = MapType(platform.WindowsTag | iota + 1) + WindowsArray + WindowsProgramArray + WindowsPerCPUHash + WindowsPerCPUArray + WindowsHashOfMaps + WindowsArrayOfMaps + WindowsLRUHash + WindowsLPMTrie + WindowsQueue + WindowsLRUCPUHash + WindowsStack + WindowsRingBuf +) + +// MapTypeForPlatform returns a platform specific map type. +// +// Use this if the library doesn't provide a constant yet. +func MapTypeForPlatform(plat string, typ uint32) (MapType, error) { + return platform.EncodeConstant[MapType](plat, typ) +} + // hasPerCPUValue returns true if the Map stores a value per CPU. func (mt MapType) hasPerCPUValue() bool { - return mt == PerCPUHash || mt == PerCPUArray || mt == LRUCPUHash || mt == PerCPUCGroupStorage + switch mt { + case PerCPUHash, PerCPUArray, LRUCPUHash, PerCPUCGroupStorage: + return true + case WindowsPerCPUHash, WindowsPerCPUArray, WindowsLRUCPUHash: + return true + default: + return false + } } // canStoreMapOrProgram returns true if the Map stores references to another Map // or Program. func (mt MapType) canStoreMapOrProgram() bool { - return mt.canStoreMap() || mt.canStoreProgram() + return mt.canStoreMap() || mt.canStoreProgram() || mt == StructOpsMap } // canStoreMap returns true if the map type accepts a map fd // for update and returns a map id for lookup. func (mt MapType) canStoreMap() bool { - return mt == ArrayOfMaps || mt == HashOfMaps + return mt == ArrayOfMaps || mt == HashOfMaps || mt == WindowsArrayOfMaps || mt == WindowsHashOfMaps } // canStoreProgram returns true if the map type accepts a program fd // for update and returns a program id for lookup. func (mt MapType) canStoreProgram() bool { - return mt == ProgramArray + return mt == ProgramArray || mt == WindowsProgramArray } // canHaveValueSize returns true if the map type supports setting a value size. @@ -142,10 +174,34 @@ func (mt MapType) canHaveValueSize() bool { return true } +// mustHaveNoPrealloc returns true if the map type does not support +// preallocation and needs the BPF_F_NO_PREALLOC flag set to be created +// successfully. +func (mt MapType) mustHaveNoPrealloc() bool { + switch mt { + case CgroupStorage, InodeStorage, TaskStorage, SkStorage: + return true + case LPMTrie: + return true + } + + return false +} + +// mustHaveZeroMaxEntries returns true if the map type requires MaxEntries to be zero. +func (mt MapType) mustHaveZeroMaxEntries() bool { + switch mt { + case CgroupStorage, CGroupStorage, PerCPUCGroupStorage, InodeStorage, TaskStorage, SkStorage: + return true + } + + return false +} + // ProgramType of the eBPF program type ProgramType uint32 -// eBPF program types +// eBPF program types (Linux). const ( UnspecifiedProgram = ProgramType(sys.BPF_PROG_TYPE_UNSPEC) SocketFilter = ProgramType(sys.BPF_PROG_TYPE_SOCKET_FILTER) @@ -182,16 +238,36 @@ const ( Netfilter = ProgramType(sys.BPF_PROG_TYPE_NETFILTER) ) +// eBPF program types (Windows). +// +// See https://github.com/microsoft/ebpf-for-windows/blob/main/include/ebpf_structs.h#L170 +const ( + WindowsXDP ProgramType = ProgramType(platform.WindowsTag) | (iota + 1) + WindowsBind + WindowsCGroupSockAddr + WindowsSockOps + WindowsXDPTest ProgramType = ProgramType(platform.WindowsTag) | 998 + WindowsSample ProgramType = ProgramType(platform.WindowsTag) | 999 +) + +// ProgramTypeForPlatform returns a platform specific program type. +// +// Use this if the library doesn't provide a constant yet. +func ProgramTypeForPlatform(plat string, value uint32) (ProgramType, error) { + return platform.EncodeConstant[ProgramType](plat, value) +} + // AttachType of the eBPF program, needed to differentiate allowed context accesses in // some newer program types like CGroupSockAddr. Should be set to AttachNone if not required. // Will cause invalid argument (EINVAL) at program load time if set incorrectly. type AttachType uint32 -//go:generate go run golang.org/x/tools/cmd/stringer@latest -type AttachType -trimprefix Attach +//go:generate go tool stringer -type AttachType -trimprefix Attach // AttachNone is an alias for AttachCGroupInetIngress for readability reasons. const AttachNone AttachType = 0 +// Attach types (Linux). const ( AttachCGroupInetIngress = AttachType(sys.BPF_CGROUP_INET_INGRESS) AttachCGroupInetEgress = AttachType(sys.BPF_CGROUP_INET_EGRESS) @@ -252,6 +328,28 @@ const ( AttachNetkitPeer = AttachType(sys.BPF_NETKIT_PEER) ) +// Attach types (Windows). +// +// See https://github.com/microsoft/ebpf-for-windows/blob/main/include/ebpf_structs.h#L260 +const ( + AttachWindowsXDP = AttachType(platform.WindowsTag | iota + 1) + AttachWindowsBind + AttachWindowsCGroupInet4Connect + AttachWindowsCGroupInet6Connect + AttachWindowsCgroupInet4RecvAccept + AttachWindowsCgroupInet6RecvAccept + AttachWindowsCGroupSockOps + AttachWindowsSample + AttachWindowsXDPTest +) + +// AttachTypeForPlatform returns a platform specific attach type. +// +// Use this if the library doesn't provide a constant yet. +func AttachTypeForPlatform(plat string, value uint32) (AttachType, error) { + return platform.EncodeConstant[AttachType](plat, value) +} + // AttachFlags of the eBPF program used in BPF_PROG_ATTACH command type AttachFlags uint32 diff --git a/vendor/github.com/cilium/ebpf/types_string.go b/vendor/github.com/cilium/ebpf/types_string.go index f06685112c..94bc2e26cd 100644 --- a/vendor/github.com/cilium/ebpf/types_string.go +++ b/vendor/github.com/cilium/ebpf/types_string.go @@ -42,17 +42,41 @@ func _() { _ = x[UserRingbuf-31] _ = x[CgroupStorage-32] _ = x[Arena-33] + _ = x[WindowsHash-268435457] + _ = x[WindowsArray-268435458] + _ = x[WindowsProgramArray-268435459] + _ = x[WindowsPerCPUHash-268435460] + _ = x[WindowsPerCPUArray-268435461] + _ = x[WindowsHashOfMaps-268435462] + _ = x[WindowsArrayOfMaps-268435463] + _ = x[WindowsLRUHash-268435464] + _ = x[WindowsLPMTrie-268435465] + _ = x[WindowsQueue-268435466] + _ = x[WindowsLRUCPUHash-268435467] + _ = x[WindowsStack-268435468] + _ = x[WindowsRingBuf-268435469] } -const _MapType_name = "UnspecifiedMapHashArrayProgramArrayPerfEventArrayPerCPUHashPerCPUArrayStackTraceCGroupArrayLRUHashLRUCPUHashLPMTrieArrayOfMapsHashOfMapsDevMapSockMapCPUMapXSKMapSockHashCGroupStorageReusePortSockArrayPerCPUCGroupStorageQueueStackSkStorageDevMapHashStructOpsMapRingBufInodeStorageTaskStorageBloomFilterUserRingbufCgroupStorageArena" +const ( + _MapType_name_0 = "UnspecifiedMapHashArrayProgramArrayPerfEventArrayPerCPUHashPerCPUArrayStackTraceCGroupArrayLRUHashLRUCPUHashLPMTrieArrayOfMapsHashOfMapsDevMapSockMapCPUMapXSKMapSockHashCGroupStorageReusePortSockArrayPerCPUCGroupStorageQueueStackSkStorageDevMapHashStructOpsMapRingBufInodeStorageTaskStorageBloomFilterUserRingbufCgroupStorageArena" + _MapType_name_1 = "WindowsHashWindowsArrayWindowsProgramArrayWindowsPerCPUHashWindowsPerCPUArrayWindowsHashOfMapsWindowsArrayOfMapsWindowsLRUHashWindowsLPMTrieWindowsQueueWindowsLRUCPUHashWindowsStackWindowsRingBuf" +) -var _MapType_index = [...]uint16{0, 14, 18, 23, 35, 49, 59, 70, 80, 91, 98, 108, 115, 126, 136, 142, 149, 155, 161, 169, 182, 200, 219, 224, 229, 238, 248, 260, 267, 279, 290, 301, 312, 325, 330} +var ( + _MapType_index_0 = [...]uint16{0, 14, 18, 23, 35, 49, 59, 70, 80, 91, 98, 108, 115, 126, 136, 142, 149, 155, 161, 169, 182, 200, 219, 224, 229, 238, 248, 260, 267, 279, 290, 301, 312, 325, 330} + _MapType_index_1 = [...]uint8{0, 11, 23, 42, 59, 77, 94, 112, 126, 140, 152, 169, 181, 195} +) func (i MapType) String() string { - if i >= MapType(len(_MapType_index)-1) { + switch { + case i <= 33: + return _MapType_name_0[_MapType_index_0[i]:_MapType_index_0[i+1]] + case 268435457 <= i && i <= 268435469: + i -= 268435457 + return _MapType_name_1[_MapType_index_1[i]:_MapType_index_1[i+1]] + default: return "MapType(" + strconv.FormatInt(int64(i), 10) + ")" } - return _MapType_name[_MapType_index[i]:_MapType_index[i+1]] } func _() { // An "invalid array index" compiler error signifies that the constant values have changed. @@ -91,17 +115,39 @@ func _() { _ = x[SkLookup-30] _ = x[Syscall-31] _ = x[Netfilter-32] + _ = x[WindowsXDP-268435457] + _ = x[WindowsBind-268435458] + _ = x[WindowsCGroupSockAddr-268435459] + _ = x[WindowsSockOps-268435460] + _ = x[WindowsXDPTest-268436454] + _ = x[WindowsSample-268436455] } -const _ProgramType_name = "UnspecifiedProgramSocketFilterKprobeSchedCLSSchedACTTracePointXDPPerfEventCGroupSKBCGroupSockLWTInLWTOutLWTXmitSockOpsSkSKBCGroupDeviceSkMsgRawTracepointCGroupSockAddrLWTSeg6LocalLircMode2SkReuseportFlowDissectorCGroupSysctlRawTracepointWritableCGroupSockoptTracingStructOpsExtensionLSMSkLookupSyscallNetfilter" +const ( + _ProgramType_name_0 = "UnspecifiedProgramSocketFilterKprobeSchedCLSSchedACTTracePointXDPPerfEventCGroupSKBCGroupSockLWTInLWTOutLWTXmitSockOpsSkSKBCGroupDeviceSkMsgRawTracepointCGroupSockAddrLWTSeg6LocalLircMode2SkReuseportFlowDissectorCGroupSysctlRawTracepointWritableCGroupSockoptTracingStructOpsExtensionLSMSkLookupSyscallNetfilter" + _ProgramType_name_1 = "WindowsXDPWindowsBindWindowsCGroupSockAddrWindowsSockOps" + _ProgramType_name_2 = "WindowsXDPTestWindowsSample" +) -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, 310} +var ( + _ProgramType_index_0 = [...]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, 310} + _ProgramType_index_1 = [...]uint8{0, 10, 21, 42, 56} + _ProgramType_index_2 = [...]uint8{0, 14, 27} +) func (i ProgramType) String() string { - if i >= ProgramType(len(_ProgramType_index)-1) { + switch { + case i <= 32: + return _ProgramType_name_0[_ProgramType_index_0[i]:_ProgramType_index_0[i+1]] + case 268435457 <= i && i <= 268435460: + i -= 268435457 + return _ProgramType_name_1[_ProgramType_index_1[i]:_ProgramType_index_1[i+1]] + case 268436454 <= i && i <= 268436455: + i -= 268436454 + return _ProgramType_name_2[_ProgramType_index_2[i]:_ProgramType_index_2[i+1]] + default: return "ProgramType(" + strconv.FormatInt(int64(i), 10) + ")" } - return _ProgramType_name[_ProgramType_index[i]:_ProgramType_index[i+1]] } func _() { // An "invalid array index" compiler error signifies that the constant values have changed. @@ -116,8 +162,9 @@ const _PinType_name = "PinNonePinByName" var _PinType_index = [...]uint8{0, 7, 16} func (i PinType) String() string { - if i >= PinType(len(_PinType_index)-1) { + idx := int(i) - 0 + if i < 0 || idx >= len(_PinType_index)-1 { return "PinType(" + strconv.FormatInt(int64(i), 10) + ")" } - return _PinType_name[_PinType_index[i]:_PinType_index[i+1]] + return _PinType_name[_PinType_index[idx]:_PinType_index[idx+1]] } diff --git a/vendor/github.com/cilium/ebpf/types_windows.go b/vendor/github.com/cilium/ebpf/types_windows.go new file mode 100644 index 0000000000..0b7e836b0d --- /dev/null +++ b/vendor/github.com/cilium/ebpf/types_windows.go @@ -0,0 +1,57 @@ +package ebpf + +import ( + "fmt" + "os" + + "golang.org/x/sys/windows" + + "github.com/cilium/ebpf/internal/efw" + "github.com/cilium/ebpf/internal/platform" +) + +// WindowsProgramTypeForGUID resolves a GUID to a ProgramType. +// +// The GUID must be in the form of "{XXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}". +// +// Returns an error wrapping [os.ErrNotExist] if the GUID is not recignized. +func WindowsProgramTypeForGUID(guid string) (ProgramType, error) { + progTypeGUID, err := windows.GUIDFromString(guid) + if err != nil { + return 0, fmt.Errorf("parse GUID: %w", err) + } + + rawProgramType, err := efw.EbpfGetBpfProgramType(progTypeGUID) + if err != nil { + return 0, fmt.Errorf("get program type: %w", err) + } + + if rawProgramType == 0 { + return 0, fmt.Errorf("program type not found for GUID %v: %w", guid, os.ErrNotExist) + } + + return ProgramTypeForPlatform(platform.Windows, rawProgramType) +} + +// WindowsAttachTypeForGUID resolves a GUID to an AttachType. +// +// The GUID must be in the form of "{XXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}". +// +// Returns an error wrapping [os.ErrNotExist] if the GUID is not recignized. +func WindowsAttachTypeForGUID(guid string) (AttachType, error) { + attachTypeGUID, err := windows.GUIDFromString(guid) + if err != nil { + return 0, fmt.Errorf("parse GUID: %w", err) + } + + rawAttachType, err := efw.EbpfGetBpfAttachType(attachTypeGUID) + if err != nil { + return 0, fmt.Errorf("get attach type: %w", err) + } + + if rawAttachType == 0 { + return 0, fmt.Errorf("attach type not found for GUID %v: %w", attachTypeGUID, os.ErrNotExist) + } + + return AttachTypeForPlatform(platform.Windows, rawAttachType) +} diff --git a/vendor/github.com/cilium/ebpf/variable.go b/vendor/github.com/cilium/ebpf/variable.go index 288b173a11..51691bb4fd 100644 --- a/vendor/github.com/cilium/ebpf/variable.go +++ b/vendor/github.com/cilium/ebpf/variable.go @@ -1,8 +1,11 @@ package ebpf import ( + "encoding/binary" "fmt" "io" + "reflect" + "slices" "github.com/cilium/ebpf/btf" "github.com/cilium/ebpf/internal/sysenc" @@ -14,56 +17,52 @@ import ( // All operations on a VariableSpec's underlying MapSpec are performed in the // host's native endianness. type VariableSpec struct { - name string - offset uint64 - size uint64 - - m *MapSpec - t *btf.Var + Name string + // Name of the section this variable was allocated in. + SectionName string + // Offset of the variable within the datasec. + Offset uint32 + // Byte representation of the variable's value. + Value []byte + // Type information of the variable. Optional. + Type *btf.Var } // Set sets the value of the VariableSpec to the provided input using the host's // native endianness. func (s *VariableSpec) Set(in any) error { - buf, err := sysenc.Marshal(in, int(s.size)) - if err != nil { - return fmt.Errorf("marshaling value %s: %w", s.name, err) + size := int(s.Size()) + if size == 0 { + bs := binary.Size(in) + if bs < 0 { + return fmt.Errorf("cannot determine binary size of value %v", in) + } + size = bs } - b, _, err := s.m.dataSection() - if err != nil { - return fmt.Errorf("getting data section of map %s: %w", s.m.Name, err) + if s.Value == nil { + s.Value = make([]byte, size) } - if int(s.offset+s.size) > len(b) { - return fmt.Errorf("offset %d(+%d) for variable %s is out of bounds", s.offset, s.size, s.name) + buf, err := sysenc.Marshal(in, size) + if err != nil { + return fmt.Errorf("marshaling value %s: %w", s.Name, err) } - // MapSpec.Copy() performs a shallow copy. Fully copy the byte slice - // to avoid any changes affecting other copies of the MapSpec. - cpy := make([]byte, len(b)) - copy(cpy, b) - - buf.CopyTo(cpy[s.offset : s.offset+s.size]) - - s.m.Contents[0] = MapKV{Key: uint32(0), Value: cpy} - + buf.CopyTo(s.Value) return nil } // Get writes the value of the VariableSpec to the provided output using the // host's native endianness. +// +// Returns an error if the variable is not initialized or if the unmarshaling fails. func (s *VariableSpec) Get(out any) error { - b, _, err := s.m.dataSection() - if err != nil { - return fmt.Errorf("getting data section of map %s: %w", s.m.Name, err) + if s.Value == nil { + return fmt.Errorf("variable is not initialized") } - if int(s.offset+s.size) > len(b) { - return fmt.Errorf("offset %d(+%d) for variable %s is out of bounds", s.offset, s.size, s.name) - } - - if err := sysenc.Unmarshal(out, b[s.offset:s.offset+s.size]); err != nil { + if err := sysenc.Unmarshal(out, s.Value); err != nil { return fmt.Errorf("unmarshaling value: %w", err) } @@ -71,61 +70,42 @@ func (s *VariableSpec) Get(out any) error { } // Size returns the size of the variable in bytes. -func (s *VariableSpec) Size() uint64 { - return s.size -} +func (s *VariableSpec) Size() uint32 { + if s.Value != nil { + return uint32(len(s.Value)) + } -// MapName returns the name of the underlying MapSpec. -func (s *VariableSpec) MapName() string { - return s.m.Name -} + if s.Type != nil { + size, err := btf.Sizeof(s.Type.Type) + if err != nil { + return 0 + } + return uint32(size) + } -// Offset returns the offset of the variable in the underlying MapSpec. -func (s *VariableSpec) Offset() uint64 { - return s.offset + return 0 } -// Constant returns true if the VariableSpec represents a variable that is -// read-only from the perspective of the BPF program. +// Constant returns true if the variable is located in a data section intended +// for constant values. func (s *VariableSpec) Constant() bool { - return s.m.readOnly() -} - -// Type returns the [btf.Var] representing the variable in its data section. -// This is useful for inspecting the variable's decl tags and the type -// information of the inner type. -// -// Returns nil if the original ELF object did not contain BTF information. -func (s *VariableSpec) Type() *btf.Var { - return s.t + return isConstantDataSection(s.SectionName) } func (s *VariableSpec) String() string { - return fmt.Sprintf("%s (type=%v, map=%s, offset=%d, size=%d)", s.name, s.t, s.m.Name, s.offset, s.size) + return fmt.Sprintf("%s (type=%v, section=%s, offset=%d, size=%d)", s.Name, s.Type, s.SectionName, s.Offset, s.Size()) } -// copy returns a new VariableSpec with the same values as the original, -// but with a different underlying MapSpec. This is useful when copying a -// CollectionSpec. Returns nil if a MapSpec with the same name is not found. -func (s *VariableSpec) copy(cpy *CollectionSpec) *VariableSpec { - out := &VariableSpec{ - name: s.name, - offset: s.offset, - size: s.size, - } - if s.t != nil { - out.t = btf.Copy(s.t).(*btf.Var) - } +// Copy the VariableSpec. +func (s *VariableSpec) Copy() *VariableSpec { + cpy := *s + cpy.Value = slices.Clone(s.Value) - // Attempt to find a MapSpec with the same name in the copied CollectionSpec. - for _, m := range cpy.Maps { - if m.Name == s.m.Name { - out.m = m - return out - } + if s.Type != nil { + cpy.Type = btf.Copy(s.Type).(*btf.Var) } - return nil + return &cpy } // Variable is a convenience wrapper for modifying global variables of a @@ -136,16 +116,16 @@ func (s *VariableSpec) copy(cpy *CollectionSpec) *VariableSpec { // [ErrNotSupported]. type Variable struct { name string - offset uint64 - size uint64 + offset uint32 + size uint32 t *btf.Var mm *Memory } -func newVariable(name string, offset, size uint64, t *btf.Var, mm *Memory) (*Variable, error) { +func newVariable(name string, offset, size uint32, t *btf.Var, mm *Memory) (*Variable, error) { if mm != nil { - if int(offset+size) > mm.Size() { + if !mm.bounds(offset, size) { return nil, fmt.Errorf("offset %d(+%d) is out of bounds", offset, size) } } @@ -160,7 +140,7 @@ func newVariable(name string, offset, size uint64, t *btf.Var, mm *Memory) (*Var } // Size returns the size of the variable. -func (v *Variable) Size() uint64 { +func (v *Variable) Size() uint32 { return v.size } @@ -199,6 +179,10 @@ func (v *Variable) Set(in any) error { return fmt.Errorf("variable %s: %w", v.name, ErrReadOnly) } + if !v.mm.bounds(v.offset, v.size) { + return fmt.Errorf("variable %s: access out of bounds: %w", v.name, io.EOF) + } + buf, err := sysenc.Marshal(in, int(v.size)) if err != nil { return fmt.Errorf("marshaling value %s: %w", v.name, err) @@ -228,3 +212,37 @@ func (v *Variable) Get(out any) error { return nil } + +func checkVariable[T any](v *Variable) error { + if v.ReadOnly() { + return ErrReadOnly + } + + t := reflect.TypeFor[T]() + if t.Kind() == reflect.Uintptr && v.size == 8 { + // uintptr is 8 bytes on 64-bit and 4 on 32-bit. In BPF/BTF, pointers are + // always 8 bytes. For the sake of portability, allow accessing 8-byte BPF + // variables as uintptr on 32-bit systems, since the upper 32 bits of the + // pointer should be zero anyway. + return nil + } + if uintptr(v.size) != t.Size() { + return fmt.Errorf("can't create %d-byte accessor to %d-byte variable: %w", t.Size(), v.size, ErrInvalidType) + } + + return nil +} + +// VariablePointer returns a pointer to a variable of type T backed by memory +// shared with the BPF program. Requires building the Go application with -tags +// ebpf_unsafe_memory_experiment. +// +// T must contain only fixed-size, non-Go-pointer types: bools, floats, +// (u)int[8-64], arrays, and structs containing them. Structs must embed +// [structs.HostLayout]. [ErrInvalidType] is returned if T is not a valid type. +func VariablePointer[T comparable](v *Variable) (*T, error) { + if err := checkVariable[T](v); err != nil { + return nil, fmt.Errorf("variable pointer %s: %w", v.name, err) + } + return memoryPointer[T](v.mm, v.offset) +} diff --git a/vendor/modules.txt b/vendor/modules.txt index b12e418341..4ed1ccb2a2 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -41,18 +41,22 @@ github.com/cespare/xxhash/v2 ## explicit; go 1.20 github.com/checkpoint-restore/go-criu/v7 github.com/checkpoint-restore/go-criu/v7/rpc -# github.com/cilium/ebpf v0.17.3 -## explicit; go 1.22 +# github.com/cilium/ebpf v0.22.0 +## explicit; go 1.25.0 github.com/cilium/ebpf github.com/cilium/ebpf/asm github.com/cilium/ebpf/btf +github.com/cilium/ebpf/features github.com/cilium/ebpf/internal +github.com/cilium/ebpf/internal/efw github.com/cilium/ebpf/internal/kallsyms github.com/cilium/ebpf/internal/kconfig github.com/cilium/ebpf/internal/linux +github.com/cilium/ebpf/internal/mountinfo +github.com/cilium/ebpf/internal/platform github.com/cilium/ebpf/internal/sys github.com/cilium/ebpf/internal/sysenc -github.com/cilium/ebpf/internal/testutils/fdtrace +github.com/cilium/ebpf/internal/testutils/testmain github.com/cilium/ebpf/internal/tracefs github.com/cilium/ebpf/internal/unix github.com/cilium/ebpf/link From 7c477b8c0c17bc73eb2e0a0726f7a537fb271ce1 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 17 Aug 2026 13:46:18 +0000 Subject: [PATCH 2/2] go mod tidy && go mod vendor Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- test/go.mod | 2 +- test/go.sum | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/test/go.mod b/test/go.mod index 5bdb27f48c..b155664db5 100644 --- a/test/go.mod +++ b/test/go.mod @@ -39,7 +39,7 @@ require ( github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/checkpoint-restore/go-criu/v7 v7.2.0 // indirect - github.com/cilium/ebpf v0.17.3 // indirect + github.com/cilium/ebpf v0.22.0 // indirect github.com/containerd/console v1.0.5 // indirect github.com/containerd/continuity v0.5.0 // indirect github.com/containerd/errdefs/pkg v0.3.0 // indirect diff --git a/test/go.sum b/test/go.sum index 20e54455b7..d96ce345a0 100644 --- a/test/go.sum +++ b/test/go.sum @@ -22,8 +22,8 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UF github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/checkpoint-restore/go-criu/v7 v7.2.0 h1:qGiWA4App1gGlEfIJ68WR9jbezV9J7yZdjzglezcqKo= github.com/checkpoint-restore/go-criu/v7 v7.2.0/go.mod h1:u0LCWLg0w4yqqu14aXhiB4YD3a1qd8EcCEg7vda5dwo= -github.com/cilium/ebpf v0.17.3 h1:FnP4r16PWYSE4ux6zN+//jMcW4nMVRvuTLVTvCjyyjg= -github.com/cilium/ebpf v0.17.3/go.mod h1:G5EDHij8yiLzaqn0WjyfJHvRa+3aDlReIaLVRMvOyJk= +github.com/cilium/ebpf v0.22.0 h1:v2ktp0roffpMOj2MMf3idtCQZOsAoC4BJbAJN+ke2bY= +github.com/cilium/ebpf v0.22.0/go.mod h1:CDzZbe2hC5JjlDC+CY3KFCzlYwN4gbxppYM+Z10bQt4= github.com/containerd/cgroups/v3 v3.1.3 h1:eUNflyMddm18+yrDmZPn3jI7C5hJ9ahABE5q6dyLYXQ= github.com/containerd/cgroups/v3 v3.1.3/go.mod h1:PKZ2AcWmSBsY/tJUVhtS/rluX0b1uq1GmPO1ElCmbOw= github.com/containerd/console v1.0.5 h1:R0ymNeydRqH2DmakFNdmjR2k0t7UPuiOV/N/27/qqsc= @@ -96,8 +96,8 @@ github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8= github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI= -github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow= +github.com/go-quicktest/qt v1.101.1-0.20240301121107-c6c8733fa1e6 h1:teYtXy9B7y5lHTp8V9KPxpYRAVA7dozigQcMiBust1s= +github.com/go-quicktest/qt v1.101.1-0.20240301121107-c6c8733fa1e6/go.mod h1:p4lGIVX+8Wa6ZPNDvqcxq36XpUDLh42FLetFU7odllI= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/goccy/go-json v0.10.6 h1:p8HrPJzOakx/mn/bQtjgNjdTcN+/S6FcG2CTtQOrHVU=