package main import ( "bytes" _ "embed" "fmt" "go/build/constraint" "go/token" "io" "sort" "strings" "text/template" "github.com/cilium/ebpf" "github.com/cilium/ebpf/btf" "github.com/cilium/ebpf/internal" ) //go:embed output.tpl var commonRaw string var commonTemplate = template.Must(template.New("common").Parse(commonRaw)) type templateName string func (n templateName) maybeExport(str string) string { if token.IsExported(string(n)) { return toUpperFirst(str) } return str } func (n templateName) Bytes() string { return "_" + toUpperFirst(string(n)) + "Bytes" } func (n templateName) Specs() string { return string(n) + "Specs" } func (n templateName) ProgramSpecs() string { return string(n) + "ProgramSpecs" } func (n templateName) MapSpecs() string { return string(n) + "MapSpecs" } func (n templateName) Load() string { return n.maybeExport("load" + toUpperFirst(string(n))) } func (n templateName) LoadObjects() string { return n.maybeExport("load" + toUpperFirst(string(n)) + "Objects") } func (n templateName) Objects() string { return string(n) + "Objects" } func (n templateName) Maps() string { return string(n) + "Maps" } func (n templateName) Programs() string { return string(n) + "Programs" } func (n templateName) CloseHelper() string { return "_" + toUpperFirst(string(n)) + "Close" } type outputArgs struct { // Package of the resulting file. pkg string // The prefix of all names declared at the top-level. stem string // Build constraints included in the resulting file. constraints constraint.Expr // Maps to be emitted. maps []string // Programs to be emitted. programs []string // Types to be emitted. types []btf.Type // Filename of the ELF object to embed. obj string out io.Writer } func output(args outputArgs) error { maps := make(map[string]string) for _, name := range args.maps { maps[name] = internal.Identifier(name) } programs := make(map[string]string) for _, name := range args.programs { programs[name] = internal.Identifier(name) } typeNames := make(map[btf.Type]string) for _, typ := range args.types { typeNames[typ] = args.stem + internal.Identifier(typ.TypeName()) } // Ensure we don't have conflicting names and generate a sorted list of // named types so that the output is stable. types, err := sortTypes(typeNames) if err != nil { return err } module := currentModule() gf := &btf.GoFormatter{ Names: typeNames, Identifier: internal.Identifier, } ctx := struct { *btf.GoFormatter Module string Package string Constraints constraint.Expr Name templateName Maps map[string]string Programs map[string]string Types []btf.Type TypeNames map[btf.Type]string File string }{ gf, module, args.pkg, args.constraints, templateName(args.stem), maps, programs, types, typeNames, args.obj, } var buf bytes.Buffer if err := commonTemplate.Execute(&buf, &ctx); err != nil { return fmt.Errorf("can't generate types: %s", err) } return internal.WriteFormatted(buf.Bytes(), args.out) } func collectFromSpec(spec *ebpf.CollectionSpec, cTypes []string, skipGlobalTypes bool) (maps, programs []string, types []btf.Type, _ error) { for name := range spec.Maps { // Skip .rodata, .data, .bss, etc. sections if !strings.HasPrefix(name, ".") { maps = append(maps, name) } } for name := range spec.Programs { programs = append(programs, name) } types, err := collectCTypes(spec.Types, cTypes) if err != nil { return nil, nil, nil, fmt.Errorf("collect C types: %w", err) } // Collect map key and value types, unless we've been asked not to. if skipGlobalTypes { return maps, programs, types, nil } for _, typ := range collectMapTypes(spec.Maps) { switch btf.UnderlyingType(typ).(type) { case *btf.Datasec: // Avoid emitting .rodata, .bss, etc. for now. We might want to // name these types differently, etc. continue case *btf.Int: // Don't emit primitive types by default. continue } types = append(types, typ) } return maps, programs, types, nil } func collectCTypes(types *btf.Spec, names []string) ([]btf.Type, error) { var result []btf.Type for _, cType := range names { typ, err := types.AnyTypeByName(cType) if err != nil { return nil, err } result = append(result, typ) } return result, nil } // collectMapTypes returns a list of all types used as map keys or values. func collectMapTypes(maps map[string]*ebpf.MapSpec) []btf.Type { var result []btf.Type for _, m := range maps { if m.Key != nil && m.Key.TypeName() != "" { result = append(result, m.Key) } if m.Value != nil && m.Value.TypeName() != "" { result = append(result, m.Value) } } return result } // sortTypes returns a list of types sorted by their (generated) Go type name. // // Duplicate Go type names are rejected. func sortTypes(typeNames map[btf.Type]string) ([]btf.Type, error) { var types []btf.Type var names []string for typ, name := range typeNames { i := sort.SearchStrings(names, name) if i >= len(names) { types = append(types, typ) names = append(names, name) continue } if names[i] == name { return nil, fmt.Errorf("type name %q is used multiple times", name) } types = append(types[:i], append([]btf.Type{typ}, types[i:]...)...) names = append(names[:i], append([]string{name}, names[i:]...)...) } return types, nil }