Skip to content

Commit

Permalink
bpf2go: export targets
Browse files Browse the repository at this point in the history
Move target and force users to use one of the predefined targets.
Also export logic to generate build contraints from goarches.

Signed-off-by: Lorenz Bauer <[email protected]>
  • Loading branch information
lmb committed Jun 24, 2024
1 parent 87277d3 commit 20f267d
Show file tree
Hide file tree
Showing 6 changed files with 346 additions and 289 deletions.
12 changes: 0 additions & 12 deletions cmd/bpf2go/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,15 +43,3 @@ func andConstraints(x, y constraint.Expr) constraint.Expr {

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}
}
15 changes: 10 additions & 5 deletions cmd/bpf2go/gen/compile.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ type CompileArgs struct {
Source string
// Absolute output file name
Dest string
// Target to compile for, defaults to "bpf".
Target string
// Target to compile for, defaults to compiling generic BPF in host endianness.
Target Target
DisableStripping bool
}

Expand All @@ -48,13 +48,18 @@ func Compile(args CompileArgs) error {
}

target := args.Target
if target == "" {
target = "bpf"
if target == (Target{}) {
target.clang = "bpf"
}

// C flags that can't be overridden.
if linux := target.linux; linux != "" {
cmd.Args = append(cmd.Args, "-D__TARGET_ARCH_"+linux)
}

cmd.Args = append(cmd.Args,
"-target", target,
"-Wunused-command-line-argument",
"-target", target.clang,
"-c", args.Source,
"-o", args.Dest,
// Don't include clang version
Expand Down
155 changes: 155 additions & 0 deletions cmd/bpf2go/gen/target.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
package gen

import (
"errors"
"fmt"
"go/build/constraint"
"maps"
"runtime"
"slices"
)

var ErrInvalidTarget = errors.New("unsupported target")

var targetsByGoArch = map[GoArch]Target{
"386": {"bpfel", "x86"},
"amd64": {"bpfel", "x86"},
"arm": {"bpfel", "arm"},
"arm64": {"bpfel", "arm64"},
"loong64": {"bpfel", "loongarch"},
"mips": {"bpfeb", "mips"},
"mipsle": {"bpfel", ""},
"mips64": {"bpfeb", ""},
"mips64le": {"bpfel", ""},
"ppc64": {"bpfeb", "powerpc"},
"ppc64le": {"bpfel", "powerpc"},
"riscv64": {"bpfel", "riscv"},
"s390x": {"bpfeb", "s390"},
}

type Target struct {
// Clang arch string, used to define the clang -target flag, as per
// "clang -print-targets".
clang string
// Linux arch string, used to define __TARGET_ARCH_xzy macros used by
// https://github.com/libbpf/libbpf/blob/master/src/bpf_tracing.h
linux string
}

// TargetsByGoArch returns all supported targets.
func TargetsByGoArch() map[GoArch]Target {
return maps.Clone(targetsByGoArch)
}

// IsGeneric returns true if the target will compile to generic BPF.
func (tgt *Target) IsGeneric() bool {
return tgt.linux == ""
}

// Suffix returns a a string suitable for appending to a file name to
// identify the target.
func (tgt *Target) Suffix() string {
// The output filename must not match any of the following patterns:
//
// *_GOOS
// *_GOARCH
// *_GOOS_GOARCH
//
// Otherwise it is interpreted as a build constraint by the Go toolchain.
stem := tgt.clang
if tgt.linux != "" {
stem = fmt.Sprintf("%s_%s", tgt.linux, tgt.clang)
}
return stem
}

// ObsoleteSuffix returns an obsolete suffix for a subset of targets.
//
// It's used to work around an old bug and should not be used in new code.
func (tgt *Target) ObsoleteSuffix() string {
if tgt.linux == "" {
return ""
}

return fmt.Sprintf("%s_%s", tgt.clang, tgt.linux)
}

// GoArch is a Go arch string.
//
// See https://go.dev/doc/install/source#environment for valid GOARCHes when
// GOOS=linux.
type GoArch string

type GoArches []GoArch

// Constraints is satisfied when GOARCH is any of the arches.
func (arches GoArches) Constraint() constraint.Expr {
var archConstraint constraint.Expr
for _, goarch := range arches {
tag := &constraint.TagExpr{Tag: string(goarch)}
archConstraint = orConstraints(archConstraint, tag)
}
return archConstraint
}

// FindTarget turns a list of identifiers into targets and their respective
// GoArches.
//
// The following are valid identifiers:
//
// - bpf: compile generic BPF for host endianness
// - bpfel: compile generic BPF for little endian
// - bpfeb: compile generic BPF for big endian
// - native: compile BPF for host target
// - $GOARCH: compile BPF for $GOARCH target
//
// Generic BPF can run on any target goarch with the correct endianness,
// but doesn't have access to some arch specific tracing functionality.
func FindTarget(id string) (Target, GoArches, error) {
switch id {
case "bpf", "bpfel", "bpfeb":
var goarches []GoArch
for arch, archTarget := range targetsByGoArch {
if archTarget.clang == id {
// Include tags for all goarches that have the same endianness.
goarches = append(goarches, arch)
}
}
slices.Sort(goarches)
return Target{id, ""}, goarches, nil

case "native":
id = runtime.GOARCH
fallthrough

default:
archTarget, ok := targetsByGoArch[GoArch(id)]
if !ok || archTarget.linux == "" {
return Target{}, nil, fmt.Errorf("%q: %w", id, ErrInvalidTarget)
}

var goarches []GoArch
for goarch, lt := range targetsByGoArch {
if lt == archTarget {
// Include tags for all goarches that have the same
// target.
goarches = append(goarches, goarch)
}
}

slices.Sort(goarches)
return archTarget, goarches, nil
}
}

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}
}
155 changes: 155 additions & 0 deletions cmd/bpf2go/gen/target_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
package gen

import (
"errors"
"os/exec"
"slices"
"testing"

"github.com/go-quicktest/qt"
)

func TestCollectTargets(t *testing.T) {
clangArches := make(map[string][]GoArch)
linuxArchesLE := make(map[string][]GoArch)
linuxArchesBE := make(map[string][]GoArch)
for arch, archTarget := range targetsByGoArch {
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 {
slices.Sort(clangArches[i])
}
for i := range linuxArchesLE {
slices.Sort(linuxArchesLE[i])
}
for i := range linuxArchesBE {
slices.Sort(linuxArchesBE[i])
}

nativeTarget, nativeArches, err := FindTarget("native")
qt.Assert(t, qt.IsNil(err))

tests := []struct {
short string
target Target
arches GoArches
}{
{
"bpf",
Target{"bpf", ""},
nil,
},
{
"bpfel",
Target{"bpfel", ""},
clangArches["bpfel"],
},
{
"bpfeb",
Target{"bpfeb", ""},
clangArches["bpfeb"],
},
{
"amd64",
Target{"bpfel", "x86"},
linuxArchesLE["x86"],
},
{
"386",
Target{"bpfel", "x86"},
linuxArchesLE["x86"],
},
{
"ppc64",
Target{"bpfeb", "powerpc"},
linuxArchesBE["powerpc"],
},
{
"native",
nativeTarget,
nativeArches,
},
}

for _, test := range tests {
t.Run(test.short, func(t *testing.T) {
target, arches, err := FindTarget(test.short)
qt.Assert(t, qt.IsNil(err))
qt.Assert(t, qt.Equals(target, test.target))
qt.Assert(t, qt.DeepEquals(arches, test.arches))
})
}
}

func TestCollectTargetsErrors(t *testing.T) {
tests := []struct {
name string
target string
}{
{"unknown", "frood"},
{"no linux target", "mipsle"},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
_, _, err := FindTarget(test.target)
if err == nil {
t.Fatal("Function did not return an error")
}
t.Log("Error message:", err)
})
}
}

func TestGoarches(t *testing.T) {
exe := goBin(t)

for GoArch := range targetsByGoArch {
t.Run(string(GoArch), func(t *testing.T) {
goEnv := exec.Command(exe, "env")
goEnv.Env = []string{"GOROOT=/", "GOOS=linux", "GOARCH=" + string(GoArch)}
output, err := goEnv.CombinedOutput()
qt.Assert(t, qt.IsNil(err), qt.Commentf("go output is:\n%s", string(output)))
})
}
}

func TestClangTargets(t *testing.T) {
exe := goBin(t)

clangTargets := map[string]struct{}{}
for _, tgt := range targetsByGoArch {
clangTargets[tgt.clang] = struct{}{}
}

for target := range clangTargets {
for _, env := range []string{"GOOS", "GOARCH"} {
env += "=" + target
t.Run(env, func(t *testing.T) {
goEnv := exec.Command(exe, "env")
goEnv.Env = []string{"GOROOT=/", env}
output, err := goEnv.CombinedOutput()
t.Log("go output is:", string(output))
qt.Assert(t, qt.IsNotNil(err), qt.Commentf("No clang target should be a valid build constraint"))
})
}

}
}

func goBin(t *testing.T) string {
t.Helper()

exe, err := exec.LookPath("go")
if errors.Is(err, exec.ErrNotFound) {
t.Skip("go binary is not in PATH")
}
qt.Assert(t, qt.IsNil(err))

return exe
}
Loading

0 comments on commit 20f267d

Please sign in to comment.