github.com/cilium/ebpf@v0.15.1-0.20240517100537-8079b37aa138/internal/cmd/gentypes/main.go (about)

     1  // Program gentypes reads a compressed vmlinux .BTF section and generates
     2  // syscall bindings from it.
     3  //
     4  // Output is written to "types.go".
     5  package main
     6  
     7  import (
     8  	"bytes"
     9  	"errors"
    10  	"fmt"
    11  	"os"
    12  	"slices"
    13  	"sort"
    14  	"strings"
    15  
    16  	"github.com/cilium/ebpf/btf"
    17  	"github.com/cilium/ebpf/internal"
    18  	"github.com/cilium/ebpf/internal/sys"
    19  )
    20  
    21  type syscallRetval int
    22  
    23  const (
    24  	retError syscallRetval = iota
    25  	retFd
    26  )
    27  
    28  func main() {
    29  	if err := run(os.Args[1:]); err != nil {
    30  		fmt.Fprintln(os.Stderr, "Error:", err)
    31  		os.Exit(1)
    32  	}
    33  }
    34  
    35  func run(args []string) error {
    36  	if len(args) != 1 {
    37  		return fmt.Errorf("expect location of compressed vmlinux .BTF as argument")
    38  	}
    39  
    40  	raw, err := internal.ReadAllCompressed(args[0])
    41  	if err != nil {
    42  		return err
    43  	}
    44  
    45  	spec, err := btf.LoadSpecFromReader(bytes.NewReader(raw))
    46  	if err != nil {
    47  		return err
    48  	}
    49  
    50  	output, err := generateTypes(spec)
    51  	var fpe *failedPatchError
    52  	if errors.As(err, &fpe) {
    53  		fmt.Fprintf(os.Stderr, "  %v\n", fpe.Type)
    54  		for _, member := range fpe.Type.Members {
    55  			fmt.Fprintf(os.Stderr, "    %q %v\n", member.Name, member.Type)
    56  		}
    57  	}
    58  	if err != nil {
    59  		return err
    60  	}
    61  
    62  	w, err := os.Create("types.go")
    63  	if err != nil {
    64  		return err
    65  	}
    66  	defer w.Close()
    67  
    68  	return internal.WriteFormatted(output, w)
    69  }
    70  
    71  func generateTypes(spec *btf.Spec) ([]byte, error) {
    72  	objName := &btf.Array{Nelems: 16, Type: &btf.Int{Encoding: btf.Char, Size: 1}}
    73  	linkID := &btf.Int{Size: 4}
    74  	btfID := &btf.Int{Size: 4}
    75  	typeID := &btf.Int{Size: 4}
    76  	pointer := &btf.Int{Size: 8}
    77  	logLevel := &btf.Int{Size: 4}
    78  	mapFlags := &btf.Int{Size: 4}
    79  
    80  	gf := &btf.GoFormatter{
    81  		Names: map[btf.Type]string{
    82  			objName:  internal.GoTypeName(sys.ObjName{}),
    83  			linkID:   internal.GoTypeName(sys.LinkID(0)),
    84  			btfID:    internal.GoTypeName(sys.BTFID(0)),
    85  			typeID:   internal.GoTypeName(sys.TypeID(0)),
    86  			pointer:  internal.GoTypeName(sys.Pointer{}),
    87  			logLevel: internal.GoTypeName(sys.LogLevel(0)),
    88  			mapFlags: internal.GoTypeName(sys.MapFlags(0)),
    89  		},
    90  		Identifier: internal.Identifier,
    91  		EnumIdentifier: func(name, element string) string {
    92  			return element
    93  		},
    94  	}
    95  
    96  	w := bytes.NewBuffer(nil)
    97  	w.WriteString(`// Code generated by internal/cmd/gentypes; DO NOT EDIT.
    98  
    99  package sys
   100  
   101  import (
   102  	"unsafe"
   103  )
   104  
   105  `)
   106  
   107  	enums := []struct {
   108  		goType string
   109  		cType  string
   110  	}{
   111  		{"Cmd", "bpf_cmd"},
   112  		{"MapType", "bpf_map_type"},
   113  		{"ProgType", "bpf_prog_type"},
   114  		{"AttachType", "bpf_attach_type"},
   115  		{"LinkType", "bpf_link_type"},
   116  		{"StatsType", "bpf_stats_type"},
   117  		{"SkAction", "sk_action"},
   118  		{"StackBuildIdStatus", "bpf_stack_build_id_status"},
   119  		{"FunctionId", "bpf_func_id"},
   120  		{"AdjRoomMode", "bpf_adj_room_mode"},
   121  		{"HdrStartOff", "bpf_hdr_start_off"},
   122  		{"RetCode", "bpf_ret_code"},
   123  		{"XdpAction", "xdp_action"},
   124  		{"TcxActionBase", "tcx_action_base"},
   125  		{"PerfEventType", "bpf_perf_event_type"},
   126  	}
   127  
   128  	sort.Slice(enums, func(i, j int) bool {
   129  		return enums[i].goType < enums[j].goType
   130  	})
   131  
   132  	enumTypes := make(map[string]btf.Type)
   133  	for _, o := range enums {
   134  		fmt.Println("enum", o.goType)
   135  
   136  		var t *btf.Enum
   137  		if err := spec.TypeByName(o.cType, &t); err != nil {
   138  			return nil, err
   139  		}
   140  
   141  		// Add the enum as a predeclared type so that generated structs
   142  		// refer to the Go types.
   143  		if name := gf.Names[t]; name != "" {
   144  			return nil, fmt.Errorf("type %q is already declared as %s", o.cType, name)
   145  		}
   146  		gf.Names[t] = o.goType
   147  		enumTypes[o.goType] = t
   148  
   149  		decl, err := gf.TypeDeclaration(o.goType, t)
   150  		if err != nil {
   151  			return nil, fmt.Errorf("generate %q: %w", o.goType, err)
   152  		}
   153  
   154  		w.WriteString(decl)
   155  		w.WriteRune('\n')
   156  	}
   157  
   158  	// Assorted structs
   159  
   160  	structs := []struct {
   161  		goType  string
   162  		cType   string
   163  		patches []patch
   164  	}{
   165  		{
   166  			"ProgInfo", "bpf_prog_info",
   167  			[]patch{
   168  				replace(objName, "name"),
   169  				replace(pointer, "xlated_prog_insns"),
   170  				replace(pointer, "map_ids"),
   171  				replace(pointer, "line_info"),
   172  				replace(pointer, "func_info"),
   173  				replace(btfID, "btf_id", "attach_btf_obj_id"),
   174  				replace(typeID, "attach_btf_id"),
   175  			},
   176  		},
   177  		{
   178  			"MapInfo", "bpf_map_info",
   179  			[]patch{
   180  				replace(objName, "name"),
   181  				replace(mapFlags, "map_flags"),
   182  				replace(typeID, "btf_vmlinux_value_type_id", "btf_key_type_id", "btf_value_type_id"),
   183  			},
   184  		},
   185  		{
   186  			"BtfInfo", "bpf_btf_info",
   187  			[]patch{
   188  				replace(pointer, "btf", "name"),
   189  				replace(btfID, "id"),
   190  			},
   191  		},
   192  		{
   193  			"LinkInfo", "bpf_link_info",
   194  			[]patch{
   195  				replace(enumTypes["LinkType"], "type"),
   196  				replace(linkID, "id"),
   197  				name(3, "extra"),
   198  				replaceWithBytes("extra"),
   199  			},
   200  		},
   201  		{"FuncInfo", "bpf_func_info", nil},
   202  		{"LineInfo", "bpf_line_info", nil},
   203  		{"XdpMd", "xdp_md", nil},
   204  		{
   205  			"SkLookup", "bpf_sk_lookup",
   206  			[]patch{
   207  				choose(0, "cookie"),
   208  				replaceWithBytes("remote_ip4", "remote_ip6", "local_ip4", "local_ip6"),
   209  			},
   210  		},
   211  	}
   212  
   213  	sort.Slice(structs, func(i, j int) bool {
   214  		return structs[i].goType < structs[j].goType
   215  	})
   216  
   217  	for _, s := range structs {
   218  		fmt.Println("struct", s.goType)
   219  
   220  		var t *btf.Struct
   221  		if err := spec.TypeByName(s.cType, &t); err != nil {
   222  			return nil, err
   223  		}
   224  
   225  		if err := outputPatchedStruct(gf, w, s.goType, t, s.patches); err != nil {
   226  			return nil, fmt.Errorf("output %q: %w", s.goType, err)
   227  		}
   228  	}
   229  
   230  	// Attrs
   231  
   232  	attrs := []struct {
   233  		goType  string
   234  		ret     syscallRetval
   235  		cType   string
   236  		cmd     string
   237  		patches []patch
   238  	}{
   239  		{
   240  			"MapCreate", retFd, "map_create", "BPF_MAP_CREATE",
   241  			[]patch{
   242  				replace(objName, "map_name"),
   243  				replace(enumTypes["MapType"], "map_type"),
   244  				replace(mapFlags, "map_flags"),
   245  				replace(typeID, "btf_vmlinux_value_type_id", "btf_key_type_id", "btf_value_type_id"),
   246  			},
   247  		},
   248  		{
   249  			"MapLookupElem", retError, "map_elem", "BPF_MAP_LOOKUP_ELEM",
   250  			[]patch{choose(2, "value"), replace(pointer, "key", "value")},
   251  		},
   252  		{
   253  			"MapLookupAndDeleteElem", retError, "map_elem", "BPF_MAP_LOOKUP_AND_DELETE_ELEM",
   254  			[]patch{choose(2, "value"), replace(pointer, "key", "value")},
   255  		},
   256  		{
   257  			"MapUpdateElem", retError, "map_elem", "BPF_MAP_UPDATE_ELEM",
   258  			[]patch{choose(2, "value"), replace(pointer, "key", "value")},
   259  		},
   260  		{
   261  			"MapDeleteElem", retError, "map_elem", "BPF_MAP_DELETE_ELEM",
   262  			[]patch{choose(2, "value"), replace(pointer, "key", "value")},
   263  		},
   264  		{
   265  			"MapGetNextKey", retError, "map_elem", "BPF_MAP_GET_NEXT_KEY",
   266  			[]patch{
   267  				choose(2, "next_key"), replace(pointer, "key", "next_key"),
   268  				truncateAfter("next_key"),
   269  			},
   270  		},
   271  		{
   272  			"MapFreeze", retError, "map_elem", "BPF_MAP_FREEZE",
   273  			[]patch{truncateAfter("map_fd")},
   274  		},
   275  		{
   276  			"MapLookupBatch", retError, "map_elem_batch", "BPF_MAP_LOOKUP_BATCH",
   277  			[]patch{replace(pointer, "in_batch", "out_batch", "keys", "values")},
   278  		},
   279  		{
   280  			"MapLookupAndDeleteBatch", retError, "map_elem_batch", "BPF_MAP_LOOKUP_AND_DELETE_BATCH",
   281  			[]patch{replace(pointer, "in_batch", "out_batch", "keys", "values")},
   282  		},
   283  		{
   284  			"MapUpdateBatch", retError, "map_elem_batch", "BPF_MAP_UPDATE_BATCH",
   285  			[]patch{replace(pointer, "in_batch", "out_batch", "keys", "values")},
   286  		},
   287  		{
   288  			"MapDeleteBatch", retError, "map_elem_batch", "BPF_MAP_DELETE_BATCH",
   289  			[]patch{replace(pointer, "in_batch", "out_batch", "keys", "values")},
   290  		},
   291  		{
   292  			"ProgLoad", retFd, "prog_load", "BPF_PROG_LOAD",
   293  			[]patch{
   294  				replace(objName, "prog_name"),
   295  				replace(enumTypes["ProgType"], "prog_type"),
   296  				replace(enumTypes["AttachType"], "expected_attach_type"),
   297  				replace(logLevel, "log_level"),
   298  				replace(pointer,
   299  					"insns",
   300  					"license",
   301  					"log_buf",
   302  					"func_info",
   303  					"line_info",
   304  					"fd_array",
   305  					"core_relos",
   306  				),
   307  				replace(typeID, "attach_btf_id"),
   308  				choose(20, "attach_btf_obj_fd"),
   309  			},
   310  		},
   311  		{
   312  			"ProgBindMap", retError, "prog_bind_map", "BPF_PROG_BIND_MAP",
   313  			nil,
   314  		},
   315  		{
   316  			"ObjPin", retError, "obj_pin", "BPF_OBJ_PIN",
   317  			[]patch{replace(pointer, "pathname")},
   318  		},
   319  		{
   320  			"ObjGet", retFd, "obj_pin", "BPF_OBJ_GET",
   321  			[]patch{replace(pointer, "pathname")},
   322  		},
   323  		{
   324  			"ProgAttach", retError, "prog_attach", "BPF_PROG_ATTACH",
   325  			[]patch{
   326  				flattenAnon,
   327  				rename("target_fd", "target_fd_or_ifindex"),
   328  				rename("relative_fd", "relative_fd_or_id"),
   329  			},
   330  		},
   331  		{
   332  			"ProgDetach", retError, "prog_attach", "BPF_PROG_DETACH",
   333  			[]patch{
   334  				flattenAnon,
   335  				rename("target_fd", "target_fd_or_ifindex"),
   336  				truncateAfter("expected_revision"),
   337  				rename("relative_fd", "relative_fd_or_id"),
   338  				remove("replace_bpf_fd"),
   339  			},
   340  		},
   341  		{
   342  			"ProgRun", retError, "prog_run", "BPF_PROG_TEST_RUN",
   343  			[]patch{replace(pointer, "data_in", "data_out", "ctx_in", "ctx_out")},
   344  		},
   345  		{
   346  			"ProgGetNextId", retError, "obj_next_id", "BPF_PROG_GET_NEXT_ID",
   347  			[]patch{
   348  				choose(0, "start_id"), rename("start_id", "id"),
   349  				truncateAfter("next_id"),
   350  			},
   351  		},
   352  		{
   353  			"MapGetNextId", retError, "obj_next_id", "BPF_MAP_GET_NEXT_ID",
   354  			[]patch{
   355  				choose(0, "start_id"), rename("start_id", "id"),
   356  				truncateAfter("next_id"),
   357  			},
   358  		},
   359  		{
   360  			"BtfGetNextId", retError, "obj_next_id", "BPF_BTF_GET_NEXT_ID",
   361  			[]patch{
   362  				choose(0, "start_id"), rename("start_id", "id"),
   363  				replace(btfID, "id", "next_id"),
   364  				truncateAfter("next_id"),
   365  			},
   366  		},
   367  		{
   368  			"LinkGetNextId", retError, "obj_next_id", "BPF_LINK_GET_NEXT_ID",
   369  			[]patch{
   370  				choose(0, "start_id"), rename("start_id", "id"),
   371  				replace(linkID, "id", "next_id"),
   372  				truncateAfter("next_id"),
   373  			},
   374  		},
   375  		// These piggy back on the obj_next_id decl, but only support the
   376  		// first field...
   377  		{
   378  			"BtfGetFdById", retFd, "obj_next_id", "BPF_BTF_GET_FD_BY_ID",
   379  			[]patch{choose(0, "start_id"), rename("start_id", "id"), truncateAfter("id")},
   380  		},
   381  		{
   382  			"MapGetFdById", retFd, "obj_next_id", "BPF_MAP_GET_FD_BY_ID",
   383  			[]patch{choose(0, "start_id"), rename("start_id", "id"), truncateAfter("id")},
   384  		},
   385  		{
   386  			"ProgGetFdById", retFd, "obj_next_id", "BPF_PROG_GET_FD_BY_ID",
   387  			[]patch{choose(0, "start_id"), rename("start_id", "id"), truncateAfter("id")},
   388  		},
   389  		{
   390  			"LinkGetFdById", retFd, "obj_next_id", "BPF_LINK_GET_FD_BY_ID",
   391  			[]patch{choose(0, "start_id"), rename("start_id", "id"), replace(linkID, "id"), truncateAfter("id")},
   392  		},
   393  		{
   394  			"ObjGetInfoByFd", retError, "info_by_fd", "BPF_OBJ_GET_INFO_BY_FD",
   395  			[]patch{replace(pointer, "info")},
   396  		},
   397  		{
   398  			"RawTracepointOpen", retFd, "raw_tracepoint_open", "BPF_RAW_TRACEPOINT_OPEN",
   399  			[]patch{replace(pointer, "name")},
   400  		},
   401  		{
   402  			"BtfLoad", retFd, "btf_load", "BPF_BTF_LOAD",
   403  			[]patch{replace(pointer, "btf", "btf_log_buf")},
   404  		},
   405  		{
   406  			"LinkCreate", retFd, "link_create", "BPF_LINK_CREATE",
   407  			[]patch{
   408  				replace(enumTypes["AttachType"], "attach_type"),
   409  				choose(4, "target_btf_id"),
   410  				replace(typeID, "target_btf_id"),
   411  			},
   412  		},
   413  		{
   414  			"LinkCreateIter", retFd, "link_create", "BPF_LINK_CREATE",
   415  			[]patch{
   416  				chooseNth(4, 1),
   417  				replace(enumTypes["AttachType"], "attach_type"),
   418  				flattenAnon,
   419  				replace(pointer, "iter_info"),
   420  			},
   421  		},
   422  		{
   423  			"LinkCreatePerfEvent", retFd, "link_create", "BPF_LINK_CREATE",
   424  			[]patch{
   425  				chooseNth(4, 2),
   426  				replace(enumTypes["AttachType"], "attach_type"),
   427  				flattenAnon,
   428  			},
   429  		},
   430  		{
   431  			"LinkCreateKprobeMulti", retFd, "link_create", "BPF_LINK_CREATE",
   432  			[]patch{
   433  				chooseNth(4, 3),
   434  				replace(enumTypes["AttachType"], "attach_type"),
   435  				modify(func(m *btf.Member) error {
   436  					return rename("flags", "kprobe_multi_flags")(m.Type.(*btf.Struct))
   437  				}, "kprobe_multi"),
   438  				flattenAnon,
   439  				replace(pointer, "cookies"),
   440  				replace(pointer, "addrs"),
   441  				replace(pointer, "syms"),
   442  				rename("cnt", "count"),
   443  			},
   444  		},
   445  		{
   446  			"LinkCreateNetfilter", retFd, "link_create", "BPF_LINK_CREATE",
   447  			[]patch{
   448  				chooseNth(4, 5),
   449  				replace(enumTypes["AttachType"], "attach_type"),
   450  				modify(func(m *btf.Member) error {
   451  					return rename("flags", "netfilter_flags")(m.Type.(*btf.Struct))
   452  				}, "netfilter"),
   453  				flattenAnon,
   454  			},
   455  		},
   456  		{
   457  			"LinkCreateTracing", retFd, "link_create", "BPF_LINK_CREATE",
   458  			[]patch{
   459  				chooseNth(4, 4),
   460  				replace(enumTypes["AttachType"], "attach_type"),
   461  				flattenAnon,
   462  				replace(btfID, "target_btf_id"),
   463  			},
   464  		},
   465  		{
   466  			"LinkCreateTcx", retFd, "link_create", "BPF_LINK_CREATE",
   467  			[]patch{
   468  				choose(1, "target_ifindex"),
   469  				choose(4, "tcx"),
   470  				replace(enumTypes["AttachType"], "attach_type"),
   471  				flattenAnon,
   472  				flattenAnon, // flatten tcx member
   473  				rename("relative_fd", "relative_fd_or_id"),
   474  			},
   475  		},
   476  		{
   477  			"LinkCreateUprobeMulti", retFd, "link_create", "BPF_LINK_CREATE",
   478  			[]patch{
   479  				chooseNth(4, 7),
   480  				replace(enumTypes["AttachType"], "attach_type"),
   481  				modify(func(m *btf.Member) error {
   482  					return rename("flags", "uprobe_multi_flags")(m.Type.(*btf.Struct))
   483  				}, "uprobe_multi"),
   484  				flattenAnon,
   485  				replace(pointer, "path"),
   486  				replace(pointer, "offsets"),
   487  				replace(pointer, "ref_ctr_offsets"),
   488  				replace(pointer, "cookies"),
   489  				rename("cnt", "count"),
   490  			},
   491  		},
   492  		{
   493  			"LinkCreateNetkit", retFd, "link_create", "BPF_LINK_CREATE",
   494  			[]patch{
   495  				choose(1, "target_ifindex"),
   496  				choose(4, "netkit"),
   497  				replace(enumTypes["AttachType"], "attach_type"),
   498  				flattenAnon,
   499  				flattenAnon,
   500  				rename("relative_fd", "relative_fd_or_id"),
   501  			},
   502  		},
   503  		{
   504  			"LinkUpdate", retError, "link_update", "BPF_LINK_UPDATE",
   505  			nil,
   506  		},
   507  		{
   508  			"EnableStats", retFd, "enable_stats", "BPF_ENABLE_STATS",
   509  			nil,
   510  		},
   511  		{
   512  			"IterCreate", retFd, "iter_create", "BPF_ITER_CREATE",
   513  			nil,
   514  		},
   515  		{
   516  			"ProgQuery", retError, "prog_query", "BPF_PROG_QUERY",
   517  			[]patch{
   518  				replace(enumTypes["AttachType"], "attach_type"),
   519  				replace(pointer, "prog_ids", "prog_attach_flags"),
   520  				replace(pointer, "link_ids", "link_attach_flags"),
   521  				flattenAnon,
   522  				rename("prog_cnt", "count"),
   523  				rename("target_fd", "target_fd_or_ifindex"),
   524  			},
   525  		},
   526  	}
   527  
   528  	sort.Slice(attrs, func(i, j int) bool {
   529  		return attrs[i].goType < attrs[j].goType
   530  	})
   531  
   532  	var bpfAttr *btf.Union
   533  	if err := spec.TypeByName("bpf_attr", &bpfAttr); err != nil {
   534  		return nil, err
   535  	}
   536  	attrTypes, err := splitUnion(bpfAttr, types{
   537  		{"map_create", "map_type"},
   538  		{"map_elem", "map_fd"},
   539  		{"map_elem_batch", "batch"},
   540  		{"prog_load", "prog_type"},
   541  		{"obj_pin", "pathname"},
   542  		{"prog_attach", ""},
   543  		{"prog_run", "test"},
   544  		{"obj_next_id", ""},
   545  		{"info_by_fd", "info"},
   546  		{"prog_query", "query"},
   547  		{"raw_tracepoint_open", "raw_tracepoint"},
   548  		{"btf_load", "btf"},
   549  		{"task_fd_query", "task_fd_query"},
   550  		{"link_create", "link_create"},
   551  		{"link_update", "link_update"},
   552  		{"link_detach", "link_detach"},
   553  		{"enable_stats", "enable_stats"},
   554  		{"iter_create", "iter_create"},
   555  		{"prog_bind_map", "prog_bind_map"},
   556  	})
   557  	if err != nil {
   558  		return nil, fmt.Errorf("split bpf_attr: %w", err)
   559  	}
   560  
   561  	for _, s := range attrs {
   562  		fmt.Println("attr", s.goType)
   563  
   564  		t := attrTypes[s.cType]
   565  		if t == nil {
   566  			return nil, fmt.Errorf("unknown attr %q", s.cType)
   567  		}
   568  
   569  		goAttrType := s.goType + "Attr"
   570  		if err := outputPatchedStruct(gf, w, goAttrType, t, s.patches); err != nil {
   571  			return nil, fmt.Errorf("output %q: %w", goAttrType, err)
   572  		}
   573  
   574  		switch s.ret {
   575  		case retError:
   576  			fmt.Fprintf(w, "func %s(attr *%s) error { _, err := BPF(%s, unsafe.Pointer(attr), unsafe.Sizeof(*attr)); return err }\n\n", s.goType, goAttrType, s.cmd)
   577  		case retFd:
   578  			fmt.Fprintf(w, "func %s(attr *%s) (*FD, error) { fd, err := BPF(%s, unsafe.Pointer(attr), unsafe.Sizeof(*attr)); if err != nil { return nil, err }; return NewFD(int(fd)) }\n\n", s.goType, goAttrType, s.cmd)
   579  		}
   580  	}
   581  
   582  	// Link info type specific
   583  	linkInfoExtraTypes := []struct {
   584  		goType  string
   585  		patches []patch
   586  	}{
   587  		{"CgroupLinkInfo",
   588  			[]patch{
   589  				choose(3, "cgroup"),
   590  				flattenAnon,
   591  				replace(enumTypes["AttachType"], "attach_type"),
   592  			},
   593  		},
   594  		{"IterLinkInfo",
   595  			[]patch{
   596  				choose(3, "iter"),
   597  				flattenAnon,
   598  				replace(pointer, "target_name"),
   599  				truncateAfter("target_name_len"),
   600  			},
   601  		},
   602  		{"NetNsLinkInfo",
   603  			[]patch{choose(3, "netns"),
   604  				flattenAnon,
   605  				replace(enumTypes["AttachType"], "attach_type"),
   606  			},
   607  		},
   608  		{"RawTracepointLinkInfo",
   609  			[]patch{choose(3, "raw_tracepoint"),
   610  				flattenAnon,
   611  				replace(pointer, "tp_name"),
   612  			},
   613  		},
   614  		{"TracingLinkInfo",
   615  			[]patch{
   616  				choose(3, "tracing"),
   617  				flattenAnon,
   618  				replace(enumTypes["AttachType"], "attach_type"),
   619  				replace(typeID, "target_btf_id"),
   620  			},
   621  		},
   622  		{"XDPLinkInfo",
   623  			[]patch{choose(3, "xdp"),
   624  				flattenAnon,
   625  			},
   626  		},
   627  		{"TcxLinkInfo",
   628  			[]patch{
   629  				choose(3, "tcx"),
   630  				flattenAnon,
   631  				replace(enumTypes["AttachType"], "attach_type"),
   632  			},
   633  		},
   634  		{"NetfilterLinkInfo",
   635  			[]patch{
   636  				choose(3, "netfilter"),
   637  				flattenAnon,
   638  			},
   639  		},
   640  		{"NetkitLinkInfo",
   641  			[]patch{
   642  				choose(3, "netkit"),
   643  				flattenAnon,
   644  				replace(enumTypes["AttachType"], "attach_type"),
   645  			},
   646  		},
   647  		{"KprobeMultiLinkInfo",
   648  			[]patch{
   649  				choose(3, "kprobe_multi"),
   650  				flattenAnon,
   651  				replace(pointer, "addrs"),
   652  			},
   653  		},
   654  		{"PerfEventLinkInfo",
   655  			[]patch{
   656  				choose(3, "perf_event"),
   657  				flattenAnon,
   658  				renameNth(3, "perf_event_type"),
   659  				replace(enumTypes["PerfEventType"], "perf_event_type"),
   660  				truncateAfter("perf_event_type"),
   661  			},
   662  		},
   663  		{"KprobeLinkInfo",
   664  			[]patch{
   665  				choose(3, "perf_event"),
   666  				flattenAnon,
   667  				renameNth(3, "perf_event_type"),
   668  				replace(enumTypes["PerfEventType"], "perf_event_type"),
   669  				choose(4, "kprobe"),
   670  				flattenAnon,
   671  				replace(pointer, "func_name"),
   672  			},
   673  		},
   674  	}
   675  
   676  	sort.Slice(linkInfoExtraTypes, func(i, j int) bool {
   677  		return linkInfoExtraTypes[i].goType < linkInfoExtraTypes[j].goType
   678  	})
   679  
   680  	var bpfLinkInfo *btf.Struct
   681  	if err := spec.TypeByName("bpf_link_info", &bpfLinkInfo); err != nil {
   682  		return nil, err
   683  	}
   684  
   685  	patches := []patch{
   686  		replace(enumTypes["LinkType"], "type"),
   687  		replace(linkID, "id"),
   688  	}
   689  
   690  	for _, s := range linkInfoExtraTypes {
   691  		if err := outputPatchedStruct(gf, w, s.goType, bpfLinkInfo, append(patches, s.patches...)); err != nil {
   692  			return nil, fmt.Errorf("output %q: %w", s.goType, err)
   693  		}
   694  	}
   695  
   696  	return w.Bytes(), nil
   697  }
   698  
   699  type failedPatchError struct {
   700  	Type   *btf.Struct
   701  	number int
   702  	err    error
   703  }
   704  
   705  func (fpe *failedPatchError) Unwrap() error {
   706  	return fpe.err
   707  }
   708  
   709  func (fpe *failedPatchError) Error() string {
   710  	return fmt.Sprintf("patch %d: %v", fpe.number, fpe.err)
   711  }
   712  
   713  func outputPatchedStruct(gf *btf.GoFormatter, w *bytes.Buffer, id string, s *btf.Struct, patches []patch) error {
   714  	s = btf.Copy(s).(*btf.Struct)
   715  
   716  	for i, p := range patches {
   717  		if err := p(s); err != nil {
   718  			return &failedPatchError{s, i, err}
   719  		}
   720  	}
   721  
   722  	decl, err := gf.TypeDeclaration(id, s)
   723  	if err != nil {
   724  		return err
   725  	}
   726  
   727  	w.WriteString(decl)
   728  	w.WriteString("\n\n")
   729  	return nil
   730  }
   731  
   732  type types []struct {
   733  	name                string
   734  	cFieldOrFirstMember string
   735  }
   736  
   737  func splitUnion(union *btf.Union, types types) (map[string]*btf.Struct, error) {
   738  	structs := make(map[string]*btf.Struct)
   739  
   740  	for i, t := range types {
   741  		member := union.Members[i]
   742  		s, ok := member.Type.(*btf.Struct)
   743  		if !ok {
   744  			return nil, fmt.Errorf("%q: %s is not a struct", t.name, member.Type)
   745  		}
   746  
   747  		if member.Name == "" {
   748  			// This is an anonymous struct, check the name of the first member instead.
   749  			if name := s.Members[0].Name; name != t.cFieldOrFirstMember {
   750  				return nil, fmt.Errorf("first field of %q is %q, not %q", t.name, name, t.cFieldOrFirstMember)
   751  			}
   752  		} else if member.Name != t.cFieldOrFirstMember {
   753  			return nil, fmt.Errorf("name for %q is %q, not %q", t.name, member.Name, t.cFieldOrFirstMember)
   754  		}
   755  
   756  		structs[t.name] = s
   757  	}
   758  
   759  	return structs, nil
   760  }
   761  
   762  type patch func(*btf.Struct) error
   763  
   764  func modify(fn func(*btf.Member) error, members ...string) patch {
   765  	return func(s *btf.Struct) error {
   766  		want := make(map[string]bool)
   767  		for _, name := range members {
   768  			want[name] = true
   769  		}
   770  
   771  		for i, m := range s.Members {
   772  			if want[m.Name] {
   773  				if err := fn(&s.Members[i]); err != nil {
   774  					return err
   775  				}
   776  				delete(want, m.Name)
   777  			}
   778  		}
   779  
   780  		if len(want) == 0 {
   781  			return nil
   782  		}
   783  
   784  		var missing []string
   785  		for name := range want {
   786  			missing = append(missing, name)
   787  		}
   788  		sort.Strings(missing)
   789  
   790  		return fmt.Errorf("missing members: %v", strings.Join(missing, ", "))
   791  	}
   792  }
   793  
   794  func modifyNth(fn func(*btf.Member) error, indices ...int) patch {
   795  	return func(s *btf.Struct) error {
   796  		for _, i := range indices {
   797  			if i >= len(s.Members) {
   798  				return fmt.Errorf("index %d is out of bounds", i)
   799  			}
   800  
   801  			if err := fn(&s.Members[i]); err != nil {
   802  				return fmt.Errorf("member #%d: %w", i, err)
   803  			}
   804  		}
   805  		return nil
   806  	}
   807  }
   808  
   809  func replace(t btf.Type, members ...string) patch {
   810  	return modify(func(m *btf.Member) error {
   811  		m.Type = t
   812  		return nil
   813  	}, members...)
   814  }
   815  
   816  func choose(member int, name string) patch {
   817  	return modifyNth(func(m *btf.Member) error {
   818  		union, ok := m.Type.(*btf.Union)
   819  		if !ok {
   820  			return fmt.Errorf("member %d is %s, not a union", member, m.Type)
   821  		}
   822  
   823  		for _, um := range union.Members {
   824  			if um.Name == name {
   825  				m.Name = um.Name
   826  				m.Type = um.Type
   827  				return nil
   828  			}
   829  		}
   830  
   831  		return fmt.Errorf("%s has no member %q", union, name)
   832  	}, member)
   833  }
   834  
   835  func chooseNth(member int, n int) patch {
   836  	return modifyNth(func(m *btf.Member) error {
   837  		union, ok := m.Type.(*btf.Union)
   838  		if !ok {
   839  			return fmt.Errorf("member %d is %s, not a union", member, m.Type)
   840  		}
   841  
   842  		if n >= len(union.Members) {
   843  			return fmt.Errorf("member %d is out of bounds", n)
   844  		}
   845  
   846  		um := union.Members[n]
   847  		m.Name = um.Name
   848  		m.Type = um.Type
   849  		return nil
   850  	}, member)
   851  }
   852  
   853  func flattenAnon(s *btf.Struct) error {
   854  	for i := range s.Members {
   855  		m := &s.Members[i]
   856  
   857  		if m.Type.TypeName() != "" {
   858  			continue
   859  		}
   860  
   861  		var newMembers []btf.Member
   862  		switch cs := m.Type.(type) {
   863  		case *btf.Struct:
   864  			for j := range cs.Members {
   865  				cs.Members[j].Offset += m.Offset
   866  			}
   867  			newMembers = cs.Members
   868  
   869  		case *btf.Union:
   870  			cs.Members[0].Offset += m.Offset
   871  			newMembers = []btf.Member{cs.Members[0]}
   872  
   873  		default:
   874  			continue
   875  		}
   876  
   877  		s.Members = slices.Replace(s.Members, i, i+1, newMembers...)
   878  	}
   879  
   880  	return nil
   881  }
   882  
   883  func truncateAfter(name string) patch {
   884  	return func(s *btf.Struct) error {
   885  		for i, m := range s.Members {
   886  			if m.Name != name {
   887  				continue
   888  			}
   889  
   890  			size, err := btf.Sizeof(m.Type)
   891  			if err != nil {
   892  				return err
   893  			}
   894  
   895  			s.Members = s.Members[:i+1]
   896  			s.Size = m.Offset.Bytes() + uint32(size)
   897  			return nil
   898  		}
   899  
   900  		return fmt.Errorf("no member %q", name)
   901  	}
   902  }
   903  
   904  func rename(from, to string) patch {
   905  	return func(s *btf.Struct) error {
   906  		for i, m := range s.Members {
   907  			if m.Name == from {
   908  				s.Members[i].Name = to
   909  				return nil
   910  			}
   911  		}
   912  		return fmt.Errorf("no member named %q", from)
   913  	}
   914  }
   915  
   916  func renameNth(idx int, to string) patch {
   917  	return func(s *btf.Struct) error {
   918  		if idx >= len(s.Members) {
   919  			return fmt.Errorf("index %d is out of bounds", idx)
   920  		}
   921  		s.Members[idx].Name = to
   922  		return nil
   923  	}
   924  }
   925  
   926  func name(member int, name string) patch {
   927  	return modifyNth(func(m *btf.Member) error {
   928  		if m.Name != "" {
   929  			return fmt.Errorf("member already has name %q", m.Name)
   930  		}
   931  
   932  		m.Name = name
   933  		return nil
   934  	}, member)
   935  }
   936  
   937  func replaceWithBytes(members ...string) patch {
   938  	return modify(func(m *btf.Member) error {
   939  		if m.BitfieldSize != 0 {
   940  			return errors.New("replaceWithBytes: member is a bitfield")
   941  		}
   942  
   943  		size, err := btf.Sizeof(m.Type)
   944  		if err != nil {
   945  			return fmt.Errorf("replaceWithBytes: size of %s: %w", m.Type, err)
   946  		}
   947  
   948  		m.Type = &btf.Array{
   949  			Type:   &btf.Int{Size: 1},
   950  			Nelems: uint32(size),
   951  		}
   952  
   953  		return nil
   954  	}, members...)
   955  }
   956  
   957  func remove(member string) patch {
   958  	return func(s *btf.Struct) error {
   959  		for i, m := range s.Members {
   960  			if m.Name == member {
   961  				s.Members = slices.Delete(s.Members, i, i+1)
   962  				return nil
   963  			}
   964  		}
   965  		return fmt.Errorf("member %q not found", member)
   966  	}
   967  }