Skip to content

Commit 13a828e

Browse files
committed
bpf2go: export targets
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]>
1 parent ba84df3 commit 13a828e

File tree

6 files changed

+346
-289
lines changed

6 files changed

+346
-289
lines changed

cmd/bpf2go/flags.go

-12
Original file line numberDiff line numberDiff line change
@@ -43,15 +43,3 @@ func andConstraints(x, y constraint.Expr) constraint.Expr {
4343

4444
return &constraint.AndExpr{X: x, Y: y}
4545
}
46-
47-
func orConstraints(x, y constraint.Expr) constraint.Expr {
48-
if x == nil {
49-
return y
50-
}
51-
52-
if y == nil {
53-
return x
54-
}
55-
56-
return &constraint.OrExpr{X: x, Y: y}
57-
}

cmd/bpf2go/gen/compile.go

+10-5
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ type CompileArgs struct {
2020
Source string
2121
// Absolute output file name
2222
Dest string
23-
// Target to compile for, defaults to "bpf".
24-
Target string
23+
// Target to compile for, defaults to compiling generic BPF in host endianness.
24+
Target Target
2525
DisableStripping bool
2626
}
2727

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

5050
target := args.Target
51-
if target == "" {
52-
target = "bpf"
51+
if target == (Target{}) {
52+
target.clang = "bpf"
5353
}
5454

5555
// C flags that can't be overridden.
56+
if linux := target.linux; linux != "" {
57+
cmd.Args = append(cmd.Args, "-D__TARGET_ARCH_"+linux)
58+
}
59+
5660
cmd.Args = append(cmd.Args,
57-
"-target", target,
61+
"-Wunused-command-line-argument",
62+
"-target", target.clang,
5863
"-c", args.Source,
5964
"-o", args.Dest,
6065
// Don't include clang version

cmd/bpf2go/gen/target.go

+155
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
package gen
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"go/build/constraint"
7+
"maps"
8+
"runtime"
9+
"slices"
10+
)
11+
12+
var ErrInvalidTarget = errors.New("unsupported target")
13+
14+
var targetsByGoArch = map[GoArch]Target{
15+
"386": {"bpfel", "x86"},
16+
"amd64": {"bpfel", "x86"},
17+
"arm": {"bpfel", "arm"},
18+
"arm64": {"bpfel", "arm64"},
19+
"loong64": {"bpfel", "loongarch"},
20+
"mips": {"bpfeb", "mips"},
21+
"mipsle": {"bpfel", ""},
22+
"mips64": {"bpfeb", ""},
23+
"mips64le": {"bpfel", ""},
24+
"ppc64": {"bpfeb", "powerpc"},
25+
"ppc64le": {"bpfel", "powerpc"},
26+
"riscv64": {"bpfel", "riscv"},
27+
"s390x": {"bpfeb", "s390"},
28+
}
29+
30+
type Target struct {
31+
// Clang arch string, used to define the clang -target flag, as per
32+
// "clang -print-targets".
33+
clang string
34+
// Linux arch string, used to define __TARGET_ARCH_xzy macros used by
35+
// https://github.com/libbpf/libbpf/blob/master/src/bpf_tracing.h
36+
linux string
37+
}
38+
39+
// TargetsByGoArch returns all supported targets.
40+
func TargetsByGoArch() map[GoArch]Target {
41+
return maps.Clone(targetsByGoArch)
42+
}
43+
44+
// IsGeneric returns true if the target will compile to generic BPF.
45+
func (tgt *Target) IsGeneric() bool {
46+
return tgt.linux == ""
47+
}
48+
49+
// Suffix returns a a string suitable for appending to a file name to
50+
// identify the target.
51+
func (tgt *Target) Suffix() string {
52+
// The output filename must not match any of the following patterns:
53+
//
54+
// *_GOOS
55+
// *_GOARCH
56+
// *_GOOS_GOARCH
57+
//
58+
// Otherwise it is interpreted as a build constraint by the Go toolchain.
59+
stem := tgt.clang
60+
if tgt.linux != "" {
61+
stem = fmt.Sprintf("%s_%s", tgt.linux, tgt.clang)
62+
}
63+
return stem
64+
}
65+
66+
// ObsoleteSuffix returns an obsolete suffix for a subset of targets.
67+
//
68+
// It's used to work around an old bug and should not be used in new code.
69+
func (tgt *Target) ObsoleteSuffix() string {
70+
if tgt.linux == "" {
71+
return ""
72+
}
73+
74+
return fmt.Sprintf("%s_%s", tgt.clang, tgt.linux)
75+
}
76+
77+
// GoArch is a Go arch string.
78+
//
79+
// See https://go.dev/doc/install/source#environment for valid GOARCHes when
80+
// GOOS=linux.
81+
type GoArch string
82+
83+
type GoArches []GoArch
84+
85+
// Constraints is satisfied when GOARCH is any of the arches.
86+
func (arches GoArches) Constraint() constraint.Expr {
87+
var archConstraint constraint.Expr
88+
for _, goarch := range arches {
89+
tag := &constraint.TagExpr{Tag: string(goarch)}
90+
archConstraint = orConstraints(archConstraint, tag)
91+
}
92+
return archConstraint
93+
}
94+
95+
// FindTarget turns a list of identifiers into targets and their respective
96+
// GoArches.
97+
//
98+
// The following are valid identifiers:
99+
//
100+
// - bpf: compile generic BPF for host endianness
101+
// - bpfel: compile generic BPF for little endian
102+
// - bpfeb: compile generic BPF for big endian
103+
// - native: compile BPF for host target
104+
// - $GOARCH: compile BPF for $GOARCH target
105+
//
106+
// Generic BPF can run on any target goarch with the correct endianness,
107+
// but doesn't have access to some arch specific tracing functionality.
108+
func FindTarget(id string) (Target, GoArches, error) {
109+
switch id {
110+
case "bpf", "bpfel", "bpfeb":
111+
var goarches []GoArch
112+
for arch, archTarget := range targetsByGoArch {
113+
if archTarget.clang == id {
114+
// Include tags for all goarches that have the same endianness.
115+
goarches = append(goarches, arch)
116+
}
117+
}
118+
slices.Sort(goarches)
119+
return Target{id, ""}, goarches, nil
120+
121+
case "native":
122+
id = runtime.GOARCH
123+
fallthrough
124+
125+
default:
126+
archTarget, ok := targetsByGoArch[GoArch(id)]
127+
if !ok || archTarget.linux == "" {
128+
return Target{}, nil, fmt.Errorf("%q: %w", id, ErrInvalidTarget)
129+
}
130+
131+
var goarches []GoArch
132+
for goarch, lt := range targetsByGoArch {
133+
if lt == archTarget {
134+
// Include tags for all goarches that have the same
135+
// target.
136+
goarches = append(goarches, goarch)
137+
}
138+
}
139+
140+
slices.Sort(goarches)
141+
return archTarget, goarches, nil
142+
}
143+
}
144+
145+
func orConstraints(x, y constraint.Expr) constraint.Expr {
146+
if x == nil {
147+
return y
148+
}
149+
150+
if y == nil {
151+
return x
152+
}
153+
154+
return &constraint.OrExpr{X: x, Y: y}
155+
}

cmd/bpf2go/gen/target_test.go

+155
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
package gen
2+
3+
import (
4+
"errors"
5+
"os/exec"
6+
"slices"
7+
"testing"
8+
9+
"github.com/go-quicktest/qt"
10+
)
11+
12+
func TestCollectTargets(t *testing.T) {
13+
clangArches := make(map[string][]GoArch)
14+
linuxArchesLE := make(map[string][]GoArch)
15+
linuxArchesBE := make(map[string][]GoArch)
16+
for arch, archTarget := range targetsByGoArch {
17+
clangArches[archTarget.clang] = append(clangArches[archTarget.clang], arch)
18+
if archTarget.clang == "bpfel" {
19+
linuxArchesLE[archTarget.linux] = append(linuxArchesLE[archTarget.linux], arch)
20+
continue
21+
}
22+
linuxArchesBE[archTarget.linux] = append(linuxArchesBE[archTarget.linux], arch)
23+
}
24+
for i := range clangArches {
25+
slices.Sort(clangArches[i])
26+
}
27+
for i := range linuxArchesLE {
28+
slices.Sort(linuxArchesLE[i])
29+
}
30+
for i := range linuxArchesBE {
31+
slices.Sort(linuxArchesBE[i])
32+
}
33+
34+
nativeTarget, nativeArches, err := FindTarget("native")
35+
qt.Assert(t, qt.IsNil(err))
36+
37+
tests := []struct {
38+
short string
39+
target Target
40+
arches GoArches
41+
}{
42+
{
43+
"bpf",
44+
Target{"bpf", ""},
45+
nil,
46+
},
47+
{
48+
"bpfel",
49+
Target{"bpfel", ""},
50+
clangArches["bpfel"],
51+
},
52+
{
53+
"bpfeb",
54+
Target{"bpfeb", ""},
55+
clangArches["bpfeb"],
56+
},
57+
{
58+
"amd64",
59+
Target{"bpfel", "x86"},
60+
linuxArchesLE["x86"],
61+
},
62+
{
63+
"386",
64+
Target{"bpfel", "x86"},
65+
linuxArchesLE["x86"],
66+
},
67+
{
68+
"ppc64",
69+
Target{"bpfeb", "powerpc"},
70+
linuxArchesBE["powerpc"],
71+
},
72+
{
73+
"native",
74+
nativeTarget,
75+
nativeArches,
76+
},
77+
}
78+
79+
for _, test := range tests {
80+
t.Run(test.short, func(t *testing.T) {
81+
target, arches, err := FindTarget(test.short)
82+
qt.Assert(t, qt.IsNil(err))
83+
qt.Assert(t, qt.Equals(target, test.target))
84+
qt.Assert(t, qt.DeepEquals(arches, test.arches))
85+
})
86+
}
87+
}
88+
89+
func TestCollectTargetsErrors(t *testing.T) {
90+
tests := []struct {
91+
name string
92+
target string
93+
}{
94+
{"unknown", "frood"},
95+
{"no linux target", "mipsle"},
96+
}
97+
98+
for _, test := range tests {
99+
t.Run(test.name, func(t *testing.T) {
100+
_, _, err := FindTarget(test.target)
101+
if err == nil {
102+
t.Fatal("Function did not return an error")
103+
}
104+
t.Log("Error message:", err)
105+
})
106+
}
107+
}
108+
109+
func TestGoarches(t *testing.T) {
110+
exe := goBin(t)
111+
112+
for GoArch := range targetsByGoArch {
113+
t.Run(string(GoArch), func(t *testing.T) {
114+
goEnv := exec.Command(exe, "env")
115+
goEnv.Env = []string{"GOROOT=/", "GOOS=linux", "GOARCH=" + string(GoArch)}
116+
output, err := goEnv.CombinedOutput()
117+
qt.Assert(t, qt.IsNil(err), qt.Commentf("go output is:\n%s", string(output)))
118+
})
119+
}
120+
}
121+
122+
func TestClangTargets(t *testing.T) {
123+
exe := goBin(t)
124+
125+
clangTargets := map[string]struct{}{}
126+
for _, tgt := range targetsByGoArch {
127+
clangTargets[tgt.clang] = struct{}{}
128+
}
129+
130+
for target := range clangTargets {
131+
for _, env := range []string{"GOOS", "GOARCH"} {
132+
env += "=" + target
133+
t.Run(env, func(t *testing.T) {
134+
goEnv := exec.Command(exe, "env")
135+
goEnv.Env = []string{"GOROOT=/", env}
136+
output, err := goEnv.CombinedOutput()
137+
t.Log("go output is:", string(output))
138+
qt.Assert(t, qt.IsNotNil(err), qt.Commentf("No clang target should be a valid build constraint"))
139+
})
140+
}
141+
142+
}
143+
}
144+
145+
func goBin(t *testing.T) string {
146+
t.Helper()
147+
148+
exe, err := exec.LookPath("go")
149+
if errors.Is(err, exec.ErrNotFound) {
150+
t.Skip("go binary is not in PATH")
151+
}
152+
qt.Assert(t, qt.IsNil(err))
153+
154+
return exe
155+
}

0 commit comments

Comments
 (0)