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