github.com/cilium/ebpf@v0.16.0/cmd/bpf2go/gen/output.go (about)

     1  package gen
     2  
     3  import (
     4  	"bytes"
     5  	_ "embed"
     6  	"fmt"
     7  	"go/build/constraint"
     8  	"go/token"
     9  	"io"
    10  	"sort"
    11  	"strings"
    12  	"text/template"
    13  	"unicode"
    14  	"unicode/utf8"
    15  
    16  	"github.com/cilium/ebpf/btf"
    17  	b2gInt "github.com/cilium/ebpf/cmd/bpf2go/internal"
    18  	"github.com/cilium/ebpf/internal"
    19  )
    20  
    21  //go:embed output.tpl
    22  var commonRaw string
    23  
    24  var commonTemplate = template.Must(template.New("common").Parse(commonRaw))
    25  
    26  type templateName string
    27  
    28  func (n templateName) maybeExport(str string) string {
    29  	if token.IsExported(string(n)) {
    30  		return toUpperFirst(str)
    31  	}
    32  
    33  	return str
    34  }
    35  
    36  func (n templateName) Bytes() string {
    37  	return "_" + toUpperFirst(string(n)) + "Bytes"
    38  }
    39  
    40  func (n templateName) Specs() string {
    41  	return string(n) + "Specs"
    42  }
    43  
    44  func (n templateName) ProgramSpecs() string {
    45  	return string(n) + "ProgramSpecs"
    46  }
    47  
    48  func (n templateName) MapSpecs() string {
    49  	return string(n) + "MapSpecs"
    50  }
    51  
    52  func (n templateName) Load() string {
    53  	return n.maybeExport("load" + toUpperFirst(string(n)))
    54  }
    55  
    56  func (n templateName) LoadObjects() string {
    57  	return n.maybeExport("load" + toUpperFirst(string(n)) + "Objects")
    58  }
    59  
    60  func (n templateName) Objects() string {
    61  	return string(n) + "Objects"
    62  }
    63  
    64  func (n templateName) Maps() string {
    65  	return string(n) + "Maps"
    66  }
    67  
    68  func (n templateName) Programs() string {
    69  	return string(n) + "Programs"
    70  }
    71  
    72  func (n templateName) CloseHelper() string {
    73  	return "_" + toUpperFirst(string(n)) + "Close"
    74  }
    75  
    76  type GenerateArgs struct {
    77  	// Package of the resulting file.
    78  	Package string
    79  	// The prefix of all names declared at the top-level.
    80  	Stem string
    81  	// Build Constraints included in the resulting file.
    82  	Constraints constraint.Expr
    83  	// Maps to be emitted.
    84  	Maps []string
    85  	// Programs to be emitted.
    86  	Programs []string
    87  	// Types to be emitted.
    88  	Types []btf.Type
    89  	// Filename of the object to embed.
    90  	ObjectFile string
    91  	// Output to write template to.
    92  	Output io.Writer
    93  }
    94  
    95  // Generate bindings for a BPF ELF file.
    96  func Generate(args GenerateArgs) error {
    97  	if !token.IsIdentifier(args.Stem) {
    98  		return fmt.Errorf("%q is not a valid identifier", args.Stem)
    99  	}
   100  
   101  	if strings.ContainsAny(args.ObjectFile, "\n") {
   102  		// Prevent injecting newlines into the template.
   103  		return fmt.Errorf("file %q contains an invalid character", args.ObjectFile)
   104  	}
   105  
   106  	for _, typ := range args.Types {
   107  		if _, ok := btf.As[*btf.Datasec](typ); ok {
   108  			// Avoid emitting .rodata, .bss, etc. for now. We might want to
   109  			// name these types differently, etc.
   110  			return fmt.Errorf("can't output btf.Datasec: %s", typ)
   111  		}
   112  	}
   113  
   114  	maps := make(map[string]string)
   115  	for _, name := range args.Maps {
   116  		maps[name] = internal.Identifier(name)
   117  	}
   118  
   119  	programs := make(map[string]string)
   120  	for _, name := range args.Programs {
   121  		programs[name] = internal.Identifier(name)
   122  	}
   123  
   124  	typeNames := make(map[btf.Type]string)
   125  	for _, typ := range args.Types {
   126  		// NB: This also deduplicates types.
   127  		typeNames[typ] = args.Stem + internal.Identifier(typ.TypeName())
   128  	}
   129  
   130  	// Ensure we don't have conflicting names and generate a sorted list of
   131  	// named types so that the output is stable.
   132  	types, err := sortTypes(typeNames)
   133  	if err != nil {
   134  		return err
   135  	}
   136  
   137  	gf := &btf.GoFormatter{
   138  		Names:      typeNames,
   139  		Identifier: internal.Identifier,
   140  	}
   141  
   142  	ctx := struct {
   143  		*btf.GoFormatter
   144  		Module      string
   145  		Package     string
   146  		Constraints constraint.Expr
   147  		Name        templateName
   148  		Maps        map[string]string
   149  		Programs    map[string]string
   150  		Types       []btf.Type
   151  		TypeNames   map[btf.Type]string
   152  		File        string
   153  	}{
   154  		gf,
   155  		b2gInt.CurrentModule,
   156  		args.Package,
   157  		args.Constraints,
   158  		templateName(args.Stem),
   159  		maps,
   160  		programs,
   161  		types,
   162  		typeNames,
   163  		args.ObjectFile,
   164  	}
   165  
   166  	var buf bytes.Buffer
   167  	if err := commonTemplate.Execute(&buf, &ctx); err != nil {
   168  		return fmt.Errorf("can't generate types: %s", err)
   169  	}
   170  
   171  	return internal.WriteFormatted(buf.Bytes(), args.Output)
   172  }
   173  
   174  // sortTypes returns a list of types sorted by their (generated) Go type name.
   175  //
   176  // Duplicate Go type names are rejected.
   177  func sortTypes(typeNames map[btf.Type]string) ([]btf.Type, error) {
   178  	var types []btf.Type
   179  	var names []string
   180  	for typ, name := range typeNames {
   181  		i := sort.SearchStrings(names, name)
   182  		if i >= len(names) {
   183  			types = append(types, typ)
   184  			names = append(names, name)
   185  			continue
   186  		}
   187  
   188  		if names[i] == name {
   189  			return nil, fmt.Errorf("type name %q is used multiple times", name)
   190  		}
   191  
   192  		types = append(types[:i], append([]btf.Type{typ}, types[i:]...)...)
   193  		names = append(names[:i], append([]string{name}, names[i:]...)...)
   194  	}
   195  
   196  	return types, nil
   197  }
   198  
   199  func toUpperFirst(str string) string {
   200  	first, n := utf8.DecodeRuneInString(str)
   201  	return string(unicode.ToUpper(first)) + str[n:]
   202  }