github.com/cilium/ebpf@v0.10.0/cmd/bpf2go/main.go (about)

     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(s) to compile for (comma separated)")
    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  	fs.StringVar(&b2g.outputStem, "output-stem", "", "alternative stem for names of generated files (defaults to ident)")
    85  
    86  	fs.SetOutput(stdout)
    87  	fs.Usage = func() {
    88  		fmt.Fprintf(fs.Output(), helpText, fs.Name())
    89  		fs.PrintDefaults()
    90  		fmt.Fprintln(fs.Output())
    91  		printTargets(fs.Output())
    92  	}
    93  	if err := fs.Parse(args); errors.Is(err, flag.ErrHelp) {
    94  		return nil
    95  	} else if err != nil {
    96  		return err
    97  	}
    98  
    99  	if b2g.pkg == "" {
   100  		return errors.New("missing package, are you running via go generate?")
   101  	}
   102  
   103  	if b2g.cc == "" {
   104  		return errors.New("no compiler specified")
   105  	}
   106  
   107  	args, cFlags := splitCFlagsFromArgs(fs.Args())
   108  
   109  	if *flagCFlags != "" {
   110  		splitCFlags, err := splitArguments(*flagCFlags)
   111  		if err != nil {
   112  			return err
   113  		}
   114  
   115  		// Command line arguments take precedence over C flags
   116  		// from the flag.
   117  		cFlags = append(splitCFlags, cFlags...)
   118  	}
   119  
   120  	for _, cFlag := range cFlags {
   121  		if strings.HasPrefix(cFlag, "-M") {
   122  			return fmt.Errorf("use -makebase instead of %q", cFlag)
   123  		}
   124  	}
   125  
   126  	b2g.cFlags = cFlags[:len(cFlags):len(cFlags)]
   127  
   128  	if len(args) < 2 {
   129  		return errors.New("expected at least two arguments")
   130  	}
   131  
   132  	b2g.ident = args[0]
   133  	if !token.IsIdentifier(b2g.ident) {
   134  		return fmt.Errorf("%q is not a valid identifier", b2g.ident)
   135  	}
   136  
   137  	input := args[1]
   138  	if _, err := os.Stat(input); os.IsNotExist(err) {
   139  		return fmt.Errorf("file %s doesn't exist", input)
   140  	} else if err != nil {
   141  		return fmt.Errorf("state %s: %s", input, err)
   142  	}
   143  
   144  	b2g.sourceFile, err = filepath.Abs(input)
   145  	if err != nil {
   146  		return err
   147  	}
   148  
   149  	if b2g.makeBase != "" {
   150  		b2g.makeBase, err = filepath.Abs(b2g.makeBase)
   151  		if err != nil {
   152  			return err
   153  		}
   154  	}
   155  
   156  	if b2g.outputStem != "" && strings.ContainsRune(b2g.outputStem, filepath.Separator) {
   157  		return fmt.Errorf("-output-stem %q must not contain path separation characters", b2g.outputStem)
   158  	}
   159  
   160  	if strings.ContainsRune(b2g.tags, '\n') {
   161  		return fmt.Errorf("-tags mustn't contain new line characters")
   162  	}
   163  
   164  	targetArches := strings.Split(*flagTarget, ",")
   165  	if len(targetArches) == 0 {
   166  		return fmt.Errorf("no targets specified")
   167  	}
   168  
   169  	targets, err := collectTargets(targetArches)
   170  	if errors.Is(err, errInvalidTarget) {
   171  		printTargets(stdout)
   172  		fmt.Fprintln(stdout)
   173  		return err
   174  	}
   175  	if err != nil {
   176  		return err
   177  	}
   178  
   179  	if !b2g.disableStripping {
   180  		// Try to find a suitable llvm-strip, possibly with a version suffix derived
   181  		// from the clang binary.
   182  		if b2g.strip == "" {
   183  			b2g.strip = "llvm-strip"
   184  			if strings.HasPrefix(b2g.cc, "clang") {
   185  				b2g.strip += strings.TrimPrefix(b2g.cc, "clang")
   186  			}
   187  		}
   188  
   189  		b2g.strip, err = exec.LookPath(b2g.strip)
   190  		if err != nil {
   191  			return err
   192  		}
   193  	}
   194  
   195  	for target, arches := range targets {
   196  		if err := b2g.convert(target, arches); err != nil {
   197  			return err
   198  		}
   199  	}
   200  
   201  	return nil
   202  }
   203  
   204  // cTypes collects the C type names a user wants to generate Go types for.
   205  //
   206  // Names are guaranteed to be unique, and only a subset of names is accepted so
   207  // that we may extend the flag syntax in the future.
   208  type cTypes []string
   209  
   210  var _ flag.Value = (*cTypes)(nil)
   211  
   212  func (ct *cTypes) String() string {
   213  	if ct == nil {
   214  		return "[]"
   215  	}
   216  	return fmt.Sprint(*ct)
   217  }
   218  
   219  const validCTypeChars = `[a-z0-9_]`
   220  
   221  var reValidCType = regexp.MustCompile(`(?i)^` + validCTypeChars + `+$`)
   222  
   223  func (ct *cTypes) Set(value string) error {
   224  	if !reValidCType.MatchString(value) {
   225  		return fmt.Errorf("%q contains characters outside of %s", value, validCTypeChars)
   226  	}
   227  
   228  	i := sort.SearchStrings(*ct, value)
   229  	if i >= len(*ct) {
   230  		*ct = append(*ct, value)
   231  		return nil
   232  	}
   233  
   234  	if (*ct)[i] == value {
   235  		return fmt.Errorf("duplicate type %q", value)
   236  	}
   237  
   238  	*ct = append((*ct)[:i], append([]string{value}, (*ct)[i:]...)...)
   239  	return nil
   240  }
   241  
   242  type bpf2go struct {
   243  	stdout io.Writer
   244  	// Absolute path to a .c file.
   245  	sourceFile string
   246  	// Absolute path to a directory where .go are written
   247  	outputDir string
   248  	// Alternative output stem. If empty, ident is used.
   249  	outputStem string
   250  	// Valid go package name.
   251  	pkg string
   252  	// Valid go identifier.
   253  	ident string
   254  	// C compiler.
   255  	cc string
   256  	// Command used to strip DWARF.
   257  	strip            string
   258  	disableStripping bool
   259  	// C flags passed to the compiler.
   260  	cFlags          []string
   261  	skipGlobalTypes bool
   262  	// C types to include in the generatd output.
   263  	cTypes cTypes
   264  	// Go tags included in the .go
   265  	tags string
   266  	// Base directory of the Makefile. Enables outputting make-style dependencies
   267  	// in .d files.
   268  	makeBase string
   269  }
   270  
   271  func (b2g *bpf2go) convert(tgt target, arches []string) (err error) {
   272  	removeOnError := func(f *os.File) {
   273  		if err != nil {
   274  			os.Remove(f.Name())
   275  		}
   276  		f.Close()
   277  	}
   278  
   279  	outputStem := b2g.outputStem
   280  	if outputStem == "" {
   281  		outputStem = strings.ToLower(b2g.ident)
   282  	}
   283  	stem := fmt.Sprintf("%s_%s", outputStem, tgt.clang)
   284  	if tgt.linux != "" {
   285  		stem = fmt.Sprintf("%s_%s_%s", outputStem, tgt.clang, tgt.linux)
   286  	}
   287  
   288  	objFileName := filepath.Join(b2g.outputDir, stem+".o")
   289  
   290  	cwd, err := os.Getwd()
   291  	if err != nil {
   292  		return err
   293  	}
   294  
   295  	var tags []string
   296  	if len(arches) > 0 {
   297  		tags = append(tags, strings.Join(arches, " "))
   298  	}
   299  	if b2g.tags != "" {
   300  		tags = append(tags, b2g.tags)
   301  	}
   302  
   303  	cFlags := make([]string, len(b2g.cFlags))
   304  	copy(cFlags, b2g.cFlags)
   305  	if tgt.linux != "" {
   306  		cFlags = append(cFlags, "-D__TARGET_ARCH_"+tgt.linux)
   307  	}
   308  
   309  	var dep bytes.Buffer
   310  	err = compile(compileArgs{
   311  		cc:     b2g.cc,
   312  		cFlags: cFlags,
   313  		target: tgt.clang,
   314  		dir:    cwd,
   315  		source: b2g.sourceFile,
   316  		dest:   objFileName,
   317  		dep:    &dep,
   318  	})
   319  	if err != nil {
   320  		return err
   321  	}
   322  
   323  	fmt.Fprintln(b2g.stdout, "Compiled", objFileName)
   324  
   325  	if !b2g.disableStripping {
   326  		if err := strip(b2g.strip, objFileName); err != nil {
   327  			return err
   328  		}
   329  		fmt.Fprintln(b2g.stdout, "Stripped", objFileName)
   330  	}
   331  
   332  	// Write out generated go
   333  	goFileName := filepath.Join(b2g.outputDir, stem+".go")
   334  	goFile, err := os.Create(goFileName)
   335  	if err != nil {
   336  		return err
   337  	}
   338  	defer removeOnError(goFile)
   339  
   340  	err = output(outputArgs{
   341  		pkg:             b2g.pkg,
   342  		ident:           b2g.ident,
   343  		cTypes:          b2g.cTypes,
   344  		skipGlobalTypes: b2g.skipGlobalTypes,
   345  		tags:            tags,
   346  		obj:             objFileName,
   347  		out:             goFile,
   348  	})
   349  	if err != nil {
   350  		return fmt.Errorf("can't write %s: %s", goFileName, err)
   351  	}
   352  
   353  	fmt.Fprintln(b2g.stdout, "Wrote", goFileName)
   354  
   355  	if b2g.makeBase == "" {
   356  		return
   357  	}
   358  
   359  	deps, err := parseDependencies(cwd, &dep)
   360  	if err != nil {
   361  		return fmt.Errorf("can't read dependency information: %s", err)
   362  	}
   363  
   364  	// There is always at least a dependency for the main file.
   365  	deps[0].file = goFileName
   366  	depFile, err := adjustDependencies(b2g.makeBase, deps)
   367  	if err != nil {
   368  		return fmt.Errorf("can't adjust dependency information: %s", err)
   369  	}
   370  
   371  	depFileName := goFileName + ".d"
   372  	if err := os.WriteFile(depFileName, depFile, 0666); err != nil {
   373  		return fmt.Errorf("can't write dependency file: %s", err)
   374  	}
   375  
   376  	fmt.Fprintln(b2g.stdout, "Wrote", depFileName)
   377  	return nil
   378  }
   379  
   380  type target struct {
   381  	clang string
   382  	linux string
   383  }
   384  
   385  func printTargets(w io.Writer) {
   386  	var arches []string
   387  	for arch, archTarget := range targetByGoArch {
   388  		if archTarget.linux == "" {
   389  			continue
   390  		}
   391  		arches = append(arches, arch)
   392  	}
   393  	sort.Strings(arches)
   394  
   395  	fmt.Fprint(w, "Supported targets:\n")
   396  	fmt.Fprint(w, "\tbpf\n\tbpfel\n\tbpfeb\n")
   397  	for _, arch := range arches {
   398  		fmt.Fprintf(w, "\t%s\n", arch)
   399  	}
   400  }
   401  
   402  var errInvalidTarget = errors.New("unsupported target")
   403  
   404  func collectTargets(targets []string) (map[target][]string, error) {
   405  	result := make(map[target][]string)
   406  	for _, tgt := range targets {
   407  		switch tgt {
   408  		case "bpf", "bpfel", "bpfeb":
   409  			var goarches []string
   410  			for arch, archTarget := range targetByGoArch {
   411  				if archTarget.clang == tgt {
   412  					// Include tags for all goarches that have the same endianness.
   413  					goarches = append(goarches, arch)
   414  				}
   415  			}
   416  			sort.Strings(goarches)
   417  			result[target{tgt, ""}] = goarches
   418  
   419  		case "native":
   420  			tgt = runtime.GOARCH
   421  			fallthrough
   422  
   423  		default:
   424  			archTarget, ok := targetByGoArch[tgt]
   425  			if !ok || archTarget.linux == "" {
   426  				return nil, fmt.Errorf("%q: %w", tgt, errInvalidTarget)
   427  			}
   428  
   429  			var goarches []string
   430  			for goarch, lt := range targetByGoArch {
   431  				if lt == archTarget {
   432  					// Include tags for all goarches that have the same
   433  					// target.
   434  					goarches = append(goarches, goarch)
   435  				}
   436  			}
   437  
   438  			sort.Strings(goarches)
   439  			result[archTarget] = goarches
   440  		}
   441  	}
   442  
   443  	return result, nil
   444  }
   445  
   446  func main() {
   447  	outputDir, err := os.Getwd()
   448  	if err != nil {
   449  		fmt.Fprintln(os.Stderr, "Error:", err)
   450  		os.Exit(1)
   451  	}
   452  
   453  	if err := run(os.Stdout, os.Getenv("GOPACKAGE"), outputDir, os.Args[1:]); err != nil {
   454  		fmt.Fprintln(os.Stderr, "Error:", err)
   455  		os.Exit(1)
   456  	}
   457  }