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 }