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  }