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 }