...

Source file src/github.com/cilium/ebpf/cmd/bpf2go/main.go

Documentation: github.com/cilium/ebpf/cmd/bpf2go

     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  // Targets understood by bpf2go.
    41  //
    42  // Targets without a Linux string can't be used directly and are only included
    43  // for the generic bpf, bpfel, bpfeb targets.
    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  		// Command line arguments take precedence over C flags
   115  		// from the flag.
   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  		// Try to find a suitable llvm-strip, possibly with a version suffix derived
   176  		// from the clang binary.
   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  // cTypes collects the C type names a user wants to generate Go types for.
   200  //
   201  // Names are guaranteed to be unique, and only a subset of names is accepted so
   202  // that we may extend the flag syntax in the future.
   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  	// Absolute path to a .c file.
   240  	sourceFile string
   241  	// Absolute path to a directory where .go are written
   242  	outputDir string
   243  	// Valid go package name.
   244  	pkg string
   245  	// Valid go identifier.
   246  	ident string
   247  	// C compiler.
   248  	cc string
   249  	// Command used to strip DWARF.
   250  	strip            string
   251  	disableStripping bool
   252  	// C flags passed to the compiler.
   253  	cFlags          []string
   254  	skipGlobalTypes bool
   255  	// C types to include in the generatd output.
   256  	cTypes cTypes
   257  	// Go tags included in the .go
   258  	tags string
   259  	// Base directory of the Makefile. Enables outputting make-style dependencies
   260  	// in .d files.
   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  	// Write out generated go
   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  	// There is always at least a dependency for the main file.
   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  					// Include tags for all goarches that have the same endianness.
   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  					// Include tags for all goarches that have the same
   422  					// target.
   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