whatcanGOwrong

This commit is contained in:
2024-09-19 21:38:24 -04:00
commit d0ae4d841d
17908 changed files with 4096831 additions and 0 deletions
@@ -0,0 +1,19 @@
---
Language: Cpp
BasedOnStyle: LLVM
AlignAfterOpenBracket: DontAlign
AlignConsecutiveAssignments: true
AlignEscapedNewlines: DontAlign
AlwaysBreakBeforeMultilineStrings: true
AlwaysBreakTemplateDeclarations: false
AllowAllParametersOfDeclarationOnNextLine: false
AllowShortFunctionsOnASingleLine: false
BreakBeforeBraces: Attach
IndentWidth: 4
KeepEmptyLinesAtTheStartOfBlocks: false
TabWidth: 4
UseTab: ForContinuationAndIndentation
ColumnLimit: 1000
# Go compiler comments need to stay unindented.
CommentPragmas: '^go:.*'
...
@@ -0,0 +1,23 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: bug
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior. Please include:
```sh
go version
uname -a
cat /etc/issue
```
**Expected behavior**
A clear and concise description of what you expected to happen.
@@ -0,0 +1,7 @@
contact_links:
- name: Questions
url: https://github.com/cilium/ebpf/discussions/categories/q-a
about: Please ask and answer questions here.
- name: Slack
url: https://cilium.slack.com/messages/ebpf-go
about: Join our slack.
Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

@@ -0,0 +1,14 @@
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
*.o
!*_bpf*.o
# Test binary, build with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
@@ -0,0 +1,26 @@
---
issues:
exclude-rules:
# syscall param structs will have unused fields in Go code.
- path: syscall.*.go
linters:
- structcheck
linters:
disable-all: true
enable:
- errcheck
- goimports
- gosimple
- govet
- ineffassign
- misspell
- staticcheck
- typecheck
- unused
- gofmt
# Could be enabled later:
# - gocyclo
# - maligned
# - gosec
@@ -0,0 +1,73 @@
version: v1.0
name: CI Build
agent:
machine:
type: e1-standard-2
os_image: ubuntu2004
auto_cancel:
running:
when: "branch != 'master'"
blocks:
- name: Run tests
task:
prologue:
commands:
- sudo sh -c 'swapoff -a && fallocate -l 2G /swapfile && chmod 0600 /swapfile && mkswap /swapfile && swapon /swapfile'
- sem-version go 1.20.1
- export PATH="$PATH:$(go env GOPATH)/bin"
- checkout
# Disabled, see https://github.com/cilium/ebpf/issues/898
# - cache restore
- go mod tidy
- curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b "$(go env GOPATH)/bin" v1.51.2
- go install gotest.tools/gotestsum@v1.8.1
- sudo pip3 install https://github.com/amluto/virtme/archive/beb85146cd91de37ae455eccb6ab67c393e6e290.zip
- sudo apt-get update
- sudo apt-get install -y --no-install-recommends qemu-system-x86 clang-9 llvm-9
- sudo dmesg -C
epilogue:
always:
commands:
- sudo dmesg
- test-results publish junit.xml
env_vars:
- name: TMPDIR
value: /tmp
- name: CI_MAX_KERNEL_VERSION
value: "5.19"
- name: CI_MIN_CLANG_VERSION
value: "9"
jobs:
- name: Build and Lint
execution_time_limit:
minutes: 10
commands:
- ( export GOOS=darwin; go build ./... && for p in $(go list ./...) ; do go test -c $p || exit ; done )
- ( export GOARCH=arm GOARM=6; go build ./... && for p in $(go list ./...) ; do go test -c $p || exit ; done )
- ( export GOARCH=arm64; go build ./... && for p in $(go list ./...) ; do go test -c $p || exit ; done )
- make clean
- make container-all
- git diff --exit-code || { echo "found unformatted source files, or generated files are not up to date, run 'make'" >&2; false; }
- pushd ./examples
- go build -v -o "$(mktemp -d)" ./...
- popd
- golangci-lint run
- cache store
- name: Run unit tests on previous stable Go
execution_time_limit:
minutes: 10
commands:
- sem-version go 1.19.6
- go test -v ./cmd/bpf2go
- gotestsum --raw-command --ignore-non-json-output-lines --junitfile junit.xml -- ./run-tests.sh $CI_MAX_KERNEL_VERSION -short -count 1 -json ./...
- name: Run unit tests
execution_time_limit:
minutes: 10
matrix:
- env_var: KERNEL_VERSION
values: ["5.19", "5.15", "5.10", "5.4", "4.19", "4.14", "4.9"]
commands:
- gotestsum --raw-command --ignore-non-json-output-lines --junitfile junit.xml -- ./run-tests.sh $KERNEL_VERSION -short -count 1 -json ./...
@@ -0,0 +1,92 @@
Architecture of the library
===
```mermaid
graph RL
Program --> ProgramSpec --> ELF
btf.Spec --> ELF
Map --> MapSpec --> ELF
Links --> Map & Program
ProgramSpec -.-> btf.Spec
MapSpec -.-> btf.Spec
subgraph Collection
Program & Map
end
subgraph CollectionSpec
ProgramSpec & MapSpec & btf.Spec
end
```
ELF
---
BPF is usually produced by using Clang to compile a subset of C. Clang outputs
an ELF file which contains program byte code (aka BPF), but also metadata for
maps used by the program. The metadata follows the conventions set by libbpf
shipped with the kernel. Certain ELF sections have special meaning
and contain structures defined by libbpf. Newer versions of clang emit
additional metadata in [BPF Type Format](#BTF).
The library aims to be compatible with libbpf so that moving from a C toolchain
to a Go one creates little friction. To that end, the [ELF reader](elf_reader.go)
is tested against the Linux selftests and avoids introducing custom behaviour
if possible.
The output of the ELF reader is a `CollectionSpec` which encodes
all of the information contained in the ELF in a form that is easy to work with
in Go. The returned `CollectionSpec` should be deterministic: reading the same ELF
file on different systems must produce the same output.
As a corollary, any changes that depend on the runtime environment like the
current kernel version must happen when creating [Objects](#Objects).
Specifications
---
`CollectionSpec` is a very simple container for `ProgramSpec`, `MapSpec` and
`btf.Spec`. Avoid adding functionality to it if possible.
`ProgramSpec` and `MapSpec` are blueprints for in-kernel
objects and contain everything necessary to execute the relevant `bpf(2)`
syscalls. They refer to `btf.Spec` for type information such as `Map` key and
value types.
The [asm](asm/) package provides an assembler that can be used to generate
`ProgramSpec` on the fly.
Objects
---
`Program` and `Map` are the result of loading specifications into the kernel.
Features that depend on knowledge of the current system (e.g kernel version)
are implemented at this point.
Sometimes loading a spec will fail because the kernel is too old, or a feature is not
enabled. There are multiple ways the library deals with that:
* Fallback: older kernels don't allow naming programs and maps. The library
automatically detects support for names, and omits them during load if
necessary. This works since name is primarily a debug aid.
* Sentinel error: sometimes it's possible to detect that a feature isn't available.
In that case the library will return an error wrapping `ErrNotSupported`.
This is also useful to skip tests that can't run on the current kernel.
Once program and map objects are loaded they expose the kernel's low-level API,
e.g. `NextKey`. Often this API is awkward to use in Go, so there are safer
wrappers on top of the low-level API, like `MapIterator`. The low-level API is
useful when our higher-level API doesn't support a particular use case.
Links
---
Programs can be attached to many different points in the kernel and newer BPF hooks
tend to use bpf_link to do so. Older hooks unfortunately use a combination of
syscalls, netlink messages, etc. Adding support for a new link type should not
pull in large dependencies like netlink, so XDP programs or tracepoints are
out of scope.
Each bpf_link_type has one corresponding Go type, e.g. `link.tracing` corresponds
to BPF_LINK_TRACING. In general, these types should be unexported as long as they
don't export methods outside of the Link interface. Each Go type may have multiple
exported constructors. For example `AttachTracing` and `AttachLSM` create a
tracing link, but are distinct functions since they may require different arguments.
@@ -0,0 +1,46 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at nathanjsweet at gmail dot com or i at lmb dot io. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/
@@ -0,0 +1,48 @@
# How to contribute
Development is on [GitHub](https://github.com/cilium/ebpf) and contributions in
the form of pull requests and issues reporting bugs or suggesting new features
are welcome. Please take a look at [the architecture](ARCHITECTURE.md) to get
a better understanding for the high-level goals.
## Adding a new feature
1. [Join](https://ebpf.io/slack) the
[#ebpf-go](https://cilium.slack.com/messages/ebpf-go) channel to discuss your requirements and how the feature can be implemented. The most important part is figuring out how much new exported API is necessary. **The less new API is required the easier it will be to land the feature.**
2. (*optional*) Create a draft PR if you want to discuss the implementation or have hit a problem. It's fine if this doesn't compile or contains debug statements.
3. Create a PR that is ready to merge. This must pass CI and have tests.
### API stability
The library doesn't guarantee the stability of its API at the moment.
1. If possible avoid breakage by introducing new API and deprecating the old one
at the same time. If an API was deprecated in v0.x it can be removed in v0.x+1.
2. Breaking API in a way that causes compilation failures is acceptable but must
have good reasons.
3. Changing the semantics of the API without causing compilation failures is
heavily discouraged.
## Running the tests
Many of the tests require privileges to set resource limits and load eBPF code.
The easiest way to obtain these is to run the tests with `sudo`.
To test the current package with your local kernel you can simply run:
```
go test -exec sudo ./...
```
To test the current package with a different kernel version you can use the [run-tests.sh](run-tests.sh) script.
It requires [virtme](https://github.com/amluto/virtme) and qemu to be installed.
Examples:
```bash
# Run all tests on a 5.4 kernel
./run-tests.sh 5.4
# Run a subset of tests:
./run-tests.sh 5.4 ./link
```
@@ -0,0 +1,23 @@
MIT License
Copyright (c) 2017 Nathan Sweet
Copyright (c) 2018, 2019 Cloudflare
Copyright (c) 2019 Authors of Cilium
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
@@ -0,0 +1,3 @@
# Maintainers
Maintainers can be found in the [Cilium Maintainers file](https://github.com/cilium/community/blob/main/roles/Maintainers.md)
@@ -0,0 +1,115 @@
# 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-14
STRIP ?= llvm-strip-14
OBJCOPY ?= llvm-objcopy-14
CFLAGS := -O2 -g -Wall -Werror $(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}")
IMAGE := $(shell cat ${REPODIR}/testdata/docker/IMAGE)
VERSION := $(shell cat ${REPODIR}/testdata/docker/VERSION)
# clang <8 doesn't tag relocs properly (STT_NOTYPE)
# clang 9 is the first version emitting BTF
TARGETS := \
testdata/loader-clang-7 \
testdata/loader-clang-9 \
testdata/loader-$(CLANG) \
testdata/manyprogs \
testdata/btf_map_init \
testdata/invalid_map \
testdata/raw_tracepoint \
testdata/invalid_map_static \
testdata/invalid_btf_map_init \
testdata/strings \
testdata/freplace \
testdata/iproute2_map_compat \
testdata/map_spin_lock \
testdata/subprog_reloc \
testdata/fwd_decl \
testdata/kconfig \
testdata/kconfig_config \
testdata/kfunc \
testdata/invalid-kfunc \
testdata/kfunc-kmod \
btf/testdata/relocs \
btf/testdata/relocs_read \
btf/testdata/relocs_read_tgt \
cmd/bpf2go/testdata/minimal
.PHONY: all clean container-all container-shell generate
.DEFAULT_TARGET = container-all
# Build all ELF binaries using a containerized LLVM toolchain.
container-all:
+${CONTAINER_ENGINE} run --rm -ti ${CONTAINER_RUN_ARGS} \
-v "${REPODIR}":/ebpf -w /ebpf --env MAKEFLAGS \
--env CFLAGS="-fdebug-prefix-map=/ebpf=." \
--env HOME="/tmp" \
"${IMAGE}:${VERSION}" \
make all
# (debug) Drop the user into a shell inside the container as root.
container-shell:
${CONTAINER_ENGINE} run --rm -ti \
-v "${REPODIR}":/ebpf -w /ebpf \
"${IMAGE}:${VERSION}"
clean:
-$(RM) testdata/*.elf
-$(RM) btf/testdata/*.elf
format:
find . -type f -name "*.c" | xargs clang-format -i
all: format $(addsuffix -el.elf,$(TARGETS)) $(addsuffix -eb.elf,$(TARGETS)) generate
ln -srf testdata/loader-$(CLANG)-el.elf testdata/loader-el.elf
ln -srf testdata/loader-$(CLANG)-eb.elf testdata/loader-eb.elf
# $BPF_CLANG is used in go:generate invocations.
generate: export BPF_CLANG := $(CLANG)
generate: export BPF_CFLAGS := $(CFLAGS)
generate:
go generate ./...
testdata/loader-%-el.elf: testdata/loader.c
$* $(CFLAGS) -target bpfel -c $< -o $@
$(STRIP) -g $@
testdata/loader-%-eb.elf: testdata/loader.c
$* $(CFLAGS) -target bpfeb -c $< -o $@
$(STRIP) -g $@
%-el.elf: %.c
$(CLANG) $(CFLAGS) -target bpfel -c $< -o $@
$(STRIP) -g $@
%-eb.elf : %.c
$(CLANG) $(CFLAGS) -target bpfeb -c $< -o $@
$(STRIP) -g $@
.PHONY: generate-btf
generate-btf: KERNEL_VERSION?=5.19
generate-btf:
$(eval TMP := $(shell mktemp -d))
curl -fL "$(CI_KERNEL_URL)/linux-$(KERNEL_VERSION).bz" -o "$(TMP)/bzImage"
/lib/modules/$(uname -r)/build/scripts/extract-vmlinux "$(TMP)/bzImage" > "$(TMP)/vmlinux"
$(OBJCOPY) --dump-section .BTF=/dev/stdout "$(TMP)/vmlinux" /dev/null | gzip > "btf/testdata/vmlinux.btf.gz"
curl -fL "$(CI_KERNEL_URL)/linux-$(KERNEL_VERSION)-selftests-bpf.tgz" -o "$(TMP)/selftests.tgz"
tar -xf "$(TMP)/selftests.tgz" --to-stdout tools/testing/selftests/bpf/bpf_testmod/bpf_testmod.ko | \
$(OBJCOPY) --dump-section .BTF="btf/testdata/btf_testmod.btf" - /dev/null
$(RM) -r "$(TMP)"
@@ -0,0 +1,82 @@
# eBPF
[![PkgGoDev](https://pkg.go.dev/badge/github.com/cilium/ebpf)](https://pkg.go.dev/github.com/cilium/ebpf)
![HoneyGopher](.github/images/cilium-ebpf.png)
ebpf-go is a pure Go library that provides utilities for loading, compiling, and
debugging eBPF programs. It has minimal external dependencies and is intended to
be used in long running processes.
See [ebpf.io](https://ebpf.io) for complementary projects from the wider eBPF
ecosystem.
## Getting Started
A small collection of Go and eBPF programs that serve as examples for building
your own tools can be found under [examples/](examples/).
[Contributions](CONTRIBUTING.md) are highly encouraged, as they highlight certain use cases of
eBPF and the library, and help shape the future of the project.
## Getting Help
The community actively monitors our [GitHub Discussions](https://github.com/cilium/ebpf/discussions) page.
Please search for existing threads before starting a new one. Refrain from
opening issues on the bug tracker if you're just starting out or if you're not
sure if something is a bug in the library code.
Alternatively, [join](https://ebpf.io/slack) the
[#ebpf-go](https://cilium.slack.com/messages/ebpf-go) channel on Slack if you
have other questions regarding the project. Note that this channel is ephemeral
and has its history erased past a certain point, which is less helpful for
others running into the same problem later.
## Packages
This library includes the following packages:
* [asm](https://pkg.go.dev/github.com/cilium/ebpf/asm) contains a basic
assembler, allowing you to write eBPF assembly instructions directly
within your Go code. (You don't need to use this if you prefer to write your eBPF program in C.)
* [cmd/bpf2go](https://pkg.go.dev/github.com/cilium/ebpf/cmd/bpf2go) allows
compiling and embedding eBPF programs written in C within Go code. As well as
compiling the C code, it auto-generates Go code for loading and manipulating
the eBPF program and map objects.
* [link](https://pkg.go.dev/github.com/cilium/ebpf/link) allows attaching eBPF
to various hooks
* [perf](https://pkg.go.dev/github.com/cilium/ebpf/perf) allows reading from a
`PERF_EVENT_ARRAY`
* [ringbuf](https://pkg.go.dev/github.com/cilium/ebpf/ringbuf) allows reading from a
`BPF_MAP_TYPE_RINGBUF` map
* [features](https://pkg.go.dev/github.com/cilium/ebpf/features) implements the equivalent
of `bpftool feature probe` for discovering BPF-related kernel features using native Go.
* [rlimit](https://pkg.go.dev/github.com/cilium/ebpf/rlimit) provides a convenient API to lift
the `RLIMIT_MEMLOCK` constraint on kernels before 5.11.
* [btf](https://pkg.go.dev/github.com/cilium/ebpf/btf) allows reading the BPF Type Format.
## Requirements
* A version of Go that is [supported by
upstream](https://golang.org/doc/devel/release.html#policy)
* Linux >= 4.9. CI is run against kernel.org LTS releases. 4.4 should work but is
not tested against.
## Regenerating Testdata
Run `make` in the root of this repository to rebuild testdata in all
subpackages. This requires Docker, as it relies on a standardized build
environment to keep the build output stable.
It is possible to regenerate data using Podman by overriding the `CONTAINER_*`
variables: `CONTAINER_ENGINE=podman CONTAINER_RUN_ARGS= make`.
The toolchain image build files are kept in [testdata/docker/](testdata/docker/).
## License
MIT
### eBPF Gopher
The eBPF honeygopher is based on the Go gopher designed by Renee French.
@@ -0,0 +1,149 @@
package asm
//go:generate stringer -output alu_string.go -type=Source,Endianness,ALUOp
// Source of ALU / ALU64 / Branch operations
//
// msb lsb
// +----+-+---+
// |op |S|cls|
// +----+-+---+
type Source uint8
const sourceMask OpCode = 0x08
// Source bitmask
const (
// InvalidSource is returned by getters when invoked
// on non ALU / branch OpCodes.
InvalidSource Source = 0xff
// ImmSource src is from constant
ImmSource Source = 0x00
// RegSource src is from register
RegSource Source = 0x08
)
// The Endianness of a byte swap instruction.
type Endianness uint8
const endianMask = sourceMask
// Endian flags
const (
InvalidEndian Endianness = 0xff
// Convert to little endian
LE Endianness = 0x00
// Convert to big endian
BE Endianness = 0x08
)
// ALUOp are ALU / ALU64 operations
//
// msb lsb
// +----+-+---+
// |OP |s|cls|
// +----+-+---+
type ALUOp uint8
const aluMask OpCode = 0xf0
const (
// InvalidALUOp is returned by getters when invoked
// on non ALU OpCodes
InvalidALUOp ALUOp = 0xff
// Add - addition
Add ALUOp = 0x00
// Sub - subtraction
Sub ALUOp = 0x10
// Mul - multiplication
Mul ALUOp = 0x20
// Div - division
Div ALUOp = 0x30
// Or - bitwise or
Or ALUOp = 0x40
// And - bitwise and
And ALUOp = 0x50
// LSh - bitwise shift left
LSh ALUOp = 0x60
// RSh - bitwise shift right
RSh ALUOp = 0x70
// Neg - sign/unsign signing bit
Neg ALUOp = 0x80
// Mod - modulo
Mod ALUOp = 0x90
// Xor - bitwise xor
Xor ALUOp = 0xa0
// Mov - move value from one place to another
Mov ALUOp = 0xb0
// ArSh - arithmatic shift
ArSh ALUOp = 0xc0
// Swap - endian conversions
Swap ALUOp = 0xd0
)
// HostTo converts from host to another endianness.
func HostTo(endian Endianness, dst Register, size Size) Instruction {
var imm int64
switch size {
case Half:
imm = 16
case Word:
imm = 32
case DWord:
imm = 64
default:
return Instruction{OpCode: InvalidOpCode}
}
return Instruction{
OpCode: OpCode(ALUClass).SetALUOp(Swap).SetSource(Source(endian)),
Dst: dst,
Constant: imm,
}
}
// Op returns the OpCode for an ALU operation with a given source.
func (op ALUOp) Op(source Source) OpCode {
return OpCode(ALU64Class).SetALUOp(op).SetSource(source)
}
// Reg emits `dst (op) src`.
func (op ALUOp) Reg(dst, src Register) Instruction {
return Instruction{
OpCode: op.Op(RegSource),
Dst: dst,
Src: src,
}
}
// Imm emits `dst (op) value`.
func (op ALUOp) Imm(dst Register, value int32) Instruction {
return Instruction{
OpCode: op.Op(ImmSource),
Dst: dst,
Constant: int64(value),
}
}
// Op32 returns the OpCode for a 32-bit ALU operation with a given source.
func (op ALUOp) Op32(source Source) OpCode {
return OpCode(ALUClass).SetALUOp(op).SetSource(source)
}
// Reg32 emits `dst (op) src`, zeroing the upper 32 bit of dst.
func (op ALUOp) Reg32(dst, src Register) Instruction {
return Instruction{
OpCode: op.Op32(RegSource),
Dst: dst,
Src: src,
}
}
// Imm32 emits `dst (op) value`, zeroing the upper 32 bit of dst.
func (op ALUOp) Imm32(dst Register, value int32) Instruction {
return Instruction{
OpCode: op.Op32(ImmSource),
Dst: dst,
Constant: int64(value),
}
}
@@ -0,0 +1,107 @@
// Code generated by "stringer -output alu_string.go -type=Source,Endianness,ALUOp"; DO NOT EDIT.
package asm
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[InvalidSource-255]
_ = x[ImmSource-0]
_ = x[RegSource-8]
}
const (
_Source_name_0 = "ImmSource"
_Source_name_1 = "RegSource"
_Source_name_2 = "InvalidSource"
)
func (i Source) String() string {
switch {
case i == 0:
return _Source_name_0
case i == 8:
return _Source_name_1
case i == 255:
return _Source_name_2
default:
return "Source(" + strconv.FormatInt(int64(i), 10) + ")"
}
}
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[InvalidEndian-255]
_ = x[LE-0]
_ = x[BE-8]
}
const (
_Endianness_name_0 = "LE"
_Endianness_name_1 = "BE"
_Endianness_name_2 = "InvalidEndian"
)
func (i Endianness) String() string {
switch {
case i == 0:
return _Endianness_name_0
case i == 8:
return _Endianness_name_1
case i == 255:
return _Endianness_name_2
default:
return "Endianness(" + strconv.FormatInt(int64(i), 10) + ")"
}
}
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[InvalidALUOp-255]
_ = x[Add-0]
_ = x[Sub-16]
_ = x[Mul-32]
_ = x[Div-48]
_ = x[Or-64]
_ = x[And-80]
_ = x[LSh-96]
_ = x[RSh-112]
_ = x[Neg-128]
_ = x[Mod-144]
_ = x[Xor-160]
_ = x[Mov-176]
_ = x[ArSh-192]
_ = x[Swap-208]
}
const _ALUOp_name = "AddSubMulDivOrAndLShRShNegModXorMovArShSwapInvalidALUOp"
var _ALUOp_map = map[ALUOp]string{
0: _ALUOp_name[0:3],
16: _ALUOp_name[3:6],
32: _ALUOp_name[6:9],
48: _ALUOp_name[9:12],
64: _ALUOp_name[12:14],
80: _ALUOp_name[14:17],
96: _ALUOp_name[17:20],
112: _ALUOp_name[20:23],
128: _ALUOp_name[23:26],
144: _ALUOp_name[26:29],
160: _ALUOp_name[29:32],
176: _ALUOp_name[32:35],
192: _ALUOp_name[35:39],
208: _ALUOp_name[39:43],
255: _ALUOp_name[43:55],
}
func (i ALUOp) String() string {
if str, ok := _ALUOp_map[i]; ok {
return str
}
return "ALUOp(" + strconv.FormatInt(int64(i), 10) + ")"
}
@@ -0,0 +1,2 @@
// Package asm is an assembler for eBPF bytecode.
package asm
@@ -0,0 +1,46 @@
package asm
import (
"testing"
)
func TestDSL(t *testing.T) {
testcases := []struct {
name string
have Instruction
want Instruction
}{
{"Call", FnMapLookupElem.Call(), Instruction{OpCode: 0x85, Constant: 1}},
{"Exit", Return(), Instruction{OpCode: 0x95}},
{"LoadAbs", LoadAbs(2, Byte), Instruction{OpCode: 0x30, Constant: 2}},
{"Store", StoreMem(RFP, -4, R0, Word), Instruction{
OpCode: 0x63,
Dst: RFP,
Src: R0,
Offset: -4,
}},
{"Add.Imm", Add.Imm(R1, 22), Instruction{OpCode: 0x07, Dst: R1, Constant: 22}},
{"Add.Reg", Add.Reg(R1, R2), Instruction{OpCode: 0x0f, Dst: R1, Src: R2}},
{"Add.Imm32", Add.Imm32(R1, 22), Instruction{
OpCode: 0x04, Dst: R1, Constant: 22,
}},
{"JSGT.Imm", JSGT.Imm(R1, 4, "foo"), Instruction{
OpCode: 0x65, Dst: R1, Constant: 4, Offset: -1,
}.WithReference("foo")},
{"JSGT.Imm32", JSGT.Imm32(R1, -2, "foo"), Instruction{
OpCode: 0x66, Dst: R1, Constant: -2, Offset: -1,
}.WithReference("foo")},
{"JSLT.Reg", JSLT.Reg(R1, R2, "foo"), Instruction{
OpCode: 0xcd, Dst: R1, Src: R2, Offset: -1,
}.WithReference("foo")},
{"JSLT.Reg32", JSLT.Reg32(R1, R3, "foo"), Instruction{
OpCode: 0xce, Dst: R1, Src: R3, Offset: -1,
}.WithReference("foo")},
}
for _, tc := range testcases {
if !tc.have.equal(tc.want) {
t.Errorf("%s: have %v, want %v", tc.name, tc.have, tc.want)
}
}
}
@@ -0,0 +1,250 @@
package asm
//go:generate stringer -output func_string.go -type=BuiltinFunc
// BuiltinFunc is a built-in eBPF function.
type BuiltinFunc int32
func (_ BuiltinFunc) Max() BuiltinFunc {
return maxBuiltinFunc - 1
}
// eBPF built-in functions
//
// You can regenerate this list using the following gawk script:
//
// /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
maxBuiltinFunc
)
// Call emits a function call.
func (fn BuiltinFunc) Call() Instruction {
return Instruction{
OpCode: OpCode(JumpClass).SetJumpOp(Call),
Constant: int64(fn),
}
}
@@ -0,0 +1,235 @@
// Code generated by "stringer -output func_string.go -type=BuiltinFunc"; DO NOT EDIT.
package asm
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[FnUnspec-0]
_ = x[FnMapLookupElem-1]
_ = x[FnMapUpdateElem-2]
_ = x[FnMapDeleteElem-3]
_ = x[FnProbeRead-4]
_ = x[FnKtimeGetNs-5]
_ = x[FnTracePrintk-6]
_ = x[FnGetPrandomU32-7]
_ = x[FnGetSmpProcessorId-8]
_ = x[FnSkbStoreBytes-9]
_ = x[FnL3CsumReplace-10]
_ = x[FnL4CsumReplace-11]
_ = x[FnTailCall-12]
_ = x[FnCloneRedirect-13]
_ = x[FnGetCurrentPidTgid-14]
_ = x[FnGetCurrentUidGid-15]
_ = x[FnGetCurrentComm-16]
_ = x[FnGetCgroupClassid-17]
_ = x[FnSkbVlanPush-18]
_ = x[FnSkbVlanPop-19]
_ = x[FnSkbGetTunnelKey-20]
_ = x[FnSkbSetTunnelKey-21]
_ = x[FnPerfEventRead-22]
_ = x[FnRedirect-23]
_ = x[FnGetRouteRealm-24]
_ = x[FnPerfEventOutput-25]
_ = x[FnSkbLoadBytes-26]
_ = x[FnGetStackid-27]
_ = x[FnCsumDiff-28]
_ = x[FnSkbGetTunnelOpt-29]
_ = x[FnSkbSetTunnelOpt-30]
_ = x[FnSkbChangeProto-31]
_ = x[FnSkbChangeType-32]
_ = x[FnSkbUnderCgroup-33]
_ = x[FnGetHashRecalc-34]
_ = x[FnGetCurrentTask-35]
_ = x[FnProbeWriteUser-36]
_ = x[FnCurrentTaskUnderCgroup-37]
_ = x[FnSkbChangeTail-38]
_ = x[FnSkbPullData-39]
_ = x[FnCsumUpdate-40]
_ = x[FnSetHashInvalid-41]
_ = x[FnGetNumaNodeId-42]
_ = x[FnSkbChangeHead-43]
_ = x[FnXdpAdjustHead-44]
_ = x[FnProbeReadStr-45]
_ = x[FnGetSocketCookie-46]
_ = x[FnGetSocketUid-47]
_ = x[FnSetHash-48]
_ = x[FnSetsockopt-49]
_ = x[FnSkbAdjustRoom-50]
_ = x[FnRedirectMap-51]
_ = x[FnSkRedirectMap-52]
_ = x[FnSockMapUpdate-53]
_ = x[FnXdpAdjustMeta-54]
_ = x[FnPerfEventReadValue-55]
_ = x[FnPerfProgReadValue-56]
_ = x[FnGetsockopt-57]
_ = x[FnOverrideReturn-58]
_ = x[FnSockOpsCbFlagsSet-59]
_ = x[FnMsgRedirectMap-60]
_ = x[FnMsgApplyBytes-61]
_ = x[FnMsgCorkBytes-62]
_ = x[FnMsgPullData-63]
_ = x[FnBind-64]
_ = x[FnXdpAdjustTail-65]
_ = x[FnSkbGetXfrmState-66]
_ = x[FnGetStack-67]
_ = x[FnSkbLoadBytesRelative-68]
_ = x[FnFibLookup-69]
_ = x[FnSockHashUpdate-70]
_ = x[FnMsgRedirectHash-71]
_ = x[FnSkRedirectHash-72]
_ = x[FnLwtPushEncap-73]
_ = x[FnLwtSeg6StoreBytes-74]
_ = x[FnLwtSeg6AdjustSrh-75]
_ = x[FnLwtSeg6Action-76]
_ = x[FnRcRepeat-77]
_ = x[FnRcKeydown-78]
_ = x[FnSkbCgroupId-79]
_ = x[FnGetCurrentCgroupId-80]
_ = x[FnGetLocalStorage-81]
_ = x[FnSkSelectReuseport-82]
_ = x[FnSkbAncestorCgroupId-83]
_ = x[FnSkLookupTcp-84]
_ = x[FnSkLookupUdp-85]
_ = x[FnSkRelease-86]
_ = x[FnMapPushElem-87]
_ = x[FnMapPopElem-88]
_ = x[FnMapPeekElem-89]
_ = x[FnMsgPushData-90]
_ = x[FnMsgPopData-91]
_ = x[FnRcPointerRel-92]
_ = x[FnSpinLock-93]
_ = x[FnSpinUnlock-94]
_ = x[FnSkFullsock-95]
_ = x[FnTcpSock-96]
_ = x[FnSkbEcnSetCe-97]
_ = x[FnGetListenerSock-98]
_ = x[FnSkcLookupTcp-99]
_ = x[FnTcpCheckSyncookie-100]
_ = x[FnSysctlGetName-101]
_ = x[FnSysctlGetCurrentValue-102]
_ = x[FnSysctlGetNewValue-103]
_ = x[FnSysctlSetNewValue-104]
_ = x[FnStrtol-105]
_ = x[FnStrtoul-106]
_ = x[FnSkStorageGet-107]
_ = x[FnSkStorageDelete-108]
_ = x[FnSendSignal-109]
_ = x[FnTcpGenSyncookie-110]
_ = x[FnSkbOutput-111]
_ = x[FnProbeReadUser-112]
_ = x[FnProbeReadKernel-113]
_ = x[FnProbeReadUserStr-114]
_ = x[FnProbeReadKernelStr-115]
_ = x[FnTcpSendAck-116]
_ = x[FnSendSignalThread-117]
_ = x[FnJiffies64-118]
_ = x[FnReadBranchRecords-119]
_ = x[FnGetNsCurrentPidTgid-120]
_ = x[FnXdpOutput-121]
_ = x[FnGetNetnsCookie-122]
_ = x[FnGetCurrentAncestorCgroupId-123]
_ = x[FnSkAssign-124]
_ = x[FnKtimeGetBootNs-125]
_ = x[FnSeqPrintf-126]
_ = x[FnSeqWrite-127]
_ = x[FnSkCgroupId-128]
_ = x[FnSkAncestorCgroupId-129]
_ = x[FnRingbufOutput-130]
_ = x[FnRingbufReserve-131]
_ = x[FnRingbufSubmit-132]
_ = x[FnRingbufDiscard-133]
_ = x[FnRingbufQuery-134]
_ = x[FnCsumLevel-135]
_ = x[FnSkcToTcp6Sock-136]
_ = x[FnSkcToTcpSock-137]
_ = x[FnSkcToTcpTimewaitSock-138]
_ = x[FnSkcToTcpRequestSock-139]
_ = x[FnSkcToUdp6Sock-140]
_ = x[FnGetTaskStack-141]
_ = x[FnLoadHdrOpt-142]
_ = x[FnStoreHdrOpt-143]
_ = x[FnReserveHdrOpt-144]
_ = x[FnInodeStorageGet-145]
_ = x[FnInodeStorageDelete-146]
_ = x[FnDPath-147]
_ = x[FnCopyFromUser-148]
_ = x[FnSnprintfBtf-149]
_ = x[FnSeqPrintfBtf-150]
_ = x[FnSkbCgroupClassid-151]
_ = x[FnRedirectNeigh-152]
_ = x[FnPerCpuPtr-153]
_ = x[FnThisCpuPtr-154]
_ = x[FnRedirectPeer-155]
_ = x[FnTaskStorageGet-156]
_ = x[FnTaskStorageDelete-157]
_ = x[FnGetCurrentTaskBtf-158]
_ = x[FnBprmOptsSet-159]
_ = x[FnKtimeGetCoarseNs-160]
_ = x[FnImaInodeHash-161]
_ = x[FnSockFromFile-162]
_ = x[FnCheckMtu-163]
_ = x[FnForEachMapElem-164]
_ = x[FnSnprintf-165]
_ = x[FnSysBpf-166]
_ = x[FnBtfFindByNameKind-167]
_ = x[FnSysClose-168]
_ = x[FnTimerInit-169]
_ = x[FnTimerSetCallback-170]
_ = x[FnTimerStart-171]
_ = x[FnTimerCancel-172]
_ = x[FnGetFuncIp-173]
_ = x[FnGetAttachCookie-174]
_ = x[FnTaskPtRegs-175]
_ = x[FnGetBranchSnapshot-176]
_ = x[FnTraceVprintk-177]
_ = x[FnSkcToUnixSock-178]
_ = x[FnKallsymsLookupName-179]
_ = x[FnFindVma-180]
_ = x[FnLoop-181]
_ = x[FnStrncmp-182]
_ = x[FnGetFuncArg-183]
_ = x[FnGetFuncRet-184]
_ = x[FnGetFuncArgCnt-185]
_ = x[FnGetRetval-186]
_ = x[FnSetRetval-187]
_ = x[FnXdpGetBuffLen-188]
_ = x[FnXdpLoadBytes-189]
_ = x[FnXdpStoreBytes-190]
_ = x[FnCopyFromUserTask-191]
_ = x[FnSkbSetTstamp-192]
_ = x[FnImaFileHash-193]
_ = x[FnKptrXchg-194]
_ = x[FnMapLookupPercpuElem-195]
_ = x[FnSkcToMptcpSock-196]
_ = x[FnDynptrFromMem-197]
_ = x[FnRingbufReserveDynptr-198]
_ = x[FnRingbufSubmitDynptr-199]
_ = x[FnRingbufDiscardDynptr-200]
_ = x[FnDynptrRead-201]
_ = x[FnDynptrWrite-202]
_ = x[FnDynptrData-203]
_ = x[FnTcpRawGenSyncookieIpv4-204]
_ = x[FnTcpRawGenSyncookieIpv6-205]
_ = x[FnTcpRawCheckSyncookieIpv4-206]
_ = x[FnTcpRawCheckSyncookieIpv6-207]
_ = x[FnKtimeGetTaiNs-208]
_ = x[FnUserRingbufDrain-209]
_ = x[FnCgrpStorageGet-210]
_ = x[FnCgrpStorageDelete-211]
_ = x[maxBuiltinFunc-212]
}
const _BuiltinFunc_name = "FnUnspecFnMapLookupElemFnMapUpdateElemFnMapDeleteElemFnProbeReadFnKtimeGetNsFnTracePrintkFnGetPrandomU32FnGetSmpProcessorIdFnSkbStoreBytesFnL3CsumReplaceFnL4CsumReplaceFnTailCallFnCloneRedirectFnGetCurrentPidTgidFnGetCurrentUidGidFnGetCurrentCommFnGetCgroupClassidFnSkbVlanPushFnSkbVlanPopFnSkbGetTunnelKeyFnSkbSetTunnelKeyFnPerfEventReadFnRedirectFnGetRouteRealmFnPerfEventOutputFnSkbLoadBytesFnGetStackidFnCsumDiffFnSkbGetTunnelOptFnSkbSetTunnelOptFnSkbChangeProtoFnSkbChangeTypeFnSkbUnderCgroupFnGetHashRecalcFnGetCurrentTaskFnProbeWriteUserFnCurrentTaskUnderCgroupFnSkbChangeTailFnSkbPullDataFnCsumUpdateFnSetHashInvalidFnGetNumaNodeIdFnSkbChangeHeadFnXdpAdjustHeadFnProbeReadStrFnGetSocketCookieFnGetSocketUidFnSetHashFnSetsockoptFnSkbAdjustRoomFnRedirectMapFnSkRedirectMapFnSockMapUpdateFnXdpAdjustMetaFnPerfEventReadValueFnPerfProgReadValueFnGetsockoptFnOverrideReturnFnSockOpsCbFlagsSetFnMsgRedirectMapFnMsgApplyBytesFnMsgCorkBytesFnMsgPullDataFnBindFnXdpAdjustTailFnSkbGetXfrmStateFnGetStackFnSkbLoadBytesRelativeFnFibLookupFnSockHashUpdateFnMsgRedirectHashFnSkRedirectHashFnLwtPushEncapFnLwtSeg6StoreBytesFnLwtSeg6AdjustSrhFnLwtSeg6ActionFnRcRepeatFnRcKeydownFnSkbCgroupIdFnGetCurrentCgroupIdFnGetLocalStorageFnSkSelectReuseportFnSkbAncestorCgroupIdFnSkLookupTcpFnSkLookupUdpFnSkReleaseFnMapPushElemFnMapPopElemFnMapPeekElemFnMsgPushDataFnMsgPopDataFnRcPointerRelFnSpinLockFnSpinUnlockFnSkFullsockFnTcpSockFnSkbEcnSetCeFnGetListenerSockFnSkcLookupTcpFnTcpCheckSyncookieFnSysctlGetNameFnSysctlGetCurrentValueFnSysctlGetNewValueFnSysctlSetNewValueFnStrtolFnStrtoulFnSkStorageGetFnSkStorageDeleteFnSendSignalFnTcpGenSyncookieFnSkbOutputFnProbeReadUserFnProbeReadKernelFnProbeReadUserStrFnProbeReadKernelStrFnTcpSendAckFnSendSignalThreadFnJiffies64FnReadBranchRecordsFnGetNsCurrentPidTgidFnXdpOutputFnGetNetnsCookieFnGetCurrentAncestorCgroupIdFnSkAssignFnKtimeGetBootNsFnSeqPrintfFnSeqWriteFnSkCgroupIdFnSkAncestorCgroupIdFnRingbufOutputFnRingbufReserveFnRingbufSubmitFnRingbufDiscardFnRingbufQueryFnCsumLevelFnSkcToTcp6SockFnSkcToTcpSockFnSkcToTcpTimewaitSockFnSkcToTcpRequestSockFnSkcToUdp6SockFnGetTaskStackFnLoadHdrOptFnStoreHdrOptFnReserveHdrOptFnInodeStorageGetFnInodeStorageDeleteFnDPathFnCopyFromUserFnSnprintfBtfFnSeqPrintfBtfFnSkbCgroupClassidFnRedirectNeighFnPerCpuPtrFnThisCpuPtrFnRedirectPeerFnTaskStorageGetFnTaskStorageDeleteFnGetCurrentTaskBtfFnBprmOptsSetFnKtimeGetCoarseNsFnImaInodeHashFnSockFromFileFnCheckMtuFnForEachMapElemFnSnprintfFnSysBpfFnBtfFindByNameKindFnSysCloseFnTimerInitFnTimerSetCallbackFnTimerStartFnTimerCancelFnGetFuncIpFnGetAttachCookieFnTaskPtRegsFnGetBranchSnapshotFnTraceVprintkFnSkcToUnixSockFnKallsymsLookupNameFnFindVmaFnLoopFnStrncmpFnGetFuncArgFnGetFuncRetFnGetFuncArgCntFnGetRetvalFnSetRetvalFnXdpGetBuffLenFnXdpLoadBytesFnXdpStoreBytesFnCopyFromUserTaskFnSkbSetTstampFnImaFileHashFnKptrXchgFnMapLookupPercpuElemFnSkcToMptcpSockFnDynptrFromMemFnRingbufReserveDynptrFnRingbufSubmitDynptrFnRingbufDiscardDynptrFnDynptrReadFnDynptrWriteFnDynptrDataFnTcpRawGenSyncookieIpv4FnTcpRawGenSyncookieIpv6FnTcpRawCheckSyncookieIpv4FnTcpRawCheckSyncookieIpv6FnKtimeGetTaiNsFnUserRingbufDrainFnCgrpStorageGetFnCgrpStorageDeletemaxBuiltinFunc"
var _BuiltinFunc_index = [...]uint16{0, 8, 23, 38, 53, 64, 76, 89, 104, 123, 138, 153, 168, 178, 193, 212, 230, 246, 264, 277, 289, 306, 323, 338, 348, 363, 380, 394, 406, 416, 433, 450, 466, 481, 497, 512, 528, 544, 568, 583, 596, 608, 624, 639, 654, 669, 683, 700, 714, 723, 735, 750, 763, 778, 793, 808, 828, 847, 859, 875, 894, 910, 925, 939, 952, 958, 973, 990, 1000, 1022, 1033, 1049, 1066, 1082, 1096, 1115, 1133, 1148, 1158, 1169, 1182, 1202, 1219, 1238, 1259, 1272, 1285, 1296, 1309, 1321, 1334, 1347, 1359, 1373, 1383, 1395, 1407, 1416, 1429, 1446, 1460, 1479, 1494, 1517, 1536, 1555, 1563, 1572, 1586, 1603, 1615, 1632, 1643, 1658, 1675, 1693, 1713, 1725, 1743, 1754, 1773, 1794, 1805, 1821, 1849, 1859, 1875, 1886, 1896, 1908, 1928, 1943, 1959, 1974, 1990, 2004, 2015, 2030, 2044, 2066, 2087, 2102, 2116, 2128, 2141, 2156, 2173, 2193, 2200, 2214, 2227, 2241, 2259, 2274, 2285, 2297, 2311, 2327, 2346, 2365, 2378, 2396, 2410, 2424, 2434, 2450, 2460, 2468, 2487, 2497, 2508, 2526, 2538, 2551, 2562, 2579, 2591, 2610, 2624, 2639, 2659, 2668, 2674, 2683, 2695, 2707, 2722, 2733, 2744, 2759, 2773, 2788, 2806, 2820, 2833, 2843, 2864, 2880, 2895, 2917, 2938, 2960, 2972, 2985, 2997, 3021, 3045, 3071, 3097, 3112, 3130, 3146, 3165, 3179}
func (i BuiltinFunc) String() string {
if i < 0 || i >= BuiltinFunc(len(_BuiltinFunc_index)-1) {
return "BuiltinFunc(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _BuiltinFunc_name[_BuiltinFunc_index[i]:_BuiltinFunc_index[i+1]]
}
@@ -0,0 +1,877 @@
package asm
import (
"crypto/sha1"
"encoding/binary"
"encoding/hex"
"errors"
"fmt"
"io"
"math"
"sort"
"strings"
"github.com/cilium/ebpf/internal/sys"
"github.com/cilium/ebpf/internal/unix"
)
// InstructionSize is the size of a BPF instruction in bytes
const InstructionSize = 8
// RawInstructionOffset is an offset in units of raw BPF instructions.
type RawInstructionOffset uint64
var ErrUnreferencedSymbol = errors.New("unreferenced symbol")
var ErrUnsatisfiedMapReference = errors.New("unsatisfied map reference")
var ErrUnsatisfiedProgramReference = errors.New("unsatisfied program reference")
// Bytes returns the offset of an instruction in bytes.
func (rio RawInstructionOffset) Bytes() uint64 {
return uint64(rio) * InstructionSize
}
// Instruction is a single eBPF instruction.
type Instruction struct {
OpCode OpCode
Dst Register
Src Register
Offset int16
Constant int64
// Metadata contains optional metadata about this instruction.
Metadata Metadata
}
// Unmarshal decodes a BPF instruction.
func (ins *Instruction) Unmarshal(r io.Reader, bo binary.ByteOrder) (uint64, error) {
data := make([]byte, InstructionSize)
if _, err := io.ReadFull(r, data); err != nil {
return 0, err
}
ins.OpCode = OpCode(data[0])
regs := data[1]
switch bo {
case binary.LittleEndian:
ins.Dst, ins.Src = Register(regs&0xF), Register(regs>>4)
case binary.BigEndian:
ins.Dst, ins.Src = Register(regs>>4), Register(regs&0xf)
}
ins.Offset = int16(bo.Uint16(data[2:4]))
// Convert to int32 before widening to int64
// to ensure the signed bit is carried over.
ins.Constant = int64(int32(bo.Uint32(data[4:8])))
if !ins.OpCode.IsDWordLoad() {
return InstructionSize, nil
}
// 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")
}
// 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")
}
cons1 := uint32(ins.Constant)
cons2 := int32(bo.Uint32(data[4:8]))
ins.Constant = int64(cons2)<<32 | int64(cons1)
return 2 * InstructionSize, nil
}
// Marshal encodes a BPF instruction.
func (ins Instruction) Marshal(w io.Writer, bo binary.ByteOrder) (uint64, error) {
if ins.OpCode == InvalidOpCode {
return 0, errors.New("invalid opcode")
}
isDWordLoad := ins.OpCode.IsDWordLoad()
cons := int32(ins.Constant)
if isDWordLoad {
// Encode least significant 32bit first for 64bit operations.
cons = int32(uint32(ins.Constant))
}
regs, err := newBPFRegisters(ins.Dst, ins.Src, bo)
if err != nil {
return 0, fmt.Errorf("can't marshal registers: %s", err)
}
data := make([]byte, InstructionSize)
data[0] = byte(ins.OpCode)
data[1] = byte(regs)
bo.PutUint16(data[2:4], uint16(ins.Offset))
bo.PutUint32(data[4:8], uint32(cons))
if _, err := w.Write(data); err != nil {
return 0, err
}
if !isDWordLoad {
return InstructionSize, nil
}
// The first half of the second part of a double-wide instruction
// must be zero. The second half carries the value.
bo.PutUint32(data[0:4], 0)
bo.PutUint32(data[4:8], uint32(ins.Constant>>32))
if _, err := w.Write(data); err != nil {
return 0, err
}
return 2 * InstructionSize, nil
}
// AssociateMap associates a Map with this Instruction.
//
// Implicitly clears the Instruction's Reference field.
//
// Returns an error if the Instruction is not a map load.
func (ins *Instruction) AssociateMap(m FDer) error {
if !ins.IsLoadFromMap() {
return errors.New("not a load from a map")
}
ins.Metadata.Set(referenceMeta{}, nil)
ins.Metadata.Set(mapMeta{}, m)
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)
rawFd := uint64(uint32(fd))
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 {
return int(int32(ins.Constant))
}
// RewriteMapOffset changes the offset of a direct load from a map.
//
// Returns an error if the instruction is not a direct load.
func (ins *Instruction) RewriteMapOffset(offset uint32) error {
if !ins.OpCode.IsDWordLoad() {
return fmt.Errorf("%s is not a 64 bit load", ins.OpCode)
}
if ins.Src != PseudoMapValue {
return errors.New("not a direct load from a map")
}
fd := uint64(ins.Constant) & math.MaxUint32
ins.Constant = int64(uint64(offset)<<32 | fd)
return nil
}
func (ins *Instruction) mapOffset() uint32 {
return uint32(uint64(ins.Constant) >> 32)
}
// IsLoadFromMap returns true if the instruction loads from a map.
//
// This covers both loading the map pointer and direct map value loads.
func (ins *Instruction) IsLoadFromMap() bool {
return ins.OpCode == LoadImmOp(DWord) && (ins.Src == PseudoMapFD || ins.Src == PseudoMapValue)
}
// IsFunctionCall returns true if the instruction calls another BPF function.
//
// This is not the same thing as a BPF helper call.
func (ins *Instruction) IsFunctionCall() bool {
return ins.OpCode.JumpOp() == Call && ins.Src == PseudoCall
}
// IsKfuncCall returns true if the instruction calls a kfunc.
//
// This is not the same thing as a BPF helper call.
func (ins *Instruction) IsKfuncCall() bool {
return ins.OpCode.JumpOp() == Call && ins.Src == PseudoKfuncCall
}
// IsLoadOfFunctionPointer returns true if the instruction loads a function pointer.
func (ins *Instruction) IsLoadOfFunctionPointer() bool {
return ins.OpCode.IsDWordLoad() && ins.Src == PseudoFunc
}
// IsFunctionReference returns true if the instruction references another BPF
// function, either by invoking a Call jump operation or by loading a function
// pointer.
func (ins *Instruction) IsFunctionReference() bool {
return ins.IsFunctionCall() || ins.IsLoadOfFunctionPointer()
}
// IsBuiltinCall returns true if the instruction is a built-in call, i.e. BPF helper call.
func (ins *Instruction) IsBuiltinCall() bool {
return ins.OpCode.JumpOp() == Call && ins.Src == R0 && ins.Dst == R0
}
// IsConstantLoad returns true if the instruction loads a constant of the
// given size.
func (ins *Instruction) IsConstantLoad(size Size) bool {
return ins.OpCode == LoadImmOp(size) && ins.Src == R0 && ins.Offset == 0
}
// Format implements fmt.Formatter.
func (ins Instruction) Format(f fmt.State, c rune) {
if c != 'v' {
fmt.Fprintf(f, "{UNRECOGNIZED: %c}", c)
return
}
op := ins.OpCode
if op == InvalidOpCode {
fmt.Fprint(f, "INVALID")
return
}
// Omit trailing space for Exit
if op.JumpOp() == Exit {
fmt.Fprint(f, op)
return
}
if ins.IsLoadFromMap() {
fd := ins.mapFd()
m := ins.Map()
switch ins.Src {
case PseudoMapFD:
if m != nil {
fmt.Fprintf(f, "LoadMapPtr dst: %s map: %s", ins.Dst, m)
} else {
fmt.Fprintf(f, "LoadMapPtr dst: %s fd: %d", ins.Dst, fd)
}
case PseudoMapValue:
if m != nil {
fmt.Fprintf(f, "LoadMapValue dst: %s, map: %s off: %d", ins.Dst, m, ins.mapOffset())
} else {
fmt.Fprintf(f, "LoadMapValue dst: %s, fd: %d off: %d", ins.Dst, fd, ins.mapOffset())
}
}
goto ref
}
fmt.Fprintf(f, "%v ", op)
switch cls := op.Class(); {
case cls.isLoadOrStore():
switch op.Mode() {
case ImmMode:
fmt.Fprintf(f, "dst: %s imm: %d", ins.Dst, ins.Constant)
case AbsMode:
fmt.Fprintf(f, "imm: %d", ins.Constant)
case IndMode:
fmt.Fprintf(f, "dst: %s src: %s imm: %d", ins.Dst, ins.Src, ins.Constant)
case MemMode:
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 cls.IsALU():
fmt.Fprintf(f, "dst: %s ", ins.Dst)
if op.ALUOp() == Swap || op.Source() == ImmSource {
fmt.Fprintf(f, "imm: %d", ins.Constant)
} else {
fmt.Fprintf(f, "src: %s", ins.Src)
}
case cls.IsJump():
switch jop := op.JumpOp(); jop {
case Call:
switch ins.Src {
case PseudoCall:
// bpf-to-bpf call
fmt.Fprint(f, ins.Constant)
case PseudoKfuncCall:
// kfunc call
fmt.Fprintf(f, "Kfunc(%d)", ins.Constant)
default:
fmt.Fprint(f, BuiltinFunc(ins.Constant))
}
default:
fmt.Fprintf(f, "dst: %s off: %d ", ins.Dst, ins.Offset)
if op.Source() == ImmSource {
fmt.Fprintf(f, "imm: %d", ins.Constant)
} else {
fmt.Fprintf(f, "src: %s", ins.Src)
}
}
}
ref:
if ins.Reference() != "" {
fmt.Fprintf(f, " <%s>", ins.Reference())
}
}
func (ins Instruction) equal(other Instruction) bool {
return ins.OpCode == other.OpCode &&
ins.Dst == other.Dst &&
ins.Src == other.Src &&
ins.Offset == other.Offset &&
ins.Constant == other.Constant
}
// Size returns the amount of bytes ins would occupy in binary form.
func (ins Instruction) Size() uint64 {
return uint64(InstructionSize * ins.OpCode.rawInstructions())
}
// WithMetadata sets the given Metadata on the Instruction. e.g. to copy
// Metadata from another Instruction when replacing it.
func (ins Instruction) WithMetadata(meta Metadata) Instruction {
ins.Metadata = meta
return ins
}
type symbolMeta struct{}
// WithSymbol marks the Instruction as a Symbol, which other Instructions
// can point to using corresponding calls to WithReference.
func (ins Instruction) WithSymbol(name string) Instruction {
ins.Metadata.Set(symbolMeta{}, name)
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.
func (ins Instruction) Symbol() string {
sym, _ := ins.Metadata.Get(symbolMeta{}).(string)
return sym
}
type referenceMeta struct{}
// WithReference makes ins reference another Symbol or map by name.
func (ins Instruction) WithReference(ref string) Instruction {
ins.Metadata.Set(referenceMeta{}, ref)
return ins
}
// Reference returns the Symbol or map name referenced by ins, if any.
func (ins Instruction) Reference() string {
ref, _ := ins.Metadata.Get(referenceMeta{}).(string)
return ref
}
type mapMeta struct{}
// Map returns the Map referenced by ins, if any.
// An Instruction will contain a Map if e.g. it references an existing,
// pinned map that was opened during ELF loading.
func (ins Instruction) Map() FDer {
fd, _ := ins.Metadata.Get(mapMeta{}).(FDer)
return fd
}
type sourceMeta struct{}
// WithSource adds source information about the Instruction.
func (ins Instruction) WithSource(src fmt.Stringer) Instruction {
ins.Metadata.Set(sourceMeta{}, src)
return ins
}
// Source returns source information about the Instruction. The field is
// present when the compiler emits BTF line info about the Instruction and
// usually contains the line of source code responsible for it.
func (ins Instruction) Source() fmt.Stringer {
str, _ := ins.Metadata.Get(sourceMeta{}).(fmt.Stringer)
return str
}
// A Comment can be passed to Instruction.WithSource to add a comment
// to an instruction.
type Comment string
func (s Comment) String() string {
return string(s)
}
// FDer represents a resource tied to an underlying file descriptor.
// Used as a stand-in for e.g. ebpf.Map since that type cannot be
// imported here and FD() is the only method we rely on.
type FDer interface {
FD() int
}
// Instructions is an eBPF program.
type Instructions []Instruction
// Unmarshal unmarshals an Instructions from a binary instruction stream.
// All instructions in insns are replaced by instructions decoded from r.
func (insns *Instructions) Unmarshal(r io.Reader, bo binary.ByteOrder) error {
if len(*insns) > 0 {
*insns = nil
}
var offset uint64
for {
var ins Instruction
n, err := ins.Unmarshal(r, bo)
if errors.Is(err, io.EOF) {
break
}
if err != nil {
return fmt.Errorf("offset %d: %w", offset, err)
}
*insns = append(*insns, ins)
offset += n
}
return nil
}
// Name returns the name of the function insns belongs to, if any.
func (insns Instructions) Name() string {
if len(insns) == 0 {
return ""
}
return insns[0].Symbol()
}
func (insns Instructions) String() string {
return fmt.Sprint(insns)
}
// Size returns the amount of bytes insns would occupy in binary form.
func (insns Instructions) Size() uint64 {
var sum uint64
for _, ins := range insns {
sum += ins.Size()
}
return sum
}
// AssociateMap updates all Instructions that Reference the given symbol
// to point to an existing Map m instead.
//
// Returns ErrUnreferencedSymbol error if no references to symbol are found
// in insns. If symbol is anything else than the symbol name of map (e.g.
// a bpf2bpf subprogram), an error is returned.
func (insns Instructions) AssociateMap(symbol string, m FDer) error {
if symbol == "" {
return errors.New("empty symbol")
}
var found bool
for i := range insns {
ins := &insns[i]
if ins.Reference() != symbol {
continue
}
if err := ins.AssociateMap(m); err != nil {
return err
}
found = true
}
if !found {
return fmt.Errorf("symbol %s: %w", symbol, ErrUnreferencedSymbol)
}
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) {
offsets := make(map[string]int)
for i, ins := range insns {
if ins.Symbol() == "" {
continue
}
if _, ok := offsets[ins.Symbol()]; ok {
return nil, fmt.Errorf("duplicate symbol %s", ins.Symbol())
}
offsets[ins.Symbol()] = i
}
return offsets, nil
}
// FunctionReferences returns a set of symbol names these Instructions make
// bpf-to-bpf calls to.
func (insns Instructions) FunctionReferences() []string {
calls := make(map[string]struct{})
for _, ins := range insns {
if ins.Constant != -1 {
// BPF-to-BPF calls have -1 constants.
continue
}
if ins.Reference() == "" {
continue
}
if !ins.IsFunctionReference() {
continue
}
calls[ins.Reference()] = struct{}{}
}
result := make([]string, 0, len(calls))
for call := range calls {
result = append(result, call)
}
sort.Strings(result)
return result
}
// ReferenceOffsets returns the set of references and their offset in
// the instructions.
func (insns Instructions) ReferenceOffsets() map[string][]int {
offsets := make(map[string][]int)
for i, ins := range insns {
if ins.Reference() == "" {
continue
}
offsets[ins.Reference()] = append(offsets[ins.Reference()], i)
}
return offsets
}
// Format implements fmt.Formatter.
//
// You can control indentation of symbols by
// specifying a width. Setting a precision controls the indentation of
// instructions.
// The default character is a tab, which can be overridden by specifying
// the ' ' space flag.
func (insns Instructions) Format(f fmt.State, c rune) {
if c != 's' && c != 'v' {
fmt.Fprintf(f, "{UNKNOWN FORMAT '%c'}", c)
return
}
// Precision is better in this case, because it allows
// specifying 0 padding easily.
padding, ok := f.Precision()
if !ok {
padding = 1
}
indent := strings.Repeat("\t", padding)
if f.Flag(' ') {
indent = strings.Repeat(" ", padding)
}
symPadding, ok := f.Width()
if !ok {
symPadding = padding - 1
}
if symPadding < 0 {
symPadding = 0
}
symIndent := strings.Repeat("\t", symPadding)
if f.Flag(' ') {
symIndent = strings.Repeat(" ", symPadding)
}
// Guess how many digits we need at most, by assuming that all instructions
// are double wide.
highestOffset := len(insns) * 2
offsetWidth := int(math.Ceil(math.Log10(float64(highestOffset))))
iter := insns.Iterate()
for iter.Next() {
if iter.Ins.Symbol() != "" {
fmt.Fprintf(f, "%s%s:\n", symIndent, iter.Ins.Symbol())
}
if src := iter.Ins.Source(); src != nil {
line := strings.TrimSpace(src.String())
if line != "" {
fmt.Fprintf(f, "%s%*s; %s\n", indent, offsetWidth, " ", line)
}
}
fmt.Fprintf(f, "%s%*d: %v\n", indent, offsetWidth, iter.Offset, iter.Ins)
}
}
// Marshal encodes a BPF program into the kernel format.
//
// insns may be modified if there are unresolved jumps or bpf2bpf calls.
//
// Returns ErrUnsatisfiedProgramReference if there is a Reference Instruction
// without a matching Symbol Instruction within insns.
func (insns Instructions) Marshal(w io.Writer, bo binary.ByteOrder) error {
if err := insns.encodeFunctionReferences(); err != nil {
return err
}
if err := insns.encodeMapPointers(); err != nil {
return err
}
for i, ins := range insns {
if _, err := ins.Marshal(w, bo); err != nil {
return fmt.Errorf("instruction %d: %w", i, err)
}
}
return nil
}
// Tag calculates the kernel tag for a series of instructions.
//
// 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.
func (insns Instructions) Tag(bo binary.ByteOrder) (string, error) {
h := sha1.New()
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 hex.EncodeToString(h.Sum(nil)[:unix.BPF_TAG_SIZE]), nil
}
// encodeFunctionReferences populates the Offset (or Constant, depending on
// the instruction type) field of instructions with a Reference field to point
// to the offset of the corresponding instruction with a matching Symbol field.
//
// Only Reference Instructions that are either jumps or BPF function references
// (calls or function pointer loads) are populated.
//
// Returns ErrUnsatisfiedProgramReference if there is a Reference Instruction
// without at least one corresponding Symbol Instruction within insns.
func (insns Instructions) encodeFunctionReferences() error {
// Index the offsets of instructions tagged as a symbol.
symbolOffsets := make(map[string]RawInstructionOffset)
iter := insns.Iterate()
for iter.Next() {
ins := iter.Ins
if ins.Symbol() == "" {
continue
}
if _, ok := symbolOffsets[ins.Symbol()]; ok {
return fmt.Errorf("duplicate symbol %s", ins.Symbol())
}
symbolOffsets[ins.Symbol()] = iter.Offset
}
// Find all instructions tagged as references to other symbols.
// Depending on the instruction type, populate their constant or offset
// fields to point to the symbol they refer to within the insn stream.
iter = insns.Iterate()
for iter.Next() {
i := iter.Index
offset := iter.Offset
ins := iter.Ins
if ins.Reference() == "" {
continue
}
switch {
case ins.IsFunctionReference() && ins.Constant == -1:
symOffset, ok := symbolOffsets[ins.Reference()]
if !ok {
return fmt.Errorf("%s at insn %d: symbol %q: %w", ins.OpCode, i, ins.Reference(), ErrUnsatisfiedProgramReference)
}
ins.Constant = int64(symOffset - offset - 1)
case ins.OpCode.Class().IsJump() && ins.Offset == -1:
symOffset, ok := symbolOffsets[ins.Reference()]
if !ok {
return fmt.Errorf("%s at insn %d: symbol %q: %w", ins.OpCode, i, ins.Reference(), ErrUnsatisfiedProgramReference)
}
ins.Offset = int16(symOffset - offset - 1)
}
}
return nil
}
// encodeMapPointers finds all Map Instructions and encodes their FDs
// into their Constant fields.
func (insns Instructions) encodeMapPointers() error {
iter := insns.Iterate()
for iter.Next() {
ins := iter.Ins
if !ins.IsLoadFromMap() {
continue
}
m := ins.Map()
if m == nil {
continue
}
fd := m.FD()
if fd < 0 {
return fmt.Errorf("map %s: %w", m, sys.ErrClosedFd)
}
ins.encodeMapFD(m.FD())
}
return nil
}
// Iterate allows iterating a BPF program while keeping track of
// various offsets.
//
// Modifying the instruction slice will lead to undefined behaviour.
func (insns Instructions) Iterate() *InstructionIterator {
return &InstructionIterator{insns: insns}
}
// InstructionIterator iterates over a BPF program.
type InstructionIterator struct {
insns Instructions
// The instruction in question.
Ins *Instruction
// The index of the instruction in the original instruction slice.
Index int
// The offset of the instruction in raw BPF instructions. This accounts
// for double-wide instructions.
Offset RawInstructionOffset
}
// Next returns true as long as there are any instructions remaining.
func (iter *InstructionIterator) Next() bool {
if len(iter.insns) == 0 {
return false
}
if iter.Ins != nil {
iter.Index++
iter.Offset += RawInstructionOffset(iter.Ins.OpCode.rawInstructions())
}
iter.Ins = &iter.insns[0]
iter.insns = iter.insns[1:]
return true
}
type bpfRegisters uint8
func newBPFRegisters(dst, src Register, bo binary.ByteOrder) (bpfRegisters, error) {
switch bo {
case binary.LittleEndian:
return bpfRegisters((src << 4) | (dst & 0xF)), nil
case binary.BigEndian:
return bpfRegisters((dst << 4) | (src & 0xF)), nil
default:
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)
}
@@ -0,0 +1,341 @@
package asm
import (
"bytes"
"encoding/binary"
"encoding/hex"
"errors"
"fmt"
"io"
"math"
"testing"
qt "github.com/frankban/quicktest"
)
var test64bitImmProg = []byte{
// r0 = math.MinInt32 - 1
0x18, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x7f,
0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
}
func TestRead64bitImmediate(t *testing.T) {
var ins Instruction
n, err := ins.Unmarshal(bytes.NewReader(test64bitImmProg), binary.LittleEndian)
if err != nil {
t.Fatal(err)
}
if want := uint64(InstructionSize * 2); n != want {
t.Errorf("Expected %d bytes to be read, got %d", want, n)
}
if c := ins.Constant; c != math.MinInt32-1 {
t.Errorf("Expected immediate to be %v, got %v", int64(math.MinInt32)-1, c)
}
}
func BenchmarkRead64bitImmediate(b *testing.B) {
r := &bytes.Reader{}
for i := 0; i < b.N; i++ {
r.Reset(test64bitImmProg)
var ins Instruction
if _, err := ins.Unmarshal(r, binary.LittleEndian); err != nil {
b.Fatal(err)
}
}
}
func TestWrite64bitImmediate(t *testing.T) {
insns := Instructions{
LoadImm(R0, math.MinInt32-1, DWord),
}
var buf bytes.Buffer
if err := insns.Marshal(&buf, binary.LittleEndian); err != nil {
t.Fatal(err)
}
if prog := buf.Bytes(); !bytes.Equal(prog, test64bitImmProg) {
t.Errorf("Marshalled program does not match:\n%s", hex.Dump(prog))
}
}
func BenchmarkWrite64BitImmediate(b *testing.B) {
ins := LoadImm(R0, math.MinInt32-1, DWord)
var buf bytes.Buffer
for i := 0; i < b.N; i++ {
buf.Reset()
if _, err := ins.Marshal(&buf, binary.LittleEndian); err != nil {
b.Fatal(err)
}
}
}
func TestUnmarshalInstructions(t *testing.T) {
r := bytes.NewReader(test64bitImmProg)
var insns Instructions
if err := insns.Unmarshal(r, binary.LittleEndian); err != nil {
t.Fatal(err)
}
// Unmarshaling into the same Instructions multiple times replaces
// the instruction stream.
r.Reset(test64bitImmProg)
if err := insns.Unmarshal(r, binary.LittleEndian); err != nil {
t.Fatal(err)
}
if len(insns) != 1 {
t.Fatalf("Expected one instruction, got %d", len(insns))
}
}
func TestSignedJump(t *testing.T) {
insns := Instructions{
JSGT.Imm(R0, -1, "foo"),
}
insns[0].Offset = 1
err := insns.Marshal(io.Discard, binary.LittleEndian)
if err != nil {
t.Error("Can't marshal signed jump:", err)
}
}
func TestInstructionRewriteMapConstant(t *testing.T) {
ins := LoadMapValue(R0, 123, 321)
qt.Assert(t, ins.MapPtr(), qt.Equals, 123)
qt.Assert(t, ins.mapOffset(), qt.Equals, uint32(321))
qt.Assert(t, ins.RewriteMapPtr(-1), qt.IsNil)
qt.Assert(t, ins.MapPtr(), qt.Equals, -1)
qt.Assert(t, ins.RewriteMapPtr(1), qt.IsNil)
qt.Assert(t, ins.MapPtr(), qt.Equals, 1)
// mapOffset should be unchanged after rewriting the pointer.
qt.Assert(t, ins.mapOffset(), qt.Equals, uint32(321))
qt.Assert(t, ins.RewriteMapOffset(123), qt.IsNil)
qt.Assert(t, ins.mapOffset(), qt.Equals, uint32(123))
// MapPtr should be unchanged.
qt.Assert(t, ins.MapPtr(), qt.Equals, 1)
ins = Mov.Imm(R1, 32)
if err := ins.RewriteMapPtr(1); err == nil {
t.Error("RewriteMapPtr rewriting bogus instruction")
}
if err := ins.RewriteMapOffset(1); err == nil {
t.Error("RewriteMapOffset rewriting bogus instruction")
}
}
func TestInstructionLoadMapValue(t *testing.T) {
ins := LoadMapValue(R0, 1, 123)
if !ins.IsLoadFromMap() {
t.Error("isLoadFromMap returns false")
}
if fd := ins.mapFd(); fd != 1 {
t.Error("Expected map fd to be 1, got", fd)
}
if off := ins.mapOffset(); off != 123 {
t.Fatal("Expected map offset to be 123 after changin the pointer, got", off)
}
}
func TestInstructionsRewriteMapPtr(t *testing.T) {
insns := Instructions{
LoadMapPtr(R1, 0).WithReference("good"),
Return(),
}
if err := insns.RewriteMapPtr("good", 1); err != nil {
t.Fatal(err)
}
if insns[0].Constant != 1 {
t.Error("Constant should be 1, have", insns[0].Constant)
}
if err := insns.RewriteMapPtr("good", 2); err != nil {
t.Fatal(err)
}
if insns[0].Constant != 2 {
t.Error("Constant should be 2, have", insns[0].Constant)
}
if err := insns.RewriteMapPtr("bad", 1); !errors.Is(err, ErrUnreferencedSymbol) {
t.Error("Rewriting unreferenced map doesn't return appropriate error")
}
}
func TestInstructionWithMetadata(t *testing.T) {
ins := LoadImm(R0, 123, DWord).WithSymbol("abc")
ins2 := LoadImm(R0, 567, DWord).WithMetadata(ins.Metadata)
if want, got := "abc", ins2.Symbol(); want != got {
t.Fatalf("unexpected Symbol value on ins2: want: %s, got: %s", want, got)
}
if want, got := ins.Metadata, ins2.Metadata; want != got {
t.Fatal("expected ins and isn2 Metadata to match")
}
}
// You can use format flags to change the way an eBPF
// program is stringified.
func ExampleInstructions_Format() {
insns := Instructions{
FnMapLookupElem.Call().WithSymbol("my_func").WithSource(Comment("bpf_map_lookup_elem()")),
LoadImm(R0, 42, DWord).WithSource(Comment("abc = 42")),
Return(),
}
fmt.Println("Default format:")
fmt.Printf("%v\n", insns)
fmt.Println("Don't indent instructions:")
fmt.Printf("%.0v\n", insns)
fmt.Println("Indent using spaces:")
fmt.Printf("% v\n", insns)
fmt.Println("Control symbol indentation:")
fmt.Printf("%2v\n", insns)
// Output: Default format:
// my_func:
// ; bpf_map_lookup_elem()
// 0: Call FnMapLookupElem
// ; abc = 42
// 1: LdImmDW dst: r0 imm: 42
// 3: Exit
//
// Don't indent instructions:
// my_func:
// ; bpf_map_lookup_elem()
// 0: Call FnMapLookupElem
// ; abc = 42
// 1: LdImmDW dst: r0 imm: 42
// 3: Exit
//
// Indent using spaces:
// my_func:
// ; bpf_map_lookup_elem()
// 0: Call FnMapLookupElem
// ; abc = 42
// 1: LdImmDW dst: r0 imm: 42
// 3: Exit
//
// Control symbol indentation:
// my_func:
// ; bpf_map_lookup_elem()
// 0: Call FnMapLookupElem
// ; abc = 42
// 1: LdImmDW dst: r0 imm: 42
// 3: Exit
}
func TestReadSrcDst(t *testing.T) {
testSrcDstProg := []byte{
// on little-endian: r0 = r1
// on big-endian: be: r1 = r0
0xbf, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
}
testcases := []struct {
bo binary.ByteOrder
dst, src Register
}{
{binary.BigEndian, R1, R0},
{binary.LittleEndian, R0, R1},
}
for _, tc := range testcases {
t.Run(tc.bo.String(), func(t *testing.T) {
var ins Instruction
_, err := ins.Unmarshal(bytes.NewReader(testSrcDstProg), tc.bo)
if err != nil {
t.Fatal(err)
}
if ins.Dst != tc.dst {
t.Errorf("Expected destination to be %v, got %v", tc.dst, ins.Dst)
}
if ins.Src != tc.src {
t.Errorf("Expected source to be %v, got %v", tc.src, ins.Src)
}
})
}
}
func TestInstructionIterator(t *testing.T) {
insns := Instructions{
LoadImm(R0, 0, Word),
LoadImm(R0, 0, DWord),
Return(),
}
offsets := []RawInstructionOffset{0, 1, 3}
iter := insns.Iterate()
for i := 0; i < len(insns); i++ {
if !iter.Next() {
t.Fatalf("Expected %dth call to Next to return true", i)
}
if iter.Ins == nil {
t.Errorf("Expected iter.Ins to be non-nil")
}
if iter.Index != i {
t.Errorf("Expected iter.Index to be %d, got %d", i, iter.Index)
}
if iter.Offset != offsets[i] {
t.Errorf("Expected iter.Offset to be %d, got %d", offsets[i], iter.Offset)
}
}
}
func TestMetadataCopyOnWrite(t *testing.T) {
c := qt.New(t)
// Setting metadata should copy Instruction and modify the metadata pointer
// of the new object without touching the old Instruction.
// Reference
ins := Ja.Label("my_func")
ins2 := ins.WithReference("my_func2")
c.Assert(ins.Reference(), qt.Equals, "my_func", qt.Commentf("WithReference updated ins"))
c.Assert(ins2.Reference(), qt.Equals, "my_func2", qt.Commentf("WithReference didn't update ins2"))
// Symbol
ins = Ja.Label("").WithSymbol("my_sym")
ins2 = ins.WithSymbol("my_sym2")
c.Assert(ins.Symbol(), qt.Equals, "my_sym", qt.Commentf("WithSymbol updated ins"))
c.Assert(ins2.Symbol(), qt.Equals, "my_sym2", qt.Commentf("WithSymbol didn't update ins2"))
// Map
ins = LoadMapPtr(R1, 0)
ins2 = ins
testMap := testFDer(1)
c.Assert(ins2.AssociateMap(testMap), qt.IsNil, qt.Commentf("failed to associate map with ins2"))
c.Assert(ins.Map(), qt.IsNil, qt.Commentf("AssociateMap updated ins"))
c.Assert(ins2.Map(), qt.Equals, testMap, qt.Commentf("AssociateMap didn't update ins2"))
}
type testFDer int
func (t testFDer) FD() int {
return int(t)
}
@@ -0,0 +1,127 @@
package asm
//go:generate stringer -output jump_string.go -type=JumpOp
// JumpOp affect control flow.
//
// msb lsb
// +----+-+---+
// |OP |s|cls|
// +----+-+---+
type JumpOp uint8
const jumpMask OpCode = aluMask
const (
// InvalidJumpOp is returned by getters when invoked
// on non branch OpCodes
InvalidJumpOp JumpOp = 0xff
// Ja jumps by offset unconditionally
Ja JumpOp = 0x00
// JEq jumps by offset if r == imm
JEq JumpOp = 0x10
// JGT jumps by offset if r > imm
JGT JumpOp = 0x20
// JGE jumps by offset if r >= imm
JGE JumpOp = 0x30
// JSet jumps by offset if r & imm
JSet JumpOp = 0x40
// JNE jumps by offset if r != imm
JNE JumpOp = 0x50
// JSGT jumps by offset if signed r > signed imm
JSGT JumpOp = 0x60
// JSGE jumps by offset if signed r >= signed imm
JSGE JumpOp = 0x70
// Call builtin or user defined function from imm
Call JumpOp = 0x80
// Exit ends execution, with value in r0
Exit JumpOp = 0x90
// JLT jumps by offset if r < imm
JLT JumpOp = 0xa0
// JLE jumps by offset if r <= imm
JLE JumpOp = 0xb0
// JSLT jumps by offset if signed r < signed imm
JSLT JumpOp = 0xc0
// JSLE jumps by offset if signed r <= signed imm
JSLE JumpOp = 0xd0
)
// Return emits an exit instruction.
//
// Requires a return value in R0.
func Return() Instruction {
return Instruction{
OpCode: OpCode(JumpClass).SetJumpOp(Exit),
}
}
// Op returns the OpCode for a given jump source.
func (op JumpOp) Op(source Source) OpCode {
return OpCode(JumpClass).SetJumpOp(op).SetSource(source)
}
// Imm compares 64 bit dst to 64 bit value (sign extended), and adjusts PC by offset if the condition is fulfilled.
func (op JumpOp) Imm(dst Register, value int32, label string) Instruction {
return Instruction{
OpCode: op.opCode(JumpClass, ImmSource),
Dst: dst,
Offset: -1,
Constant: int64(value),
}.WithReference(label)
}
// Imm32 compares 32 bit dst to 32 bit value, and adjusts PC by offset if the condition is fulfilled.
// Requires kernel 5.1.
func (op JumpOp) Imm32(dst Register, value int32, label string) Instruction {
return Instruction{
OpCode: op.opCode(Jump32Class, ImmSource),
Dst: dst,
Offset: -1,
Constant: int64(value),
}.WithReference(label)
}
// Reg compares 64 bit dst to 64 bit src, and adjusts PC by offset if the condition is fulfilled.
func (op JumpOp) Reg(dst, src Register, label string) Instruction {
return Instruction{
OpCode: op.opCode(JumpClass, RegSource),
Dst: dst,
Src: src,
Offset: -1,
}.WithReference(label)
}
// Reg32 compares 32 bit dst to 32 bit src, and adjusts PC by offset if the condition is fulfilled.
// Requires kernel 5.1.
func (op JumpOp) Reg32(dst, src Register, label string) Instruction {
return Instruction{
OpCode: op.opCode(Jump32Class, RegSource),
Dst: dst,
Src: src,
Offset: -1,
}.WithReference(label)
}
func (op JumpOp) opCode(class Class, source Source) OpCode {
if op == Exit || op == Call || op == Ja {
return InvalidOpCode
}
return OpCode(class).SetJumpOp(op).SetSource(source)
}
// Label adjusts PC to the address of the label.
func (op JumpOp) Label(label string) Instruction {
if op == Call {
return Instruction{
OpCode: OpCode(JumpClass).SetJumpOp(Call),
Src: PseudoCall,
Constant: -1,
}.WithReference(label)
}
return Instruction{
OpCode: OpCode(JumpClass).SetJumpOp(op),
Offset: -1,
}.WithReference(label)
}
@@ -0,0 +1,53 @@
// Code generated by "stringer -output jump_string.go -type=JumpOp"; DO NOT EDIT.
package asm
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[InvalidJumpOp-255]
_ = x[Ja-0]
_ = x[JEq-16]
_ = x[JGT-32]
_ = x[JGE-48]
_ = x[JSet-64]
_ = x[JNE-80]
_ = x[JSGT-96]
_ = x[JSGE-112]
_ = x[Call-128]
_ = x[Exit-144]
_ = x[JLT-160]
_ = x[JLE-176]
_ = x[JSLT-192]
_ = x[JSLE-208]
}
const _JumpOp_name = "JaJEqJGTJGEJSetJNEJSGTJSGECallExitJLTJLEJSLTJSLEInvalidJumpOp"
var _JumpOp_map = map[JumpOp]string{
0: _JumpOp_name[0:2],
16: _JumpOp_name[2:5],
32: _JumpOp_name[5:8],
48: _JumpOp_name[8:11],
64: _JumpOp_name[11:15],
80: _JumpOp_name[15:18],
96: _JumpOp_name[18:22],
112: _JumpOp_name[22:26],
128: _JumpOp_name[26:30],
144: _JumpOp_name[30:34],
160: _JumpOp_name[34:37],
176: _JumpOp_name[37:40],
192: _JumpOp_name[40:44],
208: _JumpOp_name[44:48],
255: _JumpOp_name[48:61],
}
func (i JumpOp) String() string {
if str, ok := _JumpOp_map[i]; ok {
return str
}
return "JumpOp(" + strconv.FormatInt(int64(i), 10) + ")"
}
@@ -0,0 +1,204 @@
package asm
//go:generate stringer -output load_store_string.go -type=Mode,Size
// Mode for load and store operations
//
// msb lsb
// +---+--+---+
// |MDE|sz|cls|
// +---+--+---+
type Mode uint8
const modeMask OpCode = 0xe0
const (
// InvalidMode is returned by getters when invoked
// on non load / store OpCodes
InvalidMode Mode = 0xff
// ImmMode - immediate value
ImmMode Mode = 0x00
// AbsMode - immediate value + offset
AbsMode Mode = 0x20
// IndMode - indirect (imm+src)
IndMode Mode = 0x40
// MemMode - load from memory
MemMode Mode = 0x60
// XAddMode - add atomically across processors.
XAddMode Mode = 0xc0
)
// Size of load and store operations
//
// msb lsb
// +---+--+---+
// |mde|SZ|cls|
// +---+--+---+
type Size uint8
const sizeMask OpCode = 0x18
const (
// InvalidSize is returned by getters when invoked
// on non load / store OpCodes
InvalidSize Size = 0xff
// DWord - double word; 64 bits
DWord Size = 0x18
// Word - word; 32 bits
Word Size = 0x00
// Half - half-word; 16 bits
Half Size = 0x08
// Byte - byte; 8 bits
Byte Size = 0x10
)
// Sizeof returns the size in bytes.
func (s Size) Sizeof() int {
switch s {
case DWord:
return 8
case Word:
return 4
case Half:
return 2
case Byte:
return 1
default:
return -1
}
}
// LoadMemOp returns the OpCode to load a value of given size from memory.
func LoadMemOp(size Size) OpCode {
return OpCode(LdXClass).SetMode(MemMode).SetSize(size)
}
// LoadMem emits `dst = *(size *)(src + offset)`.
func LoadMem(dst, src Register, offset int16, size Size) Instruction {
return Instruction{
OpCode: LoadMemOp(size),
Dst: dst,
Src: src,
Offset: offset,
}
}
// LoadImmOp returns the OpCode to load an immediate of given size.
//
// As of kernel 4.20, only DWord size is accepted.
func LoadImmOp(size Size) OpCode {
return OpCode(LdClass).SetMode(ImmMode).SetSize(size)
}
// LoadImm emits `dst = (size)value`.
//
// As of kernel 4.20, only DWord size is accepted.
func LoadImm(dst Register, value int64, size Size) Instruction {
return Instruction{
OpCode: LoadImmOp(size),
Dst: dst,
Constant: value,
}
}
// LoadMapPtr stores a pointer to a map in dst.
func LoadMapPtr(dst Register, fd int) Instruction {
if fd < 0 {
return Instruction{OpCode: InvalidOpCode}
}
return Instruction{
OpCode: LoadImmOp(DWord),
Dst: dst,
Src: PseudoMapFD,
Constant: int64(uint32(fd)),
}
}
// LoadMapValue stores a pointer to the value at a certain offset of a map.
func LoadMapValue(dst Register, fd int, offset uint32) Instruction {
if fd < 0 {
return Instruction{OpCode: InvalidOpCode}
}
fdAndOffset := (uint64(offset) << 32) | uint64(uint32(fd))
return Instruction{
OpCode: LoadImmOp(DWord),
Dst: dst,
Src: PseudoMapValue,
Constant: int64(fdAndOffset),
}
}
// LoadIndOp returns the OpCode for loading a value of given size from an sk_buff.
func LoadIndOp(size Size) OpCode {
return OpCode(LdClass).SetMode(IndMode).SetSize(size)
}
// LoadInd emits `dst = ntoh(*(size *)(((sk_buff *)R6)->data + src + offset))`.
func LoadInd(dst, src Register, offset int32, size Size) Instruction {
return Instruction{
OpCode: LoadIndOp(size),
Dst: dst,
Src: src,
Constant: int64(offset),
}
}
// LoadAbsOp returns the OpCode for loading a value of given size from an sk_buff.
func LoadAbsOp(size Size) OpCode {
return OpCode(LdClass).SetMode(AbsMode).SetSize(size)
}
// LoadAbs emits `r0 = ntoh(*(size *)(((sk_buff *)R6)->data + offset))`.
func LoadAbs(offset int32, size Size) Instruction {
return Instruction{
OpCode: LoadAbsOp(size),
Dst: R0,
Constant: int64(offset),
}
}
// StoreMemOp returns the OpCode for storing a register of given size in memory.
func StoreMemOp(size Size) OpCode {
return OpCode(StXClass).SetMode(MemMode).SetSize(size)
}
// StoreMem emits `*(size *)(dst + offset) = src`
func StoreMem(dst Register, offset int16, src Register, size Size) Instruction {
return Instruction{
OpCode: StoreMemOp(size),
Dst: dst,
Src: src,
Offset: offset,
}
}
// StoreImmOp returns the OpCode for storing an immediate of given size in memory.
func StoreImmOp(size Size) OpCode {
return OpCode(StClass).SetMode(MemMode).SetSize(size)
}
// StoreImm emits `*(size *)(dst + offset) = value`.
func StoreImm(dst Register, offset int16, value int64, size Size) Instruction {
return Instruction{
OpCode: StoreImmOp(size),
Dst: dst,
Offset: offset,
Constant: value,
}
}
// 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)
}
// StoreXAdd atomically adds src to *dst.
func StoreXAdd(dst, src Register, size Size) Instruction {
return Instruction{
OpCode: StoreXAddOp(size),
Dst: dst,
Src: src,
}
}
@@ -0,0 +1,80 @@
// Code generated by "stringer -output load_store_string.go -type=Mode,Size"; DO NOT EDIT.
package asm
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[InvalidMode-255]
_ = x[ImmMode-0]
_ = x[AbsMode-32]
_ = x[IndMode-64]
_ = x[MemMode-96]
_ = x[XAddMode-192]
}
const (
_Mode_name_0 = "ImmMode"
_Mode_name_1 = "AbsMode"
_Mode_name_2 = "IndMode"
_Mode_name_3 = "MemMode"
_Mode_name_4 = "XAddMode"
_Mode_name_5 = "InvalidMode"
)
func (i Mode) String() string {
switch {
case i == 0:
return _Mode_name_0
case i == 32:
return _Mode_name_1
case i == 64:
return _Mode_name_2
case i == 96:
return _Mode_name_3
case i == 192:
return _Mode_name_4
case i == 255:
return _Mode_name_5
default:
return "Mode(" + strconv.FormatInt(int64(i), 10) + ")"
}
}
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[InvalidSize-255]
_ = x[DWord-24]
_ = x[Word-0]
_ = x[Half-8]
_ = x[Byte-16]
}
const (
_Size_name_0 = "Word"
_Size_name_1 = "Half"
_Size_name_2 = "Byte"
_Size_name_3 = "DWord"
_Size_name_4 = "InvalidSize"
)
func (i Size) String() string {
switch {
case i == 0:
return _Size_name_0
case i == 8:
return _Size_name_1
case i == 16:
return _Size_name_2
case i == 24:
return _Size_name_3
case i == 255:
return _Size_name_4
default:
return "Size(" + strconv.FormatInt(int64(i), 10) + ")"
}
}
@@ -0,0 +1,80 @@
package asm
// Metadata contains metadata about an instruction.
type Metadata struct {
head *metaElement
}
type metaElement struct {
next *metaElement
key, value interface{}
}
// Find the element containing key.
//
// Returns nil if there is no such element.
func (m *Metadata) find(key interface{}) *metaElement {
for e := m.head; e != nil; e = e.next {
if e.key == key {
return e
}
}
return nil
}
// Remove an element from the linked list.
//
// Copies as many elements of the list as necessary to remove r, but doesn't
// perform a full copy.
func (m *Metadata) remove(r *metaElement) {
current := &m.head
for e := m.head; e != nil; e = e.next {
if e == r {
// We've found the element we want to remove.
*current = e.next
// No need to copy the tail.
return
}
// There is another element in front of the one we want to remove.
// We have to copy it to be able to change metaElement.next.
cpy := &metaElement{key: e.key, value: e.value}
*current = cpy
current = &cpy.next
}
}
// Set a key to a value.
//
// If value is nil, the key is removed. Avoids modifying old metadata by
// copying if necessary.
func (m *Metadata) Set(key, value interface{}) {
if e := m.find(key); e != nil {
if e.value == value {
// Key is present and the value is the same. Nothing to do.
return
}
// Key is present with a different value. Create a copy of the list
// which doesn't have the element in it.
m.remove(e)
}
// m.head is now a linked list that doesn't contain key.
if value == nil {
return
}
m.head = &metaElement{key: key, value: value, next: m.head}
}
// Get the value of a key.
//
// Returns nil if no value with the given key is present.
func (m *Metadata) Get(key interface{}) interface{} {
if e := m.find(key); e != nil {
return e.value
}
return nil
}
@@ -0,0 +1,109 @@
package asm
import (
"testing"
"unsafe"
qt "github.com/frankban/quicktest"
)
func TestMetadata(t *testing.T) {
var m Metadata
// Metadata should be the size of a pointer.
qt.Assert(t, unsafe.Sizeof(m), qt.Equals, unsafe.Sizeof(uintptr(0)))
// A lookup in a nil meta should return nil.
qt.Assert(t, m.Get(bool(false)), qt.IsNil)
// We can look up anything we inserted.
m.Set(bool(false), int(0))
m.Set(int(1), int(1))
qt.Assert(t, m.Get(bool(false)), qt.Equals, int(0))
qt.Assert(t, m.Get(int(1)), qt.Equals, int(1))
// We have copy on write semantics
old := m
m.Set(bool(false), int(1))
qt.Assert(t, m.Get(bool(false)), qt.Equals, int(1))
qt.Assert(t, m.Get(int(1)), qt.Equals, int(1))
qt.Assert(t, old.Get(bool(false)), qt.Equals, int(0))
qt.Assert(t, old.Get(int(1)), qt.Equals, int(1))
// Newtypes are handled distinctly.
type b bool
m.Set(b(false), int(42))
qt.Assert(t, m.Get(bool(false)), qt.Equals, int(1))
qt.Assert(t, m.Get(int(1)), qt.Equals, int(1))
qt.Assert(t, m.Get(b(false)), qt.Equals, int(42))
// Setting nil removes a key.
m.Set(bool(false), nil)
qt.Assert(t, m.Get(bool(false)), qt.IsNil)
qt.Assert(t, m.Get(int(1)), qt.Equals, int(1))
qt.Assert(t, m.Get(b(false)), qt.Equals, int(42))
}
func BenchmarkMetadata(b *testing.B) {
// Assume that three bits of metadata on a single instruction is
// our worst case.
const worstCaseItems = 3
type t struct{}
b.Run("add first", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
var v Metadata
v.Set(t{}, 0)
}
})
b.Run("add last", func(b *testing.B) {
var m Metadata
for i := 0; i < worstCaseItems-1; i++ {
m.Set(i, i)
}
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
v := m
v.Set(t{}, 0)
}
})
b.Run("add existing", func(b *testing.B) {
var m Metadata
for i := 0; i < worstCaseItems-1; i++ {
m.Set(i, i)
}
m.Set(t{}, 0)
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
v := m
v.Set(t{}, 0)
}
})
b.Run("get miss", func(b *testing.B) {
var m Metadata
for i := 0; i < worstCaseItems; i++ {
m.Set(i, i)
}
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
if m.Get(t{}) != nil {
b.Fatal("got result from miss")
}
}
})
}
@@ -0,0 +1,271 @@
package asm
import (
"fmt"
"strings"
)
//go:generate stringer -output opcode_string.go -type=Class
// Class of operations
//
// msb lsb
// +---+--+---+
// | ?? |CLS|
// +---+--+---+
type Class uint8
const classMask OpCode = 0x07
const (
// LdClass loads immediate values into registers.
// Also used for non-standard load operations from cBPF.
LdClass Class = 0x00
// LdXClass loads memory into registers.
LdXClass Class = 0x01
// StClass stores immediate values to memory.
StClass Class = 0x02
// StXClass stores registers to memory.
StXClass Class = 0x03
// ALUClass describes arithmetic operators.
ALUClass Class = 0x04
// JumpClass describes jump operators.
JumpClass Class = 0x05
// Jump32Class describes jump operators with 32-bit comparisons.
// Requires kernel 5.1.
Jump32Class Class = 0x06
// ALU64Class describes arithmetic operators in 64-bit mode.
ALU64Class Class = 0x07
)
// IsLoad checks if this is either LdClass or LdXClass.
func (cls Class) IsLoad() bool {
return cls == LdClass || cls == LdXClass
}
// IsStore checks if this is either StClass or StXClass.
func (cls Class) IsStore() bool {
return cls == StClass || cls == StXClass
}
func (cls Class) isLoadOrStore() bool {
return cls.IsLoad() || cls.IsStore()
}
// IsALU checks if this is either ALUClass or ALU64Class.
func (cls Class) IsALU() bool {
return cls == ALUClass || cls == ALU64Class
}
// IsJump checks if this is either JumpClass or Jump32Class.
func (cls Class) IsJump() bool {
return cls == JumpClass || cls == Jump32Class
}
func (cls Class) isJumpOrALU() bool {
return cls.IsJump() || cls.IsALU()
}
// OpCode is a packed eBPF opcode.
//
// Its encoding is defined by a Class value:
//
// msb lsb
// +----+-+---+
// | ???? |CLS|
// +----+-+---+
type OpCode uint8
// InvalidOpCode is returned by setters on OpCode
const InvalidOpCode OpCode = 0xff
// rawInstructions returns the number of BPF instructions required
// to encode this opcode.
func (op OpCode) rawInstructions() int {
if op.IsDWordLoad() {
return 2
}
return 1
}
func (op OpCode) IsDWordLoad() bool {
return op == LoadImmOp(DWord)
}
// Class returns the class of operation.
func (op OpCode) Class() Class {
return Class(op & classMask)
}
// Mode returns the mode for load and store operations.
func (op OpCode) Mode() Mode {
if !op.Class().isLoadOrStore() {
return InvalidMode
}
return Mode(op & modeMask)
}
// Size returns the size for load and store operations.
func (op OpCode) Size() Size {
if !op.Class().isLoadOrStore() {
return InvalidSize
}
return Size(op & sizeMask)
}
// Source returns the source for branch and ALU operations.
func (op OpCode) Source() Source {
if !op.Class().isJumpOrALU() || op.ALUOp() == Swap {
return InvalidSource
}
return Source(op & sourceMask)
}
// ALUOp returns the ALUOp.
func (op OpCode) ALUOp() ALUOp {
if !op.Class().IsALU() {
return InvalidALUOp
}
return ALUOp(op & aluMask)
}
// Endianness returns the Endianness for a byte swap instruction.
func (op OpCode) Endianness() Endianness {
if op.ALUOp() != Swap {
return InvalidEndian
}
return Endianness(op & endianMask)
}
// JumpOp returns the JumpOp.
// Returns InvalidJumpOp if it doesn't encode a jump.
func (op OpCode) JumpOp() JumpOp {
if !op.Class().IsJump() {
return InvalidJumpOp
}
jumpOp := JumpOp(op & jumpMask)
// Some JumpOps are only supported by JumpClass, not Jump32Class.
if op.Class() == Jump32Class && (jumpOp == Exit || jumpOp == Call || jumpOp == Ja) {
return InvalidJumpOp
}
return jumpOp
}
// SetMode sets the mode on load and store operations.
//
// Returns InvalidOpCode if op is of the wrong class.
func (op OpCode) SetMode(mode Mode) OpCode {
if !op.Class().isLoadOrStore() || !valid(OpCode(mode), modeMask) {
return InvalidOpCode
}
return (op & ^modeMask) | OpCode(mode)
}
// SetSize sets the size on load and store operations.
//
// Returns InvalidOpCode if op is of the wrong class.
func (op OpCode) SetSize(size Size) OpCode {
if !op.Class().isLoadOrStore() || !valid(OpCode(size), sizeMask) {
return InvalidOpCode
}
return (op & ^sizeMask) | OpCode(size)
}
// SetSource sets the source on jump and ALU operations.
//
// Returns InvalidOpCode if op is of the wrong class.
func (op OpCode) SetSource(source Source) OpCode {
if !op.Class().isJumpOrALU() || !valid(OpCode(source), sourceMask) {
return InvalidOpCode
}
return (op & ^sourceMask) | OpCode(source)
}
// SetALUOp sets the ALUOp on ALU operations.
//
// Returns InvalidOpCode if op is of the wrong class.
func (op OpCode) SetALUOp(alu ALUOp) OpCode {
if !op.Class().IsALU() || !valid(OpCode(alu), aluMask) {
return InvalidOpCode
}
return (op & ^aluMask) | OpCode(alu)
}
// SetJumpOp sets the JumpOp on jump operations.
//
// Returns InvalidOpCode if op is of the wrong class.
func (op OpCode) SetJumpOp(jump JumpOp) OpCode {
if !op.Class().IsJump() || !valid(OpCode(jump), jumpMask) {
return InvalidOpCode
}
newOp := (op & ^jumpMask) | OpCode(jump)
// Check newOp is legal.
if newOp.JumpOp() == InvalidJumpOp {
return InvalidOpCode
}
return newOp
}
func (op OpCode) String() string {
var f strings.Builder
switch class := op.Class(); {
case class.isLoadOrStore():
f.WriteString(strings.TrimSuffix(class.String(), "Class"))
mode := op.Mode()
f.WriteString(strings.TrimSuffix(mode.String(), "Mode"))
switch op.Size() {
case DWord:
f.WriteString("DW")
case Word:
f.WriteString("W")
case Half:
f.WriteString("H")
case Byte:
f.WriteString("B")
}
case class.IsALU():
f.WriteString(op.ALUOp().String())
if op.ALUOp() == Swap {
// Width for Endian is controlled by Constant
f.WriteString(op.Endianness().String())
} else {
if class == ALUClass {
f.WriteString("32")
}
f.WriteString(strings.TrimSuffix(op.Source().String(), "Source"))
}
case class.IsJump():
f.WriteString(op.JumpOp().String())
if class == Jump32Class {
f.WriteString("32")
}
if jop := op.JumpOp(); jop != Exit && jop != Call {
f.WriteString(strings.TrimSuffix(op.Source().String(), "Source"))
}
default:
fmt.Fprintf(&f, "OpCode(%#x)", uint8(op))
}
return f.String()
}
// valid returns true if all bits in value are covered by mask.
func valid(value, mask OpCode) bool {
return value & ^mask == 0
}
@@ -0,0 +1,30 @@
// Code generated by "stringer -output opcode_string.go -type=Class"; DO NOT EDIT.
package asm
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[LdClass-0]
_ = x[LdXClass-1]
_ = x[StClass-2]
_ = x[StXClass-3]
_ = x[ALUClass-4]
_ = x[JumpClass-5]
_ = x[Jump32Class-6]
_ = x[ALU64Class-7]
}
const _Class_name = "LdClassLdXClassStClassStXClassALUClassJumpClassJump32ClassALU64Class"
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) {
return "Class(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _Class_name[_Class_index[i]:_Class_index[i+1]]
}
@@ -0,0 +1,52 @@
package asm
import (
"fmt"
"testing"
qt "github.com/frankban/quicktest"
)
func TestGetSetJumpOp(t *testing.T) {
test := func(class Class, op JumpOp, valid bool) {
t.Run(fmt.Sprintf("%s-%s", class, op), func(t *testing.T) {
opcode := OpCode(class).SetJumpOp(op)
if valid {
qt.Assert(t, opcode, qt.Not(qt.Equals), InvalidOpCode)
qt.Assert(t, opcode.JumpOp(), qt.Equals, op)
} else {
qt.Assert(t, opcode, qt.Equals, InvalidOpCode)
qt.Assert(t, opcode.JumpOp(), qt.Equals, InvalidJumpOp)
}
})
}
// Exit, call and JA aren't allowed with Jump32
test(Jump32Class, Exit, false)
test(Jump32Class, Call, false)
test(Jump32Class, Ja, false)
// But are with Jump
test(JumpClass, Exit, true)
test(JumpClass, Call, true)
test(JumpClass, Ja, true)
// All other ops work
for _, op := range []JumpOp{
JEq,
JGT,
JGE,
JSet,
JNE,
JSGT,
JSGE,
JLT,
JLE,
JSLT,
JSLE,
} {
test(Jump32Class, op, true)
test(JumpClass, op, true)
}
}
@@ -0,0 +1,51 @@
package asm
import (
"fmt"
)
// Register is the source or destination of most operations.
type Register uint8
// R0 contains return values.
const R0 Register = 0
// Registers for function arguments.
const (
R1 Register = R0 + 1 + iota
R2
R3
R4
R5
)
// Callee saved registers preserved by function calls.
const (
R6 Register = R5 + 1 + iota
R7
R8
R9
)
// Read-only frame pointer to access stack.
const (
R10 Register = R9 + 1
RFP = R10
)
// Pseudo registers used by 64bit loads and jumps
const (
PseudoMapFD = R1 // BPF_PSEUDO_MAP_FD
PseudoMapValue = R2 // BPF_PSEUDO_MAP_VALUE
PseudoCall = R1 // BPF_PSEUDO_CALL
PseudoFunc = R4 // BPF_PSEUDO_FUNC
PseudoKfuncCall = R2 // BPF_PSEUDO_KFUNC_CALL
)
func (r Register) String() string {
v := uint8(r)
if v == 10 {
return "rfp"
}
return fmt.Sprintf("r%d", v)
}
@@ -0,0 +1,66 @@
// Code generated by "stringer -type AttachType -trimprefix Attach"; DO NOT EDIT.
package ebpf
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[AttachNone-0]
_ = x[AttachCGroupInetIngress-0]
_ = x[AttachCGroupInetEgress-1]
_ = x[AttachCGroupInetSockCreate-2]
_ = x[AttachCGroupSockOps-3]
_ = x[AttachSkSKBStreamParser-4]
_ = x[AttachSkSKBStreamVerdict-5]
_ = x[AttachCGroupDevice-6]
_ = x[AttachSkMsgVerdict-7]
_ = x[AttachCGroupInet4Bind-8]
_ = x[AttachCGroupInet6Bind-9]
_ = x[AttachCGroupInet4Connect-10]
_ = x[AttachCGroupInet6Connect-11]
_ = x[AttachCGroupInet4PostBind-12]
_ = x[AttachCGroupInet6PostBind-13]
_ = x[AttachCGroupUDP4Sendmsg-14]
_ = x[AttachCGroupUDP6Sendmsg-15]
_ = x[AttachLircMode2-16]
_ = x[AttachFlowDissector-17]
_ = x[AttachCGroupSysctl-18]
_ = x[AttachCGroupUDP4Recvmsg-19]
_ = x[AttachCGroupUDP6Recvmsg-20]
_ = x[AttachCGroupGetsockopt-21]
_ = x[AttachCGroupSetsockopt-22]
_ = x[AttachTraceRawTp-23]
_ = x[AttachTraceFEntry-24]
_ = x[AttachTraceFExit-25]
_ = x[AttachModifyReturn-26]
_ = x[AttachLSMMac-27]
_ = x[AttachTraceIter-28]
_ = x[AttachCgroupInet4GetPeername-29]
_ = x[AttachCgroupInet6GetPeername-30]
_ = x[AttachCgroupInet4GetSockname-31]
_ = x[AttachCgroupInet6GetSockname-32]
_ = x[AttachXDPDevMap-33]
_ = x[AttachCgroupInetSockRelease-34]
_ = x[AttachXDPCPUMap-35]
_ = x[AttachSkLookup-36]
_ = x[AttachXDP-37]
_ = x[AttachSkSKBVerdict-38]
_ = x[AttachSkReuseportSelect-39]
_ = x[AttachSkReuseportSelectOrMigrate-40]
_ = x[AttachPerfEvent-41]
_ = x[AttachTraceKprobeMulti-42]
}
const _AttachType_name = "NoneCGroupInetEgressCGroupInetSockCreateCGroupSockOpsSkSKBStreamParserSkSKBStreamVerdictCGroupDeviceSkMsgVerdictCGroupInet4BindCGroupInet6BindCGroupInet4ConnectCGroupInet6ConnectCGroupInet4PostBindCGroupInet6PostBindCGroupUDP4SendmsgCGroupUDP6SendmsgLircMode2FlowDissectorCGroupSysctlCGroupUDP4RecvmsgCGroupUDP6RecvmsgCGroupGetsockoptCGroupSetsockoptTraceRawTpTraceFEntryTraceFExitModifyReturnLSMMacTraceIterCgroupInet4GetPeernameCgroupInet6GetPeernameCgroupInet4GetSocknameCgroupInet6GetSocknameXDPDevMapCgroupInetSockReleaseXDPCPUMapSkLookupXDPSkSKBVerdictSkReuseportSelectSkReuseportSelectOrMigratePerfEventTraceKprobeMulti"
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}
func (i AttachType) String() string {
if i >= AttachType(len(_AttachType_index)-1) {
return "AttachType(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _AttachType_name[_AttachType_index[i]:_AttachType_index[i+1]]
}
@@ -0,0 +1,869 @@
package btf
import (
"bufio"
"debug/elf"
"encoding/binary"
"errors"
"fmt"
"io"
"math"
"os"
"reflect"
"sync"
"github.com/cilium/ebpf/internal"
"github.com/cilium/ebpf/internal/sys"
"github.com/cilium/ebpf/internal/unix"
)
const btfMagic = 0xeB9F
// Errors returned by BTF functions.
var (
ErrNotSupported = internal.ErrNotSupported
ErrNotFound = errors.New("not found")
ErrNoExtendedInfo = errors.New("no extended info")
ErrMultipleMatches = errors.New("multiple matching types")
)
// ID represents the unique ID of a BTF object.
type ID = sys.BTFID
// Spec allows querying a set of Types and loading the set into the
// kernel.
type Spec 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][]Type
// String table from ELF, may be nil.
strings *stringTable
// Byte order of the ELF we decoded the spec from, may be nil.
byteOrder binary.ByteOrder
}
var btfHeaderLen = binary.Size(&btfHeader{})
type btfHeader struct {
Magic uint16
Version uint8
Flags uint8
HdrLen uint32
TypeOff uint32
TypeLen uint32
StringOff uint32
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)
}
// newSpec creates a Spec containing only Void.
func newSpec() *Spec {
return &Spec{
[]Type{(*Void)(nil)},
map[Type]TypeID{(*Void)(nil): 0},
0,
make(map[essentialName][]Type),
nil,
nil,
}
}
// LoadSpec opens file and calls LoadSpecFromReader on it.
func LoadSpec(file string) (*Spec, error) {
fh, err := os.Open(file)
if err != nil {
return nil, err
}
defer fh.Close()
return LoadSpecFromReader(fh)
}
// LoadSpecFromReader reads from an ELF or a raw BTF blob.
//
// Returns ErrNotFound if reading from an ELF which contains no BTF. ExtInfos
// may be nil.
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)
}
return nil, err
}
return loadSpecFromELF(file)
}
// LoadSpecAndExtInfosFromReader reads from an ELF.
//
// ExtInfos may be nil if the ELF doesn't contain section metadata.
// Returns ErrNotFound if the ELF contains no BTF.
func LoadSpecAndExtInfosFromReader(rd io.ReaderAt) (*Spec, *ExtInfos, error) {
file, err := internal.NewSafeELFFile(rd)
if err != nil {
return nil, nil, err
}
spec, err := loadSpecFromELF(file)
if err != nil {
return nil, nil, err
}
extInfos, err := loadExtInfosFromELF(file, spec)
if err != nil && !errors.Is(err, ErrNotFound) {
return nil, nil, err
}
return spec, extInfos, nil
}
// symbolOffsets extracts all symbols offsets from an ELF and indexes them by
// section and variable name.
//
// References to variables in BTF data sections carry unsigned 32-bit offsets.
// 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) {
symbols, err := file.Symbols()
if err != nil {
return nil, fmt.Errorf("can't read symbols: %v", err)
}
offsets := make(map[symbol]uint32)
for _, sym := range symbols {
if idx := sym.Section; idx >= elf.SHN_LORESERVE && idx <= elf.SHN_HIRESERVE {
// Ignore things like SHN_ABS
continue
}
if sym.Value > math.MaxUint32 {
// VarSecinfo offset is u32, cannot reference symbols in higher regions.
continue
}
if int(sym.Section) >= len(file.Sections) {
return nil, fmt.Errorf("symbol %s: invalid section %d", sym.Name, sym.Section)
}
secName := file.Sections[sym.Section].Name
offsets[symbol{secName, sym.Name}] = uint32(sym.Value)
}
return offsets, nil
}
func loadSpecFromELF(file *internal.SafeELFFile) (*Spec, error) {
var (
btfSection *elf.Section
sectionSizes = make(map[string]uint32)
)
for _, sec := range file.Sections {
switch sec.Name {
case ".BTF":
btfSection = sec
default:
if sec.Type != elf.SHT_PROGBITS && sec.Type != elf.SHT_NOBITS {
break
}
if sec.Size > math.MaxUint32 {
return nil, fmt.Errorf("section %s exceeds maximum size", sec.Name)
}
sectionSizes[sec.Name] = uint32(sec.Size)
}
}
if btfSection == nil {
return nil, fmt.Errorf("btf: %w", ErrNotFound)
}
offsets, err := symbolOffsets(file)
if err != nil {
return nil, err
}
if btfSection.ReaderAt == nil {
return nil, fmt.Errorf("compressed BTF is not supported")
}
spec, err := loadRawSpec(btfSection.ReaderAt, file.ByteOrder, nil)
if err != nil {
return nil, err
}
err = fixupDatasec(spec.types, sectionSizes, offsets)
if err != nil {
return nil, err
}
return spec, nil
}
func loadRawSpec(btf io.ReaderAt, bo binary.ByteOrder, base *Spec) (*Spec, error) {
var (
baseStrings *stringTable
firstTypeID TypeID
err error
)
if base != nil {
if base.firstTypeID != 0 {
return nil, fmt.Errorf("can't use split BTF as base")
}
if base.strings == nil {
return nil, fmt.Errorf("parse split BTF: base must be loaded from an ELF")
}
baseStrings = base.strings
firstTypeID, err = base.nextTypeID()
if err != nil {
return nil, err
}
}
rawTypes, rawStrings, err := parseBTF(btf, bo, baseStrings)
if err != nil {
return nil, err
}
types, err := inflateRawTypes(rawTypes, rawStrings, base)
if err != nil {
return nil, err
}
typeIDs, typesByName := indexTypes(types, firstTypeID)
return &Spec{
namedTypes: typesByName,
typeIDs: typeIDs,
types: types,
firstTypeID: firstTypeID,
strings: rawStrings,
byteOrder: bo,
}, nil
}
func indexTypes(types []Type, firstTypeID TypeID) (map[Type]TypeID, map[essentialName][]Type) {
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++
}
}
typeIDs := make(map[Type]TypeID, len(types))
typesByName := make(map[essentialName][]Type, namedTypes)
for i, typ := range types {
if name := newEssentialName(typ.TypeName()); name != "" {
typesByName[name] = append(typesByName[name], typ)
}
typeIDs[typ] = firstTypeID + TypeID(i)
}
return typeIDs, typesByName
}
// 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.
func LoadKernelSpec() (*Spec, error) {
spec, _, err := kernelSpec()
if err != nil {
return nil, err
}
return spec.Copy(), nil
}
var kernelBTF struct {
sync.RWMutex
spec *Spec
// True if the spec was read from an ELF instead of raw BTF in /sys.
fallback bool
}
// FlushKernelSpec removes any cached kernel type information.
func FlushKernelSpec() {
kernelBTF.Lock()
defer kernelBTF.Unlock()
kernelBTF.spec, kernelBTF.fallback = nil, false
}
func kernelSpec() (*Spec, bool, error) {
kernelBTF.RLock()
spec, fallback := kernelBTF.spec, kernelBTF.fallback
kernelBTF.RUnlock()
if spec == nil {
kernelBTF.Lock()
defer kernelBTF.Unlock()
spec, fallback = kernelBTF.spec, kernelBTF.fallback
}
if spec != nil {
return spec, fallback, nil
}
spec, fallback, err := loadKernelSpec()
if err != nil {
return nil, false, err
}
kernelBTF.spec, kernelBTF.fallback = spec, fallback
return spec, fallback, nil
}
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
}
defer file.Close()
spec, err := loadSpecFromELF(file)
return spec, true, err
}
// findVMLinux scans multiple well-known paths for vmlinux kernel images.
func findVMLinux() (*internal.SafeELFFile, error) {
release, err := internal.KernelRelease()
if err != nil {
return nil, err
}
// use same list of locations as libbpf
// https://github.com/libbpf/libbpf/blob/9a3a42608dbe3731256a5682a125ac1e23bced8f/src/btf.c#L3114-L3122
locations := []string{
"/boot/vmlinux-%s",
"/lib/modules/%s/vmlinux-%[1]s",
"/lib/modules/%s/build/vmlinux",
"/usr/lib/modules/%s/kernel/vmlinux",
"/usr/lib/debug/boot/vmlinux-%s",
"/usr/lib/debug/boot/vmlinux-%s.debug",
"/usr/lib/debug/lib/modules/%s/vmlinux",
}
for _, loc := range locations {
file, err := internal.OpenSafeELFFile(fmt.Sprintf(loc, release))
if errors.Is(err, os.ErrNotExist) {
continue
}
return file, err
}
return nil, fmt.Errorf("no BTF found for kernel version %s: %w", release, internal.ErrNotSupported)
}
// parseBTFHeader parses the header of the .BTF section.
func parseBTFHeader(r io.Reader, bo binary.ByteOrder) (*btfHeader, error) {
var header btfHeader
if err := binary.Read(r, bo, &header); err != nil {
return nil, fmt.Errorf("can't read header: %v", err)
}
if header.Magic != btfMagic {
return nil, fmt.Errorf("incorrect magic value %v", header.Magic)
}
if header.Version != 1 {
return nil, fmt.Errorf("unexpected version %v", header.Version)
}
if header.Flags != 0 {
return nil, fmt.Errorf("unsupported flags %v", header.Flags)
}
remainder := int64(header.HdrLen) - int64(binary.Size(&header))
if remainder < 0 {
return nil, errors.New("header length shorter than btfHeader size")
}
if _, err := io.CopyN(internal.DiscardZeroes{}, r, remainder); err != nil {
return nil, fmt.Errorf("header padding: %v", err)
}
return &header, nil
}
func guessRawBTFByteOrder(r io.ReaderAt) binary.ByteOrder {
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) ([]rawType, *stringTable, error) {
buf := internal.NewBufferedSectionReader(btf, 0, math.MaxInt64)
header, err := parseBTFHeader(buf, bo)
if err != nil {
return nil, nil, fmt.Errorf("parsing .BTF header: %v", 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)
}
buf.Reset(io.NewSectionReader(btf, header.typeStart(), int64(header.TypeLen)))
rawTypes, err := readTypes(buf, bo, header.TypeLen)
if err != nil {
return nil, nil, fmt.Errorf("can't read types: %w", err)
}
return rawTypes, rawStrings, nil
}
type symbol struct {
section string
name string
}
// 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
}
name := ds.Name
// Some Datasecs are virtual and don't have corresponding ELF sections.
switch name {
case ".ksyms":
// .ksyms describes forward declarations of kfunc signatures.
// Nothing to fix up, all sizes and offsets are 0.
for _, vsi := range ds.Vars {
_, ok := vsi.Type.(*Func)
if !ok {
// Only Funcs are supported in the .ksyms Datasec.
return fmt.Errorf("data section %s: expected *btf.Func, not %T: %w", name, vsi.Type, ErrNotSupported)
}
}
continue
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.
if err := fixupDatasecLayout(ds); err != nil {
return err
}
// Fix up extern to global linkage to avoid a BTF verifier error.
for _, vsi := range ds.Vars {
vsi.Type.(*Var).Linkage = GlobalVar
}
continue
}
if ds.Size != 0 {
continue
}
ds.Size, ok = 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}]
if !ok {
return fmt.Errorf("data section %s: missing offset for symbol %s", name, symName)
}
}
}
return nil
}
// fixupDatasecLayout populates ds.Vars[].Offset according to var sizes and
// alignment. Calculate and set ds.Size.
func fixupDatasecLayout(ds *Datasec) error {
var off uint32
for i, vsi := range ds.Vars {
v, ok := vsi.Type.(*Var)
if !ok {
return fmt.Errorf("member %d: unsupported type %T", i, vsi.Type)
}
size, err := Sizeof(v.Type)
if err != nil {
return fmt.Errorf("variable %s: getting size: %w", v.Name, err)
}
align, err := alignof(v.Type)
if err != nil {
return fmt.Errorf("variable %s: getting alignment: %w", v.Name, err)
}
// Align the current member based on the offset of the end of the previous
// member and the alignment of the current member.
off = internal.Align(off, uint32(align))
ds.Vars[i].Offset = off
off += uint32(size)
}
ds.Size = off
return nil
}
// Copy creates a copy of Spec.
func (s *Spec) Copy() *Spec {
types := copyTypes(s.types, nil)
typeIDs, typesByName := indexTypes(types, s.firstTypeID)
// NB: Other parts of spec are not copied since they are immutable.
return &Spec{
types,
typeIDs,
s.firstTypeID,
typesByName,
s.strings,
s.byteOrder,
}
}
type sliceWriter []byte
func (sw sliceWriter) Write(p []byte) (int, error) {
if len(p) != len(sw) {
return 0, errors.New("size doesn't match")
}
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.firstTypeID + TypeID(len(s.types))
if id < s.firstTypeID {
return 0, fmt.Errorf("no more type IDs")
}
return id, nil
}
// TypeByID returns the BTF Type with the given type ID.
//
// Returns an error wrapping ErrNotFound if a Type with the given ID
// does not exist in the Spec.
func (s *Spec) TypeByID(id TypeID) (Type, error) {
if id < s.firstTypeID {
return nil, fmt.Errorf("look up type with ID %d (first ID is %d): %w", id, s.firstTypeID, ErrNotFound)
}
index := int(id - s.firstTypeID)
if index >= len(s.types) {
return nil, fmt.Errorf("look up type with ID %d: %w", id, ErrNotFound)
}
return s.types[index], nil
}
// TypeID returns the ID for a given Type.
//
// Returns an error wrapping ErrNoFound if the type isn't part of the Spec.
func (s *Spec) 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
}
id, ok := s.typeIDs[typ]
if !ok {
return 0, fmt.Errorf("no ID for type %s: %w", typ, ErrNotFound)
}
return id, nil
}
// AnyTypesByName returns a list of BTF Types with the given name.
//
// If the BTF blob describes multiple compilation units like vmlinux, multiple
// Types with the same name and kind can exist, but might not describe the same
// data structure.
//
// Returns an error wrapping ErrNotFound if no matching Type exists in the Spec.
func (s *Spec) AnyTypesByName(name string) ([]Type, error) {
types := s.namedTypes[newEssentialName(name)]
if len(types) == 0 {
return nil, fmt.Errorf("type name %s: %w", name, ErrNotFound)
}
// Return a copy to prevent changes to namedTypes.
result := make([]Type, 0, len(types))
for _, t := range types {
// Match against the full name, not just the essential one
// in case the type being looked up is a struct flavor.
if t.TypeName() == name {
result = append(result, t)
}
}
return result, nil
}
// AnyTypeByName returns a Type with the given name.
//
// Returns an error if multiple types of that name exist.
func (s *Spec) AnyTypeByName(name string) (Type, error) {
types, err := s.AnyTypesByName(name)
if err != nil {
return nil, err
}
if len(types) > 1 {
return nil, fmt.Errorf("found multiple types: %v", types)
}
return types[0], nil
}
// TypeByName searches for a Type with a specific name. Since multiple Types
// with the same name can exist, the parameter typ is taken to narrow down the
// search in case of a clash.
//
// typ must be a non-nil pointer to an implementation of a Type. On success, the
// address of the found Type will be copied to typ.
//
// Returns an error wrapping ErrNotFound if no matching Type exists in 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()
// typ may be **T or *Type
typValue := reflect.ValueOf(typ)
if typValue.Kind() != reflect.Ptr {
return fmt.Errorf("%T is not a pointer", typ)
}
typPtr := typValue.Elem()
if !typPtr.CanSet() {
return fmt.Errorf("%T cannot be set", typ)
}
wanted := typPtr.Type()
if wanted == typeInterface {
// This is *Type. Unwrap the value's type.
wanted = typPtr.Elem().Type()
}
if !wanted.AssignableTo(typeInterface) {
return fmt.Errorf("%T does not satisfy Type interface", typ)
}
types, err := s.AnyTypesByName(name)
if err != nil {
return err
}
var candidate Type
for _, typ := range types {
if reflect.TypeOf(typ) != wanted {
continue
}
if candidate != nil {
return fmt.Errorf("type %s(%T): %w", name, typ, ErrMultipleMatches)
}
candidate = typ
}
if candidate == nil {
return fmt.Errorf("%s %s: %w", wanted, name, ErrNotFound)
}
typPtr.Set(reflect.ValueOf(candidate))
return nil
}
// 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)
}
// TypesIterator iterates over types of a given spec.
type TypesIterator struct {
types []Type
index int
// The last visited type in the spec.
Type Type
}
// Iterate returns the types iterator.
func (s *Spec) Iterate() *TypesIterator {
// We share the backing array of types with the Spec. This is safe since
// we don't allow deletion or shuffling of types.
return &TypesIterator{types: s.types, index: 0}
}
// Next returns true as long as there are any remaining types.
func (iter *TypesIterator) Next() bool {
if len(iter.types) <= iter.index {
return false
}
iter.Type = iter.types[iter.index]
iter.index++
return true
}
// haveBTF attempts to load a BTF blob containing an Int. It should pass on any
// kernel that supports BPF_BTF_LOAD.
var haveBTF = internal.NewFeatureTest("BTF", "4.18", func() error {
// 0-length anonymous integer
err := probeBTF(&Int{})
if errors.Is(err, unix.EINVAL) || errors.Is(err, unix.EPERM) {
return internal.ErrNotSupported
}
return err
})
// haveMapBTF attempts to load a minimal BTF blob containing a Var. It is
// used as a proxy for .bss, .data and .rodata map support, which generally
// come with a Var and Datasec. These were introduced in Linux 5.2.
var haveMapBTF = internal.NewFeatureTest("Map BTF (Var/Datasec)", "5.2", func() error {
if err := haveBTF(); err != nil {
return err
}
v := &Var{
Name: "a",
Type: &Pointer{(*Void)(nil)},
}
err := probeBTF(v)
if errors.Is(err, unix.EINVAL) || errors.Is(err, unix.EPERM) {
// Treat both EINVAL and EPERM as not supported: creating the map may still
// succeed without Btf* attrs.
return internal.ErrNotSupported
}
return err
})
// haveProgBTF attempts to load a BTF blob containing a Func and FuncProto. It
// is used as a proxy for ext_info (func_info) support, which depends on
// Func(Proto) by definition.
var haveProgBTF = internal.NewFeatureTest("Program BTF (func/line_info)", "5.0", func() error {
if err := haveBTF(); err != nil {
return err
}
fn := &Func{
Name: "a",
Type: &FuncProto{Return: (*Void)(nil)},
}
err := probeBTF(fn)
if errors.Is(err, unix.EINVAL) || errors.Is(err, unix.EPERM) {
return internal.ErrNotSupported
}
return err
})
var haveFuncLinkage = internal.NewFeatureTest("BTF func linkage", "5.6", func() error {
if err := haveProgBTF(); err != nil {
return err
}
fn := &Func{
Name: "a",
Type: &FuncProto{Return: (*Void)(nil)},
Linkage: GlobalFunc,
}
err := probeBTF(fn)
if errors.Is(err, unix.EINVAL) {
return internal.ErrNotSupported
}
return err
})
func probeBTF(typ Type) error {
b, err := NewBuilder([]Type{typ})
if err != nil {
return err
}
buf, err := b.Marshal(nil, nil)
if err != nil {
return err
}
fd, err := sys.BtfLoad(&sys.BtfLoadAttr{
Btf: sys.NewSlicePointer(buf),
BtfSize: uint32(len(buf)),
})
if err == nil {
fd.Close()
}
return err
}
@@ -0,0 +1,525 @@
package btf
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"io"
"os"
"testing"
"github.com/cilium/ebpf/internal"
"github.com/cilium/ebpf/internal/testutils"
qt "github.com/frankban/quicktest"
)
func vmlinuxSpec(tb testing.TB) *Spec {
tb.Helper()
// /sys/kernel/btf was introduced in 341dfcf8d78e ("btf: expose BTF info
// through sysfs"), which shipped in Linux 5.4.
testutils.SkipOnOldKernel(tb, "5.4", "vmlinux BTF in sysfs")
spec, fallback, err := kernelSpec()
if err != nil {
tb.Fatal(err)
}
if fallback {
tb.Fatal("/sys/kernel/btf/vmlinux is not available")
}
return spec.Copy()
}
type specAndRawBTF struct {
raw []byte
spec *Spec
}
var vmlinuxTestdata = internal.Memoize(func() (specAndRawBTF, error) {
b, err := internal.ReadAllCompressed("testdata/vmlinux.btf.gz")
if err != nil {
return specAndRawBTF{}, err
}
spec, err := loadRawSpec(bytes.NewReader(b), binary.LittleEndian, nil)
if err != nil {
return specAndRawBTF{}, err
}
return specAndRawBTF{b, spec}, nil
})
func vmlinuxTestdataReader(tb testing.TB) *bytes.Reader {
tb.Helper()
td, err := vmlinuxTestdata()
if err != nil {
tb.Fatal(err)
}
return bytes.NewReader(td.raw)
}
func vmlinuxTestdataSpec(tb testing.TB) *Spec {
tb.Helper()
td, err := vmlinuxTestdata()
if err != nil {
tb.Fatal(err)
}
return td.spec.Copy()
}
func parseELFBTF(tb testing.TB, file string) *Spec {
tb.Helper()
spec, err := LoadSpec(file)
if err != nil {
tb.Fatal("Can't load BTF:", err)
}
return spec
}
func TestAnyTypesByName(t *testing.T) {
testutils.Files(t, testutils.Glob(t, "testdata/relocs-*.elf"), func(t *testing.T, file string) {
spec := parseELFBTF(t, file)
types, err := spec.AnyTypesByName("ambiguous")
if err != nil {
t.Fatal(err)
}
if len(types) != 1 {
t.Fatalf("expected to receive exactly 1 types from querying ambiguous type, got: %v", types)
}
types, err = spec.AnyTypesByName("ambiguous___flavour")
if err != nil {
t.Fatal(err)
}
if len(types) != 1 {
t.Fatalf("expected to receive exactly 1 type from querying ambiguous flavour, got: %v", types)
}
})
}
func TestTypeByNameAmbiguous(t *testing.T) {
testutils.Files(t, testutils.Glob(t, "testdata/relocs-*.elf"), func(t *testing.T, file string) {
spec := parseELFBTF(t, file)
var typ *Struct
if err := spec.TypeByName("ambiguous", &typ); err != nil {
t.Fatal(err)
}
if name := typ.TypeName(); name != "ambiguous" {
t.Fatal("expected type name 'ambiguous', got:", name)
}
if err := spec.TypeByName("ambiguous___flavour", &typ); err != nil {
t.Fatal(err)
}
if name := typ.TypeName(); name != "ambiguous___flavour" {
t.Fatal("expected type name 'ambiguous___flavour', got:", name)
}
})
}
func TestTypeByName(t *testing.T) {
spec := vmlinuxTestdataSpec(t)
for _, typ := range []interface{}{
nil,
Struct{},
&Struct{},
[]Struct{},
&[]Struct{},
map[int]Struct{},
&map[int]Struct{},
int(0),
new(int),
} {
t.Run(fmt.Sprintf("%T", typ), func(t *testing.T) {
// spec.TypeByName MUST fail if typ is a nil btf.Type.
if err := spec.TypeByName("iphdr", typ); err == nil {
t.Fatalf("TypeByName does not fail with type %T", typ)
}
})
}
// spec.TypeByName MUST return the same address for multiple calls with the same type name.
var iphdr1, iphdr2 *Struct
if err := spec.TypeByName("iphdr", &iphdr1); err != nil {
t.Fatal(err)
}
if err := spec.TypeByName("iphdr", &iphdr2); err != nil {
t.Fatal(err)
}
if iphdr1 != iphdr2 {
t.Fatal("multiple TypeByName calls for `iphdr` name do not return the same addresses")
}
// It's valid to pass a *Type to TypeByName.
typ := Type(iphdr2)
if err := spec.TypeByName("iphdr", &typ); err != nil {
t.Fatal("Can't look up using *Type:", err)
}
// Excerpt from linux/ip.h, https://elixir.bootlin.com/linux/latest/A/ident/iphdr
//
// struct iphdr {
// #if defined(__LITTLE_ENDIAN_BITFIELD)
// __u8 ihl:4, version:4;
// #elif defined (__BIG_ENDIAN_BITFIELD)
// __u8 version:4, ihl:4;
// #else
// ...
// }
//
// The BTF we test against is for little endian.
m := iphdr1.Members[1]
if m.Name != "version" {
t.Fatal("Expected version as the second member, got", m.Name)
}
td, ok := m.Type.(*Typedef)
if !ok {
t.Fatalf("version member of iphdr should be a __u8 typedef: actual: %T", m.Type)
}
u8, ok := td.Type.(*Int)
if !ok {
t.Fatalf("__u8 typedef should point to an Int type: actual: %T", td.Type)
}
if m.BitfieldSize != 4 {
t.Fatalf("incorrect bitfield size: expected: 4 actual: %d", m.BitfieldSize)
}
if u8.Encoding != 0 {
t.Fatalf("incorrect encoding of an __u8 int: expected: 0 actual: %x", u8.Encoding)
}
if m.Offset != 4 {
t.Fatalf("incorrect bitfield offset: expected: 4 actual: %d", m.Offset)
}
}
func BenchmarkParseVmlinux(b *testing.B) {
rd := vmlinuxTestdataReader(b)
b.ReportAllocs()
b.ResetTimer()
for n := 0; n < b.N; n++ {
if _, err := rd.Seek(0, io.SeekStart); err != nil {
b.Fatal(err)
}
if _, err := loadRawSpec(rd, binary.LittleEndian, nil); err != nil {
b.Fatal("Can't load BTF:", err)
}
}
}
func TestParseCurrentKernelBTF(t *testing.T) {
spec := vmlinuxSpec(t)
if len(spec.namedTypes) == 0 {
t.Fatal("Empty kernel BTF")
}
totalBytes := 0
distinct := 0
seen := make(map[string]bool)
for _, str := range spec.strings.strings {
totalBytes += len(str)
if !seen[str] {
distinct++
seen[str] = true
}
}
t.Logf("%d strings total, %d distinct", len(spec.strings.strings), distinct)
t.Logf("Average string size: %.0f", float64(totalBytes)/float64(len(spec.strings.strings)))
}
func TestFindVMLinux(t *testing.T) {
file, err := findVMLinux()
testutils.SkipIfNotSupported(t, err)
if err != nil {
t.Fatal("Can't find vmlinux:", err)
}
defer file.Close()
spec, err := loadSpecFromELF(file)
if err != nil {
t.Fatal("Can't load BTF:", err)
}
if len(spec.namedTypes) == 0 {
t.Fatal("Empty kernel BTF")
}
}
func TestLoadSpecFromElf(t *testing.T) {
testutils.Files(t, testutils.Glob(t, "../testdata/loader-e*.elf"), func(t *testing.T, file string) {
spec := parseELFBTF(t, file)
vt, err := spec.TypeByID(0)
if err != nil {
t.Error("Can't retrieve void type by ID:", err)
}
if _, ok := vt.(*Void); !ok {
t.Errorf("Expected Void for type id 0, but got: %T", vt)
}
var bpfMapDef *Struct
if err := spec.TypeByName("bpf_map_def", &bpfMapDef); err != nil {
t.Error("Can't find bpf_map_def:", err)
}
var tmp *Void
if err := spec.TypeByName("totally_bogus_type", &tmp); !errors.Is(err, ErrNotFound) {
t.Error("TypeByName doesn't return ErrNotFound:", err)
}
var fn *Func
if err := spec.TypeByName("global_fn", &fn); err != nil {
t.Error("Can't find global_fn():", err)
} else {
if fn.Linkage != GlobalFunc {
t.Error("Expected global linkage:", fn)
}
}
var v *Var
if err := spec.TypeByName("key3", &v); err != nil {
t.Error("Cant find key3:", err)
} else {
if v.Linkage != GlobalVar {
t.Error("Expected global linkage:", v)
}
}
})
}
func TestVerifierError(t *testing.T) {
_, err := NewHandle(&Builder{})
testutils.SkipIfNotSupported(t, err)
var ve *internal.VerifierError
if !errors.As(err, &ve) {
t.Fatalf("expected a VerifierError, got: %v", err)
}
if ve.Truncated {
t.Fatalf("expected non-truncated verifier log: %v", err)
}
}
func TestLoadKernelSpec(t *testing.T) {
if _, err := os.Stat("/sys/kernel/btf/vmlinux"); os.IsNotExist(err) {
t.Skip("/sys/kernel/btf/vmlinux not present")
}
_, err := LoadKernelSpec()
if err != nil {
t.Fatal("Can't load kernel spec:", err)
}
}
func TestGuessBTFByteOrder(t *testing.T) {
bo := guessRawBTFByteOrder(vmlinuxTestdataReader(t))
if bo != binary.LittleEndian {
t.Fatalf("Guessed %s instead of %s", bo, binary.LittleEndian)
}
}
func TestSpecCopy(t *testing.T) {
spec := parseELFBTF(t, "../testdata/loader-el.elf")
if len(spec.types) < 1 {
t.Fatal("Not enough types")
}
cpy := spec.Copy()
for i := range cpy.types {
if _, ok := cpy.types[i].(*Void); ok {
// Since Void is an empty struct, a Type interface value containing
// &Void{} stores (*Void, nil). Since interface equality first compares
// the type and then the concrete value, Void is always equal.
continue
}
if cpy.types[i] == spec.types[i] {
t.Fatalf("Type at index %d is not a copy: %T == %T", i, cpy.types[i], spec.types[i])
}
}
}
func TestSpecTypeByID(t *testing.T) {
_, err := newSpec().TypeByID(0)
qt.Assert(t, err, qt.IsNil)
_, err = newSpec().TypeByID(1)
qt.Assert(t, err, qt.ErrorIs, ErrNotFound)
}
func TestHaveBTF(t *testing.T) {
testutils.CheckFeatureTest(t, haveBTF)
}
func TestHaveMapBTF(t *testing.T) {
testutils.CheckFeatureTest(t, haveMapBTF)
}
func TestHaveProgBTF(t *testing.T) {
testutils.CheckFeatureTest(t, haveProgBTF)
}
func TestHaveFuncLinkage(t *testing.T) {
testutils.CheckFeatureTest(t, haveFuncLinkage)
}
func ExampleSpec_TypeByName() {
// Acquire a Spec via one of its constructors.
spec := new(Spec)
// Declare a variable of the desired type
var foo *Struct
if err := spec.TypeByName("foo", &foo); err != nil {
// There is no struct with name foo, or there
// are multiple possibilities.
}
// We've found struct foo
fmt.Println(foo.Name)
}
func TestTypesIterator(t *testing.T) {
types := []Type{(*Void)(nil), &Int{Size: 4}, &Int{Size: 2}}
b, err := NewBuilder(types[1:])
if err != nil {
t.Fatal(err)
}
raw, err := b.Marshal(nil, nil)
if err != nil {
t.Fatal(err)
}
spec, err := LoadSpecFromReader(bytes.NewReader(raw))
if err != nil {
t.Fatal(err)
}
iter := spec.Iterate()
for i, typ := range types {
if !iter.Next() {
t.Fatal("Iterator ended early at item", i)
}
qt.Assert(t, iter.Type, qt.DeepEquals, typ)
}
if iter.Next() {
t.Fatalf("Iterator yielded too many items: %p (%[1]T)", iter.Type)
}
}
func TestLoadSplitSpecFromReader(t *testing.T) {
spec := vmlinuxTestdataSpec(t)
f, err := os.Open("testdata/btf_testmod.btf")
if err != nil {
t.Fatal(err)
}
defer f.Close()
splitSpec, err := LoadSplitSpecFromReader(f, spec)
if err != nil {
t.Fatal(err)
}
typ, err := splitSpec.AnyTypeByName("bpf_testmod_init")
if err != nil {
t.Fatal(err)
}
typeID, err := splitSpec.TypeID(typ)
if err != nil {
t.Fatal(err)
}
typeByID, err := splitSpec.TypeByID(typeID)
qt.Assert(t, err, qt.IsNil)
qt.Assert(t, typeByID, qt.Equals, typ)
fnType := typ.(*Func)
fnProto := fnType.Type.(*FuncProto)
// 'int' is defined in the base BTF...
intType, err := spec.AnyTypeByName("int")
if err != nil {
t.Fatal(err)
}
// ... but not in the split BTF
_, err = splitSpec.AnyTypeByName("int")
if err == nil {
t.Fatal("'int' is not supposed to be found in the split BTF")
}
if fnProto.Return != intType {
t.Fatalf("Return type of 'bpf_testmod_init()' (%s) does not match 'int' type (%s)",
fnProto.Return, intType)
}
// Check that copied split-BTF's spec has correct type indexing
splitSpecCopy := splitSpec.Copy()
copyType, err := splitSpecCopy.AnyTypeByName("bpf_testmod_init")
if err != nil {
t.Fatal(err)
}
copyTypeID, err := splitSpecCopy.TypeID(copyType)
if err != nil {
t.Fatal(err)
}
if copyTypeID != typeID {
t.Fatalf("'bpf_testmod_init` type ID (%d) does not match copied spec's (%d)",
typeID, copyTypeID)
}
}
func TestFixupDatasecLayout(t *testing.T) {
ds := &Datasec{
Size: 0, // Populated by fixup.
Vars: []VarSecinfo{
{Type: &Var{Type: &Int{Size: 4}}},
{Type: &Var{Type: &Int{Size: 1}}},
{Type: &Var{Type: &Int{Size: 1}}},
{Type: &Var{Type: &Int{Size: 2}}},
{Type: &Var{Type: &Int{Size: 16}}},
{Type: &Var{Type: &Int{Size: 8}}},
},
}
qt.Assert(t, fixupDatasecLayout(ds), qt.IsNil)
qt.Assert(t, ds.Size, qt.Equals, uint32(40))
qt.Assert(t, ds.Vars[0].Offset, qt.Equals, uint32(0))
qt.Assert(t, ds.Vars[1].Offset, qt.Equals, uint32(4))
qt.Assert(t, ds.Vars[2].Offset, qt.Equals, uint32(5))
qt.Assert(t, ds.Vars[3].Offset, qt.Equals, uint32(6))
qt.Assert(t, ds.Vars[4].Offset, qt.Equals, uint32(16))
qt.Assert(t, ds.Vars[5].Offset, qt.Equals, uint32(32))
}
func BenchmarkSpecCopy(b *testing.B) {
spec := vmlinuxTestdataSpec(b)
b.ResetTimer()
for i := 0; i < b.N; i++ {
spec.Copy()
}
}
@@ -0,0 +1,371 @@
package btf
import (
"encoding/binary"
"fmt"
"io"
"unsafe"
)
//go:generate stringer -linecomment -output=btf_types_string.go -type=FuncLinkage,VarLinkage,btfKind
// btfKind describes a Type.
type btfKind uint8
// Equivalents of the BTF_KIND_* constants.
const (
kindUnknown btfKind = iota // Unknown
kindInt // Int
kindPointer // Pointer
kindArray // Array
kindStruct // Struct
kindUnion // Union
kindEnum // Enum
kindForward // Forward
kindTypedef // Typedef
kindVolatile // Volatile
kindConst // Const
kindRestrict // Restrict
// Added ~4.20
kindFunc // Func
kindFuncProto // FuncProto
// Added ~5.1
kindVar // Var
kindDatasec // Datasec
// Added ~5.13
kindFloat // Float
// Added 5.16
kindDeclTag // DeclTag
kindTypeTag // TypeTag
// Added 6.0
kindEnum64 // Enum64
)
// FuncLinkage describes BTF function linkage metadata.
type FuncLinkage int
// Equivalent of enum btf_func_linkage.
const (
StaticFunc FuncLinkage = iota // static
GlobalFunc // global
ExternFunc // extern
)
// VarLinkage describes BTF variable linkage metadata.
type VarLinkage int
const (
StaticVar VarLinkage = iota // static
GlobalVar // global
ExternVar // extern
)
const (
btfTypeKindShift = 24
btfTypeKindLen = 5
btfTypeVlenShift = 0
btfTypeVlenMask = 16
btfTypeKindFlagShift = 31
btfTypeKindFlagMask = 1
)
var btfTypeLen = binary.Size(btfType{})
// btfType is equivalent to struct btf_type in Documentation/bpf/btf.rst.
type btfType struct {
NameOff uint32
/* "info" bits arrangement
* bits 0-15: vlen (e.g. # of struct's members), linkage
* bits 16-23: unused
* bits 24-28: kind (e.g. int, ptr, array...etc)
* bits 29-30: unused
* bit 31: kind_flag, currently used by
* struct, union and fwd
*/
Info uint32
/* "size" is used by INT, ENUM, STRUCT and UNION.
* "size" tells the size of the type it is describing.
*
* "type" is used by PTR, TYPEDEF, VOLATILE, CONST, RESTRICT,
* FUNC and FUNC_PROTO.
* "type" is a type_id referring to another type.
*/
SizeType uint32
}
func mask(len uint32) uint32 {
return (1 << len) - 1
}
func readBits(value, len, shift uint32) uint32 {
return (value >> shift) & mask(len)
}
func writeBits(value, len, shift, new uint32) uint32 {
value &^= mask(len) << shift
value |= (new & mask(len)) << shift
return value
}
func (bt *btfType) info(len, shift uint32) uint32 {
return readBits(bt.Info, len, shift)
}
func (bt *btfType) setInfo(value, len, shift uint32) {
bt.Info = writeBits(bt.Info, len, shift, value)
}
func (bt *btfType) Kind() btfKind {
return btfKind(bt.info(btfTypeKindLen, btfTypeKindShift))
}
func (bt *btfType) SetKind(kind btfKind) {
bt.setInfo(uint32(kind), btfTypeKindLen, btfTypeKindShift)
}
func (bt *btfType) Vlen() int {
return int(bt.info(btfTypeVlenMask, btfTypeVlenShift))
}
func (bt *btfType) SetVlen(vlen int) {
bt.setInfo(uint32(vlen), btfTypeVlenMask, btfTypeVlenShift)
}
func (bt *btfType) kindFlagBool() bool {
return bt.info(btfTypeKindFlagMask, btfTypeKindFlagShift) == 1
}
func (bt *btfType) setKindFlagBool(set bool) {
var value uint32
if set {
value = 1
}
bt.setInfo(value, btfTypeKindFlagMask, btfTypeKindFlagShift)
}
// Bitfield returns true if the struct or union contain a bitfield.
func (bt *btfType) Bitfield() bool {
return bt.kindFlagBool()
}
func (bt *btfType) SetBitfield(isBitfield bool) {
bt.setKindFlagBool(isBitfield)
}
func (bt *btfType) FwdKind() FwdKind {
return FwdKind(bt.info(btfTypeKindFlagMask, btfTypeKindFlagShift))
}
func (bt *btfType) SetFwdKind(kind FwdKind) {
bt.setInfo(uint32(kind), btfTypeKindFlagMask, btfTypeKindFlagShift)
}
func (bt *btfType) Signed() bool {
return bt.kindFlagBool()
}
func (bt *btfType) SetSigned(signed bool) {
bt.setKindFlagBool(signed)
}
func (bt *btfType) Linkage() FuncLinkage {
return FuncLinkage(bt.info(btfTypeVlenMask, btfTypeVlenShift))
}
func (bt *btfType) SetLinkage(linkage FuncLinkage) {
bt.setInfo(uint32(linkage), btfTypeVlenMask, btfTypeVlenShift)
}
func (bt *btfType) Type() TypeID {
// TODO: Panic here if wrong kind?
return TypeID(bt.SizeType)
}
func (bt *btfType) SetType(id TypeID) {
bt.SizeType = uint32(id)
}
func (bt *btfType) Size() uint32 {
// TODO: Panic here if wrong kind?
return bt.SizeType
}
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))
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{}
}
func (rt *rawType) Marshal(w io.Writer, bo binary.ByteOrder) error {
if err := rt.btfType.Marshal(w, bo); err != nil {
return err
}
if rt.data == nil {
return nil
}
return binary.Write(w, bo, rt.data)
}
// btfInt encodes additional data for integers.
//
// ? ? ? ? e e e e o o o o o o o o ? ? ? ? ? ? ? ? b b b b b b b b
// ? = undefined
// e = encoding
// o = offset (bitfields?)
// b = bits (bitfields)
type btfInt struct {
Raw uint32
}
const (
btfIntEncodingLen = 4
btfIntEncodingShift = 24
btfIntOffsetLen = 8
btfIntOffsetShift = 16
btfIntBitsLen = 8
btfIntBitsShift = 0
)
func (bi btfInt) Encoding() IntEncoding {
return IntEncoding(readBits(bi.Raw, btfIntEncodingLen, btfIntEncodingShift))
}
func (bi *btfInt) SetEncoding(e IntEncoding) {
bi.Raw = writeBits(uint32(bi.Raw), btfIntEncodingLen, btfIntEncodingShift, uint32(e))
}
func (bi btfInt) Offset() Bits {
return Bits(readBits(bi.Raw, btfIntOffsetLen, btfIntOffsetShift))
}
func (bi *btfInt) SetOffset(offset uint32) {
bi.Raw = writeBits(bi.Raw, btfIntOffsetLen, btfIntOffsetShift, offset)
}
func (bi btfInt) Bits() Bits {
return Bits(readBits(bi.Raw, btfIntBitsLen, btfIntBitsShift))
}
func (bi *btfInt) SetBits(bits byte) {
bi.Raw = writeBits(bi.Raw, btfIntBitsLen, btfIntBitsShift, uint32(bits))
}
type btfArray struct {
Type TypeID
IndexType TypeID
Nelems uint32
}
type btfMember struct {
NameOff uint32
Type TypeID
Offset uint32
}
type btfVarSecinfo struct {
Type TypeID
Offset uint32
Size uint32
}
type btfVariable struct {
Linkage uint32
}
type btfEnum struct {
NameOff uint32
Val uint32
}
type btfEnum64 struct {
NameOff uint32
ValLo32 uint32
ValHi32 uint32
}
type btfParam struct {
NameOff uint32
Type TypeID
}
type btfDeclTag struct {
ComponentIdx uint32
}
func readTypes(r io.Reader, bo binary.ByteOrder, typeLen uint32) ([]rawType, error) {
var header btfType
// 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([]rawType, 0, tyMaxCount)
for id := TypeID(1); ; id++ {
if err := binary.Read(r, bo, &header); err == io.EOF {
return types, nil
} else if err != nil {
return nil, fmt.Errorf("can't read type info for id %v: %v", id, err)
}
var data interface{}
switch header.Kind() {
case kindInt:
data = new(btfInt)
case kindPointer:
case kindArray:
data = new(btfArray)
case kindStruct:
fallthrough
case kindUnion:
data = make([]btfMember, header.Vlen())
case kindEnum:
data = make([]btfEnum, header.Vlen())
case kindForward:
case kindTypedef:
case kindVolatile:
case kindConst:
case kindRestrict:
case kindFunc:
case kindFuncProto:
data = make([]btfParam, header.Vlen())
case kindVar:
data = new(btfVariable)
case kindDatasec:
data = make([]btfVarSecinfo, header.Vlen())
case kindFloat:
case kindDeclTag:
data = new(btfDeclTag)
case kindTypeTag:
case kindEnum64:
data = make([]btfEnum64, header.Vlen())
default:
return nil, fmt.Errorf("type id %v: unknown kind: %v", id, header.Kind())
}
if data == nil {
types = append(types, rawType{header, nil})
continue
}
if err := binary.Read(r, bo, data); err != nil {
return nil, fmt.Errorf("type id %d: kind %v: can't read %T: %v", id, header.Kind(), data, err)
}
types = append(types, rawType{header, data})
}
}
@@ -0,0 +1,80 @@
// Code generated by "stringer -linecomment -output=btf_types_string.go -type=FuncLinkage,VarLinkage,btfKind"; DO NOT EDIT.
package btf
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[StaticFunc-0]
_ = x[GlobalFunc-1]
_ = x[ExternFunc-2]
}
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) {
return "FuncLinkage(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _FuncLinkage_name[_FuncLinkage_index[i]:_FuncLinkage_index[i+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[StaticVar-0]
_ = x[GlobalVar-1]
_ = x[ExternVar-2]
}
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) {
return "VarLinkage(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _VarLinkage_name[_VarLinkage_index[i]:_VarLinkage_index[i+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[kindUnknown-0]
_ = x[kindInt-1]
_ = x[kindPointer-2]
_ = x[kindArray-3]
_ = x[kindStruct-4]
_ = x[kindUnion-5]
_ = x[kindEnum-6]
_ = x[kindForward-7]
_ = x[kindTypedef-8]
_ = x[kindVolatile-9]
_ = x[kindConst-10]
_ = x[kindRestrict-11]
_ = x[kindFunc-12]
_ = x[kindFuncProto-13]
_ = x[kindVar-14]
_ = x[kindDatasec-15]
_ = x[kindFloat-16]
_ = x[kindDeclTag-17]
_ = x[kindTypeTag-18]
_ = x[kindEnum64-19]
}
const _btfKind_name = "UnknownIntPointerArrayStructUnionEnumForwardTypedefVolatileConstRestrictFuncFuncProtoVarDatasecFloatDeclTagTypeTagEnum64"
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) {
return "btfKind(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _btfKind_name[_btfKind_index[i]:_btfKind_index[i+1]]
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,111 @@
package btf_test
import (
"fmt"
"io"
"os"
"strings"
"testing"
"github.com/cilium/ebpf"
"github.com/cilium/ebpf/btf"
"github.com/cilium/ebpf/internal"
"github.com/cilium/ebpf/internal/testutils"
)
func TestCORERelocationLoad(t *testing.T) {
testutils.Files(t, testutils.Glob(t, "testdata/relocs-*.elf"), func(t *testing.T, file string) {
fh, err := os.Open(file)
if err != nil {
t.Fatal(err)
}
defer fh.Close()
spec, err := ebpf.LoadCollectionSpecFromReader(fh)
if err != nil {
t.Fatal(err)
}
if spec.ByteOrder != internal.NativeEndian {
return
}
for _, progSpec := range spec.Programs {
t.Run(progSpec.Name, func(t *testing.T) {
if _, err := fh.Seek(0, io.SeekStart); err != nil {
t.Fatal(err)
}
prog, err := ebpf.NewProgramWithOptions(progSpec, ebpf.ProgramOptions{
KernelTypes: spec.Types,
})
if strings.HasPrefix(progSpec.Name, "err_") {
if err == nil {
prog.Close()
t.Fatal("Expected an error")
}
t.Log("Got expected error:", err)
return
}
if err != nil {
t.Fatal("Load program:", err)
}
defer prog.Close()
ret, _, err := prog.Test(internal.EmptyBPFContext)
testutils.SkipIfNotSupported(t, err)
if err != nil {
t.Fatal("Error when running:", err)
}
if ret != 0 {
t.Error("Assertion failed on line", ret)
}
})
}
})
}
func TestCORERelocationRead(t *testing.T) {
testutils.Files(t, testutils.Glob(t, "testdata/relocs_read-*.elf"), func(t *testing.T, file string) {
spec, err := ebpf.LoadCollectionSpec(file)
if err != nil {
t.Fatal(err)
}
if spec.ByteOrder != internal.NativeEndian {
return
}
targetFile := fmt.Sprintf("testdata/relocs_read_tgt-%s.elf", internal.ClangEndian)
targetSpec, err := btf.LoadSpec(targetFile)
if err != nil {
t.Fatal(err)
}
for _, progSpec := range spec.Programs {
t.Run(progSpec.Name, func(t *testing.T) {
prog, err := ebpf.NewProgramWithOptions(progSpec, ebpf.ProgramOptions{
KernelTypes: targetSpec,
})
testutils.SkipIfNotSupported(t, err)
if err != nil {
t.Fatal("Load program:", err)
}
defer prog.Close()
ret, _, err := prog.Test(internal.EmptyBPFContext)
testutils.SkipIfNotSupported(t, err)
if err != nil {
t.Fatal("Error when running:", err)
}
if ret != 0 {
t.Error("Assertion failed on line", ret)
}
})
}
})
}
@@ -0,0 +1,746 @@
package btf
import (
"errors"
"fmt"
"os"
"strings"
"testing"
"github.com/cilium/ebpf/internal"
"github.com/cilium/ebpf/internal/testutils"
"github.com/google/go-cmp/cmp"
qt "github.com/frankban/quicktest"
)
func TestCheckTypeCompatibility(t *testing.T) {
tests := []struct {
a, b Type
compatible bool
}{
{&FuncProto{Return: &Typedef{Type: &Int{}}}, &FuncProto{Return: &Int{}}, true},
{&FuncProto{Return: &Typedef{Type: &Int{}}}, &FuncProto{Return: &Void{}}, false},
}
for _, test := range tests {
err := CheckTypeCompatibility(test.a, test.b)
if test.compatible {
if err != nil {
t.Errorf("Expected types to be compatible: %s\na = %#v\nb = %#v", err, test.a, test.b)
continue
}
} else {
if !errors.Is(err, errIncompatibleTypes) {
t.Errorf("Expected types to be incompatible: %s\na = %#v\nb = %#v", err, test.a, test.b)
continue
}
}
err = CheckTypeCompatibility(test.b, test.a)
if test.compatible {
if err != nil {
t.Errorf("Expected reversed types to be compatible: %s\na = %#v\nb = %#v", err, test.a, test.b)
}
} else {
if !errors.Is(err, errIncompatibleTypes) {
t.Errorf("Expected reversed types to be incompatible: %s\na = %#v\nb = %#v", err, test.a, test.b)
}
}
}
}
func TestCOREAreTypesCompatible(t *testing.T) {
tests := []struct {
a, b Type
compatible bool
}{
{&Void{}, &Void{}, true},
{&Struct{Name: "a"}, &Struct{Name: "b"}, true},
{&Union{Name: "a"}, &Union{Name: "b"}, true},
{&Union{Name: "a"}, &Struct{Name: "b"}, false},
{&Enum{Name: "a"}, &Enum{Name: "b"}, true},
{&Fwd{Name: "a"}, &Fwd{Name: "b"}, true},
{&Int{Name: "a", Size: 2}, &Int{Name: "b", Size: 4}, true},
{&Pointer{Target: &Void{}}, &Pointer{Target: &Void{}}, true},
{&Pointer{Target: &Void{}}, &Void{}, false},
{&Array{Index: &Void{}, Type: &Void{}}, &Array{Index: &Void{}, Type: &Void{}}, true},
{&Array{Index: &Void{}, Type: &Int{}}, &Array{Index: &Void{}, Type: &Void{}}, false},
{&FuncProto{Return: &Int{}}, &FuncProto{Return: &Void{}}, false},
{
&FuncProto{Return: &Void{}, Params: []FuncParam{{Name: "a", Type: &Void{}}}},
&FuncProto{Return: &Void{}, Params: []FuncParam{{Name: "b", Type: &Void{}}}},
true,
},
{
&FuncProto{Return: &Void{}, Params: []FuncParam{{Type: &Void{}}}},
&FuncProto{Return: &Void{}, Params: []FuncParam{{Type: &Int{}}}},
false,
},
{
&FuncProto{Return: &Void{}, Params: []FuncParam{{Type: &Void{}}, {Type: &Void{}}}},
&FuncProto{Return: &Void{}, Params: []FuncParam{{Type: &Void{}}}},
false,
},
}
for _, test := range tests {
err := coreAreTypesCompatible(test.a, test.b)
if test.compatible {
if err != nil {
t.Errorf("Expected types to be compatible: %s\na = %#v\nb = %#v", err, test.a, test.b)
continue
}
} else {
if !errors.Is(err, errIncompatibleTypes) {
t.Errorf("Expected types to be incompatible: %s\na = %#v\nb = %#v", err, test.a, test.b)
continue
}
}
err = coreAreTypesCompatible(test.b, test.a)
if test.compatible {
if err != nil {
t.Errorf("Expected reversed types to be compatible: %s\na = %#v\nb = %#v", err, test.a, test.b)
}
} else {
if !errors.Is(err, errIncompatibleTypes) {
t.Errorf("Expected reversed types to be incompatible: %s\na = %#v\nb = %#v", err, test.a, test.b)
}
}
}
for _, invalid := range []Type{&Var{}, &Datasec{}} {
err := coreAreTypesCompatible(invalid, invalid)
if errors.Is(err, errIncompatibleTypes) {
t.Errorf("Expected an error for %T, not errIncompatibleTypes", invalid)
} else if err == nil {
t.Errorf("Expected an error for %T", invalid)
}
}
}
func TestCOREAreMembersCompatible(t *testing.T) {
tests := []struct {
a, b Type
compatible bool
}{
{&Struct{Name: "a"}, &Struct{Name: "b"}, true},
{&Union{Name: "a"}, &Union{Name: "b"}, true},
{&Union{Name: "a"}, &Struct{Name: "b"}, true},
{&Enum{Name: "a"}, &Enum{Name: "b"}, false},
{&Enum{Name: "a"}, &Enum{Name: "a___foo"}, true},
{&Enum{Name: "a"}, &Enum{Name: ""}, true},
{&Fwd{Name: "a"}, &Fwd{Name: "b"}, false},
{&Fwd{Name: "a"}, &Fwd{Name: "a___foo"}, true},
{&Fwd{Name: "a"}, &Fwd{Name: ""}, true},
{&Int{Name: "a", Size: 2}, &Int{Name: "b", Size: 4}, true},
{&Pointer{Target: &Void{}}, &Pointer{Target: &Void{}}, true},
{&Pointer{Target: &Void{}}, &Void{}, false},
{&Array{Type: &Int{Size: 1}}, &Array{Type: &Int{Encoding: Signed}}, true},
{&Float{Size: 2}, &Float{Size: 4}, true},
}
for _, test := range tests {
err := coreAreMembersCompatible(test.a, test.b)
if test.compatible {
if err != nil {
t.Errorf("Expected members to be compatible: %s\na = %#v\nb = %#v", err, test.a, test.b)
continue
}
} else {
if !errors.Is(err, errImpossibleRelocation) {
t.Errorf("Expected members to be incompatible: %s\na = %#v\nb = %#v", err, test.a, test.b)
continue
}
}
err = coreAreMembersCompatible(test.b, test.a)
if test.compatible {
if err != nil {
t.Errorf("Expected reversed members to be compatible: %s\na = %#v\nb = %#v", err, test.a, test.b)
}
} else {
if !errors.Is(err, errImpossibleRelocation) {
t.Errorf("Expected reversed members to be incompatible: %s\na = %#v\nb = %#v", err, test.a, test.b)
}
}
}
for _, invalid := range []Type{&Void{}, &FuncProto{}, &Var{}, &Datasec{}} {
err := coreAreMembersCompatible(invalid, invalid)
if errors.Is(err, errImpossibleRelocation) {
t.Errorf("Expected an error for %T, not errImpossibleRelocation", invalid)
} else if err == nil {
t.Errorf("Expected an error for %T", invalid)
}
}
}
func TestCOREAccessor(t *testing.T) {
for _, valid := range []string{
"0",
"1:0",
"1:0:3:34:10:1",
} {
_, err := parseCOREAccessor(valid)
if err != nil {
t.Errorf("Parse %q: %s", valid, err)
}
}
for _, invalid := range []string{
"",
"-1",
":",
"0:",
":12",
"4294967296",
} {
_, err := parseCOREAccessor(invalid)
if err == nil {
t.Errorf("Accepted invalid accessor %q", invalid)
}
}
}
func TestCOREFindEnumValue(t *testing.T) {
a := &Enum{Values: []EnumValue{{"foo", 23}, {"bar", 42}}}
b := &Enum{Values: []EnumValue{
{"foo___flavour", 0},
{"bar", 123},
{"garbage", 3},
}}
invalid := []struct {
name string
local Type
target Type
acc coreAccessor
err error
}{
{"o-o-b accessor", a, b, coreAccessor{len(a.Values)}, nil},
{"long accessor", a, b, coreAccessor{0, 1}, nil},
{"wrong target", a, &Void{}, coreAccessor{0, 1}, nil},
{
"no matching value",
b, a,
coreAccessor{2},
errImpossibleRelocation,
},
}
for _, test := range invalid {
t.Run(test.name, func(t *testing.T) {
_, _, err := coreFindEnumValue(test.local, test.acc, test.target)
if test.err != nil && !errors.Is(err, test.err) {
t.Fatalf("Expected %s, got %s", test.err, err)
}
if err == nil {
t.Fatal("Accepted invalid case")
}
})
}
valid := []struct {
name string
local, target Type
acc coreAccessor
localValue, targetValue uint64
}{
{"a to b", a, b, coreAccessor{0}, 23, 0},
{"b to a", b, a, coreAccessor{1}, 123, 42},
}
for _, test := range valid {
t.Run(test.name, func(t *testing.T) {
local, target, err := coreFindEnumValue(test.local, test.acc, test.target)
qt.Assert(t, err, qt.IsNil)
qt.Check(t, local.Value, qt.Equals, test.localValue)
qt.Check(t, target.Value, qt.Equals, test.targetValue)
})
}
}
func TestCOREFindField(t *testing.T) {
ptr := &Pointer{}
u16 := &Int{Size: 2}
u32 := &Int{Size: 4}
aFields := []Member{
{Name: "foo", Type: ptr, Offset: 8},
{Name: "bar", Type: u16, Offset: 16},
{Name: "baz", Type: u32, Offset: 32, BitfieldSize: 3},
{Name: "quux", Type: u32, Offset: 35, BitfieldSize: 10},
{Name: "quuz", Type: u32, Offset: 45, BitfieldSize: 8},
}
bFields := []Member{
{Name: "foo", Type: ptr, Offset: 16},
{Name: "bar", Type: u32, Offset: 8},
{Name: "other", Offset: 4},
// baz is separated out from the other bitfields
{Name: "baz", Type: u32, Offset: 64, BitfieldSize: 3},
// quux's type changes u32->u16
{Name: "quux", Type: u16, Offset: 96, BitfieldSize: 10},
// quuz becomes a normal field
{Name: "quuz", Type: u16, Offset: 112},
}
aStruct := &Struct{Members: aFields, Size: 48}
bStruct := &Struct{Members: bFields, Size: 80}
aArray := &Array{Nelems: 4, Type: u16}
bArray := &Array{Nelems: 3, Type: u32}
invalid := []struct {
name string
local, target Type
acc coreAccessor
err error
}{
{
"unsupported type",
&Void{}, &Void{},
coreAccessor{0, 0},
ErrNotSupported,
},
{
"different types",
&Union{}, &Array{Type: u16},
coreAccessor{0},
errImpossibleRelocation,
},
{
"invalid composite accessor",
aStruct, aStruct,
coreAccessor{0, len(aStruct.Members)},
nil,
},
{
"invalid array accessor",
aArray, aArray,
coreAccessor{0, int(aArray.Nelems)},
nil,
},
{
"o-o-b array accessor",
aArray, bArray,
coreAccessor{0, int(bArray.Nelems)},
errImpossibleRelocation,
},
{
"no match",
bStruct, aStruct,
coreAccessor{0, 2},
errImpossibleRelocation,
},
{
"incompatible match",
&Union{Members: []Member{{Name: "foo", Type: &Pointer{}}}},
&Union{Members: []Member{{Name: "foo", Type: &Int{}}}},
coreAccessor{0, 0},
errImpossibleRelocation,
},
{
"unsized type",
bStruct, &Func{},
// non-zero accessor to force calculating the offset.
coreAccessor{1},
errImpossibleRelocation,
},
}
for _, test := range invalid {
t.Run(test.name, func(t *testing.T) {
_, _, err := coreFindField(test.local, test.acc, test.target)
if test.err != nil && !errors.Is(err, test.err) {
t.Fatalf("Expected %s, got %s", test.err, err)
}
if err == nil {
t.Fatal("Accepted invalid case")
}
t.Log(err)
})
}
bytes := func(typ Type) uint32 {
sz, err := Sizeof(typ)
if err != nil {
t.Fatal(err)
}
return uint32(sz)
}
anon := func(t Type, offset Bits) []Member {
return []Member{{Type: t, Offset: offset}}
}
anonStruct := func(m ...Member) Member {
return Member{Type: &Struct{Members: m}}
}
anonUnion := func(m ...Member) Member {
return Member{Type: &Union{Members: m}}
}
valid := []struct {
name string
local Type
target Type
acc coreAccessor
localField, targetField coreField
}{
{
"array[0]",
aArray,
bArray,
coreAccessor{0, 0},
coreField{u16, 0, 0, 0},
coreField{u32, 0, 0, 0},
},
{
"array[1]",
aArray,
bArray,
coreAccessor{0, 1},
coreField{u16, bytes(aArray.Type), 0, 0},
coreField{u32, bytes(bArray.Type), 0, 0},
},
{
"array[0] with base offset",
aArray,
bArray,
coreAccessor{1, 0},
coreField{u16, bytes(aArray), 0, 0},
coreField{u32, bytes(bArray), 0, 0},
},
{
"array[2] with base offset",
aArray,
bArray,
coreAccessor{1, 2},
coreField{u16, bytes(aArray) + 2*bytes(aArray.Type), 0, 0},
coreField{u32, bytes(bArray) + 2*bytes(bArray.Type), 0, 0},
},
{
"flex array",
&Struct{Members: []Member{{Name: "foo", Type: &Array{Nelems: 0, Type: u16}}}},
&Struct{Members: []Member{{Name: "foo", Type: &Array{Nelems: 0, Type: u32}}}},
coreAccessor{0, 0, 9000},
coreField{u16, bytes(u16) * 9000, 0, 0},
coreField{u32, bytes(u32) * 9000, 0, 0},
},
{
"struct.0",
aStruct, bStruct,
coreAccessor{0, 0},
coreField{ptr, 1, 0, 0},
coreField{ptr, 2, 0, 0},
},
{
"struct.0 anon",
aStruct, &Struct{Members: anon(bStruct, 24)},
coreAccessor{0, 0},
coreField{ptr, 1, 0, 0},
coreField{ptr, 3 + 2, 0, 0},
},
{
"struct.0 with base offset",
aStruct, bStruct,
coreAccessor{3, 0},
coreField{ptr, 3*bytes(aStruct) + 1, 0, 0},
coreField{ptr, 3*bytes(bStruct) + 2, 0, 0},
},
{
"struct.1",
aStruct, bStruct,
coreAccessor{0, 1},
coreField{u16, 2, 0, 0},
coreField{u32, 1, 0, 0},
},
{
"struct.1 anon",
aStruct, &Struct{Members: anon(bStruct, 24)},
coreAccessor{0, 1},
coreField{u16, 2, 0, 0},
coreField{u32, 3 + 1, 0, 0},
},
{
"union.1",
&Union{Members: aFields, Size: 32},
&Union{Members: bFields, Size: 32},
coreAccessor{0, 1},
coreField{u16, 2, 0, 0},
coreField{u32, 1, 0, 0},
},
{
"interchangeable composites",
&Struct{
Members: []Member{
anonStruct(anonUnion(Member{Name: "_1", Type: u16})),
},
},
&Struct{
Members: []Member{
anonUnion(anonStruct(Member{Name: "_1", Type: u16})),
},
},
coreAccessor{0, 0, 0, 0},
coreField{u16, 0, 0, 0},
coreField{u16, 0, 0, 0},
},
{
"struct.2 (bitfield baz)",
aStruct, bStruct,
coreAccessor{0, 2},
coreField{u32, 4, 0, 3},
coreField{u32, 8, 0, 3},
},
{
"struct.3 (bitfield quux)",
aStruct, bStruct,
coreAccessor{0, 3},
coreField{u32, 4, 3, 10},
coreField{u16, 12, 0, 10},
},
{
"struct.4 (bitfield quuz)",
aStruct, bStruct,
coreAccessor{0, 4},
coreField{u32, 4, 13, 8},
coreField{u16, 14, 0, 0},
},
}
allowCoreField := cmp.AllowUnexported(coreField{})
checkCOREField := func(t *testing.T, which string, got, want coreField) {
t.Helper()
if diff := cmp.Diff(want, got, allowCoreField); diff != "" {
t.Errorf("%s mismatch (-want +got):\n%s", which, diff)
}
}
for _, test := range valid {
t.Run(test.name, func(t *testing.T) {
localField, targetField, err := coreFindField(test.local, test.acc, test.target)
qt.Assert(t, err, qt.IsNil)
checkCOREField(t, "local", localField, test.localField)
checkCOREField(t, "target", targetField, test.targetField)
})
}
}
func TestCOREFindFieldCyclical(t *testing.T) {
members := []Member{{Name: "foo", Type: &Pointer{}}}
cyclicStruct := &Struct{}
cyclicStruct.Members = []Member{{Type: cyclicStruct}}
cyclicUnion := &Union{}
cyclicUnion.Members = []Member{{Type: cyclicUnion}}
cyclicArray := &Array{Nelems: 1}
cyclicArray.Type = &Pointer{Target: cyclicArray}
tests := []struct {
name string
local, cyclic Type
}{
{"struct", &Struct{Members: members}, cyclicStruct},
{"union", &Union{Members: members}, cyclicUnion},
{"array", &Array{Nelems: 2, Type: &Int{}}, cyclicArray},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
_, _, err := coreFindField(test.local, coreAccessor{0, 0}, test.cyclic)
if !errors.Is(err, errImpossibleRelocation) {
t.Fatal("Should return errImpossibleRelocation, got", err)
}
})
}
}
func TestCORERelocation(t *testing.T) {
testutils.Files(t, testutils.Glob(t, "testdata/*.elf"), func(t *testing.T, file string) {
rd, err := os.Open(file)
if err != nil {
t.Fatal(err)
}
defer rd.Close()
spec, extInfos, err := LoadSpecAndExtInfosFromReader(rd)
if err != nil {
t.Fatal(err)
}
if extInfos == nil {
t.Skip("No ext_infos")
}
errs := map[string]error{
"err_ambiguous": errAmbiguousRelocation,
"err_ambiguous_flavour": errAmbiguousRelocation,
}
for section := range extInfos.funcInfos {
name := strings.TrimPrefix(section, "socket_filter/")
t.Run(name, func(t *testing.T) {
var relos []*CORERelocation
for _, reloInfo := range extInfos.relocationInfos[section] {
relos = append(relos, reloInfo.relo)
}
fixups, err := CORERelocate(relos, spec, spec.byteOrder)
if want := errs[name]; want != nil {
if !errors.Is(err, want) {
t.Fatal("Expected", want, "got", err)
}
return
}
if err != nil {
t.Fatal("Can't relocate against itself:", err)
}
for offset, fixup := range fixups {
if want := fixup.local; !fixup.skipLocalValidation && want != fixup.target {
// Since we're relocating against ourselves both values
// should match.
t.Errorf("offset %d: local %v doesn't match target %d (kind %s)", offset, fixup.local, fixup.target, fixup.kind)
}
}
})
}
})
}
func TestCORECopyWithoutQualifiers(t *testing.T) {
qualifiers := []struct {
name string
fn func(Type) Type
}{
{"const", func(t Type) Type { return &Const{Type: t} }},
{"volatile", func(t Type) Type { return &Volatile{Type: t} }},
{"restrict", func(t Type) Type { return &Restrict{Type: t} }},
{"typedef", func(t Type) Type { return &Typedef{Type: t} }},
}
for _, test := range qualifiers {
t.Run(test.name+" cycle", func(t *testing.T) {
root := &Volatile{}
root.Type = test.fn(root)
cycle, ok := Copy(root, UnderlyingType).(*cycle)
qt.Assert(t, ok, qt.IsTrue)
qt.Assert(t, cycle.root, qt.Equals, root)
})
}
for _, a := range qualifiers {
for _, b := range qualifiers {
t.Run(a.name+" "+b.name, func(t *testing.T) {
v := a.fn(&Pointer{Target: b.fn(&Int{Name: "z"})})
want := &Pointer{Target: &Int{Name: "z"}}
got := Copy(v, UnderlyingType)
qt.Assert(t, got, qt.DeepEquals, want)
})
}
}
t.Run("long chain", func(t *testing.T) {
root := &Int{Name: "abc"}
v := Type(root)
for i := 0; i < maxTypeDepth; i++ {
q := qualifiers[testutils.Rand().Intn(len(qualifiers))]
v = q.fn(v)
t.Log(q.name)
}
got := Copy(v, UnderlyingType)
qt.Assert(t, got, qt.DeepEquals, root)
})
}
func TestCOREReloFieldSigned(t *testing.T) {
for _, typ := range []Type{&Int{}, &Enum{}} {
t.Run(fmt.Sprintf("%T with invalid target", typ), func(t *testing.T) {
relo := &CORERelocation{
typ, coreAccessor{0}, reloFieldSigned, 0,
}
fixup, err := coreCalculateFixup(relo, &Void{}, 0, internal.NativeEndian)
qt.Assert(t, fixup.poison, qt.IsTrue)
qt.Assert(t, err, qt.IsNil)
})
}
t.Run("type without signedness", func(t *testing.T) {
relo := &CORERelocation{
&Array{}, coreAccessor{0}, reloFieldSigned, 0,
}
_, err := coreCalculateFixup(relo, &Array{}, 0, internal.NativeEndian)
qt.Assert(t, err, qt.ErrorIs, errNoSignedness)
})
}
func TestCOREReloFieldShiftU64(t *testing.T) {
typ := &Struct{
Members: []Member{
{Name: "A", Type: &Fwd{}},
},
}
for _, relo := range []*CORERelocation{
{typ, coreAccessor{0, 0}, reloFieldRShiftU64, 1},
{typ, coreAccessor{0, 0}, reloFieldLShiftU64, 1},
} {
t.Run(relo.kind.String(), func(t *testing.T) {
_, err := coreCalculateFixup(relo, typ, 1, internal.NativeEndian)
qt.Assert(t, err, qt.ErrorIs, errUnsizedType)
})
}
}
func BenchmarkCORESkBuff(b *testing.B) {
spec := vmlinuxTestdataSpec(b)
var skb *Struct
err := spec.TypeByName("sk_buff", &skb)
qt.Assert(b, err, qt.IsNil)
skbID, err := spec.TypeID(skb)
qt.Assert(b, err, qt.IsNil)
var pktHashTypes *Enum
err = spec.TypeByName("pkt_hash_types", &pktHashTypes)
qt.Assert(b, err, qt.IsNil)
pktHashTypesID, err := spec.TypeID(pktHashTypes)
qt.Assert(b, err, qt.IsNil)
for _, relo := range []*CORERelocation{
{skb, coreAccessor{0, 0}, reloFieldByteOffset, skbID},
{skb, coreAccessor{0, 0}, reloFieldByteSize, skbID},
{skb, coreAccessor{0, 0}, reloFieldExists, skbID},
{skb, coreAccessor{0, 0}, reloFieldSigned, skbID},
{skb, coreAccessor{0, 0}, reloFieldLShiftU64, skbID},
{skb, coreAccessor{0, 0}, reloFieldRShiftU64, skbID},
{skb, coreAccessor{0}, reloTypeIDLocal, skbID},
{skb, coreAccessor{0}, reloTypeIDTarget, skbID},
{skb, coreAccessor{0}, reloTypeExists, skbID},
{skb, coreAccessor{0}, reloTypeSize, skbID},
{pktHashTypes, coreAccessor{0}, reloEnumvalExists, pktHashTypesID},
{pktHashTypes, coreAccessor{0}, reloEnumvalValue, pktHashTypesID},
} {
b.Run(relo.kind.String(), func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_, err = CORERelocate([]*CORERelocation{relo}, spec, spec.byteOrder)
if err != nil {
b.Fatal(err)
}
}
})
}
}
@@ -0,0 +1,5 @@
// Package btf handles data encoded according to the BPF Type Format.
//
// The canonical documentation lives in the Linux kernel repository and is
// available at https://www.kernel.org/doc/html/latest/bpf/btf.html
package btf
@@ -0,0 +1,768 @@
package btf
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"io"
"math"
"sort"
"github.com/cilium/ebpf/asm"
"github.com/cilium/ebpf/internal"
)
// ExtInfos contains ELF section metadata.
type ExtInfos struct {
// The slices are sorted by offset in ascending order.
funcInfos map[string][]funcInfo
lineInfos map[string][]lineInfo
relocationInfos map[string][]coreRelocationInfo
}
// loadExtInfosFromELF parses ext infos from the .BTF.ext section in an ELF.
//
// Returns an error wrapping ErrNotFound if no ext infos are present.
func loadExtInfosFromELF(file *internal.SafeELFFile, spec *Spec) (*ExtInfos, error) {
section := file.Section(".BTF.ext")
if section == nil {
return nil, fmt.Errorf("btf ext infos: %w", ErrNotFound)
}
if section.ReaderAt == nil {
return nil, fmt.Errorf("compressed ext_info is not supported")
}
return loadExtInfos(section.ReaderAt, file.ByteOrder, spec, spec.strings)
}
// loadExtInfos parses bare ext infos.
func loadExtInfos(r io.ReaderAt, bo binary.ByteOrder, spec *Spec, strings *stringTable) (*ExtInfos, error) {
// Open unbuffered section reader. binary.Read() calls io.ReadFull on
// the header structs, resulting in one syscall per header.
headerRd := io.NewSectionReader(r, 0, math.MaxInt64)
extHeader, err := parseBTFExtHeader(headerRd, bo)
if err != nil {
return nil, fmt.Errorf("parsing BTF extension header: %w", err)
}
coreHeader, err := parseBTFExtCOREHeader(headerRd, bo, extHeader)
if err != nil {
return nil, fmt.Errorf("parsing BTF CO-RE header: %w", err)
}
buf := internal.NewBufferedSectionReader(r, extHeader.funcInfoStart(), int64(extHeader.FuncInfoLen))
btfFuncInfos, err := parseFuncInfos(buf, bo, strings)
if err != nil {
return nil, fmt.Errorf("parsing BTF function info: %w", err)
}
funcInfos := make(map[string][]funcInfo, len(btfFuncInfos))
for section, bfis := range btfFuncInfos {
funcInfos[section], err = newFuncInfos(bfis, spec)
if err != nil {
return nil, fmt.Errorf("section %s: func infos: %w", section, err)
}
}
buf = internal.NewBufferedSectionReader(r, extHeader.lineInfoStart(), int64(extHeader.LineInfoLen))
btfLineInfos, err := parseLineInfos(buf, bo, strings)
if err != nil {
return nil, fmt.Errorf("parsing BTF line info: %w", err)
}
lineInfos := make(map[string][]lineInfo, len(btfLineInfos))
for section, blis := range btfLineInfos {
lineInfos[section], err = newLineInfos(blis, strings)
if err != nil {
return nil, fmt.Errorf("section %s: line infos: %w", section, err)
}
}
if coreHeader == nil || coreHeader.COREReloLen == 0 {
return &ExtInfos{funcInfos, lineInfos, nil}, nil
}
var btfCORERelos map[string][]bpfCORERelo
buf = internal.NewBufferedSectionReader(r, extHeader.coreReloStart(coreHeader), int64(coreHeader.COREReloLen))
btfCORERelos, err = parseCORERelos(buf, bo, strings)
if err != nil {
return nil, fmt.Errorf("parsing CO-RE relocation info: %w", err)
}
coreRelos := make(map[string][]coreRelocationInfo, len(btfCORERelos))
for section, brs := range btfCORERelos {
coreRelos[section], err = newRelocationInfos(brs, spec, strings)
if err != nil {
return nil, fmt.Errorf("section %s: CO-RE relocations: %w", section, err)
}
}
return &ExtInfos{funcInfos, lineInfos, coreRelos}, nil
}
type funcInfoMeta struct{}
type 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]
iter := insns.Iterate()
for iter.Next() {
if len(funcInfos) > 0 && funcInfos[0].offset == iter.Offset {
*iter.Ins = WithFuncMetadata(*iter.Ins, funcInfos[0].fn)
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) > 0 && reloInfos[0].offset == iter.Offset {
iter.Ins.Metadata.Set(coreRelocationMeta{}, reloInfos[0].relo)
reloInfos = reloInfos[1:]
}
}
}
// MarshalExtInfos encodes function and line info embedded in insns into kernel
// wire format.
//
// Returns ErrNotSupported if the kernel doesn't support BTF-associated programs.
func MarshalExtInfos(insns asm.Instructions) (_ *Handle, funcInfos, lineInfos []byte, _ error) {
// Bail out early if the kernel doesn't support Func(Proto). If this is the
// case, func_info will also be unsupported.
if err := haveProgBTF(); err != nil {
return nil, nil, nil, err
}
iter := insns.Iterate()
for iter.Next() {
_, ok := iter.Ins.Source().(*Line)
fn := FuncMetadata(iter.Ins)
if ok || fn != nil {
goto marshal
}
}
return nil, nil, nil, nil
marshal:
var b Builder
var fiBuf, liBuf bytes.Buffer
for {
if fn := FuncMetadata(iter.Ins); fn != nil {
fi := &funcInfo{
fn: fn,
offset: iter.Offset,
}
if err := fi.marshal(&fiBuf, &b); err != nil {
return nil, nil, nil, fmt.Errorf("write func info: %w", err)
}
}
if line, ok := iter.Ins.Source().(*Line); ok {
li := &lineInfo{
line: line,
offset: iter.Offset,
}
if err := li.marshal(&liBuf, &b); err != nil {
return nil, nil, nil, fmt.Errorf("write line info: %w", err)
}
}
if !iter.Next() {
break
}
}
handle, err := NewHandle(&b)
return handle, fiBuf.Bytes(), liBuf.Bytes(), err
}
// btfExtHeader is found at the start of the .BTF.ext section.
type btfExtHeader struct {
Magic uint16
Version uint8
Flags uint8
// HdrLen is larger than the size of struct btfExtHeader when it is
// immediately followed by a btfExtCOREHeader.
HdrLen uint32
FuncInfoOff uint32
FuncInfoLen uint32
LineInfoOff uint32
LineInfoLen uint32
}
// parseBTFExtHeader parses the header of the .BTF.ext section.
func parseBTFExtHeader(r io.Reader, bo binary.ByteOrder) (*btfExtHeader, error) {
var header btfExtHeader
if err := binary.Read(r, bo, &header); err != nil {
return nil, fmt.Errorf("can't read header: %v", err)
}
if header.Magic != btfMagic {
return nil, fmt.Errorf("incorrect magic value %v", header.Magic)
}
if header.Version != 1 {
return nil, fmt.Errorf("unexpected version %v", header.Version)
}
if header.Flags != 0 {
return nil, fmt.Errorf("unsupported flags %v", header.Flags)
}
if int64(header.HdrLen) < int64(binary.Size(&header)) {
return nil, fmt.Errorf("header length shorter than btfExtHeader size")
}
return &header, nil
}
// funcInfoStart returns the offset from the beginning of the .BTF.ext section
// to the start of its func_info entries.
func (h *btfExtHeader) funcInfoStart() int64 {
return int64(h.HdrLen + h.FuncInfoOff)
}
// lineInfoStart returns the offset from the beginning of the .BTF.ext section
// to the start of its line_info entries.
func (h *btfExtHeader) lineInfoStart() int64 {
return int64(h.HdrLen + h.LineInfoOff)
}
// coreReloStart returns the offset from the beginning of the .BTF.ext section
// to the start of its CO-RE relocation entries.
func (h *btfExtHeader) coreReloStart(ch *btfExtCOREHeader) int64 {
return int64(h.HdrLen + ch.COREReloOff)
}
// btfExtCOREHeader is found right after the btfExtHeader when its HdrLen
// field is larger than its size.
type btfExtCOREHeader struct {
COREReloOff uint32
COREReloLen uint32
}
// parseBTFExtCOREHeader parses the tail of the .BTF.ext header. If additional
// header bytes are present, extHeader.HdrLen will be larger than the struct,
// indicating the presence of a CO-RE extension header.
func parseBTFExtCOREHeader(r io.Reader, bo binary.ByteOrder, extHeader *btfExtHeader) (*btfExtCOREHeader, error) {
extHdrSize := int64(binary.Size(&extHeader))
remainder := int64(extHeader.HdrLen) - extHdrSize
if remainder == 0 {
return nil, nil
}
var coreHeader btfExtCOREHeader
if err := binary.Read(r, bo, &coreHeader); err != nil {
return nil, fmt.Errorf("can't read header: %v", err)
}
return &coreHeader, nil
}
type btfExtInfoSec struct {
SecNameOff uint32
NumInfo uint32
}
// parseExtInfoSec parses a btf_ext_info_sec header within .BTF.ext,
// appearing within func_info and line_info sub-sections.
// These headers appear once for each program section in the ELF and are
// followed by one or more func/line_info records for the section.
func parseExtInfoSec(r io.Reader, bo binary.ByteOrder, strings *stringTable) (string, *btfExtInfoSec, error) {
var infoHeader btfExtInfoSec
if err := binary.Read(r, bo, &infoHeader); err != nil {
return "", nil, fmt.Errorf("read ext info header: %w", err)
}
secName, err := strings.Lookup(infoHeader.SecNameOff)
if err != nil {
return "", nil, fmt.Errorf("get section name: %w", err)
}
if secName == "" {
return "", nil, fmt.Errorf("extinfo header refers to empty section name")
}
if infoHeader.NumInfo == 0 {
return "", nil, fmt.Errorf("section %s has zero records", secName)
}
return secName, &infoHeader, nil
}
// parseExtInfoRecordSize parses the uint32 at the beginning of a func_infos
// or line_infos segment that describes the length of all extInfoRecords in
// that segment.
func parseExtInfoRecordSize(r io.Reader, bo binary.ByteOrder) (uint32, error) {
const maxRecordSize = 256
var recordSize uint32
if err := binary.Read(r, bo, &recordSize); err != nil {
return 0, fmt.Errorf("can't read record size: %v", err)
}
if recordSize < 4 {
// Need at least InsnOff worth of bytes per record.
return 0, errors.New("record size too short")
}
if recordSize > maxRecordSize {
return 0, fmt.Errorf("record size %v exceeds %v", recordSize, maxRecordSize)
}
return recordSize, nil
}
// The size of a FuncInfo in BTF wire format.
var FuncInfoSize = uint32(binary.Size(bpfFuncInfo{}))
type funcInfo struct {
fn *Func
offset asm.RawInstructionOffset
}
type bpfFuncInfo struct {
// Instruction offset of the function within an ELF section.
InsnOff uint32
TypeID TypeID
}
func newFuncInfo(fi bpfFuncInfo, spec *Spec) (*funcInfo, error) {
typ, err := spec.TypeByID(fi.TypeID)
if err != nil {
return nil, err
}
fn, ok := typ.(*Func)
if !ok {
return nil, fmt.Errorf("type ID %d is a %T, but expected a Func", fi.TypeID, typ)
}
// C doesn't have anonymous functions, but check just in case.
if fn.Name == "" {
return nil, fmt.Errorf("func with type ID %d doesn't have a name", fi.TypeID)
}
return &funcInfo{
fn,
asm.RawInstructionOffset(fi.InsnOff),
}, nil
}
func newFuncInfos(bfis []bpfFuncInfo, spec *Spec) ([]funcInfo, error) {
fis := make([]funcInfo, 0, len(bfis))
for _, bfi := range bfis {
fi, err := newFuncInfo(bfi, spec)
if err != nil {
return nil, fmt.Errorf("offset %d: %w", bfi.InsnOff, err)
}
fis = append(fis, *fi)
}
sort.Slice(fis, func(i, j int) bool {
return fis[i].offset <= fis[j].offset
})
return fis, nil
}
// marshal into the BTF wire format.
func (fi *funcInfo) marshal(w *bytes.Buffer, b *Builder) error {
id, err := b.Add(fi.fn)
if err != nil {
return err
}
bfi := bpfFuncInfo{
InsnOff: uint32(fi.offset),
TypeID: id,
}
buf := make([]byte, FuncInfoSize)
internal.NativeEndian.PutUint32(buf, bfi.InsnOff)
internal.NativeEndian.PutUint32(buf[4:], uint32(bfi.TypeID))
_, err = w.Write(buf)
return err
}
// parseFuncInfos parses a func_info sub-section within .BTF.ext ito a map of
// func infos indexed by section name.
func parseFuncInfos(r io.Reader, bo binary.ByteOrder, strings *stringTable) (map[string][]bpfFuncInfo, error) {
recordSize, err := parseExtInfoRecordSize(r, bo)
if err != nil {
return nil, err
}
result := make(map[string][]bpfFuncInfo)
for {
secName, infoHeader, err := parseExtInfoSec(r, bo, strings)
if errors.Is(err, io.EOF) {
return result, nil
}
if err != nil {
return nil, err
}
records, err := parseFuncInfoRecords(r, bo, recordSize, infoHeader.NumInfo)
if err != nil {
return nil, fmt.Errorf("section %v: %w", secName, err)
}
result[secName] = records
}
}
// parseFuncInfoRecords parses a stream of func_infos into a funcInfos.
// These records appear after a btf_ext_info_sec header in the func_info
// sub-section of .BTF.ext.
func parseFuncInfoRecords(r io.Reader, bo binary.ByteOrder, recordSize uint32, recordNum uint32) ([]bpfFuncInfo, error) {
var out []bpfFuncInfo
var fi bpfFuncInfo
if exp, got := FuncInfoSize, recordSize; exp != got {
// BTF blob's record size is longer than we know how to parse.
return nil, fmt.Errorf("expected FuncInfo record size %d, but BTF blob contains %d", exp, got)
}
for i := uint32(0); i < recordNum; i++ {
if err := binary.Read(r, bo, &fi); err != nil {
return nil, fmt.Errorf("can't read function info: %v", err)
}
if fi.InsnOff%asm.InstructionSize != 0 {
return nil, fmt.Errorf("offset %v is not aligned with instruction size", fi.InsnOff)
}
// ELF tracks offset in bytes, the kernel expects raw BPF instructions.
// Convert as early as possible.
fi.InsnOff /= asm.InstructionSize
out = append(out, fi)
}
return out, nil
}
var LineInfoSize = uint32(binary.Size(bpfLineInfo{}))
// Line represents the location and contents of a single line of source
// code a BPF ELF was compiled from.
type Line struct {
fileName string
line string
lineNumber uint32
lineColumn uint32
}
func (li *Line) FileName() string {
return li.fileName
}
func (li *Line) Line() string {
return li.line
}
func (li *Line) LineNumber() uint32 {
return li.lineNumber
}
func (li *Line) LineColumn() uint32 {
return li.lineColumn
}
func (li *Line) String() string {
return li.line
}
type lineInfo struct {
line *Line
offset asm.RawInstructionOffset
}
// Constants for the format of bpfLineInfo.LineCol.
const (
bpfLineShift = 10
bpfLineMax = (1 << (32 - bpfLineShift)) - 1
bpfColumnMax = (1 << bpfLineShift) - 1
)
type bpfLineInfo struct {
// Instruction offset of the line within the whole instruction stream, in instructions.
InsnOff uint32
FileNameOff uint32
LineOff uint32
LineCol uint32
}
func newLineInfo(li bpfLineInfo, strings *stringTable) (*lineInfo, error) {
line, err := strings.Lookup(li.LineOff)
if err != nil {
return nil, fmt.Errorf("lookup of line: %w", err)
}
fileName, err := strings.Lookup(li.FileNameOff)
if err != nil {
return nil, fmt.Errorf("lookup of filename: %w", err)
}
lineNumber := li.LineCol >> bpfLineShift
lineColumn := li.LineCol & bpfColumnMax
return &lineInfo{
&Line{
fileName,
line,
lineNumber,
lineColumn,
},
asm.RawInstructionOffset(li.InsnOff),
}, nil
}
func newLineInfos(blis []bpfLineInfo, strings *stringTable) ([]lineInfo, error) {
lis := make([]lineInfo, 0, len(blis))
for _, bli := range blis {
li, err := newLineInfo(bli, strings)
if err != nil {
return nil, fmt.Errorf("offset %d: %w", bli.InsnOff, err)
}
lis = append(lis, *li)
}
sort.Slice(lis, func(i, j int) bool {
return lis[i].offset <= lis[j].offset
})
return lis, nil
}
// marshal writes the binary representation of the LineInfo to w.
func (li *lineInfo) marshal(w *bytes.Buffer, b *Builder) error {
line := li.line
if line.lineNumber > bpfLineMax {
return fmt.Errorf("line %d exceeds %d", line.lineNumber, bpfLineMax)
}
if line.lineColumn > bpfColumnMax {
return fmt.Errorf("column %d exceeds %d", line.lineColumn, bpfColumnMax)
}
fileNameOff, err := b.addString(line.fileName)
if err != nil {
return fmt.Errorf("file name %q: %w", line.fileName, err)
}
lineOff, err := b.addString(line.line)
if err != nil {
return fmt.Errorf("line %q: %w", line.line, err)
}
bli := bpfLineInfo{
uint32(li.offset),
fileNameOff,
lineOff,
(line.lineNumber << bpfLineShift) | line.lineColumn,
}
buf := make([]byte, LineInfoSize)
internal.NativeEndian.PutUint32(buf, bli.InsnOff)
internal.NativeEndian.PutUint32(buf[4:], bli.FileNameOff)
internal.NativeEndian.PutUint32(buf[8:], bli.LineOff)
internal.NativeEndian.PutUint32(buf[12:], bli.LineCol)
_, err = w.Write(buf)
return err
}
// parseLineInfos parses a line_info sub-section within .BTF.ext ito a map of
// line infos indexed by section name.
func parseLineInfos(r io.Reader, bo binary.ByteOrder, strings *stringTable) (map[string][]bpfLineInfo, error) {
recordSize, err := parseExtInfoRecordSize(r, bo)
if err != nil {
return nil, err
}
result := make(map[string][]bpfLineInfo)
for {
secName, infoHeader, err := parseExtInfoSec(r, bo, strings)
if errors.Is(err, io.EOF) {
return result, nil
}
if err != nil {
return nil, err
}
records, err := parseLineInfoRecords(r, bo, recordSize, infoHeader.NumInfo)
if err != nil {
return nil, fmt.Errorf("section %v: %w", secName, err)
}
result[secName] = records
}
}
// parseLineInfoRecords parses a stream of line_infos into a lineInfos.
// These records appear after a btf_ext_info_sec header in the line_info
// sub-section of .BTF.ext.
func parseLineInfoRecords(r io.Reader, bo binary.ByteOrder, recordSize uint32, recordNum uint32) ([]bpfLineInfo, error) {
var out []bpfLineInfo
var li bpfLineInfo
if exp, got := uint32(binary.Size(li)), recordSize; exp != got {
// BTF blob's record size is longer than we know how to parse.
return nil, fmt.Errorf("expected LineInfo record size %d, but BTF blob contains %d", exp, got)
}
for i := uint32(0); i < recordNum; i++ {
if err := binary.Read(r, bo, &li); err != nil {
return nil, fmt.Errorf("can't read line info: %v", err)
}
if li.InsnOff%asm.InstructionSize != 0 {
return nil, fmt.Errorf("offset %v is not aligned with instruction size", li.InsnOff)
}
// ELF tracks offset in bytes, the kernel expects raw BPF instructions.
// Convert as early as possible.
li.InsnOff /= asm.InstructionSize
out = append(out, li)
}
return out, nil
}
// bpfCORERelo matches the kernel's struct bpf_core_relo.
type bpfCORERelo struct {
InsnOff uint32
TypeID TypeID
AccessStrOff uint32
Kind coreKind
}
type CORERelocation struct {
// The local type of the relocation, stripped of typedefs and qualifiers.
typ Type
accessor coreAccessor
kind coreKind
// The ID of the local type in the source BTF.
id TypeID
}
func (cr *CORERelocation) String() string {
return fmt.Sprintf("CORERelocation(%s, %s[%s], local_id=%d)", cr.kind, cr.typ, cr.accessor, cr.id)
}
func CORERelocationMetadata(ins *asm.Instruction) *CORERelocation {
relo, _ := ins.Metadata.Get(coreRelocationMeta{}).(*CORERelocation)
return relo
}
type coreRelocationInfo struct {
relo *CORERelocation
offset asm.RawInstructionOffset
}
func newRelocationInfo(relo bpfCORERelo, spec *Spec, strings *stringTable) (*coreRelocationInfo, error) {
typ, err := spec.TypeByID(relo.TypeID)
if err != nil {
return nil, err
}
accessorStr, err := strings.Lookup(relo.AccessStrOff)
if err != nil {
return nil, err
}
accessor, err := parseCOREAccessor(accessorStr)
if err != nil {
return nil, fmt.Errorf("accessor %q: %s", accessorStr, err)
}
return &coreRelocationInfo{
&CORERelocation{
typ,
accessor,
relo.Kind,
relo.TypeID,
},
asm.RawInstructionOffset(relo.InsnOff),
}, nil
}
func newRelocationInfos(brs []bpfCORERelo, spec *Spec, strings *stringTable) ([]coreRelocationInfo, error) {
rs := make([]coreRelocationInfo, 0, len(brs))
for _, br := range brs {
relo, err := newRelocationInfo(br, spec, strings)
if err != nil {
return nil, fmt.Errorf("offset %d: %w", br.InsnOff, err)
}
rs = append(rs, *relo)
}
sort.Slice(rs, func(i, j int) bool {
return rs[i].offset < rs[j].offset
})
return rs, nil
}
var extInfoReloSize = binary.Size(bpfCORERelo{})
// parseCORERelos parses a core_relos sub-section within .BTF.ext ito a map of
// CO-RE relocations indexed by section name.
func parseCORERelos(r io.Reader, bo binary.ByteOrder, strings *stringTable) (map[string][]bpfCORERelo, error) {
recordSize, err := parseExtInfoRecordSize(r, bo)
if err != nil {
return nil, err
}
if recordSize != uint32(extInfoReloSize) {
return nil, fmt.Errorf("expected record size %d, got %d", extInfoReloSize, recordSize)
}
result := make(map[string][]bpfCORERelo)
for {
secName, infoHeader, err := parseExtInfoSec(r, bo, strings)
if errors.Is(err, io.EOF) {
return result, nil
}
if err != nil {
return nil, err
}
records, err := parseCOREReloRecords(r, bo, recordSize, infoHeader.NumInfo)
if err != nil {
return nil, fmt.Errorf("section %v: %w", secName, err)
}
result[secName] = records
}
}
// parseCOREReloRecords parses a stream of CO-RE relocation entries into a
// coreRelos. These records appear after a btf_ext_info_sec header in the
// core_relos sub-section of .BTF.ext.
func parseCOREReloRecords(r io.Reader, bo binary.ByteOrder, recordSize uint32, recordNum uint32) ([]bpfCORERelo, error) {
var out []bpfCORERelo
var relo bpfCORERelo
for i := uint32(0); i < recordNum; i++ {
if err := binary.Read(r, bo, &relo); err != nil {
return nil, fmt.Errorf("can't read CO-RE relocation: %v", err)
}
if relo.InsnOff%asm.InstructionSize != 0 {
return nil, fmt.Errorf("offset %v is not aligned with instruction size", relo.InsnOff)
}
// ELF tracks offset in bytes, the kernel expects raw BPF instructions.
// Convert as early as possible.
relo.InsnOff /= asm.InstructionSize
out = append(out, relo)
}
return out, nil
}
@@ -0,0 +1,25 @@
package btf
import (
"bytes"
"strings"
"testing"
"github.com/cilium/ebpf/internal"
)
func TestParseExtInfoBigRecordSize(t *testing.T) {
rd := strings.NewReader("\xff\xff\xff\xff\x00\x00\x00\x000709171295166016")
table, err := readStringTable(bytes.NewReader([]byte{0}), nil)
if err != nil {
t.Fatal(err)
}
if _, err := parseFuncInfos(rd, internal.NativeEndian, table); err == nil {
t.Error("Parsing func info with large record size doesn't return an error")
}
if _, err := parseLineInfos(rd, internal.NativeEndian, table); err == nil {
t.Error("Parsing line info with large record size doesn't return an error")
}
}
@@ -0,0 +1,344 @@
package btf
import (
"errors"
"fmt"
"strings"
)
var errNestedTooDeep = errors.New("nested too deep")
// GoFormatter converts a Type to Go syntax.
//
// A zero GoFormatter is valid to use.
type GoFormatter struct {
w strings.Builder
// Types present in this map are referred to using the given name if they
// are encountered when outputting another type.
Names map[Type]string
// Identifier is called for each field of struct-like types. By default the
// field name is used as is.
Identifier func(string) string
// EnumIdentifier is called for each element of an enum. By default the
// name of the enum type is concatenated with Identifier(element).
EnumIdentifier func(name, element string) string
}
// TypeDeclaration generates a Go type declaration for a BTF type.
func (gf *GoFormatter) TypeDeclaration(name string, typ Type) (string, error) {
gf.w.Reset()
if err := gf.writeTypeDecl(name, typ); err != nil {
return "", err
}
return gf.w.String(), nil
}
func (gf *GoFormatter) identifier(s string) string {
if gf.Identifier != nil {
return gf.Identifier(s)
}
return s
}
func (gf *GoFormatter) enumIdentifier(name, element string) string {
if gf.EnumIdentifier != nil {
return gf.EnumIdentifier(name, element)
}
return name + gf.identifier(element)
}
// writeTypeDecl outputs a declaration of the given type.
//
// It encodes https://golang.org/ref/spec#Type_declarations:
//
// type foo struct { bar uint32; }
// type bar int32
func (gf *GoFormatter) writeTypeDecl(name string, typ Type) error {
if name == "" {
return fmt.Errorf("need a name for type %s", typ)
}
typ = skipQualifiers(typ)
fmt.Fprintf(&gf.w, "type %s ", name)
if err := gf.writeTypeLit(typ, 0); err != nil {
return err
}
e, ok := typ.(*Enum)
if !ok || len(e.Values) == 0 {
return nil
}
gf.w.WriteString("; const ( ")
for _, ev := range e.Values {
id := gf.enumIdentifier(name, ev.Name)
fmt.Fprintf(&gf.w, "%s %s = %d; ", id, name, ev.Value)
}
gf.w.WriteString(")")
return nil
}
// writeType outputs the name of a named type or a literal describing the type.
//
// It encodes https://golang.org/ref/spec#Types.
//
// foo (if foo is a named type)
// uint32
func (gf *GoFormatter) writeType(typ Type, depth int) error {
typ = skipQualifiers(typ)
name := gf.Names[typ]
if name != "" {
gf.w.WriteString(name)
return nil
}
return gf.writeTypeLit(typ, depth)
}
// writeTypeLit outputs a literal describing the type.
//
// The function ignores named types.
//
// It encodes https://golang.org/ref/spec#TypeLit.
//
// struct { bar uint32; }
// uint32
func (gf *GoFormatter) writeTypeLit(typ Type, depth int) error {
depth++
if depth > maxTypeDepth {
return errNestedTooDeep
}
var err error
switch v := skipQualifiers(typ).(type) {
case *Int:
err = gf.writeIntLit(v)
case *Enum:
if !v.Signed {
gf.w.WriteRune('u')
}
switch v.Size {
case 1:
gf.w.WriteString("int8")
case 2:
gf.w.WriteString("int16")
case 4:
gf.w.WriteString("int32")
case 8:
gf.w.WriteString("int64")
default:
err = fmt.Errorf("invalid enum size %d", v.Size)
}
case *Typedef:
err = gf.writeType(v.Type, depth)
case *Array:
fmt.Fprintf(&gf.w, "[%d]", v.Nelems)
err = gf.writeType(v.Type, depth)
case *Struct:
err = gf.writeStructLit(v.Size, v.Members, depth)
case *Union:
// Always choose the first member to represent the union in Go.
err = gf.writeStructLit(v.Size, v.Members[:1], depth)
case *Datasec:
err = gf.writeDatasecLit(v, depth)
default:
return fmt.Errorf("type %T: %w", v, ErrNotSupported)
}
if err != nil {
return fmt.Errorf("%s: %w", typ, err)
}
return nil
}
func (gf *GoFormatter) writeIntLit(i *Int) error {
bits := i.Size * 8
switch i.Encoding {
case Bool:
if i.Size != 1 {
return fmt.Errorf("bool with size %d", i.Size)
}
gf.w.WriteString("bool")
case Char:
if i.Size != 1 {
return fmt.Errorf("char with size %d", i.Size)
}
// BTF doesn't have a way to specify the signedness of a char. Assume
// we are dealing with unsigned, since this works nicely with []byte
// in Go code.
fallthrough
case Unsigned, Signed:
stem := "uint"
if i.Encoding == Signed {
stem = "int"
}
if i.Size > 8 {
fmt.Fprintf(&gf.w, "[%d]byte /* %s%d */", i.Size, stem, i.Size*8)
} else {
fmt.Fprintf(&gf.w, "%s%d", stem, bits)
}
default:
return fmt.Errorf("can't encode %s", i.Encoding)
}
return nil
}
func (gf *GoFormatter) writeStructLit(size uint32, members []Member, depth int) error {
gf.w.WriteString("struct { ")
prevOffset := uint32(0)
skippedBitfield := false
for i, m := range members {
if m.BitfieldSize > 0 {
skippedBitfield = true
continue
}
offset := m.Offset.Bytes()
if n := offset - prevOffset; skippedBitfield && n > 0 {
fmt.Fprintf(&gf.w, "_ [%d]byte /* unsupported bitfield */; ", n)
} else {
gf.writePadding(n)
}
fieldSize, err := Sizeof(m.Type)
if err != nil {
return fmt.Errorf("field %d: %w", i, err)
}
prevOffset = offset + uint32(fieldSize)
if prevOffset > size {
return fmt.Errorf("field %d of size %d exceeds type size %d", i, fieldSize, size)
}
if err := gf.writeStructField(m, depth); err != nil {
return fmt.Errorf("field %d: %w", i, err)
}
}
gf.writePadding(size - prevOffset)
gf.w.WriteString("}")
return nil
}
func (gf *GoFormatter) writeStructField(m Member, depth int) error {
if m.BitfieldSize > 0 {
return fmt.Errorf("bitfields are not supported")
}
if m.Offset%8 != 0 {
return fmt.Errorf("unsupported offset %d", m.Offset)
}
if m.Name == "" {
// Special case a nested anonymous union like
// struct foo { union { int bar; int baz }; }
// by replacing the whole union with its first member.
union, ok := m.Type.(*Union)
if !ok {
return fmt.Errorf("anonymous fields are not supported")
}
if len(union.Members) == 0 {
return errors.New("empty anonymous union")
}
depth++
if depth > maxTypeDepth {
return errNestedTooDeep
}
m := union.Members[0]
size, err := Sizeof(m.Type)
if err != nil {
return err
}
if err := gf.writeStructField(m, depth); err != nil {
return err
}
gf.writePadding(union.Size - uint32(size))
return nil
}
fmt.Fprintf(&gf.w, "%s ", gf.identifier(m.Name))
if err := gf.writeType(m.Type, depth); err != nil {
return err
}
gf.w.WriteString("; ")
return nil
}
func (gf *GoFormatter) writeDatasecLit(ds *Datasec, depth int) error {
gf.w.WriteString("struct { ")
prevOffset := uint32(0)
for i, vsi := range ds.Vars {
v, ok := vsi.Type.(*Var)
if !ok {
return fmt.Errorf("can't format %s as part of data section", vsi.Type)
}
if v.Linkage != GlobalVar {
// Ignore static, extern, etc. for now.
continue
}
if v.Name == "" {
return fmt.Errorf("variable %d: empty name", i)
}
gf.writePadding(vsi.Offset - prevOffset)
prevOffset = vsi.Offset + vsi.Size
fmt.Fprintf(&gf.w, "%s ", gf.identifier(v.Name))
if err := gf.writeType(v.Type, depth); err != nil {
return fmt.Errorf("variable %d: %w", i, err)
}
gf.w.WriteString("; ")
}
gf.writePadding(ds.Size - prevOffset)
gf.w.WriteString("}")
return nil
}
func (gf *GoFormatter) writePadding(bytes uint32) {
if bytes > 0 {
fmt.Fprintf(&gf.w, "_ [%d]byte; ", bytes)
}
}
func skipQualifiers(typ Type) Type {
result := typ
for depth := 0; depth <= maxTypeDepth; depth++ {
switch v := (result).(type) {
case qualifier:
result = v.qualify()
default:
return result
}
}
return &cycle{typ}
}
@@ -0,0 +1,267 @@
package btf
import (
"errors"
"fmt"
"go/format"
"strings"
"testing"
)
func TestGoTypeDeclaration(t *testing.T) {
tests := []struct {
typ Type
output string
}{
{&Int{Size: 1}, "type t uint8"},
{&Int{Size: 1, Encoding: Bool}, "type t bool"},
{&Int{Size: 1, Encoding: Char}, "type t uint8"},
{&Int{Size: 2, Encoding: Signed}, "type t int16"},
{&Int{Size: 4, Encoding: Signed}, "type t int32"},
{&Int{Size: 8}, "type t uint64"},
{&Typedef{Name: "frob", Type: &Int{Size: 8}}, "type t uint64"},
{&Int{Size: 16}, "type t [16]byte /* uint128 */"},
{&Enum{Values: []EnumValue{{"FOO", 32}}, Size: 4}, "type t uint32; const ( tFOO t = 32; )"},
{&Enum{Values: []EnumValue{{"BAR", 1}}, Size: 1, Signed: true}, "type t int8; const ( tBAR t = 1; )"},
{
&Struct{
Name: "enum literals",
Size: 2,
Members: []Member{
{Name: "enum", Type: &Enum{Values: []EnumValue{{"BAR", 1}}, Size: 2}, Offset: 0},
},
},
"type t struct { enum uint16; }",
},
{&Array{Nelems: 2, Type: &Int{Size: 1}}, "type t [2]uint8"},
{
&Union{
Size: 8,
Members: []Member{
{Name: "a", Type: &Int{Size: 4}},
{Name: "b", Type: &Int{Size: 8}},
},
},
"type t struct { a uint32; _ [4]byte; }",
},
{
&Struct{
Name: "field padding",
Size: 16,
Members: []Member{
{Name: "frob", Type: &Int{Size: 4}, Offset: 0},
{Name: "foo", Type: &Int{Size: 8}, Offset: 8 * 8},
},
},
"type t struct { frob uint32; _ [4]byte; foo uint64; }",
},
{
&Struct{
Name: "end padding",
Size: 16,
Members: []Member{
{Name: "foo", Type: &Int{Size: 8}, Offset: 0},
{Name: "frob", Type: &Int{Size: 4}, Offset: 8 * 8},
},
},
"type t struct { foo uint64; frob uint32; _ [4]byte; }",
},
{
&Struct{
Name: "bitfield",
Size: 8,
Members: []Member{
{Name: "foo", Type: &Int{Size: 4}, Offset: 0, BitfieldSize: 1},
{Name: "frob", Type: &Int{Size: 4}, Offset: 4 * 8},
},
},
"type t struct { _ [4]byte /* unsupported bitfield */; frob uint32; }",
},
{
&Struct{
Name: "nested",
Size: 8,
Members: []Member{
{
Name: "foo",
Type: &Struct{
Size: 4,
Members: []Member{
{Name: "bar", Type: &Int{Size: 4}, Offset: 0},
},
},
},
{Name: "frob", Type: &Int{Size: 4}, Offset: 4 * 8},
},
},
"type t struct { foo struct { bar uint32; }; frob uint32; }",
},
{
&Struct{
Name: "nested anon union",
Size: 8,
Members: []Member{
{
Name: "",
Type: &Union{
Size: 4,
Members: []Member{
{Name: "foo", Type: &Int{Size: 4}, Offset: 0},
{Name: "bar", Type: &Int{Size: 4}, Offset: 0},
},
},
},
},
},
"type t struct { foo uint32; _ [4]byte; }",
},
{
&Datasec{
Size: 16,
Vars: []VarSecinfo{
{&Var{Name: "s", Type: &Int{Size: 2}, Linkage: StaticVar}, 0, 2},
{&Var{Name: "g", Type: &Int{Size: 4}, Linkage: GlobalVar}, 4, 4},
{&Var{Name: "e", Type: &Int{Size: 8}, Linkage: ExternVar}, 8, 8},
},
},
"type t struct { _ [4]byte; g uint32; _ [8]byte; }",
},
}
for _, test := range tests {
t.Run(fmt.Sprint(test.typ), func(t *testing.T) {
have := mustGoTypeDeclaration(t, test.typ, nil, nil)
if have != test.output {
t.Errorf("Unexpected output:\n\t-%s\n\t+%s", test.output, have)
}
})
}
}
func TestGoTypeDeclarationNamed(t *testing.T) {
e1 := &Enum{Name: "e1", Size: 4}
s1 := &Struct{
Name: "s1",
Size: 4,
Members: []Member{
{Name: "frob", Type: e1},
},
}
s2 := &Struct{
Name: "s2",
Size: 4,
Members: []Member{
{Name: "frood", Type: s1},
},
}
td := &Typedef{Name: "td", Type: e1}
arr := &Array{Nelems: 1, Type: td}
tests := []struct {
typ Type
named []Type
output string
}{
{e1, []Type{e1}, "type t uint32"},
{s1, []Type{e1, s1}, "type t struct { frob E1; }"},
{s2, []Type{e1}, "type t struct { frood struct { frob E1; }; }"},
{s2, []Type{e1, s1}, "type t struct { frood S1; }"},
{td, nil, "type t uint32"},
{td, []Type{td}, "type t uint32"},
{arr, []Type{td}, "type t [1]TD"},
}
for _, test := range tests {
t.Run(fmt.Sprint(test.typ), func(t *testing.T) {
names := make(map[Type]string)
for _, t := range test.named {
names[t] = strings.ToUpper(t.TypeName())
}
have := mustGoTypeDeclaration(t, test.typ, names, nil)
if have != test.output {
t.Errorf("Unexpected output:\n\t-%s\n\t+%s", test.output, have)
}
})
}
}
func TestGoTypeDeclarationQualifiers(t *testing.T) {
i := &Int{Size: 4}
want := mustGoTypeDeclaration(t, i, nil, nil)
tests := []struct {
typ Type
}{
{&Volatile{Type: i}},
{&Const{Type: i}},
{&Restrict{Type: i}},
}
for _, test := range tests {
t.Run(fmt.Sprint(test.typ), func(t *testing.T) {
have := mustGoTypeDeclaration(t, test.typ, nil, nil)
if have != want {
t.Errorf("Unexpected output:\n\t-%s\n\t+%s", want, have)
}
})
}
}
func TestGoTypeDeclarationCycle(t *testing.T) {
s := &Struct{Name: "cycle"}
s.Members = []Member{{Name: "f", Type: s}}
var gf GoFormatter
_, err := gf.TypeDeclaration("t", s)
if !errors.Is(err, errNestedTooDeep) {
t.Fatal("Expected errNestedTooDeep, got", err)
}
}
func TestRejectBogusTypes(t *testing.T) {
tests := []struct {
typ Type
}{
{&Struct{
Size: 1,
Members: []Member{
{Name: "foo", Type: &Int{Size: 2}, Offset: 0},
},
}},
{&Int{Size: 2, Encoding: Bool}},
{&Int{Size: 1, Encoding: Char | Signed}},
{&Int{Size: 2, Encoding: Char}},
}
for _, test := range tests {
t.Run(fmt.Sprint(test.typ), func(t *testing.T) {
var gf GoFormatter
_, err := gf.TypeDeclaration("t", test.typ)
if err == nil {
t.Fatal("TypeDeclaration does not reject bogus type")
}
})
}
}
func mustGoTypeDeclaration(tb testing.TB, typ Type, names map[Type]string, id func(string) string) string {
tb.Helper()
gf := GoFormatter{
Names: names,
Identifier: id,
}
have, err := gf.TypeDeclaration("t", typ)
if err != nil {
tb.Fatal(err)
}
_, err = format.Source([]byte(have))
if err != nil {
tb.Fatalf("Output can't be formatted: %s\n%s", err, have)
}
return have
}
@@ -0,0 +1,80 @@
package btf
import (
"bytes"
"encoding/binary"
"fmt"
"io"
"testing"
"github.com/cilium/ebpf/internal"
)
func FuzzSpec(f *testing.F) {
var buf bytes.Buffer
err := binary.Write(&buf, internal.NativeEndian, &btfHeader{
Magic: btfMagic,
Version: 1,
HdrLen: uint32(binary.Size(btfHeader{})),
})
if err != nil {
f.Fatal(err)
}
f.Add(buf.Bytes())
f.Fuzz(func(t *testing.T, data []byte) {
if len(data) < binary.Size(btfHeader{}) {
t.Skip("data is too short")
}
spec, err := loadRawSpec(bytes.NewReader(data), internal.NativeEndian, nil)
if err != nil {
if spec != nil {
t.Fatal("spec is not nil")
}
return
}
if spec == nil {
t.Fatal("spec is nil")
}
for _, typ := range spec.types {
fmt.Fprintf(io.Discard, "%+10v", typ)
}
})
}
func FuzzExtInfo(f *testing.F) {
var buf bytes.Buffer
err := binary.Write(&buf, internal.NativeEndian, &btfExtHeader{
Magic: btfMagic,
Version: 1,
HdrLen: uint32(binary.Size(btfExtHeader{})),
})
if err != nil {
f.Fatal(err)
}
f.Add(buf.Bytes(), []byte("\x00foo\x00barfoo\x00"))
emptySpec := newSpec()
f.Fuzz(func(t *testing.T, data, strings []byte) {
if len(data) < binary.Size(btfExtHeader{}) {
t.Skip("data is too short")
}
table, err := readStringTable(bytes.NewReader(strings), nil)
if err != nil {
t.Skip("invalid string table")
}
info, err := loadExtInfos(bytes.NewReader(data), internal.NativeEndian, emptySpec, table)
if err != nil {
if info != nil {
t.Fatal("info is not nil")
}
} else if info == nil {
t.Fatal("info is nil")
}
})
}
@@ -0,0 +1,287 @@
package btf
import (
"bytes"
"errors"
"fmt"
"math"
"os"
"github.com/cilium/ebpf/internal"
"github.com/cilium/ebpf/internal/sys"
"github.com/cilium/ebpf/internal/unix"
)
// Handle is a reference to BTF loaded into the kernel.
type Handle struct {
fd *sys.FD
// Size of the raw BTF in bytes.
size uint32
needsKernelBase bool
}
// NewHandle loads the contents of a [Builder] into the kernel.
//
// Returns an error wrapping ErrNotSupported if the kernel doesn't support BTF.
func NewHandle(b *Builder) (*Handle, error) {
small := getByteSlice()
defer putByteSlice(small)
buf, err := b.Marshal(*small, KernelMarshalOptions())
if err != nil {
return nil, fmt.Errorf("marshal BTF: %w", err)
}
return NewHandleFromRawBTF(buf)
}
// NewHandleFromRawBTF loads raw BTF into the kernel.
//
// Returns an error wrapping ErrNotSupported if the kernel doesn't support BTF.
func NewHandleFromRawBTF(btf []byte) (*Handle, error) {
if uint64(len(btf)) > math.MaxUint32 {
return nil, errors.New("BTF exceeds the maximum size")
}
attr := &sys.BtfLoadAttr{
Btf: sys.NewSlicePointer(btf),
BtfSize: uint32(len(btf)),
}
fd, err := sys.BtfLoad(attr)
if err == nil {
return &Handle{fd, attr.BtfSize, false}, nil
}
if err := haveBTF(); err != nil {
return nil, err
}
logBuf := make([]byte, 64*1024)
attr.BtfLogBuf = sys.NewSlicePointer(logBuf)
attr.BtfLogSize = uint32(len(logBuf))
attr.BtfLogLevel = 1
// Up until at least kernel 6.0, the BTF verifier does not return ENOSPC
// if there are other verification errors. ENOSPC is only returned when
// the BTF blob is correct, a log was requested, and the provided buffer
// is too small.
_, ve := sys.BtfLoad(attr)
return nil, internal.ErrorWithLog("load btf", err, logBuf, errors.Is(ve, unix.ENOSPC))
}
// NewHandleFromID returns the BTF handle for a given id.
//
// Prefer calling [ebpf.Program.Handle] or [ebpf.Map.Handle] if possible.
//
// Returns ErrNotExist, if there is no BTF with the given id.
//
// Requires CAP_SYS_ADMIN.
func NewHandleFromID(id ID) (*Handle, error) {
fd, err := sys.BtfGetFdById(&sys.BtfGetFdByIdAttr{
Id: uint32(id),
})
if err != nil {
return nil, fmt.Errorf("get FD for ID %d: %w", id, err)
}
info, err := newHandleInfoFromFD(fd)
if err != nil {
_ = fd.Close()
return nil, err
}
return &Handle{fd, info.size, info.IsModule()}, nil
}
// Spec parses the kernel BTF into Go types.
//
// base must contain type information for vmlinux if the handle is for
// a kernel module. It may be nil otherwise.
func (h *Handle) Spec(base *Spec) (*Spec, error) {
var btfInfo sys.BtfInfo
btfBuffer := make([]byte, h.size)
btfInfo.Btf, btfInfo.BtfSize = sys.NewSlicePointerLen(btfBuffer)
if err := sys.ObjInfo(h.fd, &btfInfo); err != nil {
return nil, err
}
if h.needsKernelBase && base == nil {
return nil, fmt.Errorf("missing base types")
}
return loadRawSpec(bytes.NewReader(btfBuffer), internal.NativeEndian, base)
}
// Close destroys the handle.
//
// Subsequent calls to FD will return an invalid value.
func (h *Handle) Close() error {
if h == nil {
return nil
}
return h.fd.Close()
}
// FD returns the file descriptor for the handle.
func (h *Handle) FD() int {
return h.fd.Int()
}
// Info returns metadata about the handle.
func (h *Handle) Info() (*HandleInfo, error) {
return newHandleInfoFromFD(h.fd)
}
// HandleInfo describes a Handle.
type HandleInfo struct {
// ID of this handle in the kernel. The ID is only valid as long as the
// associated handle is kept alive.
ID ID
// Name is an identifying name for the BTF, currently only used by the
// kernel.
Name string
// IsKernel is true if the BTF originated with the kernel and not
// userspace.
IsKernel bool
// Size of the raw BTF in bytes.
size uint32
}
func newHandleInfoFromFD(fd *sys.FD) (*HandleInfo, error) {
// We invoke the syscall once with a empty BTF and name buffers to get size
// information to allocate buffers. Then we invoke it a second time with
// buffers to receive the data.
var btfInfo sys.BtfInfo
if err := sys.ObjInfo(fd, &btfInfo); err != nil {
return nil, fmt.Errorf("get BTF info for fd %s: %w", fd, err)
}
if btfInfo.NameLen > 0 {
// NameLen doesn't account for the terminating NUL.
btfInfo.NameLen++
}
// Don't pull raw BTF by default, since it may be quite large.
btfSize := btfInfo.BtfSize
btfInfo.BtfSize = 0
nameBuffer := make([]byte, btfInfo.NameLen)
btfInfo.Name, btfInfo.NameLen = sys.NewSlicePointerLen(nameBuffer)
if err := sys.ObjInfo(fd, &btfInfo); err != nil {
return nil, err
}
return &HandleInfo{
ID: ID(btfInfo.Id),
Name: unix.ByteSliceToString(nameBuffer),
IsKernel: btfInfo.KernelBtf != 0,
size: btfSize,
}, nil
}
// IsVmlinux returns true if the BTF is for the kernel itself.
func (i *HandleInfo) IsVmlinux() bool {
return i.IsKernel && i.Name == "vmlinux"
}
// IsModule returns true if the BTF is for a kernel module.
func (i *HandleInfo) IsModule() bool {
return i.IsKernel && i.Name != "vmlinux"
}
// HandleIterator allows enumerating BTF blobs loaded into the kernel.
type HandleIterator struct {
// The ID of the current handle. Only valid after a call to Next.
ID ID
// The current Handle. Only valid until a call to Next.
// See Take if you want to retain the handle.
Handle *Handle
err error
}
// Next retrieves a handle for the next BTF object.
//
// Returns true if another BTF object was found. Call [HandleIterator.Err] after
// the function returns false.
func (it *HandleIterator) Next() bool {
id := it.ID
for {
attr := &sys.BtfGetNextIdAttr{Id: id}
err := sys.BtfGetNextId(attr)
if errors.Is(err, os.ErrNotExist) {
// There are no more BTF objects.
break
} else if err != nil {
it.err = fmt.Errorf("get next BTF ID: %w", err)
break
}
id = attr.NextId
handle, err := NewHandleFromID(id)
if errors.Is(err, os.ErrNotExist) {
// Try again with the next ID.
continue
} else if err != nil {
it.err = fmt.Errorf("retrieve handle for ID %d: %w", id, err)
break
}
it.Handle.Close()
it.ID, it.Handle = id, handle
return true
}
// No more handles or we encountered an error.
it.Handle.Close()
it.Handle = nil
return false
}
// Take the ownership of the current handle.
//
// It's the callers responsibility to close the handle.
func (it *HandleIterator) Take() *Handle {
handle := it.Handle
it.Handle = nil
return handle
}
// Err returns an error if iteration failed for some reason.
func (it *HandleIterator) Err() error {
return it.err
}
// FindHandle returns the first handle for which predicate returns true.
//
// Requires CAP_SYS_ADMIN.
//
// Returns an error wrapping ErrNotFound if predicate never returns true or if
// there is no BTF loaded into the kernel.
func FindHandle(predicate func(info *HandleInfo) bool) (*Handle, error) {
it := new(HandleIterator)
defer it.Handle.Close()
for it.Next() {
info, err := it.Handle.Info()
if err != nil {
return nil, fmt.Errorf("info for ID %d: %w", it.ID, err)
}
if predicate(info) {
return it.Take(), nil
}
}
if err := it.Err(); err != nil {
return nil, fmt.Errorf("iterate handles: %w", err)
}
return nil, fmt.Errorf("find handle: %w", ErrNotFound)
}
@@ -0,0 +1,99 @@
package btf_test
import (
"fmt"
"testing"
"github.com/cilium/ebpf/btf"
"github.com/cilium/ebpf/internal/testutils"
)
func TestHandleIterator(t *testing.T) {
// There is no guarantee that there is a BTF ID allocated, but loading a module
// triggers loading vmlinux.
// See https://github.com/torvalds/linux/commit/5329722057d41aebc31e391907a501feaa42f7d9
testutils.SkipOnOldKernel(t, "5.11", "vmlinux BTF ID")
it := new(btf.HandleIterator)
defer it.Handle.Close()
if !it.Next() {
t.Fatalf("No BTF loaded")
}
if it.Handle == nil {
t.Fatal("Next doesn't assign handle")
}
prev := it.ID
for it.Next() {
// Iterate all loaded BTF.
if it.Handle == nil {
t.Fatal("Next doesn't assign handle")
}
if it.ID == prev {
t.Fatal("Iterator doesn't advance ID")
}
prev = it.ID
}
if err := it.Err(); err != nil {
t.Fatal("Iteration returned an error:", err)
}
if it.Handle != nil {
t.Fatal("Next doesn't clean up handle on last iteration")
}
if prev != it.ID {
t.Fatal("Next changes ID on last iteration")
}
}
func TestParseModuleSplitSpec(t *testing.T) {
// See TestNewHandleFromID for reasoning.
testutils.SkipOnOldKernel(t, "5.11", "vmlinux BTF ID")
module, err := btf.FindHandle(func(info *btf.HandleInfo) bool {
if info.IsModule() {
t.Log("Using module", info.Name)
return true
}
return false
})
if err != nil {
t.Fatal(err)
}
defer module.Close()
vmlinux, err := btf.FindHandle(func(info *btf.HandleInfo) bool {
return info.IsVmlinux()
})
if err != nil {
t.Fatal(err)
}
defer vmlinux.Close()
base, err := vmlinux.Spec(nil)
if err != nil {
t.Fatal(err)
}
_, err = module.Spec(base)
if err != nil {
t.Fatal("Parse module BTF:", err)
}
}
func ExampleHandleIterator() {
it := new(btf.HandleIterator)
defer it.Handle.Close()
for it.Next() {
info, err := it.Handle.Info()
if err != nil {
panic(err)
}
fmt.Printf("Found handle with ID %d and name %s\n", it.ID, info.Name)
}
if err := it.Err(); err != nil {
panic(err)
}
}
@@ -0,0 +1,543 @@
package btf
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"math"
"sync"
"github.com/cilium/ebpf/internal"
"golang.org/x/exp/slices"
)
type MarshalOptions struct {
// Target byte order. Defaults to the system's native endianness.
Order binary.ByteOrder
// Remove function linkage information for compatibility with <5.6 kernels.
StripFuncLinkage bool
}
// KernelMarshalOptions will generate BTF suitable for the current kernel.
func KernelMarshalOptions() *MarshalOptions {
return &MarshalOptions{
Order: internal.NativeEndian,
StripFuncLinkage: haveFuncLinkage() != nil,
}
}
// encoder turns Types into raw BTF.
type encoder struct {
MarshalOptions
pending internal.Deque[Type]
buf *bytes.Buffer
strings *stringTableBuilder
ids map[Type]TypeID
lastID TypeID
}
var bufferPool = sync.Pool{
New: func() any {
buf := make([]byte, btfHeaderLen+128)
return &buf
},
}
func getByteSlice() *[]byte {
return bufferPool.Get().(*[]byte)
}
func putByteSlice(buf *[]byte) {
*buf = (*buf)[:0]
bufferPool.Put(buf)
}
// Builder turns Types into raw BTF.
//
// The default value may be used and represents an empty BTF blob. Void is
// added implicitly if necessary.
type Builder struct {
// Explicitly added types.
types []Type
// IDs for all added types which the user knows about.
stableIDs map[Type]TypeID
// Explicitly added strings.
strings *stringTableBuilder
}
// NewBuilder creates a Builder from a list of types.
//
// It is more efficient than calling [Add] individually.
//
// Returns an error if adding any of the types fails.
func NewBuilder(types []Type) (*Builder, error) {
b := &Builder{
make([]Type, 0, len(types)),
make(map[Type]TypeID, len(types)),
nil,
}
for _, typ := range types {
_, err := b.Add(typ)
if err != nil {
return nil, fmt.Errorf("add %s: %w", typ, err)
}
}
return b, nil
}
// Add a Type and allocate a stable ID for it.
//
// Adding the identical Type multiple times is valid and will return the same ID.
//
// See [Type] for details on identity.
func (b *Builder) Add(typ Type) (TypeID, error) {
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 ds, ok := typ.(*Datasec); ok {
if err := datasecResolveWorkaround(b, ds); err != nil {
return 0, err
}
}
id, ok := b.stableIDs[typ]
if ok {
return id, nil
}
b.types = append(b.types, typ)
id = TypeID(len(b.types))
if int(id) != len(b.types) {
return 0, fmt.Errorf("no more type IDs")
}
b.stableIDs[typ] = id
return id, nil
}
// Marshal encodes all types in the Marshaler into BTF wire format.
//
// opts may be nil.
func (b *Builder) Marshal(buf []byte, opts *MarshalOptions) ([]byte, error) {
stb := b.strings
if stb == nil {
// Assume that most types are named. This makes encoding large BTF like
// vmlinux a lot cheaper.
stb = newStringTableBuilder(len(b.types))
} else {
// Avoid modifying the Builder's string table.
stb = b.strings.Copy()
}
if opts == nil {
opts = &MarshalOptions{Order: internal.NativeEndian}
}
// 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)),
ids: make(map[Type]TypeID, len(b.types)),
}
// Ensure that types are marshaled in the exact order they were Add()ed.
// Otherwise the ID returned from Add() won't match.
e.pending.Grow(len(b.types))
for _, typ := range b.types {
e.pending.Push(typ)
e.ids[typ] = b.stableIDs[typ]
}
if err := e.deflatePending(); err != nil {
return nil, err
}
length := e.buf.Len()
typeLen := uint32(length - btfHeaderLen)
stringLen := e.strings.Length()
buf = e.strings.AppendEncoded(e.buf.Bytes())
// Fill out the header, and write it out.
header := &btfHeader{
Magic: btfMagic,
Version: 1,
Flags: 0,
HdrLen: uint32(btfHeaderLen),
TypeOff: 0,
TypeLen: typeLen,
StringOff: typeLen,
StringLen: uint32(stringLen),
}
err := binary.Write(sliceWriter(buf[:btfHeaderLen]), e.Order, header)
if err != nil {
return nil, fmt.Errorf("write header: %v", err)
}
return buf, nil
}
// addString adds a string to the resulting BTF.
//
// Adding the same string multiple times will return the same result.
//
// Returns an identifier into the string table or an error if the string
// contains invalid characters.
func (b *Builder) addString(str string) (uint32, error) {
if b.strings == nil {
b.strings = newStringTableBuilder(0)
}
return b.strings.Add(str)
}
func (e *encoder) allocateID(typ Type) error {
id := e.lastID + 1
if id < e.lastID {
return errors.New("type ID overflow")
}
e.pending.Push(typ)
e.ids[typ] = id
e.lastID = id
return nil
}
// id returns the ID for the given type or panics with an error.
func (e *encoder) id(typ Type) TypeID {
if _, ok := typ.(*Void); ok {
return 0
}
id, ok := e.ids[typ]
if !ok {
panic(fmt.Errorf("no ID for type %v", typ))
}
return id
}
func (e *encoder) deflatePending() error {
// Declare root outside of the loop to avoid repeated heap allocations.
var root Type
skip := func(t Type) (skip bool) {
if t == root {
// Force descending into the current root type even if it already
// has an ID. Otherwise we miss children of types that have their
// ID pre-allocated via Add.
return false
}
_, isVoid := t.(*Void)
_, alreadyEncoded := e.ids[t]
return isVoid || alreadyEncoded
}
for !e.pending.Empty() {
root = e.pending.Shift()
// Allocate IDs for all children of typ, including transitive dependencies.
iter := postorderTraversal(root, skip)
for iter.Next() {
if iter.Type == root {
// The iterator yields root at the end, do not allocate another ID.
break
}
if err := e.allocateID(iter.Type); err != nil {
return err
}
}
if err := e.deflateType(root); err != nil {
id := e.ids[root]
return fmt.Errorf("deflate %v with ID %d: %w", root, id, err)
}
}
return nil
}
func (e *encoder) deflateType(typ Type) (err error) {
defer func() {
if r := recover(); r != nil {
var ok bool
err, ok = r.(error)
if !ok {
panic(r)
}
}
}()
var raw rawType
raw.NameOff, err = e.strings.Add(typ.TypeName())
if err != nil {
return err
}
switch v := typ.(type) {
case *Void:
return errors.New("Void is implicit in BTF wire format")
case *Int:
raw.SetKind(kindInt)
raw.SetSize(v.Size)
var bi btfInt
bi.SetEncoding(v.Encoding)
// We need to set bits in addition to size, since btf_type_int_is_regular
// otherwise flags this as a bitfield.
bi.SetBits(byte(v.Size) * 8)
raw.data = bi
case *Pointer:
raw.SetKind(kindPointer)
raw.SetType(e.id(v.Target))
case *Array:
raw.SetKind(kindArray)
raw.data = &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)
case *Union:
raw.SetKind(kindUnion)
raw.SetSize(v.Size)
raw.data, err = e.convertMembers(&raw.btfType, v.Members)
case *Enum:
raw.SetSize(v.size())
raw.SetVlen(len(v.Values))
raw.SetSigned(v.Signed)
if v.has64BitValues() {
raw.SetKind(kindEnum64)
raw.data, err = e.deflateEnum64Values(v.Values)
} else {
raw.SetKind(kindEnum)
raw.data, err = e.deflateEnumValues(v.Values)
}
case *Fwd:
raw.SetKind(kindForward)
raw.SetFwdKind(v.Kind)
case *Typedef:
raw.SetKind(kindTypedef)
raw.SetType(e.id(v.Type))
case *Volatile:
raw.SetKind(kindVolatile)
raw.SetType(e.id(v.Type))
case *Const:
raw.SetKind(kindConst)
raw.SetType(e.id(v.Type))
case *Restrict:
raw.SetKind(kindRestrict)
raw.SetType(e.id(v.Type))
case *Func:
raw.SetKind(kindFunc)
raw.SetType(e.id(v.Type))
if !e.StripFuncLinkage {
raw.SetLinkage(v.Linkage)
}
case *FuncProto:
raw.SetKind(kindFuncProto)
raw.SetType(e.id(v.Return))
raw.SetVlen(len(v.Params))
raw.data, err = e.deflateFuncParams(v.Params)
case *Var:
raw.SetKind(kindVar)
raw.SetType(e.id(v.Type))
raw.data = btfVariable{uint32(v.Linkage)}
case *Datasec:
raw.SetKind(kindDatasec)
raw.SetSize(v.Size)
raw.SetVlen(len(v.Vars))
raw.data = e.deflateVarSecinfos(v.Vars)
case *Float:
raw.SetKind(kindFloat)
raw.SetSize(v.Size)
case *declTag:
raw.SetKind(kindDeclTag)
raw.SetType(e.id(v.Type))
raw.data = &btfDeclTag{uint32(v.Index)}
raw.NameOff, err = e.strings.Add(v.Value)
case *typeTag:
raw.SetKind(kindTypeTag)
raw.SetType(e.id(v.Type))
raw.NameOff, err = e.strings.Add(v.Value)
default:
return fmt.Errorf("don't know how to deflate %T", v)
}
if err != nil {
return err
}
return raw.Marshal(e.buf, e.Order)
}
func (e *encoder) convertMembers(header *btfType, members []Member) ([]btfMember, error) {
bms := make([]btfMember, 0, len(members))
isBitfield := false
for _, member := range members {
isBitfield = isBitfield || member.BitfieldSize > 0
offset := member.Offset
if isBitfield {
offset = member.BitfieldSize<<24 | (member.Offset & 0xffffff)
}
nameOff, err := e.strings.Add(member.Name)
if err != nil {
return nil, err
}
bms = append(bms, btfMember{
nameOff,
e.id(member.Type),
uint32(offset),
})
}
header.SetVlen(len(members))
header.SetBitfield(isBitfield)
return bms, nil
}
func (e *encoder) deflateEnumValues(values []EnumValue) ([]btfEnum, error) {
bes := make([]btfEnum, 0, len(values))
for _, value := range values {
nameOff, err := e.strings.Add(value.Name)
if err != nil {
return nil, err
}
if value.Value > math.MaxUint32 {
return nil, fmt.Errorf("value of enum %q exceeds 32 bits", value.Name)
}
bes = append(bes, btfEnum{
nameOff,
uint32(value.Value),
})
}
return bes, nil
}
func (e *encoder) deflateEnum64Values(values []EnumValue) ([]btfEnum64, error) {
bes := make([]btfEnum64, 0, len(values))
for _, value := range values {
nameOff, err := e.strings.Add(value.Name)
if err != nil {
return nil, err
}
bes = append(bes, btfEnum64{
nameOff,
uint32(value.Value),
uint32(value.Value >> 32),
})
}
return bes, nil
}
func (e *encoder) deflateFuncParams(params []FuncParam) ([]btfParam, error) {
bps := make([]btfParam, 0, len(params))
for _, param := range params {
nameOff, err := e.strings.Add(param.Name)
if err != nil {
return nil, err
}
bps = append(bps, btfParam{
nameOff,
e.id(param.Type),
})
}
return bps, nil
}
func (e *encoder) deflateVarSecinfos(vars []VarSecinfo) []btfVarSecinfo {
vsis := make([]btfVarSecinfo, 0, len(vars))
for _, v := range vars {
vsis = append(vsis, btfVarSecinfo{
e.id(v.Type),
v.Offset,
v.Size,
})
}
return vsis
}
// MarshalMapKV creates a BTF object containing a map key and value.
//
// The function is intended for the use of the ebpf package and may be removed
// at any point in time.
func MarshalMapKV(key, value Type) (_ *Handle, keyID, valueID TypeID, err error) {
var b Builder
if key != nil {
keyID, err = b.Add(key)
if err != nil {
return nil, 0, 0, fmt.Errorf("add key type: %w", err)
}
}
if value != nil {
valueID, err = b.Add(value)
if err != nil {
return nil, 0, 0, fmt.Errorf("add value type: %w", err)
}
}
handle, err := NewHandle(&b)
if err != nil {
// Check for 'full' map BTF support, since kernels between 4.18 and 5.2
// already support BTF blobs for maps without Var or Datasec just fine.
if err := haveMapBTF(); err != nil {
return nil, 0, 0, err
}
}
return handle, keyID, valueID, err
}
@@ -0,0 +1,163 @@
package btf
import (
"bytes"
"encoding/binary"
"math"
"testing"
"github.com/cilium/ebpf/internal"
"github.com/cilium/ebpf/internal/testutils"
"github.com/google/go-cmp/cmp"
qt "github.com/frankban/quicktest"
)
func TestBuilderMarshal(t *testing.T) {
typ := &Int{
Name: "foo",
Size: 2,
Encoding: Signed | Char,
}
want := []Type{
(*Void)(nil),
typ,
&Pointer{typ},
&Typedef{"baz", typ},
}
b, err := NewBuilder(want)
qt.Assert(t, err, qt.IsNil)
cpy := *b
buf, err := b.Marshal(nil, &MarshalOptions{Order: internal.NativeEndian})
qt.Assert(t, err, qt.IsNil)
qt.Assert(t, b, qt.CmpEquals(cmp.AllowUnexported(*b)), &cpy, qt.Commentf("Marshaling should not change Builder state"))
have, err := loadRawSpec(bytes.NewReader(buf), internal.NativeEndian, nil)
qt.Assert(t, err, qt.IsNil, qt.Commentf("Couldn't parse BTF"))
qt.Assert(t, have.types, qt.DeepEquals, want)
}
func TestBuilderAdd(t *testing.T) {
i := &Int{
Name: "foo",
Size: 2,
Encoding: Signed | Char,
}
pi := &Pointer{i}
var b Builder
id, err := b.Add(pi)
qt.Assert(t, err, qt.IsNil)
qt.Assert(t, id, qt.Equals, TypeID(1), qt.Commentf("First non-void type doesn't get id 1"))
id, err = b.Add(pi)
qt.Assert(t, err, qt.IsNil)
qt.Assert(t, id, qt.Equals, TypeID(1))
id, err = b.Add(i)
qt.Assert(t, err, qt.IsNil)
qt.Assert(t, id, qt.Equals, TypeID(2), qt.Commentf("Second type doesn't get id 2"))
id, err = b.Add(i)
qt.Assert(t, err, qt.IsNil)
qt.Assert(t, id, qt.Equals, TypeID(2), qt.Commentf("Adding a type twice returns different ids"))
id, err = b.Add(&Typedef{"baz", i})
qt.Assert(t, err, qt.IsNil)
qt.Assert(t, id, qt.Equals, TypeID(3))
}
func TestRoundtripVMlinux(t *testing.T) {
types := vmlinuxSpec(t).types
// Randomize the order to force different permutations of walking the type
// graph. Keep Void at index 0.
testutils.Rand().Shuffle(len(types[1:]), func(i, j int) {
types[i+1], types[j+1] = types[j+1], types[i+1]
})
// Skip per CPU datasec, see https://github.com/cilium/ebpf/issues/921
for i, typ := range types {
if ds, ok := typ.(*Datasec); ok && ds.Name == ".data..percpu" {
types[i] = types[len(types)-1]
types = types[:len(types)-1]
break
}
}
seen := make(map[Type]bool)
limitTypes:
for i, typ := range types {
iter := postorderTraversal(typ, func(t Type) (skip bool) {
return seen[t]
})
for iter.Next() {
seen[iter.Type] = true
}
if len(seen) >= math.MaxInt16 {
// IDs exceeding math.MaxUint16 can trigger a bug when loading BTF.
// This can be removed once the patch lands.
// See https://lore.kernel.org/bpf/20220909092107.3035-1-oss@lmb.io/
types = types[:i]
break limitTypes
}
}
buf := marshalNativeEndian(t, types)
rebuilt, err := loadRawSpec(bytes.NewReader(buf), binary.LittleEndian, nil)
qt.Assert(t, err, qt.IsNil, qt.Commentf("round tripping BTF failed"))
if n := len(rebuilt.types); n > math.MaxUint16 {
t.Logf("Rebuilt BTF contains %d types which exceeds uint16, test may fail on older kernels", n)
}
h, err := NewHandleFromRawBTF(buf)
testutils.SkipIfNotSupported(t, err)
qt.Assert(t, err, qt.IsNil, qt.Commentf("loading rebuilt BTF failed"))
h.Close()
}
func BenchmarkMarshaler(b *testing.B) {
spec := vmlinuxTestdataSpec(b)
types := spec.types[:100]
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
var b Builder
for _, typ := range types {
_, _ = b.Add(typ)
}
_, _ = b.Marshal(nil, nil)
}
}
func BenchmarkBuildVmlinux(b *testing.B) {
types := vmlinuxTestdataSpec(b).types
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
var b Builder
for _, typ := range types {
_, _ = b.Add(typ)
}
_, _ = b.Marshal(nil, nil)
}
}
func marshalNativeEndian(tb testing.TB, types []Type) []byte {
tb.Helper()
b, err := NewBuilder(types)
qt.Assert(tb, err, qt.IsNil)
buf, err := b.Marshal(nil, nil)
qt.Assert(tb, err, qt.IsNil)
return buf
}
@@ -0,0 +1,214 @@
package btf
import (
"bufio"
"bytes"
"errors"
"fmt"
"io"
"strings"
"golang.org/x/exp/maps"
)
type stringTable struct {
base *stringTable
offsets []uint32
strings []string
}
// sizedReader is implemented by bytes.Reader, io.SectionReader, strings.Reader, etc.
type sizedReader interface {
io.Reader
Size() int64
}
func readStringTable(r sizedReader, 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
}
// Derived from vmlinux BTF.
const averageStringLength = 16
n := int(r.Size() / averageStringLength)
offsets := make([]uint32, 0, n)
strings := make([]string, 0, n)
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 len(strings) == 0 {
return nil, errors.New("string table is empty")
}
if firstStringOffset == 0 && strings[0] != "" {
return nil, errors.New("first item in string table is non-empty")
}
return &stringTable{base, offsets, strings}, nil
}
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")
}
return 0, nil, nil
}
return i + 1, data[:i], nil
}
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)
}
return st.lookup(offset)
}
func (st *stringTable) lookup(offset uint32) (string, error) {
i := search(st.offsets, offset)
if i == len(st.offsets) || st.offsets[i] != offset {
return "", fmt.Errorf("offset %d isn't start of a string", offset)
}
return st.strings[i], nil
}
func (st *stringTable) Marshal(w io.Writer) error {
for _, str := range st.strings {
_, err := io.WriteString(w, str)
if err != nil {
return err
}
_, err = w.Write([]byte{0})
if err != nil {
return err
}
}
return nil
}
// Num returns the number of strings in the table.
func (st *stringTable) Num() int {
return len(st.strings)
}
// search is a copy of sort.Search specialised for uint32.
//
// Licensed under https://go.dev/LICENSE
func search(ints []uint32, needle uint32) int {
// Define f(-1) == false and f(n) == true.
// Invariant: f(i-1) == false, f(j) == true.
i, j := 0, len(ints)
for i < j {
h := int(uint(i+j) >> 1) // avoid overflow when computing h
// i ≤ h < j
if !(ints[h] >= needle) {
i = h + 1 // preserves f(i-1) == false
} else {
j = h // preserves f(j) == true
}
}
// i == j, f(i-1) == false, and f(j) (= f(i)) == true => answer is i.
return i
}
// stringTableBuilder builds BTF string tables.
type stringTableBuilder struct {
length uint32
strings map[string]uint32
}
// newStringTableBuilder creates a builder with the given capacity.
//
// capacity may be zero.
func newStringTableBuilder(capacity int) *stringTableBuilder {
var stb stringTableBuilder
if capacity == 0 {
// Use the runtime's small default size.
stb.strings = make(map[string]uint32)
} else {
stb.strings = make(map[string]uint32, capacity)
}
// Ensure that the empty string is at index 0.
stb.append("")
return &stb
}
// Add a string to the table.
//
// Adding the same string multiple times will only store it once.
func (stb *stringTableBuilder) Add(str string) (uint32, error) {
if strings.IndexByte(str, 0) != -1 {
return 0, fmt.Errorf("string contains null: %q", str)
}
offset, ok := stb.strings[str]
if ok {
return offset, nil
}
return stb.append(str), nil
}
func (stb *stringTableBuilder) append(str string) uint32 {
offset := stb.length
stb.length += uint32(len(str)) + 1
stb.strings[str] = offset
return offset
}
// Lookup finds the offset of a string in the table.
//
// Returns an error if str hasn't been added yet.
func (stb *stringTableBuilder) Lookup(str string) (uint32, error) {
offset, ok := stb.strings[str]
if !ok {
return 0, fmt.Errorf("string %q is not in table", str)
}
return offset, nil
}
// Length returns the length in bytes.
func (stb *stringTableBuilder) Length() int {
return int(stb.length)
}
// AppendEncoded appends the string table to the end of the provided buffer.
func (stb *stringTableBuilder) AppendEncoded(buf []byte) []byte {
n := len(buf)
buf = append(buf, make([]byte, stb.Length())...)
strings := buf[n:]
for str, offset := range stb.strings {
copy(strings[offset:], str)
}
return buf
}
// Copy the string table builder.
func (stb *stringTableBuilder) Copy() *stringTableBuilder {
return &stringTableBuilder{
stb.length,
maps.Clone(stb.strings),
}
}
@@ -0,0 +1,110 @@
package btf
import (
"bytes"
"strings"
"testing"
qt "github.com/frankban/quicktest"
)
func TestStringTable(t *testing.T) {
const in = "\x00one\x00two\x00"
const splitIn = "three\x00four\x00"
st, err := readStringTable(strings.NewReader(in), nil)
if err != nil {
t.Fatal(err)
}
var buf bytes.Buffer
if err := st.Marshal(&buf); err != nil {
t.Fatal("Can't marshal string table:", err)
}
if !bytes.Equal([]byte(in), buf.Bytes()) {
t.Error("String table doesn't match input")
}
// Parse string table of split BTF
split, err := readStringTable(strings.NewReader(splitIn), st)
if err != nil {
t.Fatal(err)
}
testcases := []struct {
offset uint32
want string
}{
{0, ""},
{1, "one"},
{5, "two"},
{9, "three"},
{15, "four"},
}
for _, tc := range testcases {
have, err := split.Lookup(tc.offset)
if err != nil {
t.Errorf("Offset %d: %s", tc.offset, err)
continue
}
if have != tc.want {
t.Errorf("Offset %d: want %s but have %s", tc.offset, tc.want, have)
}
}
if _, err := st.Lookup(2); err == nil {
t.Error("No error when using offset pointing into middle of string")
}
// Make sure we reject bogus tables
_, err = readStringTable(strings.NewReader("\x00one"), nil)
if err == nil {
t.Fatal("Accepted non-terminated string")
}
_, err = readStringTable(strings.NewReader("one\x00"), nil)
if err == nil {
t.Fatal("Accepted non-empty first item")
}
}
func TestStringTableBuilder(t *testing.T) {
stb := newStringTableBuilder(0)
_, err := readStringTable(bytes.NewReader(stb.AppendEncoded(nil)), nil)
qt.Assert(t, err, qt.IsNil, qt.Commentf("Can't parse string table"))
_, err = stb.Add("foo\x00bar")
qt.Assert(t, err, qt.IsNotNil)
empty, err := stb.Add("")
qt.Assert(t, err, qt.IsNil)
qt.Assert(t, empty, qt.Equals, uint32(0), qt.Commentf("The empty string is not at index 0"))
foo1, _ := stb.Add("foo")
foo2, _ := stb.Add("foo")
qt.Assert(t, foo1, qt.Equals, foo2, qt.Commentf("Adding the same string returns different offsets"))
table := stb.AppendEncoded(nil)
if n := bytes.Count(table, []byte("foo")); n != 1 {
t.Fatalf("Marshalled string table contains foo %d times instead of once", n)
}
_, err = readStringTable(bytes.NewReader(table), nil)
qt.Assert(t, err, qt.IsNil, qt.Commentf("Can't parse string table"))
}
func newStringTable(strings ...string) *stringTable {
offsets := make([]uint32, len(strings))
var offset uint32
for i, str := range strings {
offsets[i] = offset
offset += uint32(len(str)) + 1 // account for NUL
}
return &stringTable{nil, offsets, strings}
}
@@ -0,0 +1,444 @@
/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */
#ifndef __BPF_CORE_READ_H__
#define __BPF_CORE_READ_H__
/*
* enum bpf_field_info_kind is passed as a second argument into
* __builtin_preserve_field_info() built-in to get a specific aspect of
* a field, captured as a first argument. __builtin_preserve_field_info(field,
* info_kind) returns __u32 integer and produces BTF field relocation, which
* is understood and processed by libbpf during BPF object loading. See
* selftests/bpf for examples.
*/
enum bpf_field_info_kind {
BPF_FIELD_BYTE_OFFSET = 0, /* field byte offset */
BPF_FIELD_BYTE_SIZE = 1,
BPF_FIELD_EXISTS = 2, /* field existence in target kernel */
BPF_FIELD_SIGNED = 3,
BPF_FIELD_LSHIFT_U64 = 4,
BPF_FIELD_RSHIFT_U64 = 5,
};
/* second argument to __builtin_btf_type_id() built-in */
enum bpf_type_id_kind {
BPF_TYPE_ID_LOCAL = 0, /* BTF type ID in local program */
BPF_TYPE_ID_TARGET = 1, /* BTF type ID in target kernel */
};
/* second argument to __builtin_preserve_type_info() built-in */
enum bpf_type_info_kind {
BPF_TYPE_EXISTS = 0, /* type existence in target kernel */
BPF_TYPE_SIZE = 1, /* type size in target kernel */
};
/* second argument to __builtin_preserve_enum_value() built-in */
enum bpf_enum_value_kind {
BPF_ENUMVAL_EXISTS = 0, /* enum value existence in kernel */
BPF_ENUMVAL_VALUE = 1, /* enum value value relocation */
};
#define __CORE_RELO(src, field, info) \
__builtin_preserve_field_info((src)->field, BPF_FIELD_##info)
#if __BYTE_ORDER == __LITTLE_ENDIAN
#define __CORE_BITFIELD_PROBE_READ(dst, src, fld) \
bpf_probe_read_kernel( \
(void *)dst, \
__CORE_RELO(src, fld, BYTE_SIZE), \
(const void *)src + __CORE_RELO(src, fld, BYTE_OFFSET))
#else
/* semantics of LSHIFT_64 assumes loading values into low-ordered bytes, so
* for big-endian we need to adjust destination pointer accordingly, based on
* field byte size
*/
#define __CORE_BITFIELD_PROBE_READ(dst, src, fld) \
bpf_probe_read_kernel( \
(void *)dst + (8 - __CORE_RELO(src, fld, BYTE_SIZE)), \
__CORE_RELO(src, fld, BYTE_SIZE), \
(const void *)src + __CORE_RELO(src, fld, BYTE_OFFSET))
#endif
/*
* Extract bitfield, identified by s->field, and return its value as u64.
* All this is done in relocatable manner, so bitfield changes such as
* signedness, bit size, offset changes, this will be handled automatically.
* This version of macro is using bpf_probe_read_kernel() to read underlying
* integer storage. Macro functions as an expression and its return type is
* bpf_probe_read_kernel()'s return value: 0, on success, <0 on error.
*/
#define BPF_CORE_READ_BITFIELD_PROBED(s, field) ({ \
unsigned long long val = 0; \
\
__CORE_BITFIELD_PROBE_READ(&val, s, field); \
val <<= __CORE_RELO(s, field, LSHIFT_U64); \
if (__CORE_RELO(s, field, SIGNED)) \
val = ((long long)val) >> __CORE_RELO(s, field, RSHIFT_U64); \
else \
val = val >> __CORE_RELO(s, field, RSHIFT_U64); \
val; \
})
/*
* Extract bitfield, identified by s->field, and return its value as u64.
* This version of macro is using direct memory reads and should be used from
* BPF program types that support such functionality (e.g., typed raw
* tracepoints).
*/
#define BPF_CORE_READ_BITFIELD(s, field) ({ \
const void *p = (const void *)s + __CORE_RELO(s, field, BYTE_OFFSET); \
unsigned long long val; \
\
/* This is a so-called barrier_var() operation that makes specified \
* variable "a black box" for optimizing compiler. \
* It forces compiler to perform BYTE_OFFSET relocation on p and use \
* its calculated value in the switch below, instead of applying \
* the same relocation 4 times for each individual memory load. \
*/ \
asm volatile("" : "=r"(p) : "0"(p)); \
\
switch (__CORE_RELO(s, field, BYTE_SIZE)) { \
case 1: val = *(const unsigned char *)p; break; \
case 2: val = *(const unsigned short *)p; break; \
case 4: val = *(const unsigned int *)p; break; \
case 8: val = *(const unsigned long long *)p; break; \
} \
val <<= __CORE_RELO(s, field, LSHIFT_U64); \
if (__CORE_RELO(s, field, SIGNED)) \
val = ((long long)val) >> __CORE_RELO(s, field, RSHIFT_U64); \
else \
val = val >> __CORE_RELO(s, field, RSHIFT_U64); \
val; \
})
/*
* Convenience macro to check that field actually exists in target kernel's.
* Returns:
* 1, if matching field is present in target kernel;
* 0, if no matching field found.
*/
#define bpf_core_field_exists(field) \
__builtin_preserve_field_info(field, BPF_FIELD_EXISTS)
/*
* Convenience macro to get the byte size of a field. Works for integers,
* struct/unions, pointers, arrays, and enums.
*/
#define bpf_core_field_size(field) \
__builtin_preserve_field_info(field, BPF_FIELD_BYTE_SIZE)
/*
* Convenience macro to get BTF type ID of a specified type, using a local BTF
* information. Return 32-bit unsigned integer with type ID from program's own
* BTF. Always succeeds.
*/
#define bpf_core_type_id_local(type) \
__builtin_btf_type_id(*(typeof(type) *)0, BPF_TYPE_ID_LOCAL)
/*
* Convenience macro to get BTF type ID of a target kernel's type that matches
* specified local type.
* Returns:
* - valid 32-bit unsigned type ID in kernel BTF;
* - 0, if no matching type was found in a target kernel BTF.
*/
#define bpf_core_type_id_kernel(type) \
__builtin_btf_type_id(*(typeof(type) *)0, BPF_TYPE_ID_TARGET)
/*
* Convenience macro to check that provided named type
* (struct/union/enum/typedef) exists in a target kernel.
* Returns:
* 1, if such type is present in target kernel's BTF;
* 0, if no matching type is found.
*/
#define bpf_core_type_exists(type) \
__builtin_preserve_type_info(*(typeof(type) *)0, BPF_TYPE_EXISTS)
/*
* Convenience macro to get the byte size of a provided named type
* (struct/union/enum/typedef) in a target kernel.
* Returns:
* >= 0 size (in bytes), if type is present in target kernel's BTF;
* 0, if no matching type is found.
*/
#define bpf_core_type_size(type) \
__builtin_preserve_type_info(*(typeof(type) *)0, BPF_TYPE_SIZE)
/*
* Convenience macro to check that provided enumerator value is defined in
* a target kernel.
* Returns:
* 1, if specified enum type and its enumerator value are present in target
* kernel's BTF;
* 0, if no matching enum and/or enum value within that enum is found.
*/
#define bpf_core_enum_value_exists(enum_type, enum_value) \
__builtin_preserve_enum_value(*(typeof(enum_type) *)enum_value, BPF_ENUMVAL_EXISTS)
/*
* Convenience macro to get the integer value of an enumerator value in
* a target kernel.
* Returns:
* 64-bit value, if specified enum type and its enumerator value are
* present in target kernel's BTF;
* 0, if no matching enum and/or enum value within that enum is found.
*/
#define bpf_core_enum_value(enum_type, enum_value) \
__builtin_preserve_enum_value(*(typeof(enum_type) *)enum_value, BPF_ENUMVAL_VALUE)
/*
* bpf_core_read() abstracts away bpf_probe_read_kernel() call and captures
* offset relocation for source address using __builtin_preserve_access_index()
* built-in, provided by Clang.
*
* __builtin_preserve_access_index() takes as an argument an expression of
* taking an address of a field within struct/union. It makes compiler emit
* a relocation, which records BTF type ID describing root struct/union and an
* accessor string which describes exact embedded field that was used to take
* an address. See detailed description of this relocation format and
* semantics in comments to struct bpf_field_reloc in libbpf_internal.h.
*
* This relocation allows libbpf to adjust BPF instruction to use correct
* actual field offset, based on target kernel BTF type that matches original
* (local) BTF, used to record relocation.
*/
#define bpf_core_read(dst, sz, src) \
bpf_probe_read_kernel(dst, sz, (const void *)__builtin_preserve_access_index(src))
/* NOTE: see comments for BPF_CORE_READ_USER() about the proper types use. */
#define bpf_core_read_user(dst, sz, src) \
bpf_probe_read_user(dst, sz, (const void *)__builtin_preserve_access_index(src))
/*
* bpf_core_read_str() is a thin wrapper around bpf_probe_read_str()
* additionally emitting BPF CO-RE field relocation for specified source
* argument.
*/
#define bpf_core_read_str(dst, sz, src) \
bpf_probe_read_kernel_str(dst, sz, (const void *)__builtin_preserve_access_index(src))
/* NOTE: see comments for BPF_CORE_READ_USER() about the proper types use. */
#define bpf_core_read_user_str(dst, sz, src) \
bpf_probe_read_user_str(dst, sz, (const void *)__builtin_preserve_access_index(src))
#define ___concat(a, b) a ## b
#define ___apply(fn, n) ___concat(fn, n)
#define ___nth(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, __11, N, ...) N
/*
* return number of provided arguments; used for switch-based variadic macro
* definitions (see ___last, ___arrow, etc below)
*/
#define ___narg(...) ___nth(_, ##__VA_ARGS__, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
/*
* return 0 if no arguments are passed, N - otherwise; used for
* recursively-defined macros to specify termination (0) case, and generic
* (N) case (e.g., ___read_ptrs, ___core_read)
*/
#define ___empty(...) ___nth(_, ##__VA_ARGS__, N, N, N, N, N, N, N, N, N, N, 0)
#define ___last1(x) x
#define ___last2(a, x) x
#define ___last3(a, b, x) x
#define ___last4(a, b, c, x) x
#define ___last5(a, b, c, d, x) x
#define ___last6(a, b, c, d, e, x) x
#define ___last7(a, b, c, d, e, f, x) x
#define ___last8(a, b, c, d, e, f, g, x) x
#define ___last9(a, b, c, d, e, f, g, h, x) x
#define ___last10(a, b, c, d, e, f, g, h, i, x) x
#define ___last(...) ___apply(___last, ___narg(__VA_ARGS__))(__VA_ARGS__)
#define ___nolast2(a, _) a
#define ___nolast3(a, b, _) a, b
#define ___nolast4(a, b, c, _) a, b, c
#define ___nolast5(a, b, c, d, _) a, b, c, d
#define ___nolast6(a, b, c, d, e, _) a, b, c, d, e
#define ___nolast7(a, b, c, d, e, f, _) a, b, c, d, e, f
#define ___nolast8(a, b, c, d, e, f, g, _) a, b, c, d, e, f, g
#define ___nolast9(a, b, c, d, e, f, g, h, _) a, b, c, d, e, f, g, h
#define ___nolast10(a, b, c, d, e, f, g, h, i, _) a, b, c, d, e, f, g, h, i
#define ___nolast(...) ___apply(___nolast, ___narg(__VA_ARGS__))(__VA_ARGS__)
#define ___arrow1(a) a
#define ___arrow2(a, b) a->b
#define ___arrow3(a, b, c) a->b->c
#define ___arrow4(a, b, c, d) a->b->c->d
#define ___arrow5(a, b, c, d, e) a->b->c->d->e
#define ___arrow6(a, b, c, d, e, f) a->b->c->d->e->f
#define ___arrow7(a, b, c, d, e, f, g) a->b->c->d->e->f->g
#define ___arrow8(a, b, c, d, e, f, g, h) a->b->c->d->e->f->g->h
#define ___arrow9(a, b, c, d, e, f, g, h, i) a->b->c->d->e->f->g->h->i
#define ___arrow10(a, b, c, d, e, f, g, h, i, j) a->b->c->d->e->f->g->h->i->j
#define ___arrow(...) ___apply(___arrow, ___narg(__VA_ARGS__))(__VA_ARGS__)
#define ___type(...) typeof(___arrow(__VA_ARGS__))
#define ___read(read_fn, dst, src_type, src, accessor) \
read_fn((void *)(dst), sizeof(*(dst)), &((src_type)(src))->accessor)
/* "recursively" read a sequence of inner pointers using local __t var */
#define ___rd_first(fn, src, a) ___read(fn, &__t, ___type(src), src, a);
#define ___rd_last(fn, ...) \
___read(fn, &__t, ___type(___nolast(__VA_ARGS__)), __t, ___last(__VA_ARGS__));
#define ___rd_p1(fn, ...) const void *__t; ___rd_first(fn, __VA_ARGS__)
#define ___rd_p2(fn, ...) ___rd_p1(fn, ___nolast(__VA_ARGS__)) ___rd_last(fn, __VA_ARGS__)
#define ___rd_p3(fn, ...) ___rd_p2(fn, ___nolast(__VA_ARGS__)) ___rd_last(fn, __VA_ARGS__)
#define ___rd_p4(fn, ...) ___rd_p3(fn, ___nolast(__VA_ARGS__)) ___rd_last(fn, __VA_ARGS__)
#define ___rd_p5(fn, ...) ___rd_p4(fn, ___nolast(__VA_ARGS__)) ___rd_last(fn, __VA_ARGS__)
#define ___rd_p6(fn, ...) ___rd_p5(fn, ___nolast(__VA_ARGS__)) ___rd_last(fn, __VA_ARGS__)
#define ___rd_p7(fn, ...) ___rd_p6(fn, ___nolast(__VA_ARGS__)) ___rd_last(fn, __VA_ARGS__)
#define ___rd_p8(fn, ...) ___rd_p7(fn, ___nolast(__VA_ARGS__)) ___rd_last(fn, __VA_ARGS__)
#define ___rd_p9(fn, ...) ___rd_p8(fn, ___nolast(__VA_ARGS__)) ___rd_last(fn, __VA_ARGS__)
#define ___read_ptrs(fn, src, ...) \
___apply(___rd_p, ___narg(__VA_ARGS__))(fn, src, __VA_ARGS__)
#define ___core_read0(fn, fn_ptr, dst, src, a) \
___read(fn, dst, ___type(src), src, a);
#define ___core_readN(fn, fn_ptr, dst, src, ...) \
___read_ptrs(fn_ptr, src, ___nolast(__VA_ARGS__)) \
___read(fn, dst, ___type(src, ___nolast(__VA_ARGS__)), __t, \
___last(__VA_ARGS__));
#define ___core_read(fn, fn_ptr, dst, src, a, ...) \
___apply(___core_read, ___empty(__VA_ARGS__))(fn, fn_ptr, dst, \
src, a, ##__VA_ARGS__)
/*
* BPF_CORE_READ_INTO() is a more performance-conscious variant of
* BPF_CORE_READ(), in which final field is read into user-provided storage.
* See BPF_CORE_READ() below for more details on general usage.
*/
#define BPF_CORE_READ_INTO(dst, src, a, ...) ({ \
___core_read(bpf_core_read, bpf_core_read, \
dst, (src), a, ##__VA_ARGS__) \
})
/*
* Variant of BPF_CORE_READ_INTO() for reading from user-space memory.
*
* NOTE: see comments for BPF_CORE_READ_USER() about the proper types use.
*/
#define BPF_CORE_READ_USER_INTO(dst, src, a, ...) ({ \
___core_read(bpf_core_read_user, bpf_core_read_user, \
dst, (src), a, ##__VA_ARGS__) \
})
/* Non-CO-RE variant of BPF_CORE_READ_INTO() */
#define BPF_PROBE_READ_INTO(dst, src, a, ...) ({ \
___core_read(bpf_probe_read, bpf_probe_read, \
dst, (src), a, ##__VA_ARGS__) \
})
/* Non-CO-RE variant of BPF_CORE_READ_USER_INTO().
*
* As no CO-RE relocations are emitted, source types can be arbitrary and are
* not restricted to kernel types only.
*/
#define BPF_PROBE_READ_USER_INTO(dst, src, a, ...) ({ \
___core_read(bpf_probe_read_user, bpf_probe_read_user, \
dst, (src), a, ##__VA_ARGS__) \
})
/*
* BPF_CORE_READ_STR_INTO() does same "pointer chasing" as
* BPF_CORE_READ() for intermediate pointers, but then executes (and returns
* corresponding error code) bpf_core_read_str() for final string read.
*/
#define BPF_CORE_READ_STR_INTO(dst, src, a, ...) ({ \
___core_read(bpf_core_read_str, bpf_core_read, \
dst, (src), a, ##__VA_ARGS__) \
})
/*
* Variant of BPF_CORE_READ_STR_INTO() for reading from user-space memory.
*
* NOTE: see comments for BPF_CORE_READ_USER() about the proper types use.
*/
#define BPF_CORE_READ_USER_STR_INTO(dst, src, a, ...) ({ \
___core_read(bpf_core_read_user_str, bpf_core_read_user, \
dst, (src), a, ##__VA_ARGS__) \
})
/* Non-CO-RE variant of BPF_CORE_READ_STR_INTO() */
#define BPF_PROBE_READ_STR_INTO(dst, src, a, ...) ({ \
___core_read(bpf_probe_read_str, bpf_probe_read, \
dst, (src), a, ##__VA_ARGS__) \
})
/*
* Non-CO-RE variant of BPF_CORE_READ_USER_STR_INTO().
*
* As no CO-RE relocations are emitted, source types can be arbitrary and are
* not restricted to kernel types only.
*/
#define BPF_PROBE_READ_USER_STR_INTO(dst, src, a, ...) ({ \
___core_read(bpf_probe_read_user_str, bpf_probe_read_user, \
dst, (src), a, ##__VA_ARGS__) \
})
/*
* BPF_CORE_READ() is used to simplify BPF CO-RE relocatable read, especially
* when there are few pointer chasing steps.
* E.g., what in non-BPF world (or in BPF w/ BCC) would be something like:
* int x = s->a.b.c->d.e->f->g;
* can be succinctly achieved using BPF_CORE_READ as:
* int x = BPF_CORE_READ(s, a.b.c, d.e, f, g);
*
* BPF_CORE_READ will decompose above statement into 4 bpf_core_read (BPF
* CO-RE relocatable bpf_probe_read_kernel() wrapper) calls, logically
* equivalent to:
* 1. const void *__t = s->a.b.c;
* 2. __t = __t->d.e;
* 3. __t = __t->f;
* 4. return __t->g;
*
* Equivalence is logical, because there is a heavy type casting/preservation
* involved, as well as all the reads are happening through
* bpf_probe_read_kernel() calls using __builtin_preserve_access_index() to
* emit CO-RE relocations.
*
* N.B. Only up to 9 "field accessors" are supported, which should be more
* than enough for any practical purpose.
*/
#define BPF_CORE_READ(src, a, ...) ({ \
___type((src), a, ##__VA_ARGS__) __r; \
BPF_CORE_READ_INTO(&__r, (src), a, ##__VA_ARGS__); \
__r; \
})
/*
* Variant of BPF_CORE_READ() for reading from user-space memory.
*
* NOTE: all the source types involved are still *kernel types* and need to
* exist in kernel (or kernel module) BTF, otherwise CO-RE relocation will
* fail. Custom user types are not relocatable with CO-RE.
* The typical situation in which BPF_CORE_READ_USER() might be used is to
* read kernel UAPI types from the user-space memory passed in as a syscall
* input argument.
*/
#define BPF_CORE_READ_USER(src, a, ...) ({ \
___type((src), a, ##__VA_ARGS__) __r; \
BPF_CORE_READ_USER_INTO(&__r, (src), a, ##__VA_ARGS__); \
__r; \
})
/* Non-CO-RE variant of BPF_CORE_READ() */
#define BPF_PROBE_READ(src, a, ...) ({ \
___type((src), a, ##__VA_ARGS__) __r; \
BPF_PROBE_READ_INTO(&__r, (src), a, ##__VA_ARGS__); \
__r; \
})
/*
* Non-CO-RE variant of BPF_CORE_READ_USER().
*
* As no CO-RE relocations are emitted, source types can be arbitrary and are
* not restricted to kernel types only.
*/
#define BPF_PROBE_READ_USER(src, a, ...) ({ \
___type((src), a, ##__VA_ARGS__) __r; \
BPF_PROBE_READ_USER_INTO(&__r, (src), a, ##__VA_ARGS__); \
__r; \
})
#endif
@@ -0,0 +1,3 @@
go test fuzz v1
[]byte("\x9f\xeb\x01\x00\x1c\x00\x00\x00\x00\x00\x00\x00000\x10\x00\x00\x00\x000000\xf3\xff\xff\xff0\x00\x00\x00")
[]byte("0")
@@ -0,0 +1,3 @@
go test fuzz v1
[]byte("\x9f\xeb\x01\x00\x18\x00\x00\x00\x00\x00\x00\x000000000000000\x00\x00\x000000")
[]byte("0")
@@ -0,0 +1,3 @@
go test fuzz v1
[]byte("\x9f\xeb\x01\x00\x1c\x00\x00\x00\x00\x00\x00\x000000\xe8\xff\xff\xff000000000\x00\x00\x00")
[]byte("0")
@@ -0,0 +1,268 @@
#include "../../testdata/common.h"
#include "bpf_core_read.h"
enum e {
// clang-12 doesn't allow enum relocations with zero value.
// See https://reviews.llvm.org/D97659
ONE = 1,
TWO,
};
typedef enum e e_t;
struct s {
int _1;
char _2;
unsigned int _3;
};
typedef struct s s_t;
union u {
int *_1;
char *_2;
unsigned int *_3;
};
typedef union u u_t;
#define local_id_zero(expr) \
({ \
if (bpf_core_type_id_local(expr) != 0) { \
return __LINE__; \
} \
})
#define local_id_not_zero(expr) \
({ \
if (bpf_core_type_id_local(expr) == 0) { \
return __LINE__; \
} \
})
#define target_and_local_id_match(expr) \
({ \
if (bpf_core_type_id_kernel(expr) != bpf_core_type_id_local(expr)) { \
return __LINE__; \
} \
})
__section("socket_filter/type_ids") int type_ids() {
local_id_not_zero(int);
local_id_not_zero(struct { int frob; });
local_id_not_zero(enum {FRAP});
local_id_not_zero(union { char bar; });
local_id_not_zero(struct s);
local_id_not_zero(s_t);
local_id_not_zero(const s_t);
local_id_not_zero(volatile s_t);
local_id_not_zero(enum e);
local_id_not_zero(e_t);
local_id_not_zero(const e_t);
local_id_not_zero(volatile e_t);
local_id_not_zero(union u);
local_id_not_zero(u_t);
local_id_not_zero(const u_t);
local_id_not_zero(volatile u_t);
// Qualifiers on types crash clang.
target_and_local_id_match(struct s);
target_and_local_id_match(s_t);
// target_and_local_id_match(const s_t);
// target_and_local_id_match(volatile s_t);
target_and_local_id_match(enum e);
target_and_local_id_match(e_t);
// target_and_local_id_match(const e_t);
// target_and_local_id_match(volatile e_t);
target_and_local_id_match(union u);
target_and_local_id_match(u_t);
// target_and_local_id_match(const u_t);
// target_and_local_id_match(volatile u_t);
return 0;
}
#define type_exists(expr) \
({ \
if (!bpf_core_type_exists(expr)) { \
return __LINE__; \
} \
})
#define type_size_matches(expr) \
({ \
if (bpf_core_type_size(expr) != sizeof(expr)) { \
return __LINE__; \
} \
})
__section("socket_filter/types") int types() {
type_exists(struct s);
type_exists(s_t);
type_exists(const s_t);
type_exists(volatile s_t);
type_exists(enum e);
type_exists(e_t);
type_exists(const e_t);
type_exists(volatile e_t);
type_exists(union u);
type_exists(u_t);
type_exists(const u_t);
type_exists(volatile u_t);
// TODO: Check non-existence.
type_size_matches(struct s);
type_size_matches(s_t);
type_size_matches(const s_t);
type_size_matches(volatile s_t);
type_size_matches(enum e);
type_size_matches(e_t);
type_size_matches(const e_t);
type_size_matches(volatile e_t);
type_size_matches(union u);
type_size_matches(u_t);
type_size_matches(const u_t);
type_size_matches(volatile u_t);
return 0;
}
#define enum_value_exists(t, v) \
({ \
if (!bpf_core_enum_value_exists(t, v)) { \
return __LINE__; \
} \
})
#define enum_value_matches(t, v) \
({ \
if (v != bpf_core_enum_value(t, v)) { \
return __LINE__; \
} \
})
__section("socket_filter/enums") int enums() {
enum_value_exists(enum e, ONE);
enum_value_exists(volatile enum e, ONE);
enum_value_exists(const enum e, ONE);
enum_value_exists(e_t, TWO);
// TODO: Check non-existence.
enum_value_matches(enum e, TWO);
enum_value_matches(e_t, ONE);
enum_value_matches(volatile e_t, ONE);
enum_value_matches(const e_t, ONE);
return 0;
}
#define field_exists(f) \
({ \
if (!bpf_core_field_exists(f)) { \
return __LINE__; \
} \
})
#define field_size_matches(f) \
({ \
if (sizeof(f) != bpf_core_field_size(f)) { \
return __LINE__; \
} \
})
#define field_offset_matches(t, f) \
({ \
if (__builtin_offsetof(t, f) != __builtin_preserve_field_info(((typeof(t) *)0)->f, BPF_FIELD_BYTE_OFFSET)) { \
return __LINE__; \
} \
})
#define field_is_signed(f) \
({ \
if (!__builtin_preserve_field_info(f, BPF_FIELD_SIGNED)) { \
return __LINE__; \
} \
})
#define field_is_unsigned(f) \
({ \
if (__builtin_preserve_field_info(f, BPF_FIELD_SIGNED)) { \
return __LINE__; \
} \
})
__section("socket_filter/fields") int fields() {
field_exists((struct s){}._1);
field_exists((s_t){}._2);
field_exists((union u){}._1);
field_exists((u_t){}._2);
field_is_signed((struct s){}._1);
field_is_unsigned((struct s){}._3);
// unions crash clang-14.
// field_is_signed((union u){}._1);
// field_is_unsigned((union u){}._3);
field_size_matches((struct s){}._1);
field_size_matches((s_t){}._2);
field_size_matches((union u){}._1);
field_size_matches((u_t){}._2);
field_offset_matches(struct s, _1);
field_offset_matches(s_t, _2);
field_offset_matches(union u, _1);
field_offset_matches(u_t, _2);
struct t {
union {
s_t s[10];
};
struct {
union u u;
};
} bar, *barp = &bar;
field_exists(bar.s[2]._1);
field_exists(bar.s[1]._2);
field_exists(bar.u._1);
field_exists(bar.u._2);
field_exists(barp[1].u._2);
field_is_signed(bar.s[2]._1);
field_is_unsigned(bar.s[2]._3);
// unions crash clang-14.
// field_is_signed(bar.u._1);
// field_is_signed(bar.u._3);
field_size_matches(bar.s[2]._1);
field_size_matches(bar.s[1]._2);
field_size_matches(bar.u._1);
field_size_matches(bar.u._2);
field_size_matches(barp[1].u._2);
field_offset_matches(struct t, s[2]._1);
field_offset_matches(struct t, s[1]._2);
field_offset_matches(struct t, u._1);
field_offset_matches(struct t, u._2);
return 0;
}
struct ambiguous {
int _1;
char _2;
};
struct ambiguous___flavour {
char _1;
int _2;
};
__section("socket_filter/err_ambiguous") int err_ambiguous() {
return bpf_core_type_id_kernel(struct ambiguous);
}
__section("socket_filter/err_ambiguous_flavour") int err_ambiguous_flavour() {
return bpf_core_type_id_kernel(struct ambiguous___flavour);
}
@@ -0,0 +1,116 @@
#include "../../testdata/common.h"
#include "bpf_core_read.h"
#define core_access __builtin_preserve_access_index
// Struct with the members declared in the wrong order. Accesses need
// a successful CO-RE relocation against the type in relocs_read_tgt.c
// for the test below to pass.
struct s {
char b;
char a;
};
typedef unsigned char u8;
typedef unsigned short u16;
typedef unsigned int u32;
typedef unsigned long u64;
// Struct with bitfields.
struct bits {
int x;
u8 a : 4, b : 2;
u16 c : 1;
unsigned int d : 2;
enum { ZERO = 0, ONE = 1 } e : 1;
u64 f : 16, g : 30;
};
struct nonexist {
int non_exist;
};
enum nonexist_enum { NON_EXIST = 1 };
// Perform a read from a subprog to ensure CO-RE relocations
// occurring there are tracked and executed in the final linked program.
__attribute__((noinline)) int read_subprog() {
struct s foo = {
.a = 0,
.b = 1,
};
if (core_access(foo.a) == 0)
return __LINE__;
if (core_access(foo.b) == 1)
return __LINE__;
struct bits bar;
char *p = (char *)&bar;
/* Target:
* [4] STRUCT 'bits' size=8 vlen=7
* 'b' type_id=5 bits_offset=0 bitfield_size=2
* 'a' type_id=5 bits_offset=2 bitfield_size=4
* 'd' type_id=7 bits_offset=6 bitfield_size=2
* 'c' type_id=9 bits_offset=8 bitfield_size=1
* 'e' type_id=11 bits_offset=9 bitfield_size=1
* 'f' type_id=9 bits_offset=16
* 'g' type_id=12 bits_offset=32 bitfield_size=30
*/
*p++ = 0xff; // a, b, d
*p++ = 0x00; // c, e
*p++ = 0x56; // f
*p++ = 0x56; // f
#ifdef __BIG_ENDIAN__
*p++ = 0x55; // g
*p++ = 0x44; // g
*p++ = 0x33; // g
*p++ = 0x22; // g
#else
*p++ = 0x22; // g
*p++ = 0x33; // g
*p++ = 0x44; // g
*p++ = 0x55; // g
#endif
if (BPF_CORE_READ_BITFIELD(&bar, a) != (1 << 4) - 1)
return __LINE__;
if (BPF_CORE_READ_BITFIELD(&bar, b) != (1 << 2) - 1)
return __LINE__;
if (BPF_CORE_READ_BITFIELD(&bar, d) != (1 << 2) - 1)
return __LINE__;
if (BPF_CORE_READ_BITFIELD(&bar, c) != 0)
return __LINE__;
if (BPF_CORE_READ_BITFIELD(&bar, e) != 0)
return __LINE__;
if (BPF_CORE_READ_BITFIELD(&bar, f) != 0x5656)
return __LINE__;
if (BPF_CORE_READ_BITFIELD(&bar, g) != 0x15443322)
return __LINE__;
if (bpf_core_type_exists(struct nonexist) != 0)
return __LINE__;
if (bpf_core_field_exists(((struct nonexist *)0)->non_exist) != 0)
return __LINE__;
if (bpf_core_enum_value_exists(enum nonexist_enum, NON_EXIST) != 0)
return __LINE__;
return 0;
}
__section("socket") int reads() {
int ret = read_subprog();
if (ret)
return ret;
return 0;
}
@@ -0,0 +1,30 @@
/*
This file exists to emit ELFs with specific BTF types to use as target BTF
in tests. It can be made redundant when btf.Spec can be handcrafted and
passed as a CO-RE target in the future.
*/
struct s {
char a;
char b;
};
struct s *unused_s __attribute__((unused));
typedef unsigned int my_u32;
typedef unsigned char u8;
typedef unsigned short u16;
typedef unsigned int u32;
typedef unsigned long u64;
struct bits {
/*int x;*/
u8 b : 2, a : 4; /* a was before b */
my_u32 d : 2; /* was 'unsigned int' */
u16 c : 1; /* was before d */
enum { ZERO = 0, ONE = 1 } e : 1;
u16 f; /* was: u64 f:16 */
u32 g : 30; /* was: u64 g:30 */
};
struct bits *unused_bits __attribute__((unused));
@@ -0,0 +1,141 @@
package btf
import (
"fmt"
"github.com/cilium/ebpf/internal"
)
// 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
type postorderIterator struct {
// Iteration skips types for which this function returns true.
skip func(Type) bool
// The root type. May be nil if skip(root) is true.
root Type
// Contains types which need to be either walked or yielded.
types typeDeque
// Contains a boolean whether the type has been walked or not.
walked internal.Deque[bool]
// The set of types which has been pushed onto types.
pushed map[Type]struct{}
// The current type. Only valid after a call to Next().
Type Type
}
// postorderTraversal iterates all types reachable from root by visiting the
// leaves of the graph first.
//
// Types for which skip returns true are ignored. skip may be nil.
func postorderTraversal(root Type, skip func(Type) (skip bool)) postorderIterator {
// Avoid allocations for the common case of a skipped root.
if skip != nil && skip(root) {
return postorderIterator{}
}
po := postorderIterator{root: root, skip: skip}
walkType(root, po.push)
return po
}
func (po *postorderIterator) push(t *Type) {
if _, ok := po.pushed[*t]; ok || *t == po.root {
return
}
if po.skip != nil && po.skip(*t) {
return
}
if po.pushed == nil {
// Lazily allocate pushed to avoid an allocation for Types without children.
po.pushed = make(map[Type]struct{})
}
po.pushed[*t] = struct{}{}
po.types.Push(t)
po.walked.Push(false)
}
// Next returns true if there is another Type to traverse.
func (po *postorderIterator) Next() bool {
for !po.types.Empty() {
t := po.types.Pop()
if !po.walked.Pop() {
// Push the type again, so that we re-evaluate it in done state
// after all children have been handled.
po.types.Push(t)
po.walked.Push(true)
// Add all direct children to todo.
walkType(*t, po.push)
} else {
// We've walked this type previously, so we now know that all
// children have been handled.
po.Type = *t
return true
}
}
// Only return root once.
po.Type, po.root = po.root, nil
return po.Type != nil
}
// walkType calls fn on each child of typ.
func walkType(typ Type, fn func(*Type)) {
// 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.
switch v := typ.(type) {
case *Void, *Int, *Enum, *Fwd, *Float:
// No children to traverse.
case *Pointer:
fn(&v.Target)
case *Array:
fn(&v.Index)
fn(&v.Type)
case *Struct:
for i := range v.Members {
fn(&v.Members[i].Type)
}
case *Union:
for i := range v.Members {
fn(&v.Members[i].Type)
}
case *Typedef:
fn(&v.Type)
case *Volatile:
fn(&v.Type)
case *Const:
fn(&v.Type)
case *Restrict:
fn(&v.Type)
case *Func:
fn(&v.Type)
case *FuncProto:
fn(&v.Return)
for i := range v.Params {
fn(&v.Params[i].Type)
}
case *Var:
fn(&v.Type)
case *Datasec:
for i := range v.Vars {
fn(&v.Vars[i].Type)
}
case *declTag:
fn(&v.Type)
case *typeTag:
fn(&v.Type)
case *cycle:
// cycle has children, but we ignore them deliberately.
default:
panic(fmt.Sprintf("don't know how to walk Type %T", v))
}
}
@@ -0,0 +1,97 @@
package btf
import (
"fmt"
"testing"
qt "github.com/frankban/quicktest"
)
func TestPostorderTraversal(t *testing.T) {
ptr := newCyclicalType(2).(*Pointer)
cst := ptr.Target.(*Const)
str := cst.Type.(*Struct)
t.Logf("%3v", ptr)
pending := []Type{str, cst, ptr}
iter := postorderTraversal(ptr, nil)
for iter.Next() {
qt.Assert(t, iter.Type, qt.Equals, pending[0])
pending = pending[1:]
}
qt.Assert(t, pending, qt.HasLen, 0)
i := &Int{Name: "foo"}
// i appears twice at the same nesting depth.
arr := &Array{Index: i, Type: i}
seen := make(map[Type]bool)
iter = postorderTraversal(arr, nil)
for iter.Next() {
qt.Assert(t, seen[iter.Type], qt.IsFalse)
seen[iter.Type] = true
}
qt.Assert(t, seen[arr], qt.IsTrue)
qt.Assert(t, seen[i], qt.IsTrue)
}
func TestPostorderTraversalVmlinux(t *testing.T) {
spec := vmlinuxTestdataSpec(t)
typ, err := spec.AnyTypeByName("gov_update_cpu_data")
if err != nil {
t.Fatal(err)
}
for _, typ := range []Type{typ} {
t.Run(fmt.Sprintf("%s", typ), func(t *testing.T) {
seen := make(map[Type]bool)
var last Type
iter := postorderTraversal(typ, nil)
for iter.Next() {
if seen[iter.Type] {
t.Fatalf("%s visited twice", iter.Type)
}
seen[iter.Type] = true
last = iter.Type
}
if last != typ {
t.Fatalf("Expected %s got %s as last type", typ, last)
}
walkType(typ, func(child *Type) {
qt.Check(t, seen[*child], qt.IsTrue, qt.Commentf("missing child %s", *child))
})
})
}
}
func BenchmarkPostorderTraversal(b *testing.B) {
spec := vmlinuxTestdataSpec(b)
var fn *Func
err := spec.TypeByName("gov_update_cpu_data", &fn)
if err != nil {
b.Fatal(err)
}
for _, test := range []struct {
name string
typ Type
}{
{"single type", &Int{}},
{"cycle(1)", newCyclicalType(1)},
{"cycle(10)", newCyclicalType(10)},
{"gov_update_cpu_data", fn},
} {
b.Logf("%10v", test.typ)
b.Run(test.name, func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
iter := postorderTraversal(test.typ, nil)
for iter.Next() {
}
}
})
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,512 @@
package btf
import (
"bytes"
"fmt"
"reflect"
"testing"
"github.com/cilium/ebpf/internal"
qt "github.com/frankban/quicktest"
"github.com/google/go-cmp/cmp"
)
func TestSizeof(t *testing.T) {
testcases := []struct {
size int
typ Type
}{
{0, (*Void)(nil)},
{1, &Int{Size: 1}},
{8, &Enum{Size: 8}},
{0, &Array{Type: &Pointer{Target: (*Void)(nil)}, Nelems: 0}},
{12, &Array{Type: &Enum{Size: 4}, Nelems: 3}},
}
for _, tc := range testcases {
name := fmt.Sprint(tc.typ)
t.Run(name, func(t *testing.T) {
have, err := Sizeof(tc.typ)
if err != nil {
t.Fatal("Can't calculate size:", err)
}
if have != tc.size {
t.Errorf("Expected size %d, got %d", tc.size, have)
}
})
}
}
func TestPow(t *testing.T) {
tests := []struct {
n int
r bool
}{
{0, false},
{1, true},
{2, true},
{3, false},
{4, true},
{5, false},
{8, true},
}
for _, tt := range tests {
t.Run(fmt.Sprintf("%d", tt.n), func(t *testing.T) {
if want, got := tt.r, pow(tt.n); want != got {
t.Errorf("unexpected result for n %d; want: %v, got: %v", tt.n, want, got)
}
})
}
}
func TestCopy(t *testing.T) {
_ = Copy((*Void)(nil), nil)
in := &Int{Size: 4}
out := Copy(in, nil)
in.Size = 8
if size := out.(*Int).Size; size != 4 {
t.Error("Copy doesn't make a copy, expected size 4, got", size)
}
t.Run("cyclical", func(t *testing.T) {
_ = Copy(newCyclicalType(2), nil)
})
t.Run("identity", func(t *testing.T) {
u16 := &Int{Size: 2}
out := Copy(&Struct{
Members: []Member{
{Name: "a", Type: u16},
{Name: "b", Type: u16},
},
}, nil)
outStruct := out.(*Struct)
qt.Assert(t, outStruct.Members[0].Type, qt.Equals, outStruct.Members[1].Type)
})
}
func TestAs(t *testing.T) {
i := &Int{}
ptr := &Pointer{i}
td := &Typedef{Type: ptr}
cst := &Const{td}
vol := &Volatile{cst}
// It's possible to retrieve qualifiers and Typedefs.
haveVol, ok := as[*Volatile](vol)
qt.Assert(t, ok, qt.IsTrue)
qt.Assert(t, haveVol, qt.Equals, vol)
haveTd, ok := as[*Typedef](vol)
qt.Assert(t, ok, qt.IsTrue)
qt.Assert(t, haveTd, qt.Equals, td)
haveCst, ok := as[*Const](vol)
qt.Assert(t, ok, qt.IsTrue)
qt.Assert(t, haveCst, qt.Equals, cst)
// Make sure we don't skip Pointer.
haveI, ok := as[*Int](vol)
qt.Assert(t, ok, qt.IsFalse)
qt.Assert(t, haveI, qt.IsNil)
// Make sure we can always retrieve Pointer.
for _, typ := range []Type{
td, cst, vol, ptr,
} {
have, ok := as[*Pointer](typ)
qt.Assert(t, ok, qt.IsTrue)
qt.Assert(t, have, qt.Equals, ptr)
}
}
func BenchmarkCopy(b *testing.B) {
typ := newCyclicalType(10)
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
Copy(typ, nil)
}
}
// The following are valid Types.
//
// There currently is no better way to document which
// types implement an interface.
func ExampleType_validTypes() {
var _ Type = &Void{}
var _ Type = &Int{}
var _ Type = &Pointer{}
var _ Type = &Array{}
var _ Type = &Struct{}
var _ Type = &Union{}
var _ Type = &Enum{}
var _ Type = &Fwd{}
var _ Type = &Typedef{}
var _ Type = &Volatile{}
var _ Type = &Const{}
var _ Type = &Restrict{}
var _ Type = &Func{}
var _ Type = &FuncProto{}
var _ Type = &Var{}
var _ Type = &Datasec{}
var _ Type = &Float{}
}
func TestType(t *testing.T) {
types := []func() Type{
func() Type { return &Void{} },
func() Type { return &Int{Size: 2} },
func() Type { return &Pointer{Target: &Void{}} },
func() Type { return &Array{Type: &Int{}} },
func() Type {
return &Struct{
Members: []Member{{Type: &Void{}}},
}
},
func() Type {
return &Union{
Members: []Member{{Type: &Void{}}},
}
},
func() Type { return &Enum{} },
func() Type { return &Fwd{Name: "thunk"} },
func() Type { return &Typedef{Type: &Void{}} },
func() Type { return &Volatile{Type: &Void{}} },
func() Type { return &Const{Type: &Void{}} },
func() Type { return &Restrict{Type: &Void{}} },
func() Type { return &Func{Name: "foo", Type: &Void{}} },
func() Type {
return &FuncProto{
Params: []FuncParam{{Name: "bar", Type: &Void{}}},
Return: &Void{},
}
},
func() Type { return &Var{Type: &Void{}} },
func() Type {
return &Datasec{
Vars: []VarSecinfo{{Type: &Void{}}},
}
},
func() Type { return &Float{} },
func() Type { return &declTag{Type: &Void{}} },
func() Type { return &typeTag{Type: &Void{}} },
func() Type { return &cycle{&Void{}} },
}
compareTypes := cmp.Comparer(func(a, b *Type) bool {
return a == b
})
for _, fn := range types {
typ := fn()
t.Run(fmt.Sprintf("%T", typ), func(t *testing.T) {
t.Logf("%v", typ)
if typ == typ.copy() {
t.Error("Copy doesn't copy")
}
var a []*Type
walkType(typ, func(t *Type) { a = append(a, t) })
if _, ok := typ.(*cycle); !ok {
if n := countChildren(t, reflect.TypeOf(typ)); len(a) < n {
t.Errorf("walkType visited %d children, expected at least %d", len(a), n)
}
}
var b []*Type
walkType(typ, func(t *Type) { b = append(b, t) })
if diff := cmp.Diff(a, b, compareTypes); diff != "" {
t.Errorf("Walk mismatch (-want +got):\n%s", diff)
}
})
}
}
func TestTagMarshaling(t *testing.T) {
for _, typ := range []Type{
&declTag{&Struct{Members: []Member{}}, "foo", -1},
&typeTag{&Int{}, "foo"},
} {
t.Run(fmt.Sprint(typ), func(t *testing.T) {
buf := marshalNativeEndian(t, []Type{typ})
s, err := loadRawSpec(bytes.NewReader(buf), internal.NativeEndian, nil)
qt.Assert(t, err, qt.IsNil)
have, err := s.TypeByID(1)
qt.Assert(t, err, qt.IsNil)
qt.Assert(t, have, qt.DeepEquals, typ)
})
}
}
func countChildren(t *testing.T, typ reflect.Type) int {
if typ.Kind() != reflect.Pointer {
t.Fatal("Expected pointer, got", typ.Kind())
}
typ = typ.Elem()
if typ.Kind() != reflect.Struct {
t.Fatal("Expected struct, got", typ.Kind())
}
var n int
for i := 0; i < typ.NumField(); i++ {
if typ.Field(i).Type == reflect.TypeOf((*Type)(nil)).Elem() {
n++
}
}
return n
}
type testFormattableType struct {
name string
extra []interface{}
}
var _ formattableType = (*testFormattableType)(nil)
func (tft *testFormattableType) TypeName() string { return tft.name }
func (tft *testFormattableType) Format(fs fmt.State, verb rune) {
formatType(fs, verb, tft, tft.extra...)
}
func TestFormatType(t *testing.T) {
t1 := &testFormattableType{"", []interface{}{"extra"}}
t1Addr := fmt.Sprintf("%#p", t1)
goType := reflect.TypeOf(t1).Elem().Name()
t2 := &testFormattableType{"foo", []interface{}{t1}}
t3 := &testFormattableType{extra: []interface{}{""}}
tests := []struct {
t formattableType
fmt string
contains []string
omits []string
}{
// %s doesn't contain address or extra.
{t1, "%s", []string{goType}, []string{t1Addr, "extra"}},
// %+s doesn't contain extra.
{t1, "%+s", []string{goType, t1Addr}, []string{"extra"}},
// %v does contain extra.
{t1, "%v", []string{goType, "extra"}, []string{t1Addr}},
// %+v does contain address.
{t1, "%+v", []string{goType, "extra", t1Addr}, nil},
// %v doesn't print nested types' extra.
{t2, "%v", []string{goType, t2.name}, []string{"extra"}},
// %1v does print nested types' extra.
{t2, "%1v", []string{goType, t2.name, "extra"}, nil},
// empty strings in extra don't emit anything.
{t3, "%v", []string{"[]"}, nil},
}
for _, test := range tests {
t.Run(test.fmt, func(t *testing.T) {
str := fmt.Sprintf(test.fmt, test.t)
t.Log(str)
for _, want := range test.contains {
qt.Assert(t, str, qt.Contains, want)
}
for _, notWant := range test.omits {
qt.Assert(t, str, qt.Not(qt.Contains), notWant)
}
})
}
}
func newCyclicalType(n int) Type {
ptr := &Pointer{}
prev := Type(ptr)
for i := 0; i < n; i++ {
switch i % 5 {
case 0:
prev = &Struct{
Members: []Member{
{Type: prev},
},
}
case 1:
prev = &Const{Type: prev}
case 2:
prev = &Volatile{Type: prev}
case 3:
prev = &Typedef{Type: prev}
case 4:
prev = &Array{Type: prev, Index: &Int{Size: 1}}
}
}
ptr.Target = prev
return ptr
}
func TestUnderlyingType(t *testing.T) {
wrappers := []struct {
name string
fn func(Type) Type
}{
{"const", func(t Type) Type { return &Const{Type: t} }},
{"volatile", func(t Type) Type { return &Volatile{Type: t} }},
{"restrict", func(t Type) Type { return &Restrict{Type: t} }},
{"typedef", func(t Type) Type { return &Typedef{Type: t} }},
{"type tag", func(t Type) Type { return &typeTag{Type: t} }},
}
for _, test := range wrappers {
t.Run(test.name+" cycle", func(t *testing.T) {
root := &Volatile{}
root.Type = test.fn(root)
got, ok := UnderlyingType(root).(*cycle)
qt.Assert(t, ok, qt.IsTrue)
qt.Assert(t, got.root, qt.Equals, root)
})
}
for _, test := range wrappers {
t.Run(test.name, func(t *testing.T) {
want := &Int{}
got := UnderlyingType(test.fn(want))
qt.Assert(t, got, qt.Equals, want)
})
}
}
func TestInflateLegacyBitfield(t *testing.T) {
const offset = 3
const size = 5
var rawInt rawType
rawInt.SetKind(kindInt)
rawInt.SetSize(4)
var data btfInt
data.SetOffset(offset)
data.SetBits(size)
rawInt.data = &data
var beforeInt rawType
beforeInt.SetKind(kindStruct)
beforeInt.SetVlen(1)
beforeInt.data = []btfMember{{Type: 2}}
afterInt := beforeInt
afterInt.data = []btfMember{{Type: 1}}
emptyStrings := newStringTable("")
for _, test := range []struct {
name string
raw []rawType
}{
{"struct before int", []rawType{beforeInt, rawInt}},
{"struct after int", []rawType{rawInt, afterInt}},
} {
t.Run(test.name, func(t *testing.T) {
types, err := inflateRawTypes(test.raw, emptyStrings, nil)
if err != nil {
t.Fatal(err)
}
for _, typ := range types {
s, ok := typ.(*Struct)
if !ok {
continue
}
i := s.Members[0]
if i.BitfieldSize != size {
t.Errorf("Expected bitfield size %d, got %d", size, i.BitfieldSize)
}
if i.Offset != offset {
t.Errorf("Expected offset %d, got %d", offset, i.Offset)
}
return
}
t.Fatal("No Struct returned from inflateRawTypes")
})
}
}
func BenchmarkWalk(b *testing.B) {
types := []Type{
&Void{},
&Int{},
&Pointer{},
&Array{},
&Struct{Members: make([]Member, 2)},
&Union{Members: make([]Member, 2)},
&Enum{},
&Fwd{},
&Typedef{},
&Volatile{},
&Const{},
&Restrict{},
&Func{},
&FuncProto{Params: make([]FuncParam, 2)},
&Var{},
&Datasec{Vars: make([]VarSecinfo, 2)},
}
for _, typ := range types {
b.Run(fmt.Sprint(typ), func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
var dq typeDeque
walkType(typ, dq.Push)
}
})
}
}
func BenchmarkUnderlyingType(b *testing.B) {
b.Run("no unwrapping", func(b *testing.B) {
v := &Int{}
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
UnderlyingType(v)
}
})
b.Run("single unwrapping", func(b *testing.B) {
v := &Typedef{Type: &Int{}}
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
UnderlyingType(v)
}
})
}
// Copy can be used with UnderlyingType to strip qualifiers from a type graph.
func ExampleCopy_stripQualifiers() {
a := &Volatile{Type: &Pointer{Target: &Typedef{Name: "foo", Type: &Int{Size: 2}}}}
b := Copy(a, UnderlyingType)
// b has Volatile and Typedef removed.
fmt.Printf("%3v\n", b)
// Output: Pointer[target=Int[unsigned size=16]]
}
@@ -0,0 +1,26 @@
package btf
// datasecResolveWorkaround ensures that certain vars in a Datasec are added
// to a Spec before the Datasec. This avoids a bug in kernel BTF validation.
//
// See https://lore.kernel.org/bpf/20230302123440.1193507-1-lmb@isovalent.com/
func datasecResolveWorkaround(b *Builder, ds *Datasec) error {
for _, vsi := range ds.Vars {
v, ok := vsi.Type.(*Var)
if !ok {
continue
}
switch v.Type.(type) {
case *Typedef, *Volatile, *Const, *Restrict, *typeTag:
// NB: We must never call Add on a Datasec, otherwise we risk
// infinite recursion.
_, err := b.Add(v.Type)
if err != nil {
return err
}
}
}
return nil
}
@@ -0,0 +1,66 @@
package btf
import (
"errors"
"fmt"
"testing"
"github.com/cilium/ebpf/internal"
"github.com/cilium/ebpf/internal/testutils"
)
func TestDatasecResolveWorkaround(t *testing.T) {
testutils.SkipOnOldKernel(t, "5.2", "BTF_KIND_DATASEC")
i := &Int{Size: 1}
for _, typ := range []Type{
&Typedef{"foo", i},
&Volatile{i},
&Const{i},
&Restrict{i},
&typeTag{i, "foo"},
} {
t.Run(fmt.Sprint(typ), func(t *testing.T) {
if _, ok := typ.(*typeTag); ok {
testutils.SkipOnOldKernel(t, "5.17", "BTF_KIND_TYPE_TAG")
}
ds := &Datasec{
Name: "a",
Size: 2,
Vars: []VarSecinfo{
{
Size: 1,
Offset: 0,
// struct, union, pointer, array will trigger the bug.
Type: &Var{Name: "a", Type: &Pointer{i}},
},
{
Size: 1,
Offset: 1,
Type: &Var{
Name: "b",
Type: typ,
},
},
},
}
b, err := NewBuilder([]Type{ds})
if err != nil {
t.Fatal(err)
}
h, err := NewHandle(b)
var ve *internal.VerifierError
if errors.As(err, &ve) {
t.Fatalf("%+v\n", ve)
}
if err != nil {
t.Fatal(err)
}
h.Close()
})
}
}
@@ -0,0 +1,34 @@
bpf2go
===
`bpf2go` compiles a C source file into eBPF bytecode and then emits a
Go file containing the eBPF. The goal is to avoid loading the
eBPF from disk at runtime and to minimise the amount of manual
work required to interact with eBPF programs. It takes inspiration
from `bpftool gen skeleton`.
Invoke the program using go generate:
//go:generate go run github.com/cilium/ebpf/cmd/bpf2go foo path/to/src.c -- -I/path/to/include
This will emit `foo_bpfel.go` and `foo_bpfeb.go`, with types using `foo`
as a stem. The two files contain compiled BPF for little and big
endian systems, respectively.
You can use environment variables to affect all bpf2go invocations
across a project, e.g. to set specific C flags:
//go:generate go run github.com/cilium/ebpf/cmd/bpf2go -cflags "$BPF_CFLAGS" foo path/to/src.c
By exporting `$BPF_CFLAGS` from your build system you can then control
all builds from a single location.
## Generated types
`bpf2go` generates Go types for all map keys and values by default. You can
disable this behaviour using `-no-global-types`. You can add to the set of
types by specifying `-type foo` for each type you'd like to generate.
## Examples
See [examples/kprobe](../../examples/kprobe/main.go) for a fully worked out example.
@@ -0,0 +1,210 @@
package main
import (
"bufio"
"bytes"
"errors"
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"strings"
)
type compileArgs struct {
// Which compiler to use
cc string
cFlags []string
// Absolute working directory
dir string
// Absolute input file name
source string
// Absolute output file name
dest string
// Target to compile for, defaults to "bpf".
target string
// Depfile will be written here if depName is not empty
dep io.Writer
}
func compile(args compileArgs) error {
// Default cflags that can be overridden by args.cFlags
overrideFlags := []string{
// Code needs to be optimized, otherwise the verifier will often fail
// to understand it.
"-O2",
// Clang defaults to mcpu=probe which checks the kernel that we are
// compiling on. This isn't appropriate for ahead of time
// compiled code so force the most compatible version.
"-mcpu=v1",
}
cmd := exec.Command(args.cc, append(overrideFlags, args.cFlags...)...)
cmd.Stderr = os.Stderr
inputDir := filepath.Dir(args.source)
relInputDir, err := filepath.Rel(args.dir, inputDir)
if err != nil {
return err
}
target := args.target
if target == "" {
target = "bpf"
}
// C flags that can't be overridden.
cmd.Args = append(cmd.Args,
"-target", target,
"-c", args.source,
"-o", args.dest,
// Don't include clang version
"-fno-ident",
// Don't output inputDir into debug info
"-fdebug-prefix-map="+inputDir+"="+relInputDir,
"-fdebug-compilation-dir", ".",
// We always want BTF to be generated, so enforce debug symbols
"-g",
fmt.Sprintf("-D__BPF_TARGET_MISSING=%q", "GCC error \"The eBPF is using target specific macros, please provide -target that is not bpf, bpfel or bpfeb\""),
)
cmd.Dir = args.dir
var depFile *os.File
if args.dep != nil {
depFile, err = os.CreateTemp("", "bpf2go")
if err != nil {
return err
}
defer depFile.Close()
defer os.Remove(depFile.Name())
cmd.Args = append(cmd.Args,
// Output dependency information.
"-MD",
// Create phony targets so that deleting a dependency doesn't
// break the build.
"-MP",
// Write it to temporary file
"-MF"+depFile.Name(),
)
}
if err := cmd.Run(); err != nil {
return fmt.Errorf("can't execute %s: %s", args.cc, err)
}
if depFile != nil {
if _, err := io.Copy(args.dep, depFile); err != nil {
return fmt.Errorf("error writing depfile: %w", err)
}
}
return nil
}
func adjustDependencies(baseDir string, deps []dependency) ([]byte, error) {
var buf bytes.Buffer
for _, dep := range deps {
relativeFile, err := filepath.Rel(baseDir, dep.file)
if err != nil {
return nil, err
}
if len(dep.prerequisites) == 0 {
_, err := fmt.Fprintf(&buf, "%s:\n\n", relativeFile)
if err != nil {
return nil, err
}
continue
}
var prereqs []string
for _, prereq := range dep.prerequisites {
relativePrereq, err := filepath.Rel(baseDir, prereq)
if err != nil {
return nil, err
}
prereqs = append(prereqs, relativePrereq)
}
_, err = fmt.Fprintf(&buf, "%s: \\\n %s\n\n", relativeFile, strings.Join(prereqs, " \\\n "))
if err != nil {
return nil, err
}
}
return buf.Bytes(), nil
}
type dependency struct {
file string
prerequisites []string
}
func parseDependencies(baseDir string, in io.Reader) ([]dependency, error) {
abs := func(path string) string {
if filepath.IsAbs(path) {
return path
}
return filepath.Join(baseDir, path)
}
scanner := bufio.NewScanner(in)
var line strings.Builder
var deps []dependency
for scanner.Scan() {
buf := scanner.Bytes()
if line.Len()+len(buf) > 1024*1024 {
return nil, errors.New("line too long")
}
if bytes.HasSuffix(buf, []byte{'\\'}) {
line.Write(buf[:len(buf)-1])
continue
}
line.Write(buf)
if line.Len() == 0 {
// Skip empty lines
continue
}
parts := strings.SplitN(line.String(), ":", 2)
if len(parts) < 2 {
return nil, fmt.Errorf("invalid line without ':'")
}
// NB: This doesn't handle filenames with spaces in them.
// It seems like make doesn't do that either, so oh well.
var prereqs []string
for _, prereq := range strings.Fields(parts[1]) {
prereqs = append(prereqs, abs(prereq))
}
deps = append(deps, dependency{
abs(string(parts[0])),
prereqs,
})
line.Reset()
}
if err := scanner.Err(); err != nil {
return nil, err
}
// There is always at least a dependency for the main file.
if len(deps) == 0 {
return nil, fmt.Errorf("empty dependency file")
}
return deps, nil
}
// strip DWARF debug info from file by executing exe.
func strip(exe, file string) error {
cmd := exec.Command(exe, "-g", file)
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return fmt.Errorf("%s: %s", exe, err)
}
return nil
}
@@ -0,0 +1,166 @@
package main
import (
"bytes"
"os"
"path/filepath"
"reflect"
"strings"
"testing"
)
const minimalSocketFilter = `__attribute__((section("socket"), used)) int main() { return 0; }`
func TestCompile(t *testing.T) {
dir := mustWriteTempFile(t, "test.c", minimalSocketFilter)
var dep bytes.Buffer
err := compile(compileArgs{
cc: clangBin(t),
dir: dir,
source: filepath.Join(dir, "test.c"),
dest: filepath.Join(dir, "test.o"),
dep: &dep,
})
if err != nil {
t.Fatal("Can't compile:", err)
}
stat, err := os.Stat(filepath.Join(dir, "test.o"))
if err != nil {
t.Fatal("Can't stat output:", err)
}
if stat.Size() == 0 {
t.Error("Compilation creates an empty file")
}
if dep.Len() == 0 {
t.Error("Compilation doesn't generate depinfo")
}
if _, err := parseDependencies(dir, &dep); err != nil {
t.Error("Can't parse dependencies:", err)
}
}
func TestReproducibleCompile(t *testing.T) {
clangBin := clangBin(t)
dir := mustWriteTempFile(t, "test.c", minimalSocketFilter)
err := compile(compileArgs{
cc: clangBin,
dir: dir,
source: filepath.Join(dir, "test.c"),
dest: filepath.Join(dir, "a.o"),
})
if err != nil {
t.Fatal("Can't compile:", err)
}
err = compile(compileArgs{
cc: clangBin,
dir: dir,
source: filepath.Join(dir, "test.c"),
dest: filepath.Join(dir, "b.o"),
})
if err != nil {
t.Fatal("Can't compile:", err)
}
aBytes, err := os.ReadFile(filepath.Join(dir, "a.o"))
if err != nil {
t.Fatal(err)
}
bBytes, err := os.ReadFile(filepath.Join(dir, "b.o"))
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(aBytes, bBytes) {
t.Error("Compiling the same file twice doesn't give the same result")
}
}
func TestTriggerMissingTarget(t *testing.T) {
dir := mustWriteTempFile(t, "test.c", `_Pragma(__BPF_TARGET_MISSING);`)
err := compile(compileArgs{
cc: clangBin(t),
dir: dir,
source: filepath.Join(dir, "test.c"),
dest: filepath.Join(dir, "a.o"),
})
if err == nil {
t.Fatal("No error when compiling __BPF_TARGET_MISSING")
}
}
func TestParseDependencies(t *testing.T) {
const input = `main.go: /foo/bar baz
frob: /gobble \
gubble
nothing:
`
have, err := parseDependencies("/foo", strings.NewReader(input))
if err != nil {
t.Fatal("Can't parse dependencies:", err)
}
want := []dependency{
{"/foo/main.go", []string{"/foo/bar", "/foo/baz"}},
{"/foo/frob", []string{"/gobble", "/foo/gubble"}},
{"/foo/nothing", nil},
}
if !reflect.DeepEqual(have, want) {
t.Logf("Have: %#v", have)
t.Logf("Want: %#v", want)
t.Error("Result doesn't match")
}
output, err := adjustDependencies("/foo", want)
if err != nil {
t.Error("Can't adjust dependencies")
}
const wantOutput = `main.go: \
bar \
baz
frob: \
../gobble \
gubble
nothing:
`
if have := string(output); have != wantOutput {
t.Logf("Have:\n%s", have)
t.Logf("Want:\n%s", wantOutput)
t.Error("Output doesn't match")
}
}
func mustWriteTempFile(t *testing.T, name, contents string) string {
t.Helper()
tmp, err := os.MkdirTemp("", "bpf2go")
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { os.RemoveAll(tmp) })
tmpFile := filepath.Join(tmp, name)
if err := os.WriteFile(tmpFile, []byte(contents), 0660); err != nil {
t.Fatal(err)
}
return tmp
}
@@ -0,0 +1,4 @@
// Program bpf2go embeds eBPF in Go.
//
// Please see the README for details how to use it.
package main
@@ -0,0 +1,57 @@
package main
import (
"flag"
"go/build/constraint"
)
// buildTags is a comma-separated list of build tags.
//
// This follows the pre-Go 1.17 syntax and is kept for compatibility reasons.
type buildTags struct {
Expr constraint.Expr
}
var _ flag.Value = (*buildTags)(nil)
func (bt *buildTags) String() string {
if bt.Expr == nil {
return ""
}
return (bt.Expr).String()
}
func (bt *buildTags) Set(value string) error {
ct, err := constraint.Parse("// +build " + value)
if err != nil {
return err
}
bt.Expr = ct
return nil
}
func andConstraints(x, y constraint.Expr) constraint.Expr {
if x == nil {
return y
}
if y == nil {
return x
}
return &constraint.AndExpr{X: x, Y: y}
}
func orConstraints(x, y constraint.Expr) constraint.Expr {
if x == nil {
return y
}
if y == nil {
return x
}
return &constraint.OrExpr{X: x, Y: y}
}
@@ -0,0 +1,498 @@
package main
import (
"bytes"
"errors"
"flag"
"fmt"
"go/build/constraint"
"go/token"
"io"
"os"
"os/exec"
"path/filepath"
"regexp"
"runtime"
"sort"
"strings"
"github.com/cilium/ebpf"
)
const helpText = `Usage: %[1]s [options] <ident> <source file> [-- <C flags>]
ident is used as the stem of all generated Go types and functions, and
must be a valid Go identifier.
source is a single C file that is compiled using the specified compiler
(usually some version of clang).
You can pass options to the compiler by appending them after a '--' argument
or by supplying -cflags. Flags passed as arguments take precedence
over flags passed via -cflags. Additionally, the program expands quotation
marks in -cflags. This means that -cflags 'foo "bar baz"' is passed to the
compiler as two arguments "foo" and "bar baz".
The program expects GOPACKAGE to be set in the environment, and should be invoked
via go generate. The generated files are written to the current directory.
Some options take defaults from the environment. Variable name is mentioned
next to the respective option.
Options:
`
// Targets understood by bpf2go.
//
// Targets without a Linux string can't be used directly and are only included
// for the generic bpf, bpfel, bpfeb targets.
var targetByGoArch = map[string]target{
"386": {"bpfel", "x86"},
"amd64": {"bpfel", "x86"},
"amd64p32": {"bpfel", ""},
"arm": {"bpfel", "arm"},
"arm64": {"bpfel", "arm64"},
"loong64": {"bpfel", ""},
"mipsle": {"bpfel", ""},
"mips64le": {"bpfel", ""},
"mips64p32le": {"bpfel", ""},
"ppc64le": {"bpfel", "powerpc"},
"riscv64": {"bpfel", ""},
"armbe": {"bpfeb", "arm"},
"arm64be": {"bpfeb", "arm64"},
"mips": {"bpfeb", ""},
"mips64": {"bpfeb", ""},
"mips64p32": {"bpfeb", ""},
"ppc64": {"bpfeb", "powerpc"},
"s390": {"bpfeb", "s390"},
"s390x": {"bpfeb", "s390"},
"sparc": {"bpfeb", "sparc"},
"sparc64": {"bpfeb", "sparc"},
}
func run(stdout io.Writer, pkg, outputDir string, args []string) (err error) {
b2g, err := newB2G(stdout, pkg, outputDir, args)
switch {
case err == nil:
return b2g.convertAll()
case errors.Is(err, flag.ErrHelp):
return nil
default:
return err
}
}
type bpf2go struct {
stdout io.Writer
// Absolute path to a .c file.
sourceFile string
// Absolute path to a directory where .go are written
outputDir string
// Alternative output stem. If empty, identStem is used.
outputStem string
// Valid go package name.
pkg string
// Valid go identifier.
identStem string
// Targets to build for.
targetArches map[target][]string
// C compiler.
cc string
// Command used to strip DWARF.
strip string
disableStripping bool
// C flags passed to the compiler.
cFlags []string
skipGlobalTypes bool
// C types to include in the generatd output.
cTypes cTypes
// Build tags to be included in the output.
tags buildTags
// Base directory of the Makefile. Enables outputting make-style dependencies
// in .d files.
makeBase string
}
func newB2G(stdout io.Writer, pkg, outputDir string, args []string) (*bpf2go, error) {
b2g := &bpf2go{
stdout: stdout,
pkg: pkg,
outputDir: outputDir,
}
fs := flag.NewFlagSet("bpf2go", flag.ContinueOnError)
fs.StringVar(&b2g.cc, "cc", getEnv("BPF2GO_CC", "clang"),
"`binary` used to compile C to BPF ($BPF2GO_CC)")
fs.StringVar(&b2g.strip, "strip", getEnv("BPF2GO_STRIP", ""),
"`binary` used to strip DWARF from compiled BPF ($BPF2GO_STRIP)")
fs.BoolVar(&b2g.disableStripping, "no-strip", false, "disable stripping of DWARF")
flagCFlags := fs.String("cflags", getEnv("BPF2GO_CFLAGS", ""),
"flags passed to the compiler, may contain quoted arguments ($BPF2GO_CFLAGS)")
fs.Var(&b2g.tags, "tags", "Comma-separated list of Go build tags to include in generated files")
flagTarget := fs.String("target", "bpfel,bpfeb", "clang target(s) to compile for (comma separated)")
fs.StringVar(&b2g.makeBase, "makebase", getEnv("BPF2GO_MAKEBASE", ""),
"write make compatible depinfo files relative to `directory` ($BPF2GO_MAKEBASE)")
fs.Var(&b2g.cTypes, "type", "`Name` of a type to generate a Go declaration for, may be repeated")
fs.BoolVar(&b2g.skipGlobalTypes, "no-global-types", false, "Skip generating types for map keys and values, etc.")
fs.StringVar(&b2g.outputStem, "output-stem", "", "alternative stem for names of generated files (defaults to ident)")
fs.SetOutput(b2g.stdout)
fs.Usage = func() {
fmt.Fprintf(fs.Output(), helpText, fs.Name())
fs.PrintDefaults()
fmt.Fprintln(fs.Output())
printTargets(fs.Output())
}
if err := fs.Parse(args); err != nil {
return nil, err
}
if b2g.pkg == "" {
return nil, errors.New("missing package, are you running via go generate?")
}
if b2g.cc == "" {
return nil, errors.New("no compiler specified")
}
args, cFlags := splitCFlagsFromArgs(fs.Args())
if *flagCFlags != "" {
splitCFlags, err := splitArguments(*flagCFlags)
if err != nil {
return nil, err
}
// Command line arguments take precedence over C flags
// from the flag.
cFlags = append(splitCFlags, cFlags...)
}
for _, cFlag := range cFlags {
if strings.HasPrefix(cFlag, "-M") {
return nil, fmt.Errorf("use -makebase instead of %q", cFlag)
}
}
b2g.cFlags = cFlags[:len(cFlags):len(cFlags)]
if len(args) < 2 {
return nil, errors.New("expected at least two arguments")
}
b2g.identStem = args[0]
if !token.IsIdentifier(b2g.identStem) {
return nil, fmt.Errorf("%q is not a valid identifier", b2g.identStem)
}
sourceFile, err := filepath.Abs(args[1])
if err != nil {
return nil, err
}
b2g.sourceFile = sourceFile
if b2g.makeBase != "" {
b2g.makeBase, err = filepath.Abs(b2g.makeBase)
if err != nil {
return nil, err
}
}
if b2g.outputStem != "" && strings.ContainsRune(b2g.outputStem, filepath.Separator) {
return nil, fmt.Errorf("-output-stem %q must not contain path separation characters", b2g.outputStem)
}
targetArches, err := collectTargets(strings.Split(*flagTarget, ","))
if errors.Is(err, errInvalidTarget) {
printTargets(b2g.stdout)
fmt.Fprintln(b2g.stdout)
return nil, err
}
if err != nil {
return nil, err
}
if len(targetArches) == 0 {
return nil, fmt.Errorf("no targets specified")
}
b2g.targetArches = targetArches
// Try to find a suitable llvm-strip, possibly with a version suffix derived
// from the clang binary.
if b2g.strip == "" {
b2g.strip = "llvm-strip"
if strings.HasPrefix(b2g.cc, "clang") {
b2g.strip += strings.TrimPrefix(b2g.cc, "clang")
}
}
return b2g, nil
}
// cTypes collects the C type names a user wants to generate Go types for.
//
// Names are guaranteed to be unique, and only a subset of names is accepted so
// that we may extend the flag syntax in the future.
type cTypes []string
var _ flag.Value = (*cTypes)(nil)
func (ct *cTypes) String() string {
if ct == nil {
return "[]"
}
return fmt.Sprint(*ct)
}
const validCTypeChars = `[a-z0-9_]`
var reValidCType = regexp.MustCompile(`(?i)^` + validCTypeChars + `+$`)
func (ct *cTypes) Set(value string) error {
if !reValidCType.MatchString(value) {
return fmt.Errorf("%q contains characters outside of %s", value, validCTypeChars)
}
i := sort.SearchStrings(*ct, value)
if i >= len(*ct) {
*ct = append(*ct, value)
return nil
}
if (*ct)[i] == value {
return fmt.Errorf("duplicate type %q", value)
}
*ct = append((*ct)[:i], append([]string{value}, (*ct)[i:]...)...)
return nil
}
func getEnv(key, defaultVal string) string {
if val, ok := os.LookupEnv(key); ok {
return val
}
return defaultVal
}
func (b2g *bpf2go) convertAll() (err error) {
if _, err := os.Stat(b2g.sourceFile); os.IsNotExist(err) {
return fmt.Errorf("file %s doesn't exist", b2g.sourceFile)
} else if err != nil {
return err
}
if !b2g.disableStripping {
b2g.strip, err = exec.LookPath(b2g.strip)
if err != nil {
return err
}
}
for target, arches := range b2g.targetArches {
if err := b2g.convert(target, arches); err != nil {
return err
}
}
return nil
}
func (b2g *bpf2go) convert(tgt target, arches []string) (err error) {
removeOnError := func(f *os.File) {
if err != nil {
os.Remove(f.Name())
}
f.Close()
}
outputStem := b2g.outputStem
if outputStem == "" {
outputStem = strings.ToLower(b2g.identStem)
}
stem := fmt.Sprintf("%s_%s", outputStem, tgt.clang)
if tgt.linux != "" {
stem = fmt.Sprintf("%s_%s_%s", outputStem, tgt.clang, tgt.linux)
}
objFileName := filepath.Join(b2g.outputDir, stem+".o")
cwd, err := os.Getwd()
if err != nil {
return err
}
var archConstraint constraint.Expr
for _, arch := range arches {
tag := &constraint.TagExpr{Tag: arch}
archConstraint = orConstraints(archConstraint, tag)
}
constraints := andConstraints(archConstraint, b2g.tags.Expr)
cFlags := make([]string, len(b2g.cFlags))
copy(cFlags, b2g.cFlags)
if tgt.linux != "" {
cFlags = append(cFlags, "-D__TARGET_ARCH_"+tgt.linux)
}
var dep bytes.Buffer
err = compile(compileArgs{
cc: b2g.cc,
cFlags: cFlags,
target: tgt.clang,
dir: cwd,
source: b2g.sourceFile,
dest: objFileName,
dep: &dep,
})
if err != nil {
return err
}
fmt.Fprintln(b2g.stdout, "Compiled", objFileName)
if !b2g.disableStripping {
if err := strip(b2g.strip, objFileName); err != nil {
return err
}
fmt.Fprintln(b2g.stdout, "Stripped", objFileName)
}
spec, err := ebpf.LoadCollectionSpec(objFileName)
if err != nil {
return fmt.Errorf("can't load BPF from ELF: %s", err)
}
maps, programs, types, err := collectFromSpec(spec, b2g.cTypes, b2g.skipGlobalTypes)
if err != nil {
return err
}
// Write out generated go
goFileName := filepath.Join(b2g.outputDir, stem+".go")
goFile, err := os.Create(goFileName)
if err != nil {
return err
}
defer removeOnError(goFile)
err = output(outputArgs{
pkg: b2g.pkg,
stem: b2g.identStem,
constraints: constraints,
maps: maps,
programs: programs,
types: types,
obj: filepath.Base(objFileName),
out: goFile,
})
if err != nil {
return fmt.Errorf("can't write %s: %s", goFileName, err)
}
fmt.Fprintln(b2g.stdout, "Wrote", goFileName)
if b2g.makeBase == "" {
return
}
deps, err := parseDependencies(cwd, &dep)
if err != nil {
return fmt.Errorf("can't read dependency information: %s", err)
}
// There is always at least a dependency for the main file.
deps[0].file = goFileName
depFile, err := adjustDependencies(b2g.makeBase, deps)
if err != nil {
return fmt.Errorf("can't adjust dependency information: %s", err)
}
depFileName := goFileName + ".d"
if err := os.WriteFile(depFileName, depFile, 0666); err != nil {
return fmt.Errorf("can't write dependency file: %s", err)
}
fmt.Fprintln(b2g.stdout, "Wrote", depFileName)
return nil
}
type target struct {
clang string
linux string
}
func printTargets(w io.Writer) {
var arches []string
for arch, archTarget := range targetByGoArch {
if archTarget.linux == "" {
continue
}
arches = append(arches, arch)
}
sort.Strings(arches)
fmt.Fprint(w, "Supported targets:\n")
fmt.Fprint(w, "\tbpf\n\tbpfel\n\tbpfeb\n")
for _, arch := range arches {
fmt.Fprintf(w, "\t%s\n", arch)
}
}
var errInvalidTarget = errors.New("unsupported target")
func collectTargets(targets []string) (map[target][]string, error) {
result := make(map[target][]string)
for _, tgt := range targets {
switch tgt {
case "bpf", "bpfel", "bpfeb":
var goarches []string
for arch, archTarget := range targetByGoArch {
if archTarget.clang == tgt {
// Include tags for all goarches that have the same endianness.
goarches = append(goarches, arch)
}
}
sort.Strings(goarches)
result[target{tgt, ""}] = goarches
case "native":
tgt = runtime.GOARCH
fallthrough
default:
archTarget, ok := targetByGoArch[tgt]
if !ok || archTarget.linux == "" {
return nil, fmt.Errorf("%q: %w", tgt, errInvalidTarget)
}
var goarches []string
for goarch, lt := range targetByGoArch {
if lt == archTarget {
// Include tags for all goarches that have the same
// target.
goarches = append(goarches, goarch)
}
}
sort.Strings(goarches)
result[archTarget] = goarches
}
}
return result, nil
}
func main() {
outputDir, err := os.Getwd()
if err != nil {
fmt.Fprintln(os.Stderr, "Error:", err)
os.Exit(1)
}
if err := run(os.Stdout, os.Getenv("GOPACKAGE"), outputDir, os.Args[1:]); err != nil {
fmt.Fprintln(os.Stderr, "Error:", err)
os.Exit(1)
}
}
@@ -0,0 +1,444 @@
package main
import (
"bytes"
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"runtime"
"sort"
"strings"
"testing"
qt "github.com/frankban/quicktest"
"github.com/google/go-cmp/cmp"
)
func TestRun(t *testing.T) {
clangBin := clangBin(t)
dir := mustWriteTempFile(t, "test.c", minimalSocketFilter)
cwd, err := os.Getwd()
if err != nil {
t.Fatal(err)
}
modRoot := filepath.Clean(filepath.Join(cwd, "../.."))
if _, err := os.Stat(filepath.Join(modRoot, "go.mod")); os.IsNotExist(err) {
t.Fatal("No go.mod file in", modRoot)
}
tmpDir, err := os.MkdirTemp("", "bpf2go-module-*")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmpDir)
execInModule := func(name string, args ...string) {
t.Helper()
cmd := exec.Command(name, args...)
cmd.Dir = tmpDir
if out, err := cmd.CombinedOutput(); err != nil {
if out := string(out); out != "" {
t.Log(out)
}
t.Fatalf("Can't execute %s: %v", name, args)
}
}
module := currentModule()
execInModule("go", "mod", "init", "bpf2go-test")
execInModule("go", "mod", "edit",
// Require the module. The version doesn't matter due to the replace
// below.
fmt.Sprintf("-require=%s@v0.0.0", module),
// Replace the module with the current version.
fmt.Sprintf("-replace=%s=%s", module, modRoot),
)
err = run(io.Discard, "foo", tmpDir, []string{
"-cc", clangBin,
"bar",
filepath.Join(dir, "test.c"),
})
if err != nil {
t.Fatal("Can't run:", err)
}
for _, arch := range []string{
"amd64", // little-endian
"s390x", // big-endian
} {
t.Run(arch, func(t *testing.T) {
goBin := exec.Command("go", "build", "-mod=mod")
goBin.Dir = tmpDir
goBin.Env = append(os.Environ(),
"GOOS=linux",
"GOARCH="+arch,
)
out, err := goBin.CombinedOutput()
if err != nil {
if out := string(out); out != "" {
t.Log(out)
}
t.Error("Can't compile package:", err)
}
})
}
}
func TestHelp(t *testing.T) {
var stdout bytes.Buffer
err := run(&stdout, "", "", []string{"-help"})
if err != nil {
t.Fatal("Can't execute -help")
}
if stdout.Len() == 0 {
t.Error("-help doesn't write to stdout")
}
}
func TestDisableStripping(t *testing.T) {
dir := mustWriteTempFile(t, "test.c", minimalSocketFilter)
err := run(io.Discard, "foo", dir, []string{
"-cc", clangBin(t),
"-strip", "binary-that-certainly-doesnt-exist",
"-no-strip",
"bar",
filepath.Join(dir, "test.c"),
})
if err != nil {
t.Fatal("Can't run with stripping disabled:", err)
}
}
func TestCollectTargets(t *testing.T) {
clangArches := make(map[string][]string)
linuxArchesLE := make(map[string][]string)
linuxArchesBE := make(map[string][]string)
for arch, archTarget := range targetByGoArch {
clangArches[archTarget.clang] = append(clangArches[archTarget.clang], arch)
if archTarget.clang == "bpfel" {
linuxArchesLE[archTarget.linux] = append(linuxArchesLE[archTarget.linux], arch)
continue
}
linuxArchesBE[archTarget.linux] = append(linuxArchesBE[archTarget.linux], arch)
}
for i := range clangArches {
sort.Strings(clangArches[i])
}
for i := range linuxArchesLE {
sort.Strings(linuxArchesLE[i])
}
for i := range linuxArchesBE {
sort.Strings(linuxArchesBE[i])
}
nativeTarget := make(map[target][]string)
for arch, archTarget := range targetByGoArch {
if arch == runtime.GOARCH {
if archTarget.clang == "bpfel" {
nativeTarget[archTarget] = linuxArchesLE[archTarget.linux]
} else {
nativeTarget[archTarget] = linuxArchesBE[archTarget.linux]
}
break
}
}
tests := []struct {
targets []string
want map[target][]string
}{
{
[]string{"bpf", "bpfel", "bpfeb"},
map[target][]string{
{"bpf", ""}: nil,
{"bpfel", ""}: clangArches["bpfel"],
{"bpfeb", ""}: clangArches["bpfeb"],
},
},
{
[]string{"amd64", "386"},
map[target][]string{
{"bpfel", "x86"}: linuxArchesLE["x86"],
},
},
{
[]string{"amd64", "arm64be"},
map[target][]string{
{"bpfeb", "arm64"}: linuxArchesBE["arm64"],
{"bpfel", "x86"}: linuxArchesLE["x86"],
},
},
{
[]string{"native"},
nativeTarget,
},
}
for _, test := range tests {
name := strings.Join(test.targets, ",")
t.Run(name, func(t *testing.T) {
have, err := collectTargets(test.targets)
if err != nil {
t.Fatal(err)
}
if diff := cmp.Diff(test.want, have); diff != "" {
t.Errorf("Result mismatch (-want +got):\n%s", diff)
}
})
}
}
func TestCollectTargetsErrors(t *testing.T) {
tests := []struct {
name string
target string
}{
{"unknown", "frood"},
{"no linux target", "mips64p32le"},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
_, err := collectTargets([]string{test.target})
if err == nil {
t.Fatal("Function did not return an error")
}
t.Log("Error message:", err)
})
}
}
func TestConvertGOARCH(t *testing.T) {
tmp := mustWriteTempFile(t, "test.c",
`
#ifndef __TARGET_ARCH_x86
#error __TARGET_ARCH_x86 is not defined
#endif`,
)
b2g := bpf2go{
pkg: "test",
stdout: io.Discard,
identStem: "test",
cc: clangBin(t),
disableStripping: true,
sourceFile: tmp + "/test.c",
outputDir: tmp,
}
if err := b2g.convert(targetByGoArch["amd64"], nil); err != nil {
t.Fatal("Can't target GOARCH:", err)
}
}
func TestCTypes(t *testing.T) {
var ct cTypes
valid := []string{
"abcdefghijklmnopqrstuvqxyABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890_",
"y",
}
for _, value := range valid {
if err := ct.Set(value); err != nil {
t.Fatalf("Set returned an error for %q: %s", value, err)
}
}
qt.Assert(t, ct, qt.ContentEquals, cTypes(valid))
for _, value := range []string{
"",
" ",
" frood",
"foo\nbar",
".",
",",
"+",
"-",
} {
ct = nil
if err := ct.Set(value); err == nil {
t.Fatalf("Set did not return an error for %q", value)
}
}
ct = nil
qt.Assert(t, ct.Set("foo"), qt.IsNil)
qt.Assert(t, ct.Set("foo"), qt.IsNotNil)
}
func TestParseArgs(t *testing.T) {
const (
pkg = "eee"
outputDir = "."
csource = "testdata/minimal.c"
stem = "a"
)
t.Run("makebase", func(t *testing.T) {
basePath, _ := filepath.Abs("barfoo")
args := []string{"-makebase", basePath, stem, csource}
b2g, err := newB2G(&bytes.Buffer{}, pkg, outputDir, args)
qt.Assert(t, err, qt.IsNil)
qt.Assert(t, b2g.makeBase, qt.Equals, basePath)
})
t.Run("makebase from env", func(t *testing.T) {
basePath, _ := filepath.Abs("barfoo")
args := []string{stem, csource}
t.Setenv("BPF2GO_MAKEBASE", basePath)
b2g, err := newB2G(&bytes.Buffer{}, pkg, outputDir, args)
qt.Assert(t, err, qt.IsNil)
qt.Assert(t, b2g.makeBase, qt.Equals, basePath)
})
t.Run("makebase flag overrides env", func(t *testing.T) {
basePathFlag, _ := filepath.Abs("barfoo")
basePathEnv, _ := filepath.Abs("foobar")
args := []string{"-makebase", basePathFlag, stem, csource}
t.Setenv("BPF2GO_MAKEBASE", basePathEnv)
b2g, err := newB2G(&bytes.Buffer{}, pkg, outputDir, args)
qt.Assert(t, err, qt.IsNil)
qt.Assert(t, b2g.makeBase, qt.Equals, basePathFlag)
})
t.Run("cc defaults to clang", func(t *testing.T) {
args := []string{stem, csource}
b2g, err := newB2G(&bytes.Buffer{}, pkg, outputDir, args)
qt.Assert(t, err, qt.IsNil)
qt.Assert(t, b2g.cc, qt.Equals, "clang")
})
t.Run("cc", func(t *testing.T) {
args := []string{"-cc", "barfoo", stem, csource}
b2g, err := newB2G(&bytes.Buffer{}, pkg, outputDir, args)
qt.Assert(t, err, qt.IsNil)
qt.Assert(t, b2g.cc, qt.Equals, "barfoo")
})
t.Run("cc from env", func(t *testing.T) {
args := []string{stem, csource}
t.Setenv("BPF2GO_CC", "barfoo")
b2g, err := newB2G(&bytes.Buffer{}, pkg, outputDir, args)
qt.Assert(t, err, qt.IsNil)
qt.Assert(t, b2g.cc, qt.Equals, "barfoo")
})
t.Run("cc flag overrides env", func(t *testing.T) {
args := []string{"-cc", "barfoo", stem, csource}
t.Setenv("BPF2GO_CC", "foobar")
b2g, err := newB2G(&bytes.Buffer{}, pkg, outputDir, args)
qt.Assert(t, err, qt.IsNil)
qt.Assert(t, b2g.cc, qt.Equals, "barfoo")
})
t.Run("strip defaults to llvm-strip", func(t *testing.T) {
args := []string{stem, csource}
b2g, err := newB2G(&bytes.Buffer{}, pkg, outputDir, args)
qt.Assert(t, err, qt.IsNil)
qt.Assert(t, b2g.strip, qt.Equals, "llvm-strip")
})
t.Run("strip", func(t *testing.T) {
args := []string{"-strip", "barfoo", stem, csource}
b2g, err := newB2G(&bytes.Buffer{}, pkg, outputDir, args)
qt.Assert(t, err, qt.IsNil)
qt.Assert(t, b2g.strip, qt.Equals, "barfoo")
})
t.Run("strip from env", func(t *testing.T) {
args := []string{stem, csource}
t.Setenv("BPF2GO_STRIP", "barfoo")
b2g, err := newB2G(&bytes.Buffer{}, pkg, outputDir, args)
qt.Assert(t, err, qt.IsNil)
qt.Assert(t, b2g.strip, qt.Equals, "barfoo")
})
t.Run("strip flag overrides env", func(t *testing.T) {
args := []string{"-strip", "barfoo", stem, csource}
t.Setenv("BPF2GO_STRIP", "foobar")
b2g, err := newB2G(&bytes.Buffer{}, pkg, outputDir, args)
qt.Assert(t, err, qt.IsNil)
qt.Assert(t, b2g.strip, qt.Equals, "barfoo")
})
t.Run("no strip defaults to false", func(t *testing.T) {
args := []string{stem, csource}
b2g, err := newB2G(&bytes.Buffer{}, pkg, outputDir, args)
qt.Assert(t, err, qt.IsNil)
qt.Assert(t, b2g.disableStripping, qt.IsFalse)
})
t.Run("no strip", func(t *testing.T) {
args := []string{"-no-strip", stem, csource}
b2g, err := newB2G(&bytes.Buffer{}, pkg, outputDir, args)
qt.Assert(t, err, qt.IsNil)
qt.Assert(t, b2g.disableStripping, qt.IsTrue)
})
t.Run("cflags flag", func(t *testing.T) {
args := []string{"-cflags", "x y z", stem, csource}
b2g, err := newB2G(&bytes.Buffer{}, pkg, outputDir, args)
qt.Assert(t, err, qt.IsNil)
qt.Assert(t, b2g.cFlags, qt.DeepEquals, []string{"x", "y", "z"})
})
t.Run("cflags multi flag", func(t *testing.T) {
args := []string{"-cflags", "x y z", "-cflags", "u v", stem, csource}
b2g, err := newB2G(&bytes.Buffer{}, pkg, outputDir, args)
qt.Assert(t, err, qt.IsNil)
qt.Assert(t, b2g.cFlags, qt.DeepEquals, []string{"u", "v"})
})
t.Run("cflags flag and args", func(t *testing.T) {
args := []string{"-cflags", "x y z", "stem", csource, "--", "u", "v"}
b2g, err := newB2G(&bytes.Buffer{}, pkg, outputDir, args)
qt.Assert(t, err, qt.IsNil)
qt.Assert(t, b2g.cFlags, qt.DeepEquals, []string{"x", "y", "z", "u", "v"})
})
t.Run("cflags from env", func(t *testing.T) {
args := []string{stem, csource}
t.Setenv("BPF2GO_CFLAGS", "x y z")
b2g, err := newB2G(&bytes.Buffer{}, pkg, outputDir, args)
qt.Assert(t, err, qt.IsNil)
qt.Assert(t, b2g.cFlags, qt.DeepEquals, []string{"x", "y", "z"})
})
t.Run("cflags flag overrides env", func(t *testing.T) {
args := []string{"-cflags", "u v", stem, csource}
t.Setenv("BPF2GO_CFLAGS", "x y z")
b2g, err := newB2G(&bytes.Buffer{}, pkg, outputDir, args)
qt.Assert(t, err, qt.IsNil)
qt.Assert(t, b2g.cFlags, qt.DeepEquals, []string{"u", "v"})
})
}
func clangBin(t *testing.T) string {
t.Helper()
if testing.Short() {
t.Skip("Not compiling with -short")
}
// Use a recent clang version for local development, but allow CI to run
// against oldest supported clang.
clang := "clang-14"
if minVersion := os.Getenv("CI_MIN_CLANG_VERSION"); minVersion != "" {
clang = fmt.Sprintf("clang-%s", minVersion)
}
t.Log("Testing against", clang)
return clang
}
@@ -0,0 +1,244 @@
package main
import (
"bytes"
_ "embed"
"fmt"
"go/build/constraint"
"go/token"
"io"
"sort"
"strings"
"text/template"
"github.com/cilium/ebpf"
"github.com/cilium/ebpf/btf"
"github.com/cilium/ebpf/internal"
)
//go:embed output.tpl
var commonRaw string
var commonTemplate = template.Must(template.New("common").Parse(commonRaw))
type templateName string
func (n templateName) maybeExport(str string) string {
if token.IsExported(string(n)) {
return toUpperFirst(str)
}
return str
}
func (n templateName) Bytes() string {
return "_" + toUpperFirst(string(n)) + "Bytes"
}
func (n templateName) Specs() string {
return string(n) + "Specs"
}
func (n templateName) ProgramSpecs() string {
return string(n) + "ProgramSpecs"
}
func (n templateName) MapSpecs() string {
return string(n) + "MapSpecs"
}
func (n templateName) Load() string {
return n.maybeExport("load" + toUpperFirst(string(n)))
}
func (n templateName) LoadObjects() string {
return n.maybeExport("load" + toUpperFirst(string(n)) + "Objects")
}
func (n templateName) Objects() string {
return string(n) + "Objects"
}
func (n templateName) Maps() string {
return string(n) + "Maps"
}
func (n templateName) Programs() string {
return string(n) + "Programs"
}
func (n templateName) CloseHelper() string {
return "_" + toUpperFirst(string(n)) + "Close"
}
type outputArgs struct {
// Package of the resulting file.
pkg string
// The prefix of all names declared at the top-level.
stem string
// Build constraints included in the resulting file.
constraints constraint.Expr
// Maps to be emitted.
maps []string
// Programs to be emitted.
programs []string
// Types to be emitted.
types []btf.Type
// Filename of the ELF object to embed.
obj string
out io.Writer
}
func output(args outputArgs) error {
maps := make(map[string]string)
for _, name := range args.maps {
maps[name] = internal.Identifier(name)
}
programs := make(map[string]string)
for _, name := range args.programs {
programs[name] = internal.Identifier(name)
}
typeNames := make(map[btf.Type]string)
for _, typ := range args.types {
typeNames[typ] = args.stem + internal.Identifier(typ.TypeName())
}
// Ensure we don't have conflicting names and generate a sorted list of
// named types so that the output is stable.
types, err := sortTypes(typeNames)
if err != nil {
return err
}
module := currentModule()
gf := &btf.GoFormatter{
Names: typeNames,
Identifier: internal.Identifier,
}
ctx := struct {
*btf.GoFormatter
Module string
Package string
Constraints constraint.Expr
Name templateName
Maps map[string]string
Programs map[string]string
Types []btf.Type
TypeNames map[btf.Type]string
File string
}{
gf,
module,
args.pkg,
args.constraints,
templateName(args.stem),
maps,
programs,
types,
typeNames,
args.obj,
}
var buf bytes.Buffer
if err := commonTemplate.Execute(&buf, &ctx); err != nil {
return fmt.Errorf("can't generate types: %s", err)
}
return internal.WriteFormatted(buf.Bytes(), args.out)
}
func collectFromSpec(spec *ebpf.CollectionSpec, cTypes []string, skipGlobalTypes bool) (maps, programs []string, types []btf.Type, _ error) {
for name := range spec.Maps {
// Skip .rodata, .data, .bss, etc. sections
if !strings.HasPrefix(name, ".") {
maps = append(maps, name)
}
}
for name := range spec.Programs {
programs = append(programs, name)
}
types, err := collectCTypes(spec.Types, cTypes)
if err != nil {
return nil, nil, nil, fmt.Errorf("collect C types: %w", err)
}
// Collect map key and value types, unless we've been asked not to.
if skipGlobalTypes {
return maps, programs, types, nil
}
for _, typ := range collectMapTypes(spec.Maps) {
switch btf.UnderlyingType(typ).(type) {
case *btf.Datasec:
// Avoid emitting .rodata, .bss, etc. for now. We might want to
// name these types differently, etc.
continue
case *btf.Int:
// Don't emit primitive types by default.
continue
}
types = append(types, typ)
}
return maps, programs, types, nil
}
func collectCTypes(types *btf.Spec, names []string) ([]btf.Type, error) {
var result []btf.Type
for _, cType := range names {
typ, err := types.AnyTypeByName(cType)
if err != nil {
return nil, err
}
result = append(result, typ)
}
return result, nil
}
// collectMapTypes returns a list of all types used as map keys or values.
func collectMapTypes(maps map[string]*ebpf.MapSpec) []btf.Type {
var result []btf.Type
for _, m := range maps {
if m.Key != nil && m.Key.TypeName() != "" {
result = append(result, m.Key)
}
if m.Value != nil && m.Value.TypeName() != "" {
result = append(result, m.Value)
}
}
return result
}
// sortTypes returns a list of types sorted by their (generated) Go type name.
//
// Duplicate Go type names are rejected.
func sortTypes(typeNames map[btf.Type]string) ([]btf.Type, error) {
var types []btf.Type
var names []string
for typ, name := range typeNames {
i := sort.SearchStrings(names, name)
if i >= len(names) {
types = append(types, typ)
names = append(names, name)
continue
}
if names[i] == name {
return nil, fmt.Errorf("type name %q is used multiple times", name)
}
types = append(types[:i], append([]btf.Type{typ}, types[i:]...)...)
names = append(names[:i], append([]string{name}, names[i:]...)...)
}
return types, nil
}
@@ -0,0 +1,137 @@
// Code generated by bpf2go; DO NOT EDIT.
{{ with .Constraints }}//go:build {{ . }}{{ end }}
package {{ .Package }}
import (
"bytes"
_ "embed"
"fmt"
"io"
"{{ .Module }}"
)
{{- if .Types }}
{{- range $type := .Types }}
{{ $.TypeDeclaration (index $.TypeNames $type) $type }}
{{ end }}
{{- end }}
// {{ .Name.Load }} returns the embedded CollectionSpec for {{ .Name }}.
func {{ .Name.Load }}() (*ebpf.CollectionSpec, error) {
reader := bytes.NewReader({{ .Name.Bytes }})
spec, err := ebpf.LoadCollectionSpecFromReader(reader)
if err != nil {
return nil, fmt.Errorf("can't load {{ .Name }}: %w", err)
}
return spec, err
}
// {{ .Name.LoadObjects }} loads {{ .Name }} and converts it into a struct.
//
// The following types are suitable as obj argument:
//
// *{{ .Name.Objects }}
// *{{ .Name.Programs }}
// *{{ .Name.Maps }}
//
// See ebpf.CollectionSpec.LoadAndAssign documentation for details.
func {{ .Name.LoadObjects }}(obj interface{}, opts *ebpf.CollectionOptions) (error) {
spec, err := {{ .Name.Load }}()
if err != nil {
return err
}
return spec.LoadAndAssign(obj, opts)
}
// {{ .Name.Specs }} contains maps and programs before they are loaded into the kernel.
//
// It can be passed ebpf.CollectionSpec.Assign.
type {{ .Name.Specs }} struct {
{{ .Name.ProgramSpecs }}
{{ .Name.MapSpecs }}
}
// {{ .Name.Specs }} contains programs before they are loaded into the kernel.
//
// It can be passed ebpf.CollectionSpec.Assign.
type {{ .Name.ProgramSpecs }} struct {
{{- range $name, $id := .Programs }}
{{ $id }} *ebpf.ProgramSpec `ebpf:"{{ $name }}"`
{{- end }}
}
// {{ .Name.MapSpecs }} contains maps before they are loaded into the kernel.
//
// It can be passed ebpf.CollectionSpec.Assign.
type {{ .Name.MapSpecs }} struct {
{{- range $name, $id := .Maps }}
{{ $id }} *ebpf.MapSpec `ebpf:"{{ $name }}"`
{{- end }}
}
// {{ .Name.Objects }} contains all objects after they have been loaded into the kernel.
//
// It can be passed to {{ .Name.LoadObjects }} or ebpf.CollectionSpec.LoadAndAssign.
type {{ .Name.Objects }} struct {
{{ .Name.Programs }}
{{ .Name.Maps }}
}
func (o *{{ .Name.Objects }}) Close() error {
return {{ .Name.CloseHelper }}(
&o.{{ .Name.Programs }},
&o.{{ .Name.Maps }},
)
}
// {{ .Name.Maps }} contains all maps after they have been loaded into the kernel.
//
// It can be passed to {{ .Name.LoadObjects }} or ebpf.CollectionSpec.LoadAndAssign.
type {{ .Name.Maps }} struct {
{{- range $name, $id := .Maps }}
{{ $id }} *ebpf.Map `ebpf:"{{ $name }}"`
{{- end }}
}
func (m *{{ .Name.Maps }}) Close() error {
return {{ .Name.CloseHelper }}(
{{- range $id := .Maps }}
m.{{ $id }},
{{- end }}
)
}
// {{ .Name.Programs }} contains all programs after they have been loaded into the kernel.
//
// It can be passed to {{ .Name.LoadObjects }} or ebpf.CollectionSpec.LoadAndAssign.
type {{ .Name.Programs }} struct {
{{- range $name, $id := .Programs }}
{{ $id }} *ebpf.Program `ebpf:"{{ $name }}"`
{{- end }}
}
func (p *{{ .Name.Programs }}) Close() error {
return {{ .Name.CloseHelper }}(
{{- range $id := .Programs }}
p.{{ $id }},
{{- end }}
)
}
func {{ .Name.CloseHelper }}(closers ...io.Closer) error {
for _, closer := range closers {
if err := closer.Close(); err != nil {
return err
}
}
return nil
}
// Do not access this directly.
//go:embed {{ .File }}
var {{ .Name.Bytes }} []byte
@@ -0,0 +1,97 @@
package main
import (
"fmt"
"testing"
"github.com/cilium/ebpf"
"github.com/cilium/ebpf/btf"
"github.com/cilium/ebpf/internal"
qt "github.com/frankban/quicktest"
"github.com/google/go-cmp/cmp"
)
func TestOrderTypes(t *testing.T) {
a := &btf.Int{}
b := &btf.Int{}
c := &btf.Int{}
for _, test := range []struct {
name string
in map[btf.Type]string
out []btf.Type
}{
{
"order",
map[btf.Type]string{
a: "foo",
b: "bar",
c: "baz",
},
[]btf.Type{b, c, a},
},
} {
t.Run(test.name, func(t *testing.T) {
result, err := sortTypes(test.in)
qt.Assert(t, err, qt.IsNil)
qt.Assert(t, len(result), qt.Equals, len(test.out))
for i, o := range test.out {
if result[i] != o {
t.Fatalf("Index %d: expected %p got %p", i, o, result[i])
}
}
})
}
for _, test := range []struct {
name string
in map[btf.Type]string
}{
{
"duplicate names",
map[btf.Type]string{
a: "foo",
b: "foo",
},
},
} {
t.Run(test.name, func(t *testing.T) {
result, err := sortTypes(test.in)
qt.Assert(t, err, qt.IsNotNil)
qt.Assert(t, result, qt.IsNil)
})
}
}
var typesEqual = qt.CmpEquals(cmp.Comparer(func(a, b btf.Type) bool {
return a == b
}))
func TestCollectFromSpec(t *testing.T) {
spec, err := ebpf.LoadCollectionSpec(fmt.Sprintf("testdata/minimal-%s.elf", internal.ClangEndian))
if err != nil {
t.Fatal(err)
}
map1 := spec.Maps["map1"]
maps, programs, types, err := collectFromSpec(spec, nil, false)
if err != nil {
t.Fatal(err)
}
qt.Assert(t, maps, qt.ContentEquals, []string{"map1"})
qt.Assert(t, programs, qt.ContentEquals, []string{"filter"})
qt.Assert(t, types, typesEqual, []btf.Type{map1.Key, map1.Value})
_, _, types, err = collectFromSpec(spec, nil, true)
if err != nil {
t.Fatal(err)
}
qt.Assert(t, types, typesEqual, ([]btf.Type)(nil))
_, _, types, err = collectFromSpec(spec, []string{"barfoo"}, true)
if err != nil {
t.Fatal(err)
}
qt.Assert(t, types, typesEqual, []btf.Type{map1.Value})
}
@@ -0,0 +1,67 @@
package test
import (
"reflect"
"testing"
"unsafe"
"github.com/cilium/ebpf/internal/testutils"
)
func TestLoadingSpec(t *testing.T) {
spec, err := loadTest()
testutils.SkipIfNotSupported(t, err)
if err != nil {
t.Fatal("Can't load spec:", err)
}
if spec == nil {
t.Fatal("Got a nil spec")
}
}
func TestLoadingObjects(t *testing.T) {
var objs testObjects
err := loadTestObjects(&objs, nil)
testutils.SkipIfNotSupported(t, err)
if err != nil {
t.Fatal("Can't load objects:", err)
}
defer objs.Close()
if objs.Filter == nil {
t.Error("Loading returns an object with nil programs")
}
if objs.Map1 == nil {
t.Error("Loading returns an object with nil maps")
}
}
func TestTypes(t *testing.T) {
if testEHOOPY != 0 {
t.Error("Expected testEHOOPY to be 0, got", testEHOOPY)
}
if testEFROOD != 1 {
t.Error("Expected testEFROOD to be 0, got", testEFROOD)
}
e := testE(0)
if size := unsafe.Sizeof(e); size != 4 {
t.Error("Expected size of exampleE to be 4, got", size)
}
bf := testBarfoo{}
if size := unsafe.Sizeof(bf); size != 16 {
t.Error("Expected size of exampleE to be 16, got", size)
}
if reflect.TypeOf(bf.Bar).Kind() != reflect.Int64 {
t.Error("Expected testBarfoo.Bar to be int64")
}
if reflect.TypeOf(bf.Baz).Kind() != reflect.Bool {
t.Error("Expected testBarfoo.Baz to be bool")
}
if reflect.TypeOf(bf.Boo) != reflect.TypeOf(e) {
t.Error("Expected testBarfoo.Boo to be exampleE")
}
}
@@ -0,0 +1,6 @@
// Package test checks that the code generated by bpf2go conforms to a
// specific API.
package test
// $BPF_CLANG and $BPF_CFLAGS are set by the Makefile.
//go:generate go run github.com/cilium/ebpf/cmd/bpf2go -cc $BPF_CLANG test ../testdata/minimal.c
@@ -0,0 +1,133 @@
// Code generated by bpf2go; DO NOT EDIT.
//go:build arm64be || armbe || mips || mips64 || mips64p32 || ppc64 || s390 || s390x || sparc || sparc64
package test
import (
"bytes"
_ "embed"
"fmt"
"io"
"github.com/cilium/ebpf"
)
type testBarfoo struct {
Bar int64
Baz bool
_ [3]byte
Boo testE
}
type testE uint32
const (
testEHOOPY testE = 0
testEFROOD testE = 1
)
// loadTest returns the embedded CollectionSpec for test.
func loadTest() (*ebpf.CollectionSpec, error) {
reader := bytes.NewReader(_TestBytes)
spec, err := ebpf.LoadCollectionSpecFromReader(reader)
if err != nil {
return nil, fmt.Errorf("can't load test: %w", err)
}
return spec, err
}
// loadTestObjects loads test and converts it into a struct.
//
// The following types are suitable as obj argument:
//
// *testObjects
// *testPrograms
// *testMaps
//
// See ebpf.CollectionSpec.LoadAndAssign documentation for details.
func loadTestObjects(obj interface{}, opts *ebpf.CollectionOptions) error {
spec, err := loadTest()
if err != nil {
return err
}
return spec.LoadAndAssign(obj, opts)
}
// testSpecs contains maps and programs before they are loaded into the kernel.
//
// It can be passed ebpf.CollectionSpec.Assign.
type testSpecs struct {
testProgramSpecs
testMapSpecs
}
// testSpecs contains programs before they are loaded into the kernel.
//
// It can be passed ebpf.CollectionSpec.Assign.
type testProgramSpecs struct {
Filter *ebpf.ProgramSpec `ebpf:"filter"`
}
// testMapSpecs contains maps before they are loaded into the kernel.
//
// It can be passed ebpf.CollectionSpec.Assign.
type testMapSpecs struct {
Map1 *ebpf.MapSpec `ebpf:"map1"`
}
// testObjects contains all objects after they have been loaded into the kernel.
//
// It can be passed to loadTestObjects or ebpf.CollectionSpec.LoadAndAssign.
type testObjects struct {
testPrograms
testMaps
}
func (o *testObjects) Close() error {
return _TestClose(
&o.testPrograms,
&o.testMaps,
)
}
// testMaps contains all maps after they have been loaded into the kernel.
//
// It can be passed to loadTestObjects or ebpf.CollectionSpec.LoadAndAssign.
type testMaps struct {
Map1 *ebpf.Map `ebpf:"map1"`
}
func (m *testMaps) Close() error {
return _TestClose(
m.Map1,
)
}
// testPrograms contains all programs after they have been loaded into the kernel.
//
// It can be passed to loadTestObjects or ebpf.CollectionSpec.LoadAndAssign.
type testPrograms struct {
Filter *ebpf.Program `ebpf:"filter"`
}
func (p *testPrograms) Close() error {
return _TestClose(
p.Filter,
)
}
func _TestClose(closers ...io.Closer) error {
for _, closer := range closers {
if err := closer.Close(); err != nil {
return err
}
}
return nil
}
// Do not access this directly.
//
//go:embed test_bpfeb.o
var _TestBytes []byte
@@ -0,0 +1,133 @@
// Code generated by bpf2go; DO NOT EDIT.
//go:build 386 || amd64 || amd64p32 || arm || arm64 || loong64 || mips64le || mips64p32le || mipsle || ppc64le || riscv64
package test
import (
"bytes"
_ "embed"
"fmt"
"io"
"github.com/cilium/ebpf"
)
type testBarfoo struct {
Bar int64
Baz bool
_ [3]byte
Boo testE
}
type testE uint32
const (
testEHOOPY testE = 0
testEFROOD testE = 1
)
// loadTest returns the embedded CollectionSpec for test.
func loadTest() (*ebpf.CollectionSpec, error) {
reader := bytes.NewReader(_TestBytes)
spec, err := ebpf.LoadCollectionSpecFromReader(reader)
if err != nil {
return nil, fmt.Errorf("can't load test: %w", err)
}
return spec, err
}
// loadTestObjects loads test and converts it into a struct.
//
// The following types are suitable as obj argument:
//
// *testObjects
// *testPrograms
// *testMaps
//
// See ebpf.CollectionSpec.LoadAndAssign documentation for details.
func loadTestObjects(obj interface{}, opts *ebpf.CollectionOptions) error {
spec, err := loadTest()
if err != nil {
return err
}
return spec.LoadAndAssign(obj, opts)
}
// testSpecs contains maps and programs before they are loaded into the kernel.
//
// It can be passed ebpf.CollectionSpec.Assign.
type testSpecs struct {
testProgramSpecs
testMapSpecs
}
// testSpecs contains programs before they are loaded into the kernel.
//
// It can be passed ebpf.CollectionSpec.Assign.
type testProgramSpecs struct {
Filter *ebpf.ProgramSpec `ebpf:"filter"`
}
// testMapSpecs contains maps before they are loaded into the kernel.
//
// It can be passed ebpf.CollectionSpec.Assign.
type testMapSpecs struct {
Map1 *ebpf.MapSpec `ebpf:"map1"`
}
// testObjects contains all objects after they have been loaded into the kernel.
//
// It can be passed to loadTestObjects or ebpf.CollectionSpec.LoadAndAssign.
type testObjects struct {
testPrograms
testMaps
}
func (o *testObjects) Close() error {
return _TestClose(
&o.testPrograms,
&o.testMaps,
)
}
// testMaps contains all maps after they have been loaded into the kernel.
//
// It can be passed to loadTestObjects or ebpf.CollectionSpec.LoadAndAssign.
type testMaps struct {
Map1 *ebpf.Map `ebpf:"map1"`
}
func (m *testMaps) Close() error {
return _TestClose(
m.Map1,
)
}
// testPrograms contains all programs after they have been loaded into the kernel.
//
// It can be passed to loadTestObjects or ebpf.CollectionSpec.LoadAndAssign.
type testPrograms struct {
Filter *ebpf.Program `ebpf:"filter"`
}
func (p *testPrograms) Close() error {
return _TestClose(
p.Filter,
)
}
func _TestClose(closers ...io.Closer) error {
for _, closer := range closers {
if err := closer.Close(); err != nil {
return err
}
}
return nil
}
// Do not access this directly.
//
//go:embed test_bpfel.o
var _TestBytes []byte
@@ -0,0 +1,28 @@
#include "../../../testdata/common.h"
char __license[] __section("license") = "MIT";
enum e { HOOPY, FROOD };
typedef long long int longint;
typedef struct {
longint bar;
_Bool baz;
enum e boo;
} barfoo;
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__type(key, enum e);
__type(value, barfoo);
__uint(max_entries, 1);
} map1 __section(".maps");
volatile const enum e my_constant = FROOD;
volatile const barfoo struct_const;
__section("socket") int filter() {
return my_constant + struct_const.bar;
}
@@ -0,0 +1,94 @@
package main
import (
"errors"
"fmt"
"runtime/debug"
"strings"
"unicode"
"unicode/utf8"
)
func splitCFlagsFromArgs(in []string) (args, cflags []string) {
for i, arg := range in {
if arg == "--" {
return in[:i], in[i+1:]
}
}
return in, nil
}
func splitArguments(in string) ([]string, error) {
var (
result []string
builder strings.Builder
escaped bool
delim = ' '
)
for _, r := range strings.TrimSpace(in) {
if escaped {
builder.WriteRune(r)
escaped = false
continue
}
switch r {
case '\\':
escaped = true
case delim:
current := builder.String()
builder.Reset()
if current != "" || delim != ' ' {
// Only append empty words if they are not
// delimited by spaces
result = append(result, current)
}
delim = ' '
case '"', '\'', ' ':
if delim == ' ' {
delim = r
continue
}
fallthrough
default:
builder.WriteRune(r)
}
}
if delim != ' ' {
return nil, fmt.Errorf("missing `%c`", delim)
}
if escaped {
return nil, errors.New("unfinished escape")
}
// Add the last word
if builder.Len() > 0 {
result = append(result, builder.String())
}
return result, nil
}
func toUpperFirst(str string) string {
first, n := utf8.DecodeRuneInString(str)
return string(unicode.ToUpper(first)) + str[n:]
}
func currentModule() string {
bi, ok := debug.ReadBuildInfo()
if !ok {
// Fall back to constant since bazel doesn't support BuildInfo.
return "github.com/cilium/ebpf"
}
return bi.Main.Path
}
@@ -0,0 +1,40 @@
package main
import (
"reflect"
"testing"
)
func TestSplitArguments(t *testing.T) {
testcases := []struct {
in string
out []string
}{
{`foo`, []string{"foo"}},
{`foo bar`, []string{"foo", "bar"}},
{`foo bar`, []string{"foo", "bar"}},
{`\\`, []string{`\`}},
{`\\\`, nil},
{`foo\ bar`, []string{"foo bar"}},
{`foo "" bar`, []string{"foo", "", "bar"}},
{`"bar baz"`, []string{"bar baz"}},
{`'bar baz'`, []string{"bar baz"}},
{`'bar " " baz'`, []string{`bar " " baz`}},
{`"bar \" baz"`, []string{`bar " baz`}},
{`"`, nil},
{`'`, nil},
}
for _, testcase := range testcases {
have, err := splitArguments(testcase.in)
if testcase.out == nil {
if err == nil {
t.Errorf("Test should fail for: %s", testcase.in)
}
} else if !reflect.DeepEqual(testcase.out, have) {
t.Logf("Have: %q\n", have)
t.Logf("Want: %q\n", testcase.out)
t.Errorf("Test fails for: %s", testcase.in)
}
}
}
@@ -0,0 +1,841 @@
package ebpf
import (
"encoding/binary"
"errors"
"fmt"
"reflect"
"strings"
"github.com/cilium/ebpf/asm"
"github.com/cilium/ebpf/btf"
"github.com/cilium/ebpf/internal"
"github.com/cilium/ebpf/internal/kconfig"
)
// CollectionOptions control loading a collection into the kernel.
//
// Maps and Programs are passed to NewMapWithOptions and NewProgramsWithOptions.
type CollectionOptions struct {
Maps MapOptions
Programs ProgramOptions
// MapReplacements takes a set of Maps that will be used instead of
// creating new ones when loading the CollectionSpec.
//
// For each given Map, there must be a corresponding MapSpec in
// CollectionSpec.Maps, and its type, key/value size, max entries and flags
// must match the values of the MapSpec.
//
// 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
}
// CollectionSpec describes a collection.
type CollectionSpec struct {
Maps map[string]*MapSpec
Programs map[string]*ProgramSpec
// Types holds type information about Maps and Programs.
// Modifications to Types are currently undefined behaviour.
Types *btf.Spec
// ByteOrder specifies whether the ELF was compiled for
// big-endian or little-endian architectures.
ByteOrder binary.ByteOrder
}
// Copy returns a recursive copy of the spec.
func (cs *CollectionSpec) Copy() *CollectionSpec {
if cs == nil {
return nil
}
cpy := CollectionSpec{
Maps: make(map[string]*MapSpec, len(cs.Maps)),
Programs: make(map[string]*ProgramSpec, len(cs.Programs)),
ByteOrder: cs.ByteOrder,
Types: cs.Types,
}
for name, spec := range cs.Maps {
cpy.Maps[name] = spec.Copy()
}
for name, spec := range cs.Programs {
cpy.Programs[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)
}
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, ", "))
}
// 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.
func (cs *CollectionSpec) RewriteConstants(consts map[string]interface{}) error {
replaced := make(map[string]bool)
for name, spec := range cs.Maps {
if !strings.HasPrefix(name, ".rodata") {
continue
}
b, ds, err := spec.dataSection()
if errors.Is(err, errMapNoBTFValue) {
// Data sections without a BTF Datasec are valid, but don't support
// constant replacements.
continue
}
if err != nil {
return fmt.Errorf("map %s: %w", name, err)
}
// MapSpec.Copy() performs a shallow copy. Fully copy the byte slice
// to avoid any changes affecting other copies of the MapSpec.
cpy := make([]byte, len(b))
copy(cpy, b)
for _, v := range ds.Vars {
vname := v.Type.TypeName()
replacement, ok := consts[vname]
if !ok {
continue
}
if _, ok := v.Type.(*btf.Var); !ok {
return fmt.Errorf("section %s: unexpected type %T for variable %s", name, v.Type, vname)
}
if replaced[vname] {
return fmt.Errorf("section %s: duplicate variable %s", name, vname)
}
if int(v.Offset+v.Size) > len(cpy) {
return fmt.Errorf("section %s: offset %d(+%d) for variable %s is out of bounds", name, v.Offset, v.Size, vname)
}
b, err := marshalBytes(replacement, int(v.Size))
if err != nil {
return fmt.Errorf("marshaling constant replacement %s: %w", vname, err)
}
copy(cpy[v.Offset:v.Offset+v.Size], b)
replaced[vname] = true
}
spec.Contents[0] = MapKV{Key: uint32(0), Value: cpy}
}
var missing []string
for c := range consts {
if !replaced[c] {
missing = append(missing, c)
}
}
if len(missing) != 0 {
return fmt.Errorf("rewrite constants: %w", &MissingConstantsError{Constants: missing})
}
return nil
}
// Assign the contents of a CollectionSpec to a struct.
//
// This function is a shortcut to manually checking the presence
// of maps and programs in a CollectionSpec. Consider using bpf2go
// if this sounds useful.
//
// 'to' must be a pointer to a struct. A field of the
// struct is updated with values from Programs or Maps if it
// has an `ebpf` tag and its type is *ProgramSpec or *MapSpec.
// The tag's value specifies the name of the program or map as
// found in the CollectionSpec.
//
// struct {
// Foo *ebpf.ProgramSpec `ebpf:"xdp_foo"`
// Bar *ebpf.MapSpec `ebpf:"bar_map"`
// Ignored int
// }
//
// Returns an error if any of the eBPF objects can't be found, or
// if the same MapSpec or ProgramSpec is assigned multiple times.
func (cs *CollectionSpec) Assign(to interface{}) error {
// Assign() only supports assigning ProgramSpecs and MapSpecs,
// so doesn't load any resources into the kernel.
getValue := func(typ reflect.Type, name string) (interface{}, error) {
switch typ {
case reflect.TypeOf((*ProgramSpec)(nil)):
if p := cs.Programs[name]; p != nil {
return p, nil
}
return nil, fmt.Errorf("missing program %q", name)
case reflect.TypeOf((*MapSpec)(nil)):
if m := cs.Maps[name]; m != nil {
return m, nil
}
return nil, fmt.Errorf("missing map %q", name)
default:
return nil, fmt.Errorf("unsupported type %s", typ)
}
}
return assignValues(to, getValue)
}
// LoadAndAssign loads Maps and Programs into the kernel and assigns them
// to a struct.
//
// Omitting Map/Program.Close() during application shutdown is an error.
// See the package documentation for details around Map and Program lifecycle.
//
// This function is a shortcut to manually checking the presence
// of maps and programs in a CollectionSpec. Consider using bpf2go
// if this sounds useful.
//
// 'to' must be a pointer to a struct. A field of the struct is updated with
// a Program or Map if it has an `ebpf` tag and its type is *Program or *Map.
// The tag's value specifies the name of the program or map as found in the
// CollectionSpec. Before updating the struct, the requested objects and their
// dependent resources are loaded into the kernel and populated with values if
// specified.
//
// struct {
// Foo *ebpf.Program `ebpf:"xdp_foo"`
// Bar *ebpf.Map `ebpf:"bar_map"`
// Ignored int
// }
//
// opts may be nil.
//
// 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 {
loader, err := newCollectionLoader(cs, opts)
if err != nil {
return err
}
defer loader.close()
// Support assigning Programs and Maps, lazy-loading the required objects.
assignedMaps := make(map[string]bool)
assignedProgs := make(map[string]bool)
getValue := func(typ reflect.Type, name string) (interface{}, error) {
switch typ {
case reflect.TypeOf((*Program)(nil)):
assignedProgs[name] = true
return loader.loadProgram(name)
case reflect.TypeOf((*Map)(nil)):
assignedMaps[name] = true
return loader.loadMap(name)
default:
return nil, fmt.Errorf("unsupported type %s", typ)
}
}
// Load the Maps and Programs requested by the annotated struct.
if err := assignValues(to, getValue); err != nil {
return err
}
// Populate the requested maps. Has a chance of lazy-loading other dependent maps.
if err := loader.populateMaps(); err != nil {
return err
}
// Evaluate the loader's objects after all (lazy)loading has taken place.
for n, m := range loader.maps {
switch m.typ {
case ProgramArray:
// 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
// closing map fds via GC finalizers this can lead to surprising behaviour.
// Only allow unassigned ProgramArrays when the library hasn't pre-populated
// any entries from static value declarations. At this point, we know the map
// is empty and there's no way for the caller to interact with the map going
// forward.
if !assignedMaps[n] && len(cs.Maps[n].Contents) > 0 {
return fmt.Errorf("ProgramArray %s must be assigned to prevent missed tail calls", n)
}
}
}
// Prevent loader.cleanup() from closing assigned Maps and Programs.
for m := range assignedMaps {
delete(loader.maps, m)
}
for p := range assignedProgs {
delete(loader.programs, p)
}
return nil
}
// Collection is a collection of Programs and Maps associated
// with their symbols
type Collection struct {
Programs map[string]*Program
Maps map[string]*Map
}
// NewCollection creates a Collection from the given spec, creating and
// loading its declared resources into the kernel.
//
// Omitting Collection.Close() during application shutdown is an error.
// See the package documentation for details around Map and Program lifecycle.
func NewCollection(spec *CollectionSpec) (*Collection, error) {
return NewCollectionWithOptions(spec, CollectionOptions{})
}
// NewCollectionWithOptions creates a Collection from the given spec using
// options, creating and loading its declared resources into the kernel.
//
// Omitting Collection.Close() during application shutdown is an error.
// See the package documentation for details around Map and Program lifecycle.
func NewCollectionWithOptions(spec *CollectionSpec, opts CollectionOptions) (*Collection, error) {
loader, err := newCollectionLoader(spec, &opts)
if err != nil {
return nil, err
}
defer loader.close()
// Create maps first, as their fds need to be linked into programs.
for mapName := range spec.Maps {
if _, err := loader.loadMap(mapName); err != nil {
return nil, err
}
}
for progName, prog := range spec.Programs {
if prog.Type == UnspecifiedProgram {
continue
}
if _, err := loader.loadProgram(progName); err != nil {
return nil, err
}
}
// Maps can contain Program and Map stubs, so populate them after
// all Maps and Programs have been successfully loaded.
if err := loader.populateMaps(); err != nil {
return nil, err
}
// Prevent loader.cleanup from closing maps and programs.
maps, progs := loader.maps, loader.programs
loader.maps, loader.programs = nil, nil
return &Collection{
progs,
maps,
}, nil
}
type collectionLoader struct {
coll *CollectionSpec
opts *CollectionOptions
maps map[string]*Map
programs map[string]*Program
}
func newCollectionLoader(coll *CollectionSpec, opts *CollectionOptions) (*collectionLoader, error) {
if opts == nil {
opts = &CollectionOptions{}
}
// Check for existing MapSpecs in the CollectionSpec for all provided replacement maps.
for name, m := range opts.MapReplacements {
spec, ok := coll.Maps[name]
if !ok {
return nil, fmt.Errorf("replacement map %s not found in CollectionSpec", name)
}
if err := spec.Compatible(m); err != nil {
return nil, fmt.Errorf("using replacement map %s: %w", spec.Name, err)
}
}
return &collectionLoader{
coll,
opts,
make(map[string]*Map),
make(map[string]*Program),
}, nil
}
// close all resources left over in the collectionLoader.
func (cl *collectionLoader) close() {
for _, m := range cl.maps {
m.Close()
}
for _, p := range cl.programs {
p.Close()
}
}
func (cl *collectionLoader) loadMap(mapName string) (*Map, error) {
if m := cl.maps[mapName]; m != nil {
return m, nil
}
mapSpec := cl.coll.Maps[mapName]
if mapSpec == nil {
return nil, fmt.Errorf("missing map %s", mapName)
}
if replaceMap, ok := cl.opts.MapReplacements[mapName]; ok {
// Clone the map to avoid closing user's map later on.
m, err := replaceMap.Clone()
if err != nil {
return nil, err
}
cl.maps[mapName] = m
return m, nil
}
m, err := newMapWithOptions(mapSpec, cl.opts.Maps)
if err != nil {
return nil, fmt.Errorf("map %s: %w", mapName, err)
}
cl.maps[mapName] = m
return m, nil
}
func (cl *collectionLoader) loadProgram(progName string) (*Program, error) {
if prog := cl.programs[progName]; prog != nil {
return prog, nil
}
progSpec := cl.coll.Programs[progName]
if progSpec == nil {
return nil, fmt.Errorf("unknown program %s", progName)
}
// Bail out early if we know the kernel is going to reject the program.
// This skips loading map dependencies, saving some cleanup work later.
if progSpec.Type == UnspecifiedProgram {
return nil, fmt.Errorf("cannot load program %s: program type is unspecified", progName)
}
progSpec = progSpec.Copy()
// Rewrite any reference to a valid map in the program's instructions,
// which includes all of its dependencies.
for i := range progSpec.Instructions {
ins := &progSpec.Instructions[i]
if !ins.IsLoadFromMap() || ins.Reference() == "" {
continue
}
// Don't overwrite map loads containing non-zero map fd's,
// they can be manually included by the caller.
// Map FDs/IDs are placed in the lower 32 bits of Constant.
if int32(ins.Constant) > 0 {
continue
}
m, err := cl.loadMap(ins.Reference())
if err != nil {
return nil, fmt.Errorf("program %s: %w", progName, err)
}
if err := ins.AssociateMap(m); err != nil {
return nil, fmt.Errorf("program %s: map %s: %w", progName, ins.Reference(), err)
}
}
prog, err := newProgramWithOptions(progSpec, cl.opts.Programs)
if err != nil {
return nil, fmt.Errorf("program %s: %w", progName, err)
}
cl.programs[progName] = prog
return prog, nil
}
func (cl *collectionLoader) populateMaps() error {
for mapName, m := range cl.maps {
mapSpec, ok := cl.coll.Maps[mapName]
if !ok {
return fmt.Errorf("missing map spec %s", mapName)
}
// MapSpecs that refer to inner maps or programs within the same
// CollectionSpec do so using strings. These strings are used as the key
// to look up the respective object in the Maps or Programs fields.
// Resolve those references to actual Map or Program resources that
// have been loaded into the kernel.
if mapSpec.Type.canStoreMap() || mapSpec.Type.canStoreProgram() {
mapSpec = mapSpec.Copy()
for i, kv := range mapSpec.Contents {
objName, ok := kv.Value.(string)
if !ok {
continue
}
switch t := mapSpec.Type; {
case t.canStoreProgram():
// loadProgram is idempotent and could return an existing Program.
prog, err := cl.loadProgram(objName)
if err != nil {
return fmt.Errorf("loading program %s, for map %s: %w", objName, mapName, err)
}
mapSpec.Contents[i] = MapKV{kv.Key, prog}
case t.canStoreMap():
// loadMap is idempotent and could return an existing Map.
innerMap, err := cl.loadMap(objName)
if err != nil {
return fmt.Errorf("loading inner map %s, for map %s: %w", objName, mapName, err)
}
mapSpec.Contents[i] = MapKV{kv.Key, innerMap}
}
}
}
// Populate and freeze the map if specified.
if err := m.finalize(mapSpec); err != nil {
return fmt.Errorf("populating map %s: %w", mapName, err)
}
}
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 {
ds, ok := m.Value.(*btf.Datasec)
if !ok {
return errors.New("map value is not a Datasec")
}
type configInfo struct {
offset uint32
typ btf.Type
}
configs := make(map[string]configInfo)
data := make([]byte, ds.Size)
for _, vsi := range ds.Vars {
v := vsi.Type.(*btf.Var)
n := v.TypeName()
switch n {
case "LINUX_KERNEL_VERSION":
if integer, ok := v.Type.(*btf.Int); !ok || integer.Size != 4 {
return fmt.Errorf("variable %s must be a 32 bits integer, got %s", n, v.Type)
}
kv, err := internal.KernelVersion()
if err != nil {
return fmt.Errorf("getting kernel version: %w", err)
}
internal.NativeEndian.PutUint32(data[vsi.Offset:], kv.Kernel())
case "LINUX_HAS_SYSCALL_WRAPPER":
if integer, ok := v.Type.(*btf.Int); !ok || integer.Size != 4 {
return fmt.Errorf("variable %s must be a 32 bits integer, got %s", n, v.Type)
}
var value uint32 = 1
if err := haveSyscallWrapper(); errors.Is(err, ErrNotSupported) {
value = 0
} else if err != nil {
return fmt.Errorf("unable to derive a value for LINUX_HAS_SYSCALL_WRAPPER: %w", err)
}
internal.NativeEndian.PutUint32(data[vsi.Offset:], value)
default: // Catch CONFIG_*.
configs[n] = configInfo{
offset: vsi.Offset,
typ: v.Type,
}
}
}
// We only parse kconfig file if a CONFIG_* variable was found.
if len(configs) > 0 {
f, err := kconfig.Find()
if err != nil {
return fmt.Errorf("cannot find a kconfig file: %w", err)
}
defer f.Close()
filter := make(map[string]struct{}, len(configs))
for config := range configs {
filter[config] = struct{}{}
}
kernelConfig, err := kconfig.Parse(f, filter)
if err != nil {
return fmt.Errorf("cannot parse kconfig file: %w", err)
}
for n, info := range configs {
value, ok := kernelConfig[n]
if !ok {
return fmt.Errorf("config option %q does not exists for this kernel", n)
}
err := kconfig.PutValue(data[info.offset:], info.typ, value)
if err != nil {
return fmt.Errorf("problem adding value for %s: %w", n, err)
}
}
}
m.Contents = []MapKV{{uint32(0), data}}
return nil
}
// LoadCollection reads an object file and creates and loads its declared
// resources into the kernel.
//
// 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) {
spec, err := LoadCollectionSpec(file)
if err != nil {
return nil, err
}
return NewCollection(spec)
}
// Close frees all maps and programs associated with the collection.
//
// The collection mustn't be used afterwards.
func (coll *Collection) Close() {
for _, prog := range coll.Programs {
prog.Close()
}
for _, m := range coll.Maps {
m.Close()
}
}
// DetachMap removes the named map from the Collection.
//
// This means that a later call to Close() will not affect this map.
//
// Returns nil if no map of that name exists.
func (coll *Collection) DetachMap(name string) *Map {
m := coll.Maps[name]
delete(coll.Maps, name)
return m
}
// DetachProgram removes the named program from the Collection.
//
// This means that a later call to Close() will not affect this program.
//
// Returns nil if no program of that name exists.
func (coll *Collection) DetachProgram(name string) *Program {
p := coll.Programs[name]
delete(coll.Programs, name)
return p
}
// structField represents a struct field containing the ebpf struct tag.
type structField struct {
reflect.StructField
value reflect.Value
}
// ebpfFields extracts field names tagged with 'ebpf' from a struct type.
// Keep track of visited types to avoid infinite recursion.
func ebpfFields(structVal reflect.Value, visited map[reflect.Type]bool) ([]structField, error) {
if visited == nil {
visited = make(map[reflect.Type]bool)
}
structType := structVal.Type()
if structType.Kind() != reflect.Struct {
return nil, fmt.Errorf("%s is not a struct", structType)
}
if visited[structType] {
return nil, fmt.Errorf("recursion on type %s", structType)
}
fields := make([]structField, 0, structType.NumField())
for i := 0; i < structType.NumField(); i++ {
field := structField{structType.Field(i), structVal.Field(i)}
// If the field is tagged, gather it and move on.
name := field.Tag.Get("ebpf")
if name != "" {
fields = append(fields, field)
continue
}
// If the field does not have an ebpf tag, but is a struct or a pointer
// to a struct, attempt to gather its fields as well.
var v reflect.Value
switch field.Type.Kind() {
case reflect.Ptr:
if field.Type.Elem().Kind() != reflect.Struct {
continue
}
if field.value.IsNil() {
return nil, fmt.Errorf("nil pointer to %s", structType)
}
// Obtain the destination type of the pointer.
v = field.value.Elem()
case reflect.Struct:
// Reference the value's type directly.
v = field.value
default:
continue
}
inner, err := ebpfFields(v, visited)
if err != nil {
return nil, fmt.Errorf("field %s: %w", field.Name, err)
}
fields = append(fields, inner...)
}
return fields, nil
}
// assignValues attempts to populate all fields of 'to' tagged with 'ebpf'.
//
// 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 {
toValue := reflect.ValueOf(to)
if toValue.Type().Kind() != reflect.Ptr {
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
}
type elem struct {
// Either *Map or *Program
typ reflect.Type
name string
}
assigned := make(map[elem]string)
for _, field := range fields {
// Get string value the field is tagged with.
tag := field.Tag.Get("ebpf")
if strings.Contains(tag, ",") {
return fmt.Errorf("field %s: ebpf tag contains a comma", field.Name)
}
// Check if the eBPF object with the requested
// type and tag was already assigned elsewhere.
e := elem{field.Type, tag}
if af := assigned[e]; af != "" {
return fmt.Errorf("field %s: object %q was already assigned to %s", field.Name, tag, af)
}
// Get the eBPF object referred to by the tag.
value, err := getValue(field.Type, tag)
if err != nil {
return fmt.Errorf("field %s: %w", field.Name, err)
}
if !field.value.CanSet() {
return fmt.Errorf("field %s: can't set value", field.Name)
}
field.value.Set(reflect.ValueOf(value))
assigned[e] = field.Name
}
return nil
}
@@ -0,0 +1,730 @@
package ebpf
import (
"errors"
"fmt"
"reflect"
"testing"
"github.com/cilium/ebpf/asm"
"github.com/cilium/ebpf/btf"
"github.com/cilium/ebpf/internal"
"github.com/cilium/ebpf/internal/testutils"
"github.com/cilium/ebpf/internal/testutils/fdtrace"
qt "github.com/frankban/quicktest"
)
func TestMain(m *testing.M) {
fdtrace.TestMain(m)
}
func TestCollectionSpecNotModified(t *testing.T) {
cs := CollectionSpec{
Maps: map[string]*MapSpec{
"my-map": {
Type: Array,
KeySize: 4,
ValueSize: 4,
MaxEntries: 1,
},
},
Programs: map[string]*ProgramSpec{
"test": {
Type: SocketFilter,
Instructions: asm.Instructions{
asm.LoadImm(asm.R1, 0, asm.DWord).WithReference("my-map"),
asm.LoadImm(asm.R0, 0, asm.DWord),
asm.Return(),
},
License: "MIT",
},
},
}
coll, err := NewCollection(&cs)
if err != nil {
t.Fatal(err)
}
coll.Close()
if cs.Programs["test"].Instructions[0].Constant != 0 {
t.Error("Creating a collection modifies input spec")
}
}
func TestCollectionSpecCopy(t *testing.T) {
cs := &CollectionSpec{
Maps: map[string]*MapSpec{
"my-map": {
Type: Array,
KeySize: 4,
ValueSize: 4,
MaxEntries: 1,
},
},
Programs: map[string]*ProgramSpec{
"test": {
Type: SocketFilter,
Instructions: asm.Instructions{
asm.LoadMapPtr(asm.R1, 0),
asm.LoadImm(asm.R0, 0, asm.DWord),
asm.Return(),
},
License: "MIT",
},
},
Types: &btf.Spec{},
}
cpy := cs.Copy()
if cpy == cs {
t.Error("Copy returned the same pointner")
}
if cpy.Maps["my-map"] == cs.Maps["my-map"] {
t.Error("Copy returned same Maps")
}
if cpy.Programs["test"] == cs.Programs["test"] {
t.Error("Copy returned same Programs")
}
if cpy.Types != cs.Types {
t.Error("Copy returned different Types")
}
}
func TestCollectionSpecLoadCopy(t *testing.T) {
file := fmt.Sprintf("testdata/loader-%s.elf", internal.ClangEndian)
spec, err := LoadCollectionSpec(file)
if err != nil {
t.Fatal(err)
}
spec2 := spec.Copy()
var objs struct {
Prog *Program `ebpf:"xdp_prog"`
}
err = spec.LoadAndAssign(&objs, nil)
testutils.SkipIfNotSupported(t, err)
if err != nil {
t.Fatal("Loading original spec:", err)
}
defer objs.Prog.Close()
if err := spec2.LoadAndAssign(&objs, nil); err != nil {
t.Fatal("Loading copied spec:", err)
}
defer objs.Prog.Close()
}
func TestCollectionSpecRewriteMaps(t *testing.T) {
insns := asm.Instructions{
// R1 map
asm.LoadMapPtr(asm.R1, 0).WithReference("test-map"),
// R2 key
asm.Mov.Reg(asm.R2, asm.R10),
asm.Add.Imm(asm.R2, -4),
asm.StoreImm(asm.R2, 0, 0, asm.Word),
// Lookup map[0]
asm.FnMapLookupElem.Call(),
asm.JEq.Imm(asm.R0, 0, "ret"),
asm.LoadMem(asm.R0, asm.R0, 0, asm.Word),
asm.Return().WithSymbol("ret"),
}
cs := &CollectionSpec{
Maps: map[string]*MapSpec{
"test-map": {
Type: Array,
KeySize: 4,
ValueSize: 4,
MaxEntries: 1,
},
},
Programs: map[string]*ProgramSpec{
"test-prog": {
Type: SocketFilter,
Instructions: insns,
License: "MIT",
},
},
}
// Override the map with another one
newMap, err := NewMap(cs.Maps["test-map"])
if err != nil {
t.Fatal(err)
}
defer newMap.Close()
err = newMap.Put(uint32(0), uint32(2))
if err != nil {
t.Fatal(err)
}
err = cs.RewriteMaps(map[string]*Map{
"test-map": newMap,
})
if err != nil {
t.Fatal(err)
}
if cs.Maps["test-map"] != nil {
t.Error("RewriteMaps doesn't remove map from CollectionSpec.Maps")
}
coll, err := NewCollection(cs)
if err != nil {
t.Fatal(err)
}
defer coll.Close()
ret, _, err := coll.Programs["test-prog"].Test(internal.EmptyBPFContext)
testutils.SkipIfNotSupported(t, err)
if err != nil {
t.Fatal(err)
}
if ret != 2 {
t.Fatal("new / override map not used")
}
}
func TestCollectionSpecMapReplacements(t *testing.T) {
insns := asm.Instructions{
// R1 map
asm.LoadMapPtr(asm.R1, 0).WithReference("test-map"),
// R2 key
asm.Mov.Reg(asm.R2, asm.R10),
asm.Add.Imm(asm.R2, -4),
asm.StoreImm(asm.R2, 0, 0, asm.Word),
// Lookup map[0]
asm.FnMapLookupElem.Call(),
asm.JEq.Imm(asm.R0, 0, "ret"),
asm.LoadMem(asm.R0, asm.R0, 0, asm.Word),
asm.Return().WithSymbol("ret"),
}
cs := &CollectionSpec{
Maps: map[string]*MapSpec{
"test-map": {
Type: Array,
KeySize: 4,
ValueSize: 4,
MaxEntries: 1,
},
},
Programs: map[string]*ProgramSpec{
"test-prog": {
Type: SocketFilter,
Instructions: insns,
License: "MIT",
},
},
}
// Replace the map with another one
newMap, err := NewMap(cs.Maps["test-map"])
if err != nil {
t.Fatal(err)
}
defer newMap.Close()
err = newMap.Put(uint32(0), uint32(2))
if err != nil {
t.Fatal(err)
}
coll, err := NewCollectionWithOptions(cs, CollectionOptions{
MapReplacements: map[string]*Map{
"test-map": newMap,
},
})
if err != nil {
t.Fatal(err)
}
defer coll.Close()
ret, _, err := coll.Programs["test-prog"].Test(internal.EmptyBPFContext)
testutils.SkipIfNotSupported(t, err)
if err != nil {
t.Fatal(err)
}
if ret != 2 {
t.Fatal("new / override map not used")
}
// Check that newMap isn't closed when the collection is closed
coll.Close()
err = newMap.Put(uint32(0), uint32(3))
if err != nil {
t.Fatalf("failed to update replaced map: %s", err)
}
}
func TestCollectionSpecMapReplacements_NonExistingMap(t *testing.T) {
cs := &CollectionSpec{
Maps: map[string]*MapSpec{
"test-map": {
Type: Array,
KeySize: 4,
ValueSize: 4,
MaxEntries: 1,
},
},
}
// Override non-existing map
newMap, err := NewMap(cs.Maps["test-map"])
if err != nil {
t.Fatal(err)
}
defer newMap.Close()
coll, err := NewCollectionWithOptions(cs, CollectionOptions{
MapReplacements: map[string]*Map{
"non-existing-map": newMap,
},
})
if err == nil {
coll.Close()
t.Fatal("Overriding a non existing map did not fail")
}
}
func TestCollectionSpecMapReplacements_SpecMismatch(t *testing.T) {
cs := &CollectionSpec{
Maps: map[string]*MapSpec{
"test-map": {
Type: Array,
KeySize: 4,
ValueSize: 4,
MaxEntries: 1,
},
},
}
// Override map with mismatching spec
newMap, err := NewMap(&MapSpec{
Type: Array,
KeySize: 4,
ValueSize: 8, // this is different
MaxEntries: 1,
})
if err != nil {
t.Fatal(err)
}
// Map fd is duplicated by MapReplacements, this one can be safely closed.
defer newMap.Close()
coll, err := NewCollectionWithOptions(cs, CollectionOptions{
MapReplacements: map[string]*Map{
"test-map": newMap,
},
})
if err == nil {
coll.Close()
t.Fatal("Overriding a map with a mismatching spec did not fail")
}
if !errors.Is(err, ErrMapIncompatible) {
t.Fatalf("Overriding a map with a mismatching spec failed with the wrong error")
}
}
func TestCollectionRewriteConstants(t *testing.T) {
cs := &CollectionSpec{
Maps: map[string]*MapSpec{
".rodata": {
Type: Array,
KeySize: 4,
ValueSize: 4,
MaxEntries: 1,
Value: &btf.Datasec{
Vars: []btf.VarSecinfo{
{
Type: &btf.Var{
Name: "the_constant",
Type: &btf.Int{Size: 4},
},
Offset: 0,
Size: 4,
},
},
},
Contents: []MapKV{
{Key: uint32(0), Value: []byte{1, 1, 1, 1}},
},
},
},
}
err := cs.RewriteConstants(map[string]interface{}{
"fake_constant_one": uint32(1),
"fake_constant_two": uint32(2),
})
qt.Assert(t, err, qt.IsNotNil, qt.Commentf("RewriteConstants did not fail"))
var mErr *MissingConstantsError
if !errors.As(err, &mErr) {
t.Fatal("Error doesn't wrap MissingConstantsError:", err)
}
qt.Assert(t, mErr.Constants, qt.ContentEquals, []string{"fake_constant_one", "fake_constant_two"})
err = cs.RewriteConstants(map[string]interface{}{
"the_constant": uint32(0x42424242),
})
qt.Assert(t, err, qt.IsNil)
qt.Assert(t, cs.Maps[".rodata"].Contents[0].Value, qt.ContentEquals, []byte{0x42, 0x42, 0x42, 0x42})
}
func TestCollectionSpec_LoadAndAssign_LazyLoading(t *testing.T) {
spec := &CollectionSpec{
Maps: map[string]*MapSpec{
"valid": {
Type: Array,
KeySize: 4,
ValueSize: 4,
MaxEntries: 1,
},
"bogus": {
Type: Array,
MaxEntries: 0,
},
},
Programs: map[string]*ProgramSpec{
"valid": {
Type: SocketFilter,
Instructions: asm.Instructions{
asm.LoadImm(asm.R0, 0, asm.DWord),
asm.Return(),
},
License: "MIT",
},
"bogus": {
Type: SocketFilter,
Instructions: asm.Instructions{
// Undefined return value is rejected
asm.Return(),
},
License: "MIT",
},
},
}
var objs struct {
Prog *Program `ebpf:"valid"`
Map *Map `ebpf:"valid"`
}
if err := spec.LoadAndAssign(&objs, nil); err != nil {
t.Fatal("Assign loads a map or program that isn't requested in the struct:", err)
}
defer objs.Prog.Close()
defer objs.Map.Close()
if objs.Prog == nil {
t.Error("Program is nil")
}
if objs.Map == nil {
t.Error("Map is nil")
}
}
func TestCollectionAssign(t *testing.T) {
var specs struct {
Program *ProgramSpec `ebpf:"prog1"`
Map *MapSpec `ebpf:"map1"`
}
mapSpec := &MapSpec{
Type: Array,
KeySize: 4,
ValueSize: 4,
MaxEntries: 1,
}
progSpec := &ProgramSpec{
Type: SocketFilter,
Instructions: asm.Instructions{
asm.LoadImm(asm.R0, 0, asm.DWord),
asm.Return(),
},
License: "MIT",
}
cs := &CollectionSpec{
Maps: map[string]*MapSpec{
"map1": mapSpec,
},
Programs: map[string]*ProgramSpec{
"prog1": progSpec,
},
}
if err := cs.Assign(&specs); err != nil {
t.Fatal("Can't assign spec:", err)
}
if specs.Program != progSpec {
t.Fatalf("Expected Program to be %p, got %p", progSpec, specs.Program)
}
if specs.Map != mapSpec {
t.Fatalf("Expected Map to be %p, got %p", mapSpec, specs.Map)
}
if err := cs.Assign(new(int)); err == nil {
t.Fatal("Assign allows to besides *struct")
}
if err := cs.Assign(new(struct{ Foo int })); err != nil {
t.Fatal("Assign doesn't ignore untagged fields")
}
unexported := new(struct {
foo *MapSpec `ebpf:"map1"`
})
if err := cs.Assign(unexported); err == nil {
t.Error("Assign should return an error on unexported fields")
}
}
func TestAssignValues(t *testing.T) {
zero := func(t reflect.Type, name string) (interface{}, error) {
return reflect.Zero(t).Interface(), nil
}
type t1 struct {
Bar int `ebpf:"bar"`
}
type t2 struct {
t1
Foo int `ebpf:"foo"`
}
type t2ptr struct {
*t1
Foo int `ebpf:"foo"`
}
invalid := []struct {
name string
to interface{}
}{
{"non-struct", 1},
{"non-pointer struct", t1{}},
{"pointer to non-struct", new(int)},
{"embedded nil pointer", &t2ptr{}},
{"unexported field", new(struct {
foo int `ebpf:"foo"`
})},
{"identical tag", new(struct {
Foo1 int `ebpf:"foo"`
Foo2 int `ebpf:"foo"`
})},
}
for _, testcase := range invalid {
t.Run(testcase.name, func(t *testing.T) {
if err := assignValues(testcase.to, zero); err == nil {
t.Fatal("assignValues didn't return an error")
} else {
t.Log(err)
}
})
}
valid := []struct {
name string
to interface{}
}{
{"pointer to struct", new(t1)},
{"embedded struct", new(t2)},
{"embedded struct pointer", &t2ptr{t1: new(t1)}},
{"untagged field", new(struct{ Foo int })},
}
for _, testcase := range valid {
t.Run(testcase.name, func(t *testing.T) {
if err := assignValues(testcase.to, zero); err != nil {
t.Fatal("assignValues returned", err)
}
})
}
}
func TestIncompleteLoadAndAssign(t *testing.T) {
spec := &CollectionSpec{
Programs: map[string]*ProgramSpec{
"valid": {
Type: SocketFilter,
Instructions: asm.Instructions{
asm.LoadImm(asm.R0, 0, asm.DWord),
asm.Return(),
},
License: "MIT",
},
"invalid": {
Type: SocketFilter,
Instructions: asm.Instructions{
asm.Return(),
},
License: "MIT",
},
},
}
s := struct {
// Assignment to Valid should execute and succeed.
Valid *Program `ebpf:"valid"`
// Assignment to Invalid should fail and cause Valid's fd to be closed.
Invalid *Program `ebpf:"invalid"`
}{}
if err := spec.LoadAndAssign(&s, nil); err == nil {
t.Fatal("expected error loading invalid ProgramSpec")
}
if s.Valid == nil {
t.Fatal("expected valid prog to be non-nil")
}
if fd := s.Valid.FD(); fd != -1 {
t.Fatal("expected valid prog to have closed fd -1, got:", fd)
}
if s.Invalid != nil {
t.Fatal("expected invalid prog to be nil due to never being assigned")
}
}
func BenchmarkNewCollection(b *testing.B) {
file := fmt.Sprintf("testdata/loader-%s.elf", internal.ClangEndian)
spec, err := LoadCollectionSpec(file)
if err != nil {
b.Fatal(err)
}
spec.Maps["array_of_hash_map"].InnerMap = spec.Maps["hash_map"]
for _, m := range spec.Maps {
m.Pinning = PinNone
}
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
coll, err := NewCollection(spec)
if err != nil {
b.Fatal(err)
}
coll.Close()
}
}
func BenchmarkNewCollectionManyProgs(b *testing.B) {
file := fmt.Sprintf("testdata/manyprogs-%s.elf", internal.ClangEndian)
spec, err := LoadCollectionSpec(file)
if err != nil {
b.Fatal(err)
}
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
coll, err := NewCollection(spec)
if err != nil {
b.Fatal(err)
}
coll.Close()
}
}
func ExampleCollectionSpec_Assign() {
spec := &CollectionSpec{
Maps: map[string]*MapSpec{
"map1": {
Type: Array,
KeySize: 4,
ValueSize: 4,
MaxEntries: 1,
},
},
Programs: map[string]*ProgramSpec{
"prog1": {
Type: SocketFilter,
Instructions: asm.Instructions{
asm.LoadImm(asm.R0, 0, asm.DWord),
asm.Return(),
},
License: "MIT",
},
},
}
type maps struct {
Map *MapSpec `ebpf:"map1"`
}
var specs struct {
maps
Program *ProgramSpec `ebpf:"prog1"`
}
if err := spec.Assign(&specs); err != nil {
panic(err)
}
fmt.Println(specs.Program.Type)
fmt.Println(specs.Map.Type)
// Output: SocketFilter
// Array
}
func ExampleCollectionSpec_LoadAndAssign() {
spec := &CollectionSpec{
Maps: map[string]*MapSpec{
"map1": {
Type: Array,
KeySize: 4,
ValueSize: 4,
MaxEntries: 1,
},
},
Programs: map[string]*ProgramSpec{
"prog1": {
Type: SocketFilter,
Instructions: asm.Instructions{
asm.LoadImm(asm.R0, 0, asm.DWord),
asm.Return(),
},
License: "MIT",
},
},
}
var objs struct {
Program *Program `ebpf:"prog1"`
Map *Map `ebpf:"map1"`
}
if err := spec.LoadAndAssign(&objs, nil); err != nil {
panic(err)
}
defer objs.Program.Close()
defer objs.Map.Close()
fmt.Println(objs.Program.Type())
fmt.Println(objs.Map.Type())
// Output: SocketFilter
// Array
}
@@ -0,0 +1,25 @@
// Package ebpf is a toolkit for working with eBPF programs.
//
// eBPF programs are small snippets of code which are executed directly
// in a VM in the Linux kernel, which makes them very fast and flexible.
// Many Linux subsystems now accept eBPF programs. This makes it possible
// to implement highly application specific logic inside the kernel,
// without having to modify the actual kernel itself.
//
// This package is designed for long-running processes which
// want to use eBPF to implement part of their application logic. It has no
// run-time dependencies outside of the library and the Linux kernel itself.
// eBPF code should be compiled ahead of time using clang, and shipped with
// your application as any other resource.
//
// Use the link subpackage to attach a loaded program to a hook in the kernel.
//
// Note that losing all references to Map and Program resources will cause
// their underlying file descriptors to be closed, potentially removing those
// objects from the kernel. Always retain a reference by e.g. deferring a
// Close() of a Collection or LoadAndAssign object until application exit.
//
// Special care needs to be taken when handling maps of type ProgramArray,
// as the kernel erases its contents when the last userspace or bpffs
// reference disappears, regardless of the map being in active use.
package ebpf
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,166 @@
//go:build linux
package ebpf_test
import (
"bytes"
"encoding/binary"
"flag"
"fmt"
"syscall"
"time"
"unsafe"
"github.com/cilium/ebpf"
)
var program = [...]byte{
0177, 0105, 0114, 0106, 0002, 0001, 0001, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000,
0001, 0000, 0367, 0000, 0001, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000,
0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0340, 0001, 0000, 0000, 0000, 0000, 0000, 0000,
0000, 0000, 0000, 0000, 0100, 0000, 0000, 0000, 0000, 0000, 0100, 0000, 0010, 0000, 0001, 0000,
0277, 0026, 0000, 0000, 0000, 0000, 0000, 0000, 0060, 0000, 0000, 0000, 0027, 0000, 0000, 0000,
0143, 0012, 0374, 0377, 0000, 0000, 0000, 0000, 0141, 0141, 0004, 0000, 0000, 0000, 0000, 0000,
0125, 0001, 0010, 0000, 0004, 0000, 0000, 0000, 0277, 0242, 0000, 0000, 0000, 0000, 0000, 0000,
0007, 0002, 0000, 0000, 0374, 0377, 0377, 0377, 0030, 0001, 0000, 0000, 0000, 0000, 0000, 0000,
0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0205, 0000, 0000, 0000, 0001, 0000, 0000, 0000,
0025, 0000, 0002, 0000, 0000, 0000, 0000, 0000, 0141, 0141, 0000, 0000, 0000, 0000, 0000, 0000,
0333, 0020, 0000, 0000, 0000, 0000, 0000, 0000, 0267, 0000, 0000, 0000, 0000, 0000, 0000, 0000,
0225, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0002, 0000, 0000, 0000, 0004, 0000, 0000, 0000,
0010, 0000, 0000, 0000, 0000, 0001, 0000, 0000, 0000, 0000, 0000, 0000, 0002, 0000, 0000, 0000,
0004, 0000, 0000, 0000, 0010, 0000, 0000, 0000, 0000, 0001, 0000, 0000, 0000, 0000, 0000, 0000,
0107, 0120, 0114, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000,
0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000,
0065, 0000, 0000, 0000, 0000, 0000, 0003, 0000, 0150, 0000, 0000, 0000, 0000, 0000, 0000, 0000,
0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0034, 0000, 0000, 0000, 0020, 0000, 0006, 0000,
0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000,
0110, 0000, 0000, 0000, 0020, 0000, 0003, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000,
0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0014, 0000, 0000, 0000, 0020, 0000, 0005, 0000,
0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000,
0023, 0000, 0000, 0000, 0020, 0000, 0005, 0000, 0024, 0000, 0000, 0000, 0000, 0000, 0000, 0000,
0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0070, 0000, 0000, 0000, 0000, 0000, 0000, 0000,
0001, 0000, 0000, 0000, 0004, 0000, 0000, 0000, 0000, 0056, 0164, 0145, 0170, 0164, 0000, 0155,
0141, 0160, 0163, 0000, 0155, 0171, 0137, 0155, 0141, 0160, 0000, 0164, 0145, 0163, 0164, 0137,
0155, 0141, 0160, 0000, 0137, 0154, 0151, 0143, 0145, 0156, 0163, 0145, 0000, 0056, 0163, 0164,
0162, 0164, 0141, 0142, 0000, 0056, 0163, 0171, 0155, 0164, 0141, 0142, 0000, 0114, 0102, 0102,
0060, 0137, 0063, 0000, 0056, 0162, 0145, 0154, 0163, 0157, 0143, 0153, 0145, 0164, 0061, 0000,
0142, 0160, 0146, 0137, 0160, 0162, 0157, 0147, 0061, 0000, 0000, 0000, 0000, 0000, 0000, 0000,
0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000,
0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000,
0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000,
0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000,
0045, 0000, 0000, 0000, 0003, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000,
0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0210, 0001, 0000, 0000, 0000, 0000, 0000, 0000,
0122, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000,
0001, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000,
0001, 0000, 0000, 0000, 0001, 0000, 0000, 0000, 0006, 0000, 0000, 0000, 0000, 0000, 0000, 0000,
0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0100, 0000, 0000, 0000, 0000, 0000, 0000, 0000,
0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000,
0004, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000,
0100, 0000, 0000, 0000, 0001, 0000, 0000, 0000, 0006, 0000, 0000, 0000, 0000, 0000, 0000, 0000,
0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0100, 0000, 0000, 0000, 0000, 0000, 0000, 0000,
0170, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000,
0010, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000,
0074, 0000, 0000, 0000, 0011, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000,
0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0170, 0001, 0000, 0000, 0000, 0000, 0000, 0000,
0020, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0007, 0000, 0000, 0000, 0003, 0000, 0000, 0000,
0010, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0020, 0000, 0000, 0000, 0000, 0000, 0000, 0000,
0007, 0000, 0000, 0000, 0001, 0000, 0000, 0000, 0003, 0000, 0000, 0000, 0000, 0000, 0000, 0000,
0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0270, 0000, 0000, 0000, 0000, 0000, 0000, 0000,
0050, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000,
0004, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000,
0035, 0000, 0000, 0000, 0001, 0000, 0000, 0000, 0003, 0000, 0000, 0000, 0000, 0000, 0000, 0000,
0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0340, 0000, 0000, 0000, 0000, 0000, 0000, 0000,
0004, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000,
0001, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000,
0055, 0000, 0000, 0000, 0002, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000,
0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0350, 0000, 0000, 0000, 0000, 0000, 0000, 0000,
0220, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0001, 0000, 0000, 0000, 0002, 0000, 0000, 0000,
0010, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0030, 0000, 0000, 0000, 0000, 0000, 0000, 0000,
}
// ExampleSocketELF demonstrates how to load an eBPF program from an ELF,
// and attach it to a raw socket.
func Example_socketELF() {
const SO_ATTACH_BPF = 50
index := flag.Int("index", 0, "specify ethernet index")
flag.Parse()
spec, err := ebpf.LoadCollectionSpecFromReader(bytes.NewReader(program[:]))
if err != nil {
panic(err)
}
var objs struct {
Prog *ebpf.Program `ebpf:"bpf_prog1"`
Stats *ebpf.Map `ebpf:"my_map"`
}
if err := spec.LoadAndAssign(&objs, nil); err != nil {
panic(err)
}
defer objs.Prog.Close()
defer objs.Stats.Close()
sock, err := openRawSock(*index)
if err != nil {
panic(err)
}
defer syscall.Close(sock)
if err := syscall.SetsockoptInt(sock, syscall.SOL_SOCKET, SO_ATTACH_BPF, objs.Prog.FD()); err != nil {
panic(err)
}
fmt.Printf("Filtering on eth index: %d\n", *index)
fmt.Println("Packet stats:")
for {
const (
ICMP = 0x01
TCP = 0x06
UDP = 0x11
)
time.Sleep(time.Second)
var icmp uint64
var tcp uint64
var udp uint64
err := objs.Stats.Lookup(uint32(ICMP), &icmp)
if err != nil {
panic(err)
}
err = objs.Stats.Lookup(uint32(TCP), &tcp)
if err != nil {
panic(err)
}
err = objs.Stats.Lookup(uint32(UDP), &udp)
if err != nil {
panic(err)
}
fmt.Printf("\r\033[m\tICMP: %d TCP: %d UDP: %d", icmp, tcp, udp)
}
}
func openRawSock(index int) (int, error) {
sock, err := syscall.Socket(syscall.AF_PACKET, syscall.SOCK_RAW|syscall.SOCK_NONBLOCK|syscall.SOCK_CLOEXEC, int(htons(syscall.ETH_P_ALL)))
if err != nil {
return 0, err
}
sll := syscall.SockaddrLinklayer{
Ifindex: index,
Protocol: htons(syscall.ETH_P_ALL),
}
if err := syscall.Bind(sock, &sll); err != nil {
return 0, err
}
return sock, nil
}
// htons converts the unsigned short integer hostshort from host byte order to network byte order.
func htons(i uint16) uint16 {
b := make([]byte, 2)
binary.BigEndian.PutUint16(b, i)
return *(*uint16)(unsafe.Pointer(&b[0]))
}

Some files were not shown because too many files have changed in this diff Show More