github.com/cilium/ebpf@v0.15.1-0.20240517100537-8079b37aa138/cmd/bpf2go/compile.go (about)

     1  package main
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"errors"
     7  	"fmt"
     8  	"io"
     9  	"os"
    10  	"os/exec"
    11  	"path/filepath"
    12  	"strings"
    13  )
    14  
    15  type compileArgs struct {
    16  	// Which compiler to use
    17  	cc     string
    18  	cFlags []string
    19  	// Absolute working directory
    20  	dir string
    21  	// Absolute input file name
    22  	source string
    23  	// Absolute output file name
    24  	dest string
    25  	// Target to compile for, defaults to "bpf".
    26  	target string
    27  	// Depfile will be written here if depName is not empty
    28  	dep io.Writer
    29  }
    30  
    31  func compile(args compileArgs) error {
    32  	// Default cflags that can be overridden by args.cFlags
    33  	overrideFlags := []string{
    34  		// Code needs to be optimized, otherwise the verifier will often fail
    35  		// to understand it.
    36  		"-O2",
    37  		// Clang defaults to mcpu=probe which checks the kernel that we are
    38  		// compiling on. This isn't appropriate for ahead of time
    39  		// compiled code so force the most compatible version.
    40  		"-mcpu=v1",
    41  	}
    42  
    43  	cmd := exec.Command(args.cc, append(overrideFlags, args.cFlags...)...)
    44  	cmd.Stderr = os.Stderr
    45  
    46  	inputDir := filepath.Dir(args.source)
    47  	relInputDir, err := filepath.Rel(args.dir, inputDir)
    48  	if err != nil {
    49  		return err
    50  	}
    51  
    52  	target := args.target
    53  	if target == "" {
    54  		target = "bpf"
    55  	}
    56  
    57  	// C flags that can't be overridden.
    58  	cmd.Args = append(cmd.Args,
    59  		"-target", target,
    60  		"-c", args.source,
    61  		"-o", args.dest,
    62  		// Don't include clang version
    63  		"-fno-ident",
    64  		// Don't output inputDir into debug info
    65  		"-fdebug-prefix-map="+inputDir+"="+relInputDir,
    66  		"-fdebug-compilation-dir", ".",
    67  		// We always want BTF to be generated, so enforce debug symbols
    68  		"-g",
    69  		fmt.Sprintf("-D__BPF_TARGET_MISSING=%q", "GCC error \"The eBPF is using target specific macros, please provide -target that is not bpf, bpfel or bpfeb\""),
    70  	)
    71  	cmd.Dir = args.dir
    72  
    73  	var depFile *os.File
    74  	if args.dep != nil {
    75  		depFile, err = os.CreateTemp("", "bpf2go")
    76  		if err != nil {
    77  			return err
    78  		}
    79  		defer depFile.Close()
    80  		defer os.Remove(depFile.Name())
    81  
    82  		cmd.Args = append(cmd.Args,
    83  			// Output dependency information.
    84  			"-MD",
    85  			// Create phony targets so that deleting a dependency doesn't
    86  			// break the build.
    87  			"-MP",
    88  			// Write it to temporary file
    89  			"-MF"+depFile.Name(),
    90  		)
    91  	}
    92  
    93  	if err := cmd.Run(); err != nil {
    94  		return fmt.Errorf("can't execute %s: %s", args.cc, err)
    95  	}
    96  
    97  	if depFile != nil {
    98  		if _, err := io.Copy(args.dep, depFile); err != nil {
    99  			return fmt.Errorf("error writing depfile: %w", err)
   100  		}
   101  	}
   102  
   103  	return nil
   104  }
   105  
   106  func adjustDependencies(baseDir string, deps []dependency) ([]byte, error) {
   107  	var buf bytes.Buffer
   108  	for _, dep := range deps {
   109  		relativeFile, err := filepath.Rel(baseDir, dep.file)
   110  		if err != nil {
   111  			return nil, err
   112  		}
   113  
   114  		if len(dep.prerequisites) == 0 {
   115  			_, err := fmt.Fprintf(&buf, "%s:\n\n", relativeFile)
   116  			if err != nil {
   117  				return nil, err
   118  			}
   119  			continue
   120  		}
   121  
   122  		var prereqs []string
   123  		for _, prereq := range dep.prerequisites {
   124  			relativePrereq, err := filepath.Rel(baseDir, prereq)
   125  			if err != nil {
   126  				return nil, err
   127  			}
   128  
   129  			prereqs = append(prereqs, relativePrereq)
   130  		}
   131  
   132  		_, err = fmt.Fprintf(&buf, "%s: \\\n %s\n\n", relativeFile, strings.Join(prereqs, " \\\n "))
   133  		if err != nil {
   134  			return nil, err
   135  		}
   136  	}
   137  	return buf.Bytes(), nil
   138  }
   139  
   140  type dependency struct {
   141  	file          string
   142  	prerequisites []string
   143  }
   144  
   145  func parseDependencies(baseDir string, in io.Reader) ([]dependency, error) {
   146  	abs := func(path string) string {
   147  		if filepath.IsAbs(path) {
   148  			return path
   149  		}
   150  		return filepath.Join(baseDir, path)
   151  	}
   152  
   153  	scanner := bufio.NewScanner(in)
   154  	var line strings.Builder
   155  	var deps []dependency
   156  	for scanner.Scan() {
   157  		buf := scanner.Bytes()
   158  		if line.Len()+len(buf) > 1024*1024 {
   159  			return nil, errors.New("line too long")
   160  		}
   161  
   162  		if bytes.HasSuffix(buf, []byte{'\\'}) {
   163  			line.Write(buf[:len(buf)-1])
   164  			continue
   165  		}
   166  
   167  		line.Write(buf)
   168  		if line.Len() == 0 {
   169  			// Skip empty lines
   170  			continue
   171  		}
   172  
   173  		parts := strings.SplitN(line.String(), ":", 2)
   174  		if len(parts) < 2 {
   175  			return nil, fmt.Errorf("invalid line without ':'")
   176  		}
   177  
   178  		// NB: This doesn't handle filenames with spaces in them.
   179  		// It seems like make doesn't do that either, so oh well.
   180  		var prereqs []string
   181  		for _, prereq := range strings.Fields(parts[1]) {
   182  			prereqs = append(prereqs, abs(prereq))
   183  		}
   184  
   185  		deps = append(deps, dependency{
   186  			abs(string(parts[0])),
   187  			prereqs,
   188  		})
   189  		line.Reset()
   190  	}
   191  	if err := scanner.Err(); err != nil {
   192  		return nil, err
   193  	}
   194  
   195  	// There is always at least a dependency for the main file.
   196  	if len(deps) == 0 {
   197  		return nil, fmt.Errorf("empty dependency file")
   198  	}
   199  	return deps, nil
   200  }
   201  
   202  // strip DWARF debug info from file by executing exe.
   203  func strip(exe, file string) error {
   204  	cmd := exec.Command(exe, "-g", file)
   205  	cmd.Stderr = os.Stderr
   206  	if err := cmd.Run(); err != nil {
   207  		return fmt.Errorf("%s: %s", exe, err)
   208  	}
   209  	return nil
   210  }