1 package main
2
3 import (
4 "bytes"
5 "errors"
6 "flag"
7 "fmt"
8 "go/token"
9 "io"
10 "os"
11 "os/exec"
12 "path/filepath"
13 "regexp"
14 "runtime"
15 "sort"
16 "strings"
17 )
18
19 const helpText = `Usage: %[1]s [options] <ident> <source file> [-- <C flags>]
20
21 ident is used as the stem of all generated Go types and functions, and
22 must be a valid Go identifier.
23
24 source is a single C file that is compiled using the specified compiler
25 (usually some version of clang).
26
27 You can pass options to the compiler by appending them after a '--' argument
28 or by supplying -cflags. Flags passed as arguments take precedence
29 over flags passed via -cflags. Additionally, the program expands quotation
30 marks in -cflags. This means that -cflags 'foo "bar baz"' is passed to the
31 compiler as two arguments "foo" and "bar baz".
32
33 The program expects GOPACKAGE to be set in the environment, and should be invoked
34 via go generate. The generated files are written to the current directory.
35
36 Options:
37
38 `
39
40
41
42
43
44 var targetByGoArch = map[string]target{
45 "386": {"bpfel", "x86"},
46 "amd64": {"bpfel", "x86"},
47 "amd64p32": {"bpfel", ""},
48 "arm": {"bpfel", "arm"},
49 "arm64": {"bpfel", "arm64"},
50 "mipsle": {"bpfel", ""},
51 "mips64le": {"bpfel", ""},
52 "mips64p32le": {"bpfel", ""},
53 "ppc64le": {"bpfel", "powerpc"},
54 "riscv64": {"bpfel", ""},
55 "armbe": {"bpfeb", "arm"},
56 "arm64be": {"bpfeb", "arm64"},
57 "mips": {"bpfeb", ""},
58 "mips64": {"bpfeb", ""},
59 "mips64p32": {"bpfeb", ""},
60 "ppc64": {"bpfeb", "powerpc"},
61 "s390": {"bpfeb", "s390"},
62 "s390x": {"bpfeb", "s390"},
63 "sparc": {"bpfeb", "sparc"},
64 "sparc64": {"bpfeb", "sparc"},
65 }
66
67 func run(stdout io.Writer, pkg, outputDir string, args []string) (err error) {
68 b2g := bpf2go{
69 stdout: stdout,
70 pkg: pkg,
71 outputDir: outputDir,
72 }
73
74 fs := flag.NewFlagSet("bpf2go", flag.ContinueOnError)
75 fs.StringVar(&b2g.cc, "cc", "clang", "`binary` used to compile C to BPF")
76 fs.StringVar(&b2g.strip, "strip", "", "`binary` used to strip DWARF from compiled BPF (default \"llvm-strip\")")
77 fs.BoolVar(&b2g.disableStripping, "no-strip", false, "disable stripping of DWARF")
78 flagCFlags := fs.String("cflags", "", "flags passed to the compiler, may contain quoted arguments")
79 fs.StringVar(&b2g.tags, "tags", "", "list of Go build tags to include in generated files")
80 flagTarget := fs.String("target", "bpfel,bpfeb", "clang target to compile for")
81 fs.StringVar(&b2g.makeBase, "makebase", "", "write make compatible depinfo files relative to `directory`")
82 fs.Var(&b2g.cTypes, "type", "`Name` of a type to generate a Go declaration for, may be repeated")
83 fs.BoolVar(&b2g.skipGlobalTypes, "no-global-types", false, "Skip generating types for map keys and values, etc.")
84
85 fs.SetOutput(stdout)
86 fs.Usage = func() {
87 fmt.Fprintf(fs.Output(), helpText, fs.Name())
88 fs.PrintDefaults()
89 fmt.Fprintln(fs.Output())
90 printTargets(fs.Output())
91 }
92 if err := fs.Parse(args); errors.Is(err, flag.ErrHelp) {
93 return nil
94 } else if err != nil {
95 return err
96 }
97
98 if b2g.pkg == "" {
99 return errors.New("missing package, are you running via go generate?")
100 }
101
102 if b2g.cc == "" {
103 return errors.New("no compiler specified")
104 }
105
106 args, cFlags := splitCFlagsFromArgs(fs.Args())
107
108 if *flagCFlags != "" {
109 splitCFlags, err := splitArguments(*flagCFlags)
110 if err != nil {
111 return err
112 }
113
114
115
116 cFlags = append(splitCFlags, cFlags...)
117 }
118
119 for _, cFlag := range cFlags {
120 if strings.HasPrefix(cFlag, "-M") {
121 return fmt.Errorf("use -makebase instead of %q", cFlag)
122 }
123 }
124
125 b2g.cFlags = cFlags[:len(cFlags):len(cFlags)]
126
127 if len(args) < 2 {
128 return errors.New("expected at least two arguments")
129 }
130
131 b2g.ident = args[0]
132 if !token.IsIdentifier(b2g.ident) {
133 return fmt.Errorf("%q is not a valid identifier", b2g.ident)
134 }
135
136 input := args[1]
137 if _, err := os.Stat(input); os.IsNotExist(err) {
138 return fmt.Errorf("file %s doesn't exist", input)
139 } else if err != nil {
140 return fmt.Errorf("state %s: %s", input, err)
141 }
142
143 b2g.sourceFile, err = filepath.Abs(input)
144 if err != nil {
145 return err
146 }
147
148 if b2g.makeBase != "" {
149 b2g.makeBase, err = filepath.Abs(b2g.makeBase)
150 if err != nil {
151 return err
152 }
153 }
154
155 if strings.ContainsRune(b2g.tags, '\n') {
156 return fmt.Errorf("-tags mustn't contain new line characters")
157 }
158
159 targetArches := strings.Split(*flagTarget, ",")
160 if len(targetArches) == 0 {
161 return fmt.Errorf("no targets specified")
162 }
163
164 targets, err := collectTargets(targetArches)
165 if errors.Is(err, errInvalidTarget) {
166 printTargets(stdout)
167 fmt.Fprintln(stdout)
168 return err
169 }
170 if err != nil {
171 return err
172 }
173
174 if !b2g.disableStripping {
175
176
177 if b2g.strip == "" {
178 b2g.strip = "llvm-strip"
179 if strings.HasPrefix(b2g.cc, "clang") {
180 b2g.strip += strings.TrimPrefix(b2g.cc, "clang")
181 }
182 }
183
184 b2g.strip, err = exec.LookPath(b2g.strip)
185 if err != nil {
186 return err
187 }
188 }
189
190 for target, arches := range targets {
191 if err := b2g.convert(target, arches); err != nil {
192 return err
193 }
194 }
195
196 return nil
197 }
198
199
200
201
202
203 type cTypes []string
204
205 var _ flag.Value = (*cTypes)(nil)
206
207 func (ct *cTypes) String() string {
208 if ct == nil {
209 return "[]"
210 }
211 return fmt.Sprint(*ct)
212 }
213
214 const validCTypeChars = `[a-z0-9_]`
215
216 var reValidCType = regexp.MustCompile(`(?i)^` + validCTypeChars + `+$`)
217
218 func (ct *cTypes) Set(value string) error {
219 if !reValidCType.MatchString(value) {
220 return fmt.Errorf("%q contains characters outside of %s", value, validCTypeChars)
221 }
222
223 i := sort.SearchStrings(*ct, value)
224 if i >= len(*ct) {
225 *ct = append(*ct, value)
226 return nil
227 }
228
229 if (*ct)[i] == value {
230 return fmt.Errorf("duplicate type %q", value)
231 }
232
233 *ct = append((*ct)[:i], append([]string{value}, (*ct)[i:]...)...)
234 return nil
235 }
236
237 type bpf2go struct {
238 stdout io.Writer
239
240 sourceFile string
241
242 outputDir string
243
244 pkg string
245
246 ident string
247
248 cc string
249
250 strip string
251 disableStripping bool
252
253 cFlags []string
254 skipGlobalTypes bool
255
256 cTypes cTypes
257
258 tags string
259
260
261 makeBase string
262 }
263
264 func (b2g *bpf2go) convert(tgt target, arches []string) (err error) {
265 removeOnError := func(f *os.File) {
266 if err != nil {
267 os.Remove(f.Name())
268 }
269 f.Close()
270 }
271
272 stem := fmt.Sprintf("%s_%s", strings.ToLower(b2g.ident), tgt.clang)
273 if tgt.linux != "" {
274 stem = fmt.Sprintf("%s_%s_%s", strings.ToLower(b2g.ident), tgt.clang, tgt.linux)
275 }
276
277 objFileName := filepath.Join(b2g.outputDir, stem+".o")
278
279 cwd, err := os.Getwd()
280 if err != nil {
281 return err
282 }
283
284 var tags []string
285 if len(arches) > 0 {
286 tags = append(tags, strings.Join(arches, " "))
287 }
288 if b2g.tags != "" {
289 tags = append(tags, b2g.tags)
290 }
291
292 cFlags := make([]string, len(b2g.cFlags))
293 copy(cFlags, b2g.cFlags)
294 if tgt.linux != "" {
295 cFlags = append(cFlags, "-D__TARGET_ARCH_"+tgt.linux)
296 }
297
298 var dep bytes.Buffer
299 err = compile(compileArgs{
300 cc: b2g.cc,
301 cFlags: cFlags,
302 target: tgt.clang,
303 dir: cwd,
304 source: b2g.sourceFile,
305 dest: objFileName,
306 dep: &dep,
307 })
308 if err != nil {
309 return err
310 }
311
312 fmt.Fprintln(b2g.stdout, "Compiled", objFileName)
313
314 if !b2g.disableStripping {
315 if err := strip(b2g.strip, objFileName); err != nil {
316 return err
317 }
318 fmt.Fprintln(b2g.stdout, "Stripped", objFileName)
319 }
320
321
322 goFileName := filepath.Join(b2g.outputDir, stem+".go")
323 goFile, err := os.Create(goFileName)
324 if err != nil {
325 return err
326 }
327 defer removeOnError(goFile)
328
329 err = output(outputArgs{
330 pkg: b2g.pkg,
331 ident: b2g.ident,
332 cTypes: b2g.cTypes,
333 skipGlobalTypes: b2g.skipGlobalTypes,
334 tags: tags,
335 obj: objFileName,
336 out: goFile,
337 })
338 if err != nil {
339 return fmt.Errorf("can't write %s: %s", goFileName, err)
340 }
341
342 fmt.Fprintln(b2g.stdout, "Wrote", goFileName)
343
344 if b2g.makeBase == "" {
345 return
346 }
347
348 deps, err := parseDependencies(cwd, &dep)
349 if err != nil {
350 return fmt.Errorf("can't read dependency information: %s", err)
351 }
352
353
354 deps[0].file = goFileName
355 depFile, err := adjustDependencies(b2g.makeBase, deps)
356 if err != nil {
357 return fmt.Errorf("can't adjust dependency information: %s", err)
358 }
359
360 depFileName := goFileName + ".d"
361 if err := os.WriteFile(depFileName, depFile, 0666); err != nil {
362 return fmt.Errorf("can't write dependency file: %s", err)
363 }
364
365 fmt.Fprintln(b2g.stdout, "Wrote", depFileName)
366 return nil
367 }
368
369 type target struct {
370 clang string
371 linux string
372 }
373
374 func printTargets(w io.Writer) {
375 var arches []string
376 for arch, archTarget := range targetByGoArch {
377 if archTarget.linux == "" {
378 continue
379 }
380 arches = append(arches, arch)
381 }
382 sort.Strings(arches)
383
384 fmt.Fprint(w, "Supported targets:\n")
385 fmt.Fprint(w, "\tbpf\n\tbpfel\n\tbpfeb\n")
386 for _, arch := range arches {
387 fmt.Fprintf(w, "\t%s\n", arch)
388 }
389 }
390
391 var errInvalidTarget = errors.New("unsupported target")
392
393 func collectTargets(targets []string) (map[target][]string, error) {
394 result := make(map[target][]string)
395 for _, tgt := range targets {
396 switch tgt {
397 case "bpf", "bpfel", "bpfeb":
398 var goarches []string
399 for arch, archTarget := range targetByGoArch {
400 if archTarget.clang == tgt {
401
402 goarches = append(goarches, arch)
403 }
404 }
405 sort.Strings(goarches)
406 result[target{tgt, ""}] = goarches
407
408 case "native":
409 tgt = runtime.GOARCH
410 fallthrough
411
412 default:
413 archTarget, ok := targetByGoArch[tgt]
414 if !ok || archTarget.linux == "" {
415 return nil, fmt.Errorf("%q: %w", tgt, errInvalidTarget)
416 }
417
418 var goarches []string
419 for goarch, lt := range targetByGoArch {
420 if lt == archTarget {
421
422
423 goarches = append(goarches, goarch)
424 }
425 }
426
427 sort.Strings(goarches)
428 result[archTarget] = goarches
429 }
430 }
431
432 return result, nil
433 }
434
435 func main() {
436 outputDir, err := os.Getwd()
437 if err != nil {
438 fmt.Fprintln(os.Stderr, "Error:", err)
439 os.Exit(1)
440 }
441
442 if err := run(os.Stdout, os.Getenv("GOPACKAGE"), outputDir, os.Args[1:]); err != nil {
443 fmt.Fprintln(os.Stderr, "Error:", err)
444 os.Exit(1)
445 }
446 }
447
View as plain text