github.com/cilium/ebpf@v0.15.1-0.20240517100537-8079b37aa138/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, truncated bool) *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, truncated}
    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, truncated}
    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  	// Whether the log output is truncated, based on several heuristics.
    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]) || le.Truncated) {
    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 && !le.Truncated {
    85  		return b.String()
    86  	}
    87  
    88  	b.WriteString(" (")
    89  	if le.Truncated {
    90  		b.WriteString("truncated")
    91  	}
    92  
    93  	if omitted > 0 {
    94  		if le.Truncated {
    95  			b.WriteString(", ")
    96  		}
    97  		fmt.Fprintf(&b, "%d line(s) omitted", omitted)
    98  	}
    99  	b.WriteString(")")
   100  
   101  	return b.String()
   102  }
   103  
   104  // includePreviousLine returns true if the given line likely is better
   105  // understood with additional context from the preceding line.
   106  func includePreviousLine(line string) bool {
   107  	// We need to find a good trade off between understandable error messages
   108  	// and too much complexity here. Checking the string prefix is ok, requiring
   109  	// regular expressions to do it is probably overkill.
   110  
   111  	if strings.HasPrefix(line, "\t") {
   112  		// [13] STRUCT drm_rect size=16 vlen=4
   113  		// \tx1 type_id=2
   114  		return true
   115  	}
   116  
   117  	if len(line) >= 2 && line[0] == 'R' && line[1] >= '0' && line[1] <= '9' {
   118  		// 0: (95) exit
   119  		// R0 !read_ok
   120  		return true
   121  	}
   122  
   123  	if strings.HasPrefix(line, "invalid bpf_context access") {
   124  		// 0: (79) r6 = *(u64 *)(r1 +0)
   125  		// func '__x64_sys_recvfrom' arg0 type FWD is not a struct
   126  		// invalid bpf_context access off=0 size=8
   127  		return true
   128  	}
   129  
   130  	return false
   131  }
   132  
   133  // Format the error.
   134  //
   135  // Understood verbs are %s and %v, which are equivalent to calling Error(). %v
   136  // allows outputting additional information using the following flags:
   137  //
   138  //	%+<width>v: Output the first <width> lines, or all lines if no width is given.
   139  //	%-<width>v: Output the last <width> lines, or all lines if no width is given.
   140  //
   141  // Use width to specify how many lines to output. Use the '-' flag to output
   142  // lines from the end of the log instead of the beginning.
   143  func (le *VerifierError) Format(f fmt.State, verb rune) {
   144  	switch verb {
   145  	case 's':
   146  		_, _ = io.WriteString(f, le.Error())
   147  
   148  	case 'v':
   149  		n, haveWidth := f.Width()
   150  		if !haveWidth || n > len(le.Log) {
   151  			n = len(le.Log)
   152  		}
   153  
   154  		if !f.Flag('+') && !f.Flag('-') {
   155  			if haveWidth {
   156  				_, _ = io.WriteString(f, "%!v(BADWIDTH)")
   157  				return
   158  			}
   159  
   160  			_, _ = io.WriteString(f, le.Error())
   161  			return
   162  		}
   163  
   164  		if f.Flag('+') && f.Flag('-') {
   165  			_, _ = io.WriteString(f, "%!v(BADFLAG)")
   166  			return
   167  		}
   168  
   169  		fmt.Fprintf(f, "%s: %s:", le.source, le.Cause.Error())
   170  
   171  		omitted := len(le.Log) - n
   172  		lines := le.Log[:n]
   173  		if f.Flag('-') {
   174  			// Print last instead of first lines.
   175  			lines = le.Log[len(le.Log)-n:]
   176  			if omitted > 0 {
   177  				fmt.Fprintf(f, "\n\t(%d line(s) omitted)", omitted)
   178  			}
   179  		}
   180  
   181  		for _, line := range lines {
   182  			fmt.Fprintf(f, "\n\t%s", line)
   183  		}
   184  
   185  		if !f.Flag('-') {
   186  			if omitted > 0 {
   187  				fmt.Fprintf(f, "\n\t(%d line(s) omitted)", omitted)
   188  			}
   189  		}
   190  
   191  		if le.Truncated {
   192  			fmt.Fprintf(f, "\n\t(truncated)")
   193  		}
   194  
   195  	default:
   196  		fmt.Fprintf(f, "%%!%c(BADVERB)", verb)
   197  	}
   198  }