...

Source file src/github.com/cilium/ebpf/internal/errors.go

Documentation: github.com/cilium/ebpf/internal

     1  package internal
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io"
     7  	"strings"
     8  )
     9  
    10  // ErrorWithLog returns an error which includes logs from the kernel verifier.
    11  //
    12  // The default error output is a summary of the full log. The latter can be
    13  // accessed via VerifierError.Log or by formatting the error, see Format.
    14  //
    15  // A set of heuristics is used to determine whether the log has been truncated.
    16  func ErrorWithLog(err error, log []byte) *VerifierError {
    17  	const whitespace = "\t\r\v\n "
    18  
    19  	// Convert verifier log C string by truncating it on the first 0 byte
    20  	// and trimming trailing whitespace before interpreting as a Go string.
    21  	truncated := false
    22  	if i := bytes.IndexByte(log, 0); i != -1 {
    23  		if i == len(log)-1 && !bytes.HasSuffix(log[:i], []byte{'\n'}) {
    24  			// The null byte is at the end of the buffer and it's not preceded
    25  			// by a newline character. Most likely the buffer was too short.
    26  			truncated = true
    27  		}
    28  
    29  		log = log[:i]
    30  	} else if len(log) > 0 {
    31  		// No null byte? Dodgy!
    32  		truncated = true
    33  	}
    34  
    35  	log = bytes.Trim(log, whitespace)
    36  	logLines := bytes.Split(log, []byte{'\n'})
    37  	lines := make([]string, 0, len(logLines))
    38  	for _, line := range logLines {
    39  		// Don't remove leading white space on individual lines. We rely on it
    40  		// when outputting logs.
    41  		lines = append(lines, string(bytes.TrimRight(line, whitespace)))
    42  	}
    43  
    44  	return &VerifierError{err, lines, truncated}
    45  }
    46  
    47  // VerifierError includes information from the eBPF verifier.
    48  //
    49  // It summarises the log output, see Format if you want to output the full contents.
    50  type VerifierError struct {
    51  	// The error which caused this error.
    52  	Cause error
    53  	// The verifier output split into lines.
    54  	Log []string
    55  	// Whether the log output is truncated, based on several heuristics.
    56  	Truncated bool
    57  }
    58  
    59  func (le *VerifierError) Unwrap() error {
    60  	return le.Cause
    61  }
    62  
    63  func (le *VerifierError) Error() string {
    64  	log := le.Log
    65  	if n := len(log); n > 0 && strings.HasPrefix(log[n-1], "processed ") {
    66  		// Get rid of "processed 39 insns (limit 1000000) ..." from summary.
    67  		log = log[:n-1]
    68  	}
    69  
    70  	n := len(log)
    71  	if n == 0 {
    72  		return le.Cause.Error()
    73  	}
    74  
    75  	lines := log[n-1:]
    76  	if n >= 2 && (includePreviousLine(log[n-1]) || le.Truncated) {
    77  		// Add one more line of context if it aids understanding the error.
    78  		lines = log[n-2:]
    79  	}
    80  
    81  	var b strings.Builder
    82  	fmt.Fprintf(&b, "%s: ", le.Cause.Error())
    83  
    84  	for i, line := range lines {
    85  		b.WriteString(strings.TrimSpace(line))
    86  		if i != len(lines)-1 {
    87  			b.WriteString(": ")
    88  		}
    89  	}
    90  
    91  	omitted := len(le.Log) - len(lines)
    92  	if omitted == 0 && !le.Truncated {
    93  		return b.String()
    94  	}
    95  
    96  	b.WriteString(" (")
    97  	if le.Truncated {
    98  		b.WriteString("truncated")
    99  	}
   100  
   101  	if omitted > 0 {
   102  		if le.Truncated {
   103  			b.WriteString(", ")
   104  		}
   105  		fmt.Fprintf(&b, "%d line(s) omitted", omitted)
   106  	}
   107  	b.WriteString(")")
   108  
   109  	return b.String()
   110  }
   111  
   112  // includePreviousLine returns true if the given line likely is better
   113  // understood with additional context from the preceding line.
   114  func includePreviousLine(line string) bool {
   115  	// We need to find a good trade off between understandable error messages
   116  	// and too much complexity here. Checking the string prefix is ok, requiring
   117  	// regular expressions to do it is probably overkill.
   118  
   119  	if strings.HasPrefix(line, "\t") {
   120  		// [13] STRUCT drm_rect size=16 vlen=4
   121  		// \tx1 type_id=2
   122  		return true
   123  	}
   124  
   125  	if len(line) >= 2 && line[0] == 'R' && line[1] >= '0' && line[1] <= '9' {
   126  		// 0: (95) exit
   127  		// R0 !read_ok
   128  		return true
   129  	}
   130  
   131  	if strings.HasPrefix(line, "invalid bpf_context access") {
   132  		// 0: (79) r6 = *(u64 *)(r1 +0)
   133  		// func '__x64_sys_recvfrom' arg0 type FWD is not a struct
   134  		// invalid bpf_context access off=0 size=8
   135  		return true
   136  	}
   137  
   138  	return false
   139  }
   140  
   141  // Format the error.
   142  //
   143  // Understood verbs are %s and %v, which are equivalent to calling Error(). %v
   144  // allows outputting additional information using the following flags:
   145  //
   146  //     +   Output the first <width> lines, or all lines if no width is given.
   147  //     -   Output the last <width> lines, or all lines if no width is given.
   148  //
   149  // Use width to specify how many lines to output. Use the '-' flag to output
   150  // lines from the end of the log instead of the beginning.
   151  func (le *VerifierError) Format(f fmt.State, verb rune) {
   152  	switch verb {
   153  	case 's':
   154  		_, _ = io.WriteString(f, le.Error())
   155  
   156  	case 'v':
   157  		n, haveWidth := f.Width()
   158  		if !haveWidth || n > len(le.Log) {
   159  			n = len(le.Log)
   160  		}
   161  
   162  		if !f.Flag('+') && !f.Flag('-') {
   163  			if haveWidth {
   164  				_, _ = io.WriteString(f, "%!v(BADWIDTH)")
   165  				return
   166  			}
   167  
   168  			_, _ = io.WriteString(f, le.Error())
   169  			return
   170  		}
   171  
   172  		if f.Flag('+') && f.Flag('-') {
   173  			_, _ = io.WriteString(f, "%!v(BADFLAG)")
   174  			return
   175  		}
   176  
   177  		fmt.Fprintf(f, "%s:", le.Cause.Error())
   178  
   179  		omitted := len(le.Log) - n
   180  		lines := le.Log[:n]
   181  		if f.Flag('-') {
   182  			// Print last instead of first lines.
   183  			lines = le.Log[len(le.Log)-n:]
   184  			if omitted > 0 {
   185  				fmt.Fprintf(f, "\n\t(%d line(s) omitted)", omitted)
   186  			}
   187  		}
   188  
   189  		for _, line := range lines {
   190  			fmt.Fprintf(f, "\n\t%s", line)
   191  		}
   192  
   193  		if !f.Flag('-') {
   194  			if omitted > 0 {
   195  				fmt.Fprintf(f, "\n\t(%d line(s) omitted)", omitted)
   196  			}
   197  		}
   198  
   199  		if le.Truncated {
   200  			fmt.Fprintf(f, "\n\t(truncated)")
   201  		}
   202  
   203  	default:
   204  		fmt.Fprintf(f, "%%!%c(BADVERB)", verb)
   205  	}
   206  }
   207  

View as plain text