errors.go (5185B)
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 }