Skip to content

kyleseneker/tinybpf

Repository files navigation

tinybpf

tinybpf

Write eBPF programs in Go. Compile with TinyGo. Run in the Linux kernel.

CI Go Report Card License


Overview

eBPF lets sandboxed programs run inside the Linux kernel. Today those programs must be written in C or Rust. tinybpf lets you write them in Go instead.

graph LR
    A["Go source"] --> B["TinyGo"]
    B --> C["LLVM IR"]
    C --> D["tinybpf"]
    D --> E["bpf.o"]
Loading

The output is a standard BPF ELF object compatible with cilium/ebpf, libbpf, and bpftool.

Why TinyGo?

Standard Go compiles to native machine code via its own backend — there is no LLVM IR to retarget to BPF, and its runtime (GC, goroutines, channels) cannot run in the kernel. TinyGo compiles through LLVM and supports bare-metal mode (-gc=none -scheduler=none -panic=trap) with no runtime, producing IR that tinybpf transforms into verifier-friendly BPF bytecode.

How it works

tinybpf sits between TinyGo's LLVM IR output and the final BPF ELF. It retargets the IR to BPF, strips the TinyGo runtime, rewrites helper calls to kernel form, and injects the metadata that loaders expect. See Architecture for the full pipeline.

Quick start

Install

go install github.com/kyleseneker/tinybpf/cmd/tinybpf@latest

Prerequisites

Dependency Version Required
Go 1.24+ Yes
TinyGo 0.40+ Yes
LLVM (llvm-link, opt, llc) 20+ (>= TinyGo's bundled LLVM) Yes
llvm-ar, llvm-objcopy 20+ For .a / .o inputs
pahole For BTF injection
make setup    # install everything
make doctor   # verify toolchain

Example

A tracepoint probe that captures outbound TCP connections, written entirely in Go:

//go:extern bpf_get_current_pid_tgid
func bpfGetCurrentPidTgid() uint64

//go:extern bpf_probe_read_user
func bpfProbeReadUser(dst unsafe.Pointer, size uint32, src unsafe.Pointer) int64

//go:extern bpf_ringbuf_output
func bpfRingbufOutput(mapPtr unsafe.Pointer, data unsafe.Pointer, size uint64, flags uint64) int64

//export handle_connect
func handle_connect(ctx unsafe.Pointer) int32 {
    args := (*tpConnectArgs)(ctx)
    var sa sockaddrIn
    bpfProbeReadUser(unsafe.Pointer(&sa), uint32(unsafe.Sizeof(sa)), unsafe.Pointer(uintptr(args.Uservaddr)))
    if sa.Family != afINET {
        return 0
    }
    pid := uint32(bpfGetCurrentPidTgid() >> 32)
    ev := connectEvent{PID: pid, DstAddrBE: sa.AddrBE, DstPortBE: sa.PortBE, Proto: ipProtoTCP}
    bpfRingbufOutput(unsafe.Pointer(&events), unsafe.Pointer(&ev), uint64(unsafe.Sizeof(ev)), 0)
    return 0
}

Build:

tinybpf build --output program.o \
  --section handle_connect=tracepoint/syscalls/sys_enter_connect \
  ./bpf

Examples

Scaffold a new project

tinybpf init xdp_filter

Generates a BPF source file, stub file for IDE compatibility, and a Makefile.

CLI reference

Run tinybpf --help or tinybpf <command> --help for details.

Subcommands

Command Description
build Compile Go source to a BPF ELF object in one step
link Link pre-compiled LLVM IR into a BPF ELF object
init Scaffold a new BPF project
verify Validate a BPF ELF object offline
doctor Check toolchain installation
version Print version information

build

tinybpf build [flags] <package>
Flag Default Description
--tinygo (PATH) Path to tinygo binary

Also accepts all shared pipeline flags and tool path overrides.

link

tinybpf link --input <file> [flags]
Flag Default Description
--input, -i (required) Input file .ll, .bc, .o, .a (repeatable)
--config Path to linker-config.json for custom passes
--jobs, -j 1 Parallel input normalization workers

Also accepts all shared pipeline flags and tool path overrides.

verify

tinybpf verify --input <file>
Flag Default Description
--input, -i (required) Path to the BPF ELF object to validate

doctor

tinybpf doctor [flags]
Flag Default Description
--timeout 10s Timeout for each version check

Also accepts tool path overrides.

init

tinybpf init <name>

Takes a single project name argument. No additional flags.

Shared pipeline flags

These flags are accepted by both build and link.

Flag Default Description
--output, -o bpf.o Output ELF path
--program (auto-detect) Program function to keep (repeatable)
--section Program-to-section mapping name=section (repeatable)
--cpu v3 BPF CPU version for llc -mcpu
--opt-profile default conservative, default, aggressive, or verifier-safe
--pass-pipeline Explicit opt pass pipeline (overrides profile)
--btf false Inject BTF via pahole
--verbose, -v false Print each pipeline stage
--timeout 30s Per-stage timeout
--dump-ir false Write intermediate IR after each transform stage
--program-type Validate sections match a BPF program type (e.g. kprobe, xdp)
--keep-temp false Preserve intermediate files
--tmpdir Directory for intermediate files

Tool path overrides

These flags are accepted by build, link, and doctor.

Flag Default Description
--llvm-link Path to llvm-link binary
--opt Path to opt binary
--llc Path to llc binary
--llvm-ar Path to llvm-ar binary
--llvm-objcopy Path to llvm-objcopy binary
--pahole Path to pahole binary

Documentation

Document Description
Writing Go for eBPF Language constraints, BPF concepts, helpers, patterns, and FAQ
Architecture Pipeline design and the 15-step IR transformation
Support Matrix Tested toolchain versions and platforms
Troubleshooting Setup issues, pipeline errors, and verifier debugging
Contributing Development setup, testing, and PR process

Related projects

Project Relationship
cilium/ebpf Go library for loading eBPF programs; loads tinybpf output
bpf2go Compiles C eBPF and generates Go bindings; replaced when the program is Go
libbpf C loader library; compatible with tinybpf output
TinyGo Go compiler targeting LLVM; provides the IR that tinybpf transforms
Aya eBPF in Rust; similar goal, different language
miekg/bpf Prior effort to add BPF to TinyGo's LLVM; archived

License

MIT

About

Build eBPF programs in Go via TinyGo

Resources

License

Contributing

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors