github.com/elfadel/cilium@v1.6.12/pkg/datapath/loader/compile.go (about) 1 // Copyright 2017-2019 Authors of Cilium 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package loader 16 17 import ( 18 "bufio" 19 "bytes" 20 "context" 21 "fmt" 22 "io" 23 "io/ioutil" 24 "os" 25 "path" 26 "runtime" 27 28 "github.com/cilium/cilium/pkg/command/exec" 29 "github.com/cilium/cilium/pkg/logging/logfields" 30 "github.com/cilium/cilium/pkg/option" 31 32 "github.com/sirupsen/logrus" 33 ) 34 35 // OutputType determines the type to be generated by the compilation steps. 36 type OutputType string 37 38 const ( 39 outputObject = OutputType("obj") 40 outputAssembly = OutputType("asm") 41 outputSource = OutputType("c") 42 ) 43 44 // progInfo describes a program to be compiled with the expected output format 45 type progInfo struct { 46 // Source is the program source (base) filename to be compiled 47 Source string 48 // Output is the expected (base) filename produced from the source 49 Output string 50 // OutputType to be created by LLVM 51 OutputType OutputType 52 } 53 54 // directoryInfo includes relevant directories for compilation and linking 55 type directoryInfo struct { 56 // Library contains the library code to be used for compilation 57 Library string 58 // Runtime contains headers for compilation 59 Runtime string 60 // State contains node, lxc, and features headers for templatization 61 State string 62 // Output is the directory where the files will be stored 63 Output string 64 } 65 66 var ( 67 compiler = "clang" 68 linker = "llc" 69 standardCFlags = []string{"-O2", "-target", "bpf", 70 fmt.Sprintf("-D__NR_CPUS__=%d", runtime.NumCPU()), 71 "-Wno-address-of-packed-member", "-Wno-unknown-warning-option"} 72 standardLDFlags = []string{"-march=bpf", "-mcpu=probe"} 73 74 endpointPrefix = "bpf_lxc" 75 endpointProg = fmt.Sprintf("%s.%s", endpointPrefix, outputSource) 76 endpointObj = fmt.Sprintf("%s.o", endpointPrefix) 77 endpointObjDebug = fmt.Sprintf("%s.dbg.o", endpointPrefix) 78 endpointAsm = fmt.Sprintf("%s.%s", endpointPrefix, outputAssembly) 79 80 // testIncludes allows the unit tests to inject additional include 81 // paths into the compile command at test time. It is usually nil. 82 testIncludes []string 83 84 debugProgs = []*progInfo{ 85 { 86 Source: endpointProg, 87 Output: endpointObjDebug, 88 OutputType: outputObject, 89 }, 90 { 91 Source: endpointProg, 92 Output: endpointAsm, 93 OutputType: outputAssembly, 94 }, 95 { 96 Source: endpointProg, 97 Output: endpointProg, 98 OutputType: outputSource, 99 }, 100 } 101 datapathProg = &progInfo{ 102 Source: endpointProg, 103 Output: endpointObj, 104 OutputType: outputObject, 105 } 106 ) 107 108 // progLDFlags determines the loader flags for the specified prog and paths. 109 func progLDFlags(prog *progInfo, dir *directoryInfo) []string { 110 return []string{ 111 fmt.Sprintf("-filetype=%s", prog.OutputType), 112 "-o", path.Join(dir.Output, prog.Output), 113 } 114 } 115 116 // prepareCmdPipes attaches pipes to the stdout and stderr of the specified 117 // command, and returns the stdout, stderr, and any error that may have 118 // occurred while creating the pipes. 119 func prepareCmdPipes(cmd *exec.Cmd) (io.ReadCloser, io.ReadCloser, error) { 120 stdout, err := cmd.StdoutPipe() 121 if err != nil { 122 return nil, nil, fmt.Errorf("Failed to get stdout pipe: %s", err) 123 } 124 125 stderr, err := cmd.StderrPipe() 126 if err != nil { 127 stdout.Close() 128 return nil, nil, fmt.Errorf("Failed to get stderr pipe: %s", err) 129 } 130 131 return stdout, stderr, nil 132 } 133 134 func pidFromProcess(proc *os.Process) string { 135 result := "not-started" 136 if proc != nil { 137 result = fmt.Sprintf("%d", proc.Pid) 138 } 139 return result 140 } 141 142 // compileAndLink links the specified program from the specified path to the 143 // intermediate representation, to the output specified in the prog's info. 144 func compileAndLink(ctx context.Context, prog *progInfo, dir *directoryInfo, debug bool, compileArgs ...string) error { 145 compileCmd, cancelCompile := exec.WithCancel(ctx, compiler, compileArgs...) 146 defer cancelCompile() 147 compilerStdout, compilerStderr, err := prepareCmdPipes(compileCmd) 148 if err != nil { 149 return err 150 } 151 152 linkArgs := make([]string, 0, 8) 153 if debug { 154 linkArgs = append(linkArgs, "-mattr=dwarfris") 155 } 156 linkArgs = append(linkArgs, standardLDFlags...) 157 linkArgs = append(linkArgs, progLDFlags(prog, dir)...) 158 159 linkCmd := exec.CommandContext(ctx, linker, linkArgs...) 160 linkCmd.Stdin = compilerStdout 161 if err := compileCmd.Start(); err != nil { 162 return fmt.Errorf("Failed to start command %s: %s", compileCmd.Args, err) 163 } 164 165 var compileOut []byte 166 /* Ignoring the output here because pkg/command/exec will log it. */ 167 _, err = linkCmd.CombinedOutput(log, true) 168 if err == nil { 169 compileOut, _ = ioutil.ReadAll(compilerStderr) 170 err = compileCmd.Wait() 171 } else { 172 cancelCompile() 173 } 174 if err != nil { 175 err = fmt.Errorf("Failed to compile %s: %s", prog.Output, err) 176 log.WithFields(logrus.Fields{ 177 "compiler-pid": pidFromProcess(compileCmd.Process), 178 "linker-pid": pidFromProcess(linkCmd.Process), 179 }).Error(err) 180 if compileOut != nil { 181 scopedLog := log.Warn 182 if debug { 183 scopedLog = log.Debug 184 } 185 scanner := bufio.NewScanner(bytes.NewReader(compileOut)) 186 for scanner.Scan() { 187 scopedLog(scanner.Text()) 188 } 189 } 190 } 191 192 return err 193 } 194 195 // progLDFlags determines the compiler flags for the specified prog and paths. 196 func progCFlags(prog *progInfo, dir *directoryInfo) []string { 197 var output string 198 199 if prog.OutputType == outputSource { 200 output = path.Join(dir.Output, prog.Output) 201 } else { 202 output = "-" // stdout 203 } 204 205 return append(testIncludes, 206 fmt.Sprintf("-I%s", path.Join(dir.Runtime, "globals")), 207 fmt.Sprintf("-I%s", dir.State), 208 fmt.Sprintf("-I%s", dir.Library), 209 fmt.Sprintf("-I%s", path.Join(dir.Library, "include")), 210 "-c", path.Join(dir.Library, prog.Source), 211 "-o", output, 212 ) 213 } 214 215 // compile and link a program. 216 func compile(ctx context.Context, prog *progInfo, dir *directoryInfo, debug bool) (err error) { 217 args := make([]string, 0, 16) 218 if prog.OutputType == outputSource { 219 args = append(args, "-E") // Preprocessor 220 } else { 221 args = append(args, "-emit-llvm") 222 if debug { 223 args = append(args, "-g") 224 } 225 } 226 args = append(args, standardCFlags...) 227 args = append(args, progCFlags(prog, dir)...) 228 229 // Compilation is split between two exec calls. First clang generates 230 // LLVM bitcode and then later llc compiles it to byte-code. 231 log.WithFields(logrus.Fields{ 232 "target": compiler, 233 "args": args, 234 }).Debug("Launching compiler") 235 if prog.OutputType == outputSource { 236 compileCmd := exec.CommandContext(ctx, compiler, args...) 237 _, err = compileCmd.CombinedOutput(log, debug) 238 } else { 239 switch prog.OutputType { 240 case outputObject: 241 err = compileAndLink(ctx, prog, dir, debug, args...) 242 case outputAssembly: 243 err = compileAndLink(ctx, prog, dir, false, args...) 244 default: 245 log.Fatalf("Unhandled progInfo.OutputType %s", prog.OutputType) 246 } 247 } 248 249 return err 250 } 251 252 // compileDatapath invokes the compiler and linker to create all state files for 253 // the BPF datapath, with the primary target being the BPF ELF binary. 254 // 255 // If debug is enabled, create also the following output files: 256 // * Preprocessed C 257 // * Assembly 258 // * Object compiled with debug symbols 259 func compileDatapath(ctx context.Context, dirs *directoryInfo, debug bool, logger *logrus.Entry) error { 260 // TODO: Consider logging kernel/clang versions here too 261 scopedLog := logger.WithField(logfields.Debug, debug) 262 263 // Write out assembly and preprocessing files for debugging purposes 264 if debug { 265 for _, p := range debugProgs { 266 if err := compile(ctx, p, dirs, debug); err != nil { 267 // Only log an error here if the context was not canceled or not 268 // timed out; this log message should only represent failures 269 // with respect to compiling the program. 270 if ctx.Err() == nil { 271 scopedLog.WithField(logfields.Params, logfields.Repr(p)). 272 WithError(err).Debug("JoinEP: Failed to compile") 273 } 274 return err 275 } 276 } 277 } 278 279 // Compile the new program 280 if err := compile(ctx, datapathProg, dirs, debug); err != nil { 281 // Only log an error here if the context was not canceled or not timed 282 // out; this log message should only represent failures with respect to 283 // compiling the program. 284 if ctx.Err() == nil { 285 scopedLog.WithField(logfields.Params, logfields.Repr(datapathProg)). 286 WithError(err).Warn("JoinEP: Failed to compile") 287 } 288 return err 289 } 290 291 return nil 292 } 293 294 // Compile compiles a BPF program generating an object file. 295 func Compile(ctx context.Context, src string, out string) error { 296 debug := option.Config.BPFCompilationDebug 297 prog := progInfo{ 298 Source: src, 299 Output: out, 300 OutputType: outputObject, 301 } 302 dirs := directoryInfo{ 303 Library: option.Config.BpfDir, 304 Runtime: option.Config.StateDir, 305 Output: option.Config.StateDir, 306 State: option.Config.StateDir, 307 } 308 return compile(ctx, &prog, &dirs, debug) 309 } 310 311 // compileTemplate compiles a BPF program generating a template object file. 312 func compileTemplate(ctx context.Context, out string) error { 313 dirs := directoryInfo{ 314 Library: option.Config.BpfDir, 315 Runtime: option.Config.StateDir, 316 Output: out, 317 State: out, 318 } 319 return compileDatapath(ctx, &dirs, option.Config.BPFCompilationDebug, log) 320 }