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 }