Repository: cilium/ebpf
Pull request #126 (2020-08-12 16:42:42)

bpf2go: embed eBPF in Go
Add a tool `bpf2go` which can be run via `go generate`. It does the following:
* Compile the provided C source for big and little endian arches
* Write out a `.go` with scaffolding to simplify loading and interacting with the eBPF

There is a flag `-makebase` to allow integrating with a Makefile based build system by writing out dependency files. This is just a wrapper around various `-M` flags passed to `clang`.

In the future, the tool can be extended to generate go type definitions for some shared structures, and to simplify rewriting constants from `.rodata`.

Based on a proof of concept by @sauercrowd.

Sorry for the big PR once again, but I'm not sure how to chop this down more.

lmb: After fighting with the CI for forever this is ready for review!
lmb: Thanks, I understand that it's a lot to take in. Maybe I can get @sauercrowd to review it. As it stands there is one issue with the generated API which I need to sort out, I'll come back to this.
sauercrowd: Yep, happy to! Need a few more days but will get back to it

Patch:
diff --git a/.gitignore b/.gitignore
index f1c181e..38b1565 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,6 +4,7 @@
 *.dll
 *.so
 *.dylib
+*.o
 
 # Test binary, build with `go test -c`
 *.test
diff --git a/.semaphore/semaphore.yml b/.semaphore/semaphore.yml
index d2f405d..7cc79dc 100644
--- a/.semaphore/semaphore.yml
+++ b/.semaphore/semaphore.yml
@@ -15,7 +15,9 @@ blocks:
           - sem-version go 1.14
           - go build ./...
           - sudo pip3 install https://github.com/amluto/virtme/archive/538f1e756139a6b57a4780e7ceb3ac6bcaa4fe6f.zip
-          - sudo apt-get install -y qemu-system-x86
+          - sudo apt-get install -y qemu-system-x86 clang-9
+          - go generate ./cmd/bpf2go
+          - git diff --exit-code || { echo "generated files are not up to date" >&2; false; }
       env_vars:
         - name: TMPDIR
           value: /tmp
diff --git a/cmd/bpf2go/compile.go b/cmd/bpf2go/compile.go
new file mode 100644
index 0000000..3822a8a
--- /dev/null
+++ b/cmd/bpf2go/compile.go
@@ -0,0 +1,186 @@
+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
+	// Depfile will be written here if depName is not empty
+	dep io.Writer
+}
+
+func compile(args compileArgs) error {
+	cmd := exec.Command(args.cc, args.cFlags...)
+	cmd.Stderr = os.Stderr
+
+	inputDir := filepath.Dir(args.source)
+	relInputDir, err := filepath.Rel(args.dir, inputDir)
+	if err != nil {
+		return err
+	}
+
+	cmd.Args = append(cmd.Args,
+		"-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",
+	)
+	cmd.Dir = args.dir
+
+	var depRd, depWr *os.File
+	if args.dep != nil {
+		depRd, depWr, err = os.Pipe()
+		if err != nil {
+			return err
+		}
+		defer depRd.Close()
+		defer depWr.Close()
+
+		// This becomes /dev/fd/3
+		cmd.ExtraFiles = append(cmd.ExtraFiles, depWr)
+		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 our pipe
+			"-MF/dev/fd/3",
+		)
+	}
+
+	if err := cmd.Start(); err != nil {
+		return fmt.Errorf("can't execute %s: %s", args.cc, err)
+	}
+
+	if depRd != nil {
+		// Close our copy of the write end so that Copy will terminate
+		// when cc exits.
+		depWr.Close()
+		io.Copy(args.dep, depRd)
+	}
+
+	if err := cmd.Wait(); err != nil {
+		return fmt.Errorf("%s: %s", args.cc, 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
+}
diff --git a/cmd/bpf2go/doc.go b/cmd/bpf2go/doc.go
new file mode 100644
index 0000000..bac9d22
--- /dev/null
+++ b/cmd/bpf2go/doc.go
@@ -0,0 +1,23 @@
+// Program bpf2go embeds eBPF in Go.
+//
+// It 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.
+//
+// For a full list of accepted options check the `-help` output. There is a
+// fully worked example at https://github.com/cilium/ebpf/tree/master/cmd/bpf2go/example.
+package main
diff --git a/cmd/bpf2go/example_bpfeb.go b/cmd/bpf2go/example_bpfeb.go
new file mode 100644
index 0000000..ba7c4ab
--- /dev/null
+++ b/cmd/bpf2go/example_bpfeb.go
@@ -0,0 +1,256 @@
+// Code generated by bpf2go; DO NOT EDIT.
+// +build armbe arm64be mips mips64 mips64p32 ppc64 s390 s390x sparc sparc64
+
+package main
+
+import (
+	"bytes"
+	"fmt"
+	"io"
+
+	"github.com/cilium/ebpf"
+)
+
+type exampleSpecs struct {
+	ProgramFilter *ebpf.ProgramSpec `ebpf:"filter"`
+}
+
+func newExampleSpecs() (*exampleSpecs, error) {
+	reader := bytes.NewReader(exampleBytes())
+	spec, err := ebpf.LoadCollectionSpecFromReader(reader)
+	if err != nil {
+		return nil, fmt.Errorf("can't load example: %w", err)
+	}
+
+	specs := new(exampleSpecs)
+	if err := spec.Assign(specs); err != nil {
+		return nil, fmt.Errorf("can't assign example: %w", err)
+	}
+
+	return specs, nil
+}
+
+func (s *exampleSpecs) CollectionSpec() *ebpf.CollectionSpec {
+	return &ebpf.CollectionSpec{
+		Programs: map[string]*ebpf.ProgramSpec{
+			"filter": s.ProgramFilter,
+		},
+		Maps: map[string]*ebpf.MapSpec{},
+	}
+}
+
+func (s *exampleSpecs) Load(opts *ebpf.CollectionOptions) (*exampleObjects, error) {
+	var objs exampleObjects
+	if err := s.CollectionSpec().LoadAndAssign(&objs, opts); err != nil {
+		return nil, err
+	}
+	return &objs, nil
+}
+
+func (s *exampleSpecs) Copy() *exampleSpecs {
+	return &exampleSpecs{
+		ProgramFilter: s.ProgramFilter.Copy(),
+	}
+}
+
+type exampleObjects struct {
+	ProgramFilter *ebpf.Program `ebpf:"filter"`
+}
+
+func (o *exampleObjects) Close() error {
+	for _, closer := range []io.Closer{
+		o.ProgramFilter,
+	} {
+		if err := closer.Close(); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func exampleBytes() []byte {
+	var c string
+	c += "\x7f\x45\x4c\x46\x02\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x01\x00\xf7\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x07\x08\x00\x00\x00"
+	c += "\x00\x00\x40\x00\x00\x00\x00\x00\x40\x00\x14\x00\x01\xb7\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x95\x00\x00\x00\x00\x00\x00\x00\x4d\x49\x54\x00\x00"
+	c += "\x74\x65\x73\x74\x64\x61\x74\x61\x2f\x6d\x69\x6e\x69\x6d\x61\x6c\x2e"
+	c += "\x63\x00\x2e\x00\x5f\x5f\x6c\x69\x63\x65\x6e\x73\x65\x00\x63\x68\x61"
+	c += "\x72\x00\x5f\x5f\x41\x52\x52\x41\x59\x5f\x53\x49\x5a\x45\x5f\x54\x59"
+	c += "\x50\x45\x5f\x5f\x00\x66\x69\x6c\x74\x65\x72\x00\x69\x6e\x74\x00\x01"
+	c += "\x11\x01\x25\x0e\x13\x05\x03\x0e\x10\x17\x1b\x0e\x11\x01\x12\x06\x00"
+	c += "\x00\x02\x34\x00\x03\x0e\x49\x13\x3f\x19\x3a\x0b\x3b\x0b\x02\x18\x00"
+	c += "\x00\x03\x01\x01\x49\x13\x00\x00\x04\x21\x00\x49\x13\x37\x0b\x00\x00"
+	c += "\x05\x24\x00\x03\x0e\x3e\x0b\x0b\x0b\x00\x00\x06\x24\x00\x03\x0e\x0b"
+	c += "\x0b\x3e\x0b\x00\x00\x07\x2e\x00\x11\x01\x12\x06\x40\x18\x03\x0e\x3a"
+	c += "\x0b\x3b\x0b\x49\x13\x3f\x19\x00\x00\x00\x00\x00\x00\x76\x00\x04\x00"
+	c += "\x00\x00\x00\x08\x01\x00\x00\x00\x00\x00\x0c\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x10\x02\x00\x00\x00\x00\x00\x00\x00\x3f\x01\x03\x09\x03\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x03\x00\x00\x00\x4b\x04\x00\x00\x00\x52\x04\x00"
+	c += "\x05\x00\x00\x00\x00\x06\x01\x06\x00\x00\x00\x00\x08\x07\x07\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x01\x5a\x00\x00\x00\x00\x01"
+	c += "\x05\x00\x00\x00\x72\x05\x00\x00\x00\x00\x05\x04\x00\x00\xeb\x9f\x01"
+	c += "\x00\x00\x00\x00\x18\x00\x00\x00\x00\x00\x00\x00\x88\x00\x00\x00\x88"
+	c += "\x00\x00\x00\x97\x00\x00\x00\x00\x0d\x00\x00\x00\x00\x00\x00\x02\x00"
+	c += "\x00\x00\x01\x01\x00\x00\x00\x00\x00\x00\x04\x01\x00\x00\x20\x00\x00"
+	c += "\x00\x05\x0c\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x6c\x01\x00\x00"
+	c += "\x00\x00\x00\x00\x01\x01\x00\x00\x08\x00\x00\x00\x00\x03\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x06\x00\x00\x00\x04\x00"
+	c += "\x00\x00\x71\x01\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x20\x00\x00"
+	c += "\x00\x85\x0e\x00\x00\x00\x00\x00\x00\x05\x00\x00\x00\x01\x00\x00\x00"
+	c += "\x8f\x0f\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x07\x00\x00\x00\x00"
+	c += "\x00\x00\x00\x04\x00\x69\x6e\x74\x00\x66\x69\x6c\x74\x65\x72\x00\x73"
+	c += "\x6f\x63\x6b\x65\x74\x00\x2e\x2f\x74\x65\x73\x74\x64\x61\x74\x61\x2f"
+	c += "\x6d\x69\x6e\x69\x6d\x61\x6c\x2e\x63\x00\x5f\x5f\x61\x74\x74\x72\x69"
+	c += "\x62\x75\x74\x65\x5f\x5f\x28\x28\x73\x65\x63\x74\x69\x6f\x6e\x28\x22"
+	c += "\x73\x6f\x63\x6b\x65\x74\x22\x29\x2c\x20\x75\x73\x65\x64\x29\x29\x20"
+	c += "\x69\x6e\x74\x20\x66\x69\x6c\x74\x65\x72\x28\x29\x20\x7b\x20\x72\x65"
+	c += "\x74\x75\x72\x6e\x20\x30\x3b\x20\x7d\x00\x63\x68\x61\x72\x00\x5f\x5f"
+	c += "\x41\x52\x52\x41\x59\x5f\x53\x49\x5a\x45\x5f\x54\x59\x50\x45\x5f\x5f"
+	c += "\x00\x5f\x5f\x6c\x69\x63\x65\x6e\x73\x65\x00\x6c\x69\x63\x65\x6e\x73"
+	c += "\x65\x00\xeb\x9f\x01\x00\x00\x00\x00\x28\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x14\x00\x00\x00\x14\x00\x00\x00\x2c\x00\x00\x00\x40\x00\x00\x00\x00"
+	c += "\x00\x00\x00\x40\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00\x0c\x00"
+	c += "\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x10\x00\x00"
+	c += "\x00\x0c\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x13\x00\x00\x00"
+	c += "\x28\x00\x00\x14\x00\x00\x00\x00\x08\x00\x00\x00\x13\x00\x00\x00\x28"
+	c += "\x00\x00\x14\x39\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0c\xff\xff\xff"
+	c += "\xff\x04\x00\x08\x00\x08\x7c\x0b\x00\x00\x00\x00\x14\x00\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00"
+	c += "\x00\x00\x45\x00\x04\x00\x00\x00\x2a\x08\x01\x01\xfb\x0e\x0d\x00\x01"
+	c += "\x01\x01\x01\x00\x00\x00\x01\x00\x00\x01\x74\x65\x73\x74\x64\x61\x74"
+	c += "\x61\x00\x00\x6d\x69\x6e\x69\x6d\x61\x6c\x2e\x63\x00\x01\x00\x00\x00"
+	c += "\x00\x09\x02\x00\x00\x00\x00\x00\x00\x00\x00\x16\x05\x39\x0a\x20\x02"
+	c += "\x01\x00\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x93\x04\x00\xff\xf1\x00\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\x00\x00\x00\x00"
+	c += "\x00\x00\x00\x14\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00\x16\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x20\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x25\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00\x39\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x40\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x03\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x06\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00"
+	c += "\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x03\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x68\x11\x00\x00\x04\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00"
+	c += "\x34\x12\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00\x06\x00\x00\x00\x0b\x00"
+	c += "\x00\x00\x0a\x00\x00\x00\x00\x00\x00\x00\x0c\x00\x00\x00\x02\x00\x00"
+	c += "\x00\x0a\x00\x00\x00\x00\x00\x00\x00\x12\x00\x00\x00\x03\x00\x00\x00"
+	c += "\x0a\x00\x00\x00\x00\x00\x00\x00\x16\x00\x00\x00\x0d\x00\x00\x00\x0a"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x1a\x00\x00\x00\x04\x00\x00\x00\x0a\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x1e\x00\x00\x00\x0a\x00\x00\x00\x01\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x2b\x00\x00\x00\x05\x00\x00\x00\x0a\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x37\x00\x00\x00\x0e\x00\x00\x00\x01\x00\x00\x00\x00"
+	c += "\x00\x00\x00\x4c\x00\x00\x00\x06\x00\x00\x00\x0a\x00\x00\x00\x00\x00"
+	c += "\x00\x00\x53\x00\x00\x00\x07\x00\x00\x00\x0a\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x5a\x00\x00\x00\x0a\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x68\x00\x00\x00\x08\x00\x00\x00\x0a\x00\x00\x00\x00\x00\x00\x00\x73"
+	c += "\x00\x00\x00\x09\x00\x00\x00\x0a\x00\x00\x00\x00\x00\x00\x00\x98\x00"
+	c += "\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x34\x00\x00"
+	c += "\x00\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x48\x00\x00\x00"
+	c += "\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x58\x00\x00\x00\x0a"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x14\x00\x00\x00\x0c\x00"
+	c += "\x00\x00\x0a\x00\x00\x00\x00\x00\x00\x00\x18\x00\x00\x00\x0a\x00\x00"
+	c += "\x00\x01\x00\x00\x00\x00\x00\x00\x00\x37\x00\x00\x00\x0a\x00\x00\x00"
+	c += "\x01\x0f\x0e\x00\x2e\x64\x65\x62\x75\x67\x5f\x61\x62\x62\x72\x65\x76"
+	c += "\x00\x2e\x74\x65\x78\x74\x00\x2e\x72\x65\x6c\x2e\x42\x54\x46\x2e\x65"
+	c += "\x78\x74\x00\x73\x6f\x63\x6b\x65\x74\x00\x2e\x64\x65\x62\x75\x67\x5f"
+	c += "\x73\x74\x72\x00\x66\x69\x6c\x74\x65\x72\x00\x2e\x64\x65\x62\x75\x67"
+	c += "\x5f\x6d\x61\x63\x69\x6e\x66\x6f\x00\x2e\x72\x65\x6c\x2e\x64\x65\x62"
+	c += "\x75\x67\x5f\x69\x6e\x66\x6f\x00\x2e\x6c\x6c\x76\x6d\x5f\x61\x64\x64"
+	c += "\x72\x73\x69\x67\x00\x5f\x5f\x6c\x69\x63\x65\x6e\x73\x65\x00\x2e\x72"
+	c += "\x65\x6c\x2e\x64\x65\x62\x75\x67\x5f\x6c\x69\x6e\x65\x00\x2e\x72\x65"
+	c += "\x6c\x2e\x64\x65\x62\x75\x67\x5f\x66\x72\x61\x6d\x65\x00\x6d\x69\x6e"
+	c += "\x69\x6d\x61\x6c\x2e\x63\x00\x2e\x73\x74\x72\x74\x61\x62\x00\x2e\x73"
+	c += "\x79\x6d\x74\x61\x62\x00\x2e\x72\x65\x6c\x2e\x42\x54\x46\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x9d\x00\x00"
+	c += "\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x06\x52\x00\x00\x00\x00\x00\x00\x00\xb6"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0f\x00\x00\x00\x01\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x40\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x22\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x40\x00\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x00\x6a\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x50\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x29\x00"
+	c += "\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x30\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x54\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x44\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x01\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x98\x00\x00\x00\x00\x00\x00\x00\x60\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x4e\x00\x00\x00\x01\x00\x00\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x00\xf8\x00\x00\x00\x00\x00\x00\x00\x7a\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x00\x00\x4a\x00\x00\x00\x09\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\x10\x00\x00"
+	c += "\x00\x00\x00\x00\x00\xd0\x00\x00\x00\x13\x00\x00\x00\x07\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00\x3b"
+	c += "\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x72\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xb1\x00\x00\x00\x01"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x01\x73\x00\x00\x00\x00\x00\x00\x01\x37\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x00\xad\x00\x00\x00\x09\x00\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x05\xe0\x00\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00\x13\x00\x00"
+	c += "\x00\x0a\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x10\x00\x00\x00\x19\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\xaa\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x68\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x15\x00\x00\x00\x09\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\xf0\x00\x00\x00\x00\x00"
+	c += "\x00\x00\x30\x00\x00\x00\x13\x00\x00\x00\x0c\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x08\x00\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00\x86\x00\x00\x00"
+	c += "\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x03\x18\x00\x00\x00\x00\x00\x00\x00\x28\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x82\x00\x00\x00\x09\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x00\x06\x20\x00\x00\x00\x00\x00\x00\x00\x20\x00\x00\x00\x13\x00"
+	c += "\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x10\x00\x00\x00\x76\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x40"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x49\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x72\x00\x00\x00\x09\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06\x40\x00\x00\x00\x00"
+	c += "\x00\x00\x00\x10\x00\x00\x00\x13\x00\x00\x00\x10\x00\x00\x00\x00\x00"
+	c += "\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00\x5a\x6f\xff"
+	c += "\x4c\x03\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x06\x50\x00\x00\x00\x00\x00\x00\x00\x02"
+	c += "\x00\x00\x00\x13\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa5\x00\x00\x00\x02\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x00\x00\x03\x90\x00\x00\x00\x00\x00\x00\x01\x80\x00\x00\x00\x01"
+	c += "\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00\x00\x00"
+	c += "\x00\x00\x18"
+	return []byte(c)
+}
diff --git a/cmd/bpf2go/example_bpfel.go b/cmd/bpf2go/example_bpfel.go
new file mode 100644
index 0000000..c8b48df
--- /dev/null
+++ b/cmd/bpf2go/example_bpfel.go
@@ -0,0 +1,256 @@
+// Code generated by bpf2go; DO NOT EDIT.
+// +build 386 amd64 amd64p32 arm arm64 mipsle mips64le mips64p32le ppc64le
+
+package main
+
+import (
+	"bytes"
+	"fmt"
+	"io"
+
+	"github.com/cilium/ebpf"
+)
+
+type exampleSpecs struct {
+	ProgramFilter *ebpf.ProgramSpec `ebpf:"filter"`
+}
+
+func newExampleSpecs() (*exampleSpecs, error) {
+	reader := bytes.NewReader(exampleBytes())
+	spec, err := ebpf.LoadCollectionSpecFromReader(reader)
+	if err != nil {
+		return nil, fmt.Errorf("can't load example: %w", err)
+	}
+
+	specs := new(exampleSpecs)
+	if err := spec.Assign(specs); err != nil {
+		return nil, fmt.Errorf("can't assign example: %w", err)
+	}
+
+	return specs, nil
+}
+
+func (s *exampleSpecs) CollectionSpec() *ebpf.CollectionSpec {
+	return &ebpf.CollectionSpec{
+		Programs: map[string]*ebpf.ProgramSpec{
+			"filter": s.ProgramFilter,
+		},
+		Maps: map[string]*ebpf.MapSpec{},
+	}
+}
+
+func (s *exampleSpecs) Load(opts *ebpf.CollectionOptions) (*exampleObjects, error) {
+	var objs exampleObjects
+	if err := s.CollectionSpec().LoadAndAssign(&objs, opts); err != nil {
+		return nil, err
+	}
+	return &objs, nil
+}
+
+func (s *exampleSpecs) Copy() *exampleSpecs {
+	return &exampleSpecs{
+		ProgramFilter: s.ProgramFilter.Copy(),
+	}
+}
+
+type exampleObjects struct {
+	ProgramFilter *ebpf.Program `ebpf:"filter"`
+}
+
+func (o *exampleObjects) Close() error {
+	for _, closer := range []io.Closer{
+		o.ProgramFilter,
+	} {
+		if err := closer.Close(); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func exampleBytes() []byte {
+	var c string
+	c += "\x7f\x45\x4c\x46\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01"
+	c += "\x00\xf7\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x08\x07\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x40\x00\x00\x00\x00\x00\x40\x00\x14\x00\x01\x00\xb7\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x95\x00\x00\x00\x00\x00\x00\x00\x4d\x49\x54\x00\x00"
+	c += "\x74\x65\x73\x74\x64\x61\x74\x61\x2f\x6d\x69\x6e\x69\x6d\x61\x6c\x2e"
+	c += "\x63\x00\x2e\x00\x5f\x5f\x6c\x69\x63\x65\x6e\x73\x65\x00\x63\x68\x61"
+	c += "\x72\x00\x5f\x5f\x41\x52\x52\x41\x59\x5f\x53\x49\x5a\x45\x5f\x54\x59"
+	c += "\x50\x45\x5f\x5f\x00\x66\x69\x6c\x74\x65\x72\x00\x69\x6e\x74\x00\x01"
+	c += "\x11\x01\x25\x0e\x13\x05\x03\x0e\x10\x17\x1b\x0e\x11\x01\x12\x06\x00"
+	c += "\x00\x02\x34\x00\x03\x0e\x49\x13\x3f\x19\x3a\x0b\x3b\x0b\x02\x18\x00"
+	c += "\x00\x03\x01\x01\x49\x13\x00\x00\x04\x21\x00\x49\x13\x37\x0b\x00\x00"
+	c += "\x05\x24\x00\x03\x0e\x3e\x0b\x0b\x0b\x00\x00\x06\x24\x00\x03\x0e\x0b"
+	c += "\x0b\x3e\x0b\x00\x00\x07\x2e\x00\x11\x01\x12\x06\x40\x18\x03\x0e\x3a"
+	c += "\x0b\x3b\x0b\x49\x13\x3f\x19\x00\x00\x00\x76\x00\x00\x00\x04\x00\x00"
+	c += "\x00\x00\x00\x08\x01\x00\x00\x00\x00\x0c\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00\x00"
+	c += "\x00\x02\x00\x00\x00\x00\x3f\x00\x00\x00\x01\x03\x09\x03\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x03\x4b\x00\x00\x00\x04\x52\x00\x00\x00\x04\x00"
+	c += "\x05\x00\x00\x00\x00\x06\x01\x06\x00\x00\x00\x00\x08\x07\x07\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00\x01\x5a\x00\x00\x00\x00\x01"
+	c += "\x05\x72\x00\x00\x00\x05\x00\x00\x00\x00\x05\x04\x00\x00\x9f\xeb\x01"
+	c += "\x00\x18\x00\x00\x00\x00\x00\x00\x00\x88\x00\x00\x00\x88\x00\x00\x00"
+	c += "\x97\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0d\x02\x00\x00\x00\x01"
+	c += "\x00\x00\x00\x00\x00\x00\x01\x04\x00\x00\x00\x20\x00\x00\x01\x05\x00"
+	c += "\x00\x00\x00\x00\x00\x0c\x01\x00\x00\x00\x6c\x00\x00\x00\x00\x00\x00"
+	c += "\x01\x01\x00\x00\x00\x08\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x03"
+	c += "\x00\x00\x00\x00\x04\x00\x00\x00\x06\x00\x00\x00\x04\x00\x00\x00\x71"
+	c += "\x00\x00\x00\x00\x00\x00\x01\x04\x00\x00\x00\x20\x00\x00\x00\x85\x00"
+	c += "\x00\x00\x00\x00\x00\x0e\x05\x00\x00\x00\x01\x00\x00\x00\x8f\x00\x00"
+	c += "\x00\x01\x00\x00\x0f\x00\x00\x00\x00\x07\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x04\x00\x00\x00\x00\x69\x6e\x74\x00\x66\x69\x6c\x74\x65\x72\x00\x73"
+	c += "\x6f\x63\x6b\x65\x74\x00\x2e\x2f\x74\x65\x73\x74\x64\x61\x74\x61\x2f"
+	c += "\x6d\x69\x6e\x69\x6d\x61\x6c\x2e\x63\x00\x5f\x5f\x61\x74\x74\x72\x69"
+	c += "\x62\x75\x74\x65\x5f\x5f\x28\x28\x73\x65\x63\x74\x69\x6f\x6e\x28\x22"
+	c += "\x73\x6f\x63\x6b\x65\x74\x22\x29\x2c\x20\x75\x73\x65\x64\x29\x29\x20"
+	c += "\x69\x6e\x74\x20\x66\x69\x6c\x74\x65\x72\x28\x29\x20\x7b\x20\x72\x65"
+	c += "\x74\x75\x72\x6e\x20\x30\x3b\x20\x7d\x00\x63\x68\x61\x72\x00\x5f\x5f"
+	c += "\x41\x52\x52\x41\x59\x5f\x53\x49\x5a\x45\x5f\x54\x59\x50\x45\x5f\x5f"
+	c += "\x00\x5f\x5f\x6c\x69\x63\x65\x6e\x73\x65\x00\x6c\x69\x63\x65\x6e\x73"
+	c += "\x65\x00\x9f\xeb\x01\x00\x28\x00\x00\x00\x00\x00\x00\x00\x14\x00\x00"
+	c += "\x00\x14\x00\x00\x00\x2c\x00\x00\x00\x40\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x40\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00\x0c\x00\x00\x00\x01"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x10\x00\x00\x00\x0c\x00"
+	c += "\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x13\x00\x00\x00\x28\x00\x00"
+	c += "\x00\x00\x14\x00\x00\x08\x00\x00\x00\x13\x00\x00\x00\x28\x00\x00\x00"
+	c += "\x39\x14\x00\x00\x00\x00\x00\x00\x00\x00\x0c\x00\x00\x00\xff\xff\xff"
+	c += "\xff\x04\x00\x08\x00\x08\x7c\x0b\x00\x14\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00\x45"
+	c += "\x00\x00\x00\x04\x00\x2a\x00\x00\x00\x08\x01\x01\xfb\x0e\x0d\x00\x01"
+	c += "\x01\x01\x01\x00\x00\x00\x01\x00\x00\x01\x74\x65\x73\x74\x64\x61\x74"
+	c += "\x61\x00\x00\x6d\x69\x6e\x69\x6d\x61\x6c\x2e\x63\x00\x01\x00\x00\x00"
+	c += "\x00\x09\x02\x00\x00\x00\x00\x00\x00\x00\x00\x16\x05\x39\x0a\x20\x02"
+	c += "\x01\x00\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x93\x00\x00\x00\x04\x00\xf1\xff\x00\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x05\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\x00\x14\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x05\x00\x16\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x00\x05\x00\x20\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\x00"
+	c += "\x25\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x05\x00\x39\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\x00\x40\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x03\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x03\x00\x06\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x00\x0e"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x03\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x68\x00\x00\x00\x11\x00\x04\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x34\x00\x00"
+	c += "\x00\x12\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\x0a\x00\x00\x00\x0b"
+	c += "\x00\x00\x00\x0c\x00\x00\x00\x00\x00\x00\x00\x0a\x00\x00\x00\x02\x00"
+	c += "\x00\x00\x12\x00\x00\x00\x00\x00\x00\x00\x0a\x00\x00\x00\x03\x00\x00"
+	c += "\x00\x16\x00\x00\x00\x00\x00\x00\x00\x0a\x00\x00\x00\x0d\x00\x00\x00"
+	c += "\x1a\x00\x00\x00\x00\x00\x00\x00\x0a\x00\x00\x00\x04\x00\x00\x00\x1e"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x0a\x00\x00\x00\x2b\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x0a\x00\x00\x00\x05\x00\x00\x00\x37\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x01\x00\x00\x00\x0e\x00\x00\x00\x4c\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x0a\x00\x00\x00\x06\x00\x00\x00\x53\x00\x00\x00\x00"
+	c += "\x00\x00\x00\x0a\x00\x00\x00\x07\x00\x00\x00\x5a\x00\x00\x00\x00\x00"
+	c += "\x00\x00\x01\x00\x00\x00\x0a\x00\x00\x00\x68\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x0a\x00\x00\x00\x08\x00\x00\x00\x73\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x0a\x00\x00\x00\x09\x00\x00\x00\x98\x00\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x00\x00\x0e\x00\x00\x00\x34\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x00\x0a\x00\x00\x00\x48\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x0a\x00\x00\x00\x58\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x0a\x00\x00\x00\x14\x00\x00\x00\x00\x00\x00\x00\x0a\x00\x00\x00\x0c"
+	c += "\x00\x00\x00\x18\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x0a\x00"
+	c += "\x00\x00\x37\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x0a\x00\x00"
+	c += "\x00\x0f\x0e\x00\x2e\x64\x65\x62\x75\x67\x5f\x61\x62\x62\x72\x65\x76"
+	c += "\x00\x2e\x74\x65\x78\x74\x00\x2e\x72\x65\x6c\x2e\x42\x54\x46\x2e\x65"
+	c += "\x78\x74\x00\x73\x6f\x63\x6b\x65\x74\x00\x2e\x64\x65\x62\x75\x67\x5f"
+	c += "\x73\x74\x72\x00\x66\x69\x6c\x74\x65\x72\x00\x2e\x64\x65\x62\x75\x67"
+	c += "\x5f\x6d\x61\x63\x69\x6e\x66\x6f\x00\x2e\x72\x65\x6c\x2e\x64\x65\x62"
+	c += "\x75\x67\x5f\x69\x6e\x66\x6f\x00\x2e\x6c\x6c\x76\x6d\x5f\x61\x64\x64"
+	c += "\x72\x73\x69\x67\x00\x5f\x5f\x6c\x69\x63\x65\x6e\x73\x65\x00\x2e\x72"
+	c += "\x65\x6c\x2e\x64\x65\x62\x75\x67\x5f\x6c\x69\x6e\x65\x00\x2e\x72\x65"
+	c += "\x6c\x2e\x64\x65\x62\x75\x67\x5f\x66\x72\x61\x6d\x65\x00\x6d\x69\x6e"
+	c += "\x69\x6d\x61\x6c\x2e\x63\x00\x2e\x73\x74\x72\x74\x61\x62\x00\x2e\x73"
+	c += "\x79\x6d\x74\x61\x62\x00\x2e\x72\x65\x6c\x2e\x42\x54\x46\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x9d\x00\x00\x00\x03\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x52\x06\x00\x00\x00\x00\x00\x00\xb6\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x0f\x00\x00\x00\x01\x00\x00\x00\x06\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x40\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x00\x00\x22\x00\x00\x00\x01\x00\x00\x00\x06\x00\x00\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x40\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x6a"
+	c += "\x00\x00\x00\x01\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x50\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x29\x00\x00\x00\x01"
+	c += "\x00\x00\x00\x30\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x00\x54\x00\x00\x00\x00\x00\x00\x00\x44\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x01\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x98\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x60\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x4e\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf8\x00\x00\x00\x00\x00"
+	c += "\x00\x00\x7a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x4a\x00\x00\x00\x09\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x10\x05\x00\x00\x00\x00\x00\x00\xd0\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x13\x00\x00\x00\x07\x00\x00\x00\x08\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00\x3b\x00\x00\x00"
+	c += "\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x00\x00\x72\x01\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\xb1\x00\x00\x00\x01\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x73"
+	c += "\x01\x00\x00\x00\x00\x00\x00\x37\x01\x00\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x00\xad\x00\x00\x00\x09\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x05\x00\x00\x00"
+	c += "\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00\x13\x00\x00\x00\x0a\x00"
+	c += "\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x19\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x00\xaa\x02\x00\x00\x00\x00\x00\x00\x68"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x15\x00\x00"
+	c += "\x00\x09\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x00\x00\x00\xf0\x05\x00\x00\x00\x00\x00\x00\x30\x00\x00\x00\x00"
+	c += "\x00\x00\x00\x13\x00\x00\x00\x0c\x00\x00\x00\x08\x00\x00\x00\x00\x00"
+	c += "\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00\x86\x00\x00\x00\x01\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x18\x03\x00\x00\x00\x00\x00\x00\x28\x00\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x82\x00\x00\x00\x09\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20\x06\x00\x00"
+	c += "\x00\x00\x00\x00\x20\x00\x00\x00\x00\x00\x00\x00\x13\x00\x00\x00\x0e"
+	c += "\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00\x00\x00"
+	c += "\x00\x00\x76\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x40\x03\x00\x00\x00\x00\x00\x00"
+	c += "\x49\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x72\x00"
+	c += "\x00\x00\x09\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x40\x06\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x13\x00\x00\x00\x10\x00\x00\x00\x08\x00\x00\x00\x00"
+	c += "\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00\x5a\x00\x00\x00\x03\x4c"
+	c += "\xff\x6f\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x50\x06\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x13\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x00\xa5\x00\x00\x00\x02\x00\x00\x00\x00\x00"
+	c += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x90\x03\x00"
+	c += "\x00\x00\x00\x00\x00\x80\x01\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00"
+	c += "\x0e\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00\x18\x00\x00\x00\x00"
+	c += "\x00\x00\x00"
+	return []byte(c)
+}
diff --git a/cmd/bpf2go/main.go b/cmd/bpf2go/main.go
new file mode 100644
index 0000000..012cb54
--- /dev/null
+++ b/cmd/bpf2go/main.go
@@ -0,0 +1,238 @@
+package main
+
+import (
+	"bytes"
+	"errors"
+	"flag"
+	"fmt"
+	"go/token"
+	"io"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"strings"
+)
+
+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.
+
+Options:
+
+`
+
+func run(stdout io.Writer, pkg, outputDir string, args []string) (err error) {
+	removeOnError := func(f *os.File) {
+		if err != nil {
+			os.Remove(f.Name())
+		}
+		f.Close()
+	}
+
+	var (
+		fs           = flag.NewFlagSet("bpf2go", flag.ContinueOnError)
+		flagCC       = fs.String("cc", "clang", "`binary` used to compile C to BPF")
+		flagCFlags   = fs.String("cflags", "", "flags passed to the compiler, may contain quoted arguments")
+		flagTags     = fs.String("tags", "", "list of Go build tags to include in generated files")
+		flagTarget   = fs.String("target", "", "clang target to compile for (bpf, bpfel, bpfeb)")
+		flagMakeBase = fs.String("makebase", "", "write make compatible depinfo files relative to `directory`")
+	)
+
+	fs.SetOutput(stdout)
+	fs.Usage = func() {
+		fmt.Fprintf(fs.Output(), helpText, fs.Name())
+		fs.PrintDefaults()
+	}
+	if err := fs.Parse(args); errors.Is(err, flag.ErrHelp) {
+		return nil
+	} else if err != nil {
+		return err
+	}
+
+	if pkg == "" {
+		return errors.New("missing package, are you running via go generate?")
+	}
+
+	if *flagCC == "" {
+		return errors.New("no compiler specified")
+	}
+
+	args, cFlags := splitCFlagsFromArgs(fs.Args())
+
+	if *flagCFlags != "" {
+		splitCFlags, err := splitArguments(*flagCFlags)
+		if err != nil {
+			return 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 fmt.Errorf("use -makebase instead of %q", cFlag)
+		}
+	}
+
+	if len(args) < 2 {
+		return errors.New("expected at least two arguments")
+	}
+
+	ident := args[0]
+	if !token.IsIdentifier(ident) {
+		return fmt.Errorf("%q is not a valid identifier", ident)
+	}
+
+	input := args[1]
+	if _, err := os.Stat(input); os.IsNotExist(err) {
+		return fmt.Errorf("file %s doesn't exist", input)
+	} else if err != nil {
+		return fmt.Errorf("state %s: %s", input, err)
+	}
+
+	inputDir, inputFile, err := splitPathAbs(input)
+	if err != nil {
+		return err
+	}
+
+	var makeBase string
+	if *flagMakeBase != "" {
+		makeBase, err = filepath.Abs(*flagMakeBase)
+		if err != nil {
+			return err
+		}
+	}
+
+	if strings.ContainsRune(*flagTags, '\n') {
+		return fmt.Errorf("-tags mustn't contain new line characters")
+	}
+
+	tagsByTarget := map[string]string{
+		"bpf":   "",
+		"bpfel": "386 amd64 amd64p32 arm arm64 mipsle mips64le mips64p32le ppc64le",
+		"bpfeb": "armbe arm64be mips mips64 mips64p32 ppc64 s390 s390x sparc sparc64",
+	}
+
+	targets := []string{"bpfel", "bpfeb"}
+	if *flagTarget != "" {
+		if _, ok := tagsByTarget[*flagTarget]; !ok {
+			return fmt.Errorf("unsupported target %q", *flagTarget)
+		}
+		targets = []string{*flagTarget}
+	}
+
+	cwd, err := os.Getwd()
+	if err != nil {
+		return err
+	}
+
+	cFlags = cFlags[:len(cFlags):len(cFlags)]
+	for _, target := range targets {
+		objFileName := fmt.Sprintf("%s_%s.o", stripExtension(inputFile), target)
+		objFileName = filepath.Join(inputDir, objFileName)
+
+		var dep bytes.Buffer
+		err = compile(compileArgs{
+			cc:     *flagCC,
+			cFlags: append(cFlags, "-target", target),
+			dir:    cwd,
+			source: inputDir + inputFile,
+			dest:   objFileName,
+			dep:    &dep,
+		})
+		if err != nil {
+			return err
+		}
+
+		fmt.Fprintln(stdout, "Compiled", objFileName)
+
+		// Write out generated go
+		goFileName := fmt.Sprintf("%s_%s.go", strings.ToLower(ident), target)
+		goFileName = filepath.Join(outputDir, goFileName)
+		goFile, err := os.Create(goFileName)
+		if err != nil {
+			return err
+		}
+		defer removeOnError(goFile)
+
+		var tags []string
+		if targetTags := tagsByTarget[target]; targetTags != "" {
+			tags = append(tags, targetTags)
+		}
+		if *flagTags != "" {
+			tags = append(tags, *flagTags)
+		}
+
+		obj, err := os.Open(objFileName)
+		if err != nil {
+			return err
+		}
+		defer obj.Close()
+
+		err = writeCommon(writeArgs{
+			pkg:   pkg,
+			ident: ident,
+			tags:  tags,
+			obj:   obj,
+			out:   goFile,
+		})
+		if err != nil {
+			return fmt.Errorf("can't write %s: %s", goFileName, err)
+		}
+
+		fmt.Fprintln(stdout, "Wrote", goFileName)
+
+		if makeBase == "" {
+			continue
+		}
+
+		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(makeBase, deps)
+		if err != nil {
+			return fmt.Errorf("can't adjust dependency information: %s", err)
+		}
+
+		depFileName := goFileName + ".d"
+		if err := ioutil.WriteFile(depFileName, depFile, 0666); err != nil {
+			return fmt.Errorf("can't write dependency file: %s", err)
+		}
+
+		fmt.Fprintln(stdout, "Wrote", depFileName)
+	}
+
+	return 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)
+	}
+}
diff --git a/cmd/bpf2go/output.go b/cmd/bpf2go/output.go
new file mode 100644
index 0000000..406b1bf
--- /dev/null
+++ b/cmd/bpf2go/output.go
@@ -0,0 +1,317 @@
+package main
+
+import (
+	"bytes"
+	"fmt"
+	"go/format"
+	"go/token"
+	"io"
+	"io/ioutil"
+	"strings"
+	"text/template"
+	"unicode"
+
+	"github.com/cilium/ebpf"
+)
+
+const commonRaw = `// Code generated by bpf2go; DO NOT EDIT.
+{{- range .Tags }}
+// +build {{ . }}
+{{- end }}
+
+package {{ .Package }}
+
+import (
+	"bytes"
+	"fmt"
+	"io"
+
+	"github.com/cilium/ebpf"
+)
+
+type {{ .Name.Specs }} struct {
+{{- range $name, $_ := .Programs }}
+	Program{{ identifier $name }} *ebpf.ProgramSpec {{ tag $name }}
+{{- end }}
+
+{{- range $name, $_ := .Maps }}
+	Map{{ identifier $name }} *ebpf.MapSpec {{ tag $name }}
+{{- end }}
+
+{{- range $name, $_ := .Sections }}
+	Section{{ identifier $name }} *ebpf.MapSpec {{ tag $name }}
+{{- end }}
+}
+
+func {{ .Name.NewSpecs }}() (*{{ .Name.Specs }}, error) {
+	reader := bytes.NewReader({{ .Name.Bytes }}())
+	spec, err := ebpf.LoadCollectionSpecFromReader(reader)
+	if err != nil {
+		return nil, fmt.Errorf("can't load {{ .Name }}: %w", err)
+	}
+
+	specs := new({{ .Name.Specs }})
+	if err := spec.Assign(specs); err != nil {
+		return nil, fmt.Errorf("can't assign {{ .Name }}: %w", err)
+	}
+
+	return specs, nil
+}
+
+func (s *{{ .Name.Specs }}) CollectionSpec() *ebpf.CollectionSpec {
+	return &ebpf.CollectionSpec{
+		Programs: map[string]*ebpf.ProgramSpec{
+{{- range $name, $_ := .Programs }}
+			"{{ $name }}": s.Program{{ identifier $name }},
+{{- end }}
+		},
+		Maps: map[string]*ebpf.MapSpec{
+{{- range $name, $_ := .Maps }}
+			"{{ $name }}": s.Map{{ identifier $name }},
+{{- end }}
+{{- range $name, $_ := .Sections }}
+			"{{ $name }}": s.Section{{ identifier $name }},
+{{- end }}
+		},
+	}
+}
+
+func (s *{{ .Name.Specs }}) Load(opts *ebpf.CollectionOptions) (*{{ .Name.Objects }}, error) {
+	var objs {{ .Name.Objects }}
+	if err := s.CollectionSpec().LoadAndAssign(&objs, opts); err != nil {
+		return nil, err
+	}
+	return &objs, nil
+}
+
+func (s *{{ .Name.Specs }}) Copy() *{{ .Name.Specs }} {
+	return &{{ .Name.Specs }}{
+{{- range $name, $_ := .Programs }}
+{{- with $id := identifier $name }}
+		Program{{ $id }}: s.Program{{ $id }}.Copy(),
+{{- end }}
+{{- end }}
+
+{{- range $name, $_ := .Maps }}
+{{- with $id := identifier $name }}
+		Map{{ $id }}: s.Map{{ $id }}.Copy(),
+{{- end }}
+{{- end }}
+
+{{- range $name, $_ := .Sections }}
+{{- with $id := identifier $name }}
+		Section{{ $id }}: s.Section{{ $id }}.Copy(),
+{{- end }}
+{{- end }}
+	}
+}
+
+type {{ .Name.Objects }} struct {
+{{- range $name, $_ := .Programs }}
+	Program{{ identifier $name }} *ebpf.Program {{ tag $name }}
+{{- end }}
+
+{{- range $name, $_ := .Maps }}
+	Map{{ identifier $name }} *ebpf.Map {{ tag $name }}
+{{- end }}
+
+{{- range $name, $_ := .Sections }}
+	Section{{ identifier $name }} *ebpf.Map {{ tag $name }}
+{{- end }}
+}
+
+func (o *{{ .Name.Objects }}) Close() error {
+	for _, closer := range []io.Closer{
+{{- range $name, $_ := .Programs }}
+		o.Program{{ identifier $name }},
+{{- end }}
+
+{{- range $name, $_ := .Maps }}
+		o.Map{{ identifier $name}},
+{{- end }}
+
+{{- range $name, $_ := .Sections }}
+		o.Section{{ identifier $name}},
+{{- end }}
+	} {
+		if err := closer.Close(); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func {{ .Name.Bytes }}() []byte {
+	var c string
+{{- range $line := .Lines }}
+	c += "{{ $line }}"
+{{- end }}
+	return []byte(c)
+}
+`
+
+var (
+	tplFuncs = map[string]interface{}{
+		"identifier": identifier,
+		"tag":        tag,
+	}
+	commonTemplate = template.Must(template.New("common").Funcs(tplFuncs).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 n.maybeExport(string(n) + "Bytes")
+}
+
+func (n templateName) Specs() string {
+	return n.maybeExport(string(n) + "Specs")
+}
+
+func (n templateName) NewSpecs() string {
+	return n.maybeExport("new" + toUpperFirst(string(n)) + "Specs")
+}
+
+func (n templateName) Objects() string {
+	return n.maybeExport(string(n) + "Objects")
+}
+
+type writeArgs struct {
+	pkg   string
+	ident string
+	tags  []string
+	obj   io.Reader
+	out   io.Writer
+}
+
+func writeCommon(args writeArgs) error {
+	obj, err := ioutil.ReadAll(args.obj)
+	if err != nil {
+		return fmt.Errorf("read object file contents: %s", err)
+	}
+
+	spec, err := ebpf.LoadCollectionSpecFromReader(bytes.NewReader(obj))
+	if err != nil {
+		return fmt.Errorf("can't load BPF from ELF: %s", err)
+	}
+
+	sections := make(map[string]struct{})
+	maps := make(map[string]struct{})
+	for name := range spec.Maps {
+		if strings.HasPrefix(name, ".") {
+			sections[name] = struct{}{}
+		} else {
+			maps[name] = struct{}{}
+		}
+	}
+
+	ctx := struct {
+		Package  string
+		Tags     []string
+		Name     templateName
+		Sections map[string]struct{}
+		Maps     map[string]struct{}
+		Programs map[string]*ebpf.ProgramSpec
+		Lines    []string
+	}{
+		args.pkg,
+		args.tags,
+		templateName(args.ident),
+		sections,
+		maps,
+		spec.Programs,
+		binaryStrings(obj),
+	}
+
+	var buf bytes.Buffer
+	if err := commonTemplate.Execute(&buf, &ctx); err != nil {
+		return fmt.Errorf("can't generate types: %s", err)
+	}
+
+	return writeFormatted(buf.Bytes(), args.out)
+}
+
+func binaryStrings(buf []byte) []string {
+	const bytesPerLine = 70 / 4
+
+	var lines []string
+	var builder strings.Builder
+	for len(buf) > 0 {
+		n := bytesPerLine
+		if n > len(buf) {
+			n = len(buf)
+		}
+
+		bytes := buf[:n]
+		buf = buf[n:]
+
+		for _, b := range bytes {
+			builder.WriteString(`\x`)
+			builder.WriteString(fmt.Sprintf("%02x", b))
+		}
+
+		lines = append(lines, builder.String())
+		builder.Reset()
+	}
+
+	return lines
+}
+
+func writeFormatted(src []byte, out io.Writer) error {
+	formatted, err := format.Source(src)
+	if err != nil {
+		fmt.Println(string(src))
+		return fmt.Errorf("can't format source: %s", err)
+	}
+
+	_, err = out.Write(formatted)
+	return err
+}
+
+func identifier(str string) string {
+	prev := rune(-1)
+	return strings.Map(func(r rune) rune {
+		// See https://golang.org/ref/spec#Identifiers
+		switch {
+		case unicode.IsLetter(r):
+			if prev == -1 {
+				r = unicode.ToUpper(r)
+			}
+
+		case r == '_':
+			switch {
+			// The previous rune was deleted, or we are at the
+			// beginning of the string.
+			case prev == -1:
+				fallthrough
+
+			// The previous rune is a lower case letter or a digit.
+			case unicode.IsDigit(prev) || (unicode.IsLetter(prev) && unicode.IsLower(prev)):
+				// delete the current rune, and force the
+				// next character to be uppercased.
+				r = -1
+			}
+
+		case unicode.IsDigit(r):
+
+		default:
+			// Delete the current rune. prev is unchanged.
+			return -1
+		}
+
+		prev = r
+		return r
+	}, str)
+}
+
+func tag(str string) string {
+	return "`ebpf:\"" + str + "\"`"
+}
diff --git a/cmd/bpf2go/tools.go b/cmd/bpf2go/tools.go
new file mode 100644
index 0000000..8499f23
--- /dev/null
+++ b/cmd/bpf2go/tools.go
@@ -0,0 +1,98 @@
+package main
+
+import (
+	"errors"
+	"fmt"
+	"path/filepath"
+	"strings"
+	"unicode"
+	"unicode/utf8"
+)
+
+func stripExtension(file string) string {
+	return file[:len(file)-len(filepath.Ext(file))]
+}
+
+func splitCFlagsFromArgs(in []string) (args, cflags []string) {
+	for i, arg := range in {
+		if arg == "--" {
+			return in[:i], in[i+1:]
+		}
+	}
+
+	return in, nil
+}
+
+func splitPathAbs(path string) (dir, file string, err error) {
+	abs, err := filepath.Abs(path)
+	if err != nil {
+		return "", "", err
+	}
+
+	dir, file = filepath.Split(abs)
+	return
+}
+
+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:]
+}
diff --git a/readme.md b/readme.md
index 936c09b..0c6614b 100644
--- a/readme.md
+++ b/readme.md
@@ -4,8 +4,10 @@ eBPF
 
 eBPF 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.
 
-* [ebpf/link](https://pkg.go.dev/github.com/cilium/ebpf/link) allows attaching eBPF to various hooks.
-* [ebpf/asm](https://pkg.go.dev/github.com/cilium/ebpf/asm) contains a basic assembler.
+* [asm](https://pkg.go.dev/github.com/cilium/ebpf/asm) contains a basic assembler.
+* [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.
+* [cmd/bpf2go](https://pkg.go.dev/github.com/cilium/ebpf/cmd/bpf2go) allows embedding eBPF in Go.
 
 The library is maintained by [Cloudflare](https://www.cloudflare.com) and [Cilium](https://www.cilium.io). Feel free to [join](https://cilium.herokuapp.com/) the [libbpf-go](https://cilium.slack.com/messages/libbpf-go) channel on Slack.
 


Test patch:
diff --git a/cmd/bpf2go/api_test.go b/cmd/bpf2go/api_test.go
new file mode 100644
index 0000000..6676fe0
--- /dev/null
+++ b/cmd/bpf2go/api_test.go
@@ -0,0 +1,32 @@
+package main
+
+import (
+	"testing"
+)
+
+func TestLoadingSpec(t *testing.T) {
+	specs, err := newExampleSpecs()
+	if err != nil {
+		t.Fatal("Can't load specs:", err)
+	}
+
+	cpy := specs.Copy()
+	if cpy == specs || cpy.ProgramFilter == specs.ProgramFilter {
+		t.Error("Copy doesn't copy all fields")
+	}
+
+	spec := specs.CollectionSpec()
+	if spec.Programs["filter"] != specs.ProgramFilter {
+		t.Error("CollectionSpec copies programs instead of using the same reference")
+	}
+
+	objs, err := specs.Load(nil)
+	if err != nil {
+		t.Fatal("Can't load objects:", err)
+	}
+	defer objs.Close()
+
+	if objs.ProgramFilter == nil {
+		t.Error("Loading returns an object with nil references")
+	}
+}
diff --git a/cmd/bpf2go/compile_test.go b/cmd/bpf2go/compile_test.go
new file mode 100644
index 0000000..b70da70
--- /dev/null
+++ b/cmd/bpf2go/compile_test.go
@@ -0,0 +1,155 @@
+package main
+
+import (
+	"bytes"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"reflect"
+	"strings"
+	"testing"
+)
+
+const minimalSocketFilter = `__attribute__((section("socket"), used)) int main() { return 0; }`
+
+func TestCompile(t *testing.T) {
+	dir, cleanup := mustWriteTempFile(t, "test.c", minimalSocketFilter)
+	defer cleanup()
+
+	var dep bytes.Buffer
+	err := compile(compileArgs{
+		cc:     "clang-9",
+		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) {
+	dir, cleanup := mustWriteTempFile(t, "test.c", minimalSocketFilter)
+	defer cleanup()
+
+	err := compile(compileArgs{
+		cc:     "clang-9",
+		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:     "clang-9",
+		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 := ioutil.ReadFile(filepath.Join(dir, "a.o"))
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	bBytes, err := ioutil.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 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, func()) {
+	t.Helper()
+
+	tmp, err := ioutil.TempDir("", "bpf2go")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	cleanup := func() { os.RemoveAll(tmp) }
+
+	tmpFile := filepath.Join(tmp, name)
+	if err := ioutil.WriteFile(tmpFile, []byte(contents), 0660); err != nil {
+		cleanup()
+		t.Fatal(err)
+	}
+
+	return tmp, cleanup
+}
diff --git a/cmd/bpf2go/example_test.go b/cmd/bpf2go/example_test.go
new file mode 100644
index 0000000..5f47da4
--- /dev/null
+++ b/cmd/bpf2go/example_test.go
@@ -0,0 +1,26 @@
+package main
+
+import (
+	"fmt"
+	"os"
+)
+
+//go:generate go run github.com/cilium/ebpf/cmd/bpf2go -cc clang-9 example testdata/minimal.c
+
+func Example() {
+	specs, err := newExampleSpecs()
+	if err != nil {
+		fmt.Println("Can't load specs:", err)
+		os.Exit(1)
+	}
+
+	objs, err := specs.Load(nil)
+	if err != nil {
+		fmt.Println("Can't load objects:", err)
+		os.Exit(1)
+	}
+	defer objs.Close()
+
+	// Do something useful with the program.
+	fmt.Println(objs.ProgramFilter.String())
+}
diff --git a/cmd/bpf2go/main_test.go b/cmd/bpf2go/main_test.go
new file mode 100644
index 0000000..ea4d216
--- /dev/null
+++ b/cmd/bpf2go/main_test.go
@@ -0,0 +1,68 @@
+package main
+
+import (
+	"bytes"
+	"io/ioutil"
+	"os"
+	"os/exec"
+	"path/filepath"
+	"testing"
+)
+
+func TestRun(t *testing.T) {
+	dir, cleanup := mustWriteTempFile(t, "test.c", minimalSocketFilter)
+	defer cleanup()
+
+	cwd, err := os.Getwd()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	// The temporary package has to be in the same module, otherwise
+	// we can't test against unreleased versions of the package.
+	tmpDir, err := ioutil.TempDir(cwd, "bpf2go-module-*")
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer os.RemoveAll(tmpDir)
+
+	err = run(ioutil.Discard, "foo", tmpDir, []string{
+		"-cc", "clang-9",
+		"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
+	} {
+		goBin := exec.Command("go", "build", 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.Errorf("Can't compile resulting package for arch %s: %s", arch, 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")
+	}
+}
diff --git a/cmd/bpf2go/output_test.go b/cmd/bpf2go/output_test.go
new file mode 100644
index 0000000..83b187b
--- /dev/null
+++ b/cmd/bpf2go/output_test.go
@@ -0,0 +1,28 @@
+package main
+
+import (
+	"testing"
+)
+
+func TestIdentifier(t *testing.T) {
+	testcases := []struct {
+		in, out string
+	}{
+		{".rodata", "Rodata"},
+		{"_foo_bar_", "FooBar"},
+		{"ipv6_test", "Ipv6Test"},
+		{"FOO_BAR", "FOO_BAR"},
+		{"FOO_", "FOO_"},
+		{"FOO__BAR", "FOO__BAR"},
+		{"FOO___BAR", "FOO___BAR"},
+		{"_FOO__BAR", "FOO__BAR"},
+		{"__FOO__BAR", "FOO__BAR"},
+	}
+
+	for _, tc := range testcases {
+		have := identifier(tc.in)
+		if have != tc.out {
+			t.Errorf("Expected %q as output of %q, got %q", tc.out, tc.in, have)
+		}
+	}
+}
diff --git a/cmd/bpf2go/testdata/minimal.c b/cmd/bpf2go/testdata/minimal.c
new file mode 100644
index 0000000..103b407
--- /dev/null
+++ b/cmd/bpf2go/testdata/minimal.c
@@ -0,0 +1,5 @@
+// +build ignore
+
+char __license[] __attribute__((section("license"), used)) = "MIT";
+
+__attribute__((section("socket"), used)) int filter() { return 0; }
diff --git a/cmd/bpf2go/tools_test.go b/cmd/bpf2go/tools_test.go
new file mode 100644
index 0000000..b91ecc4
--- /dev/null
+++ b/cmd/bpf2go/tools_test.go
@@ -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)
+		}
+	}
+}
diff --git a/run-tests.sh b/run-tests.sh
index d48b848..3a27594 100755
--- a/run-tests.sh
+++ b/run-tests.sh
@@ -22,8 +22,8 @@ if [[ "${1:-}" = "--in-vm" ]]; then
 
   echo Running tests...
   # TestLibBPFCompat runs separately to pass the "-elfs" flag only for it: https://github.com/cilium/ebpf/pull/119
-  /usr/local/bin/go test -v -run TestLibBPFCompat -elfs "$elfs"
-  /usr/local/bin/go test -v ./...
+  go test -v -run TestLibBPFCompat -elfs "$elfs"
+  go test -v ./...
   touch "$1/success"
   exit 0
 fi
@@ -68,11 +68,12 @@ fi
 
 echo Testing on "${kernel_version}"
 $sudo virtme-run --kimg "${tmp_dir}/${kernel}" --memory 512M --pwd \
+  --rw \
   --rwdir=/run/input="${input}" \
   --rwdir=/run/output="${output}" \
   --rodir=/run/go-path="$(go env GOPATH)" \
   --rwdir=/run/go-cache="$(go env GOCACHE)" \
-  --script-sh "$(realpath "$0") --in-vm /run/output" \
+  --script-sh "PATH=\"$PATH\" $(realpath "$0") --in-vm /run/output" \
   --qemu-opts -smp 2 # need at least two CPUs for some tests
 
 if [[ ! -e "${output}/success" ]]; then