github.com/cilium/ebpf@v0.16.0/internal/errors.go (about)

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