@@ -2,46 +2,186 @@ package internal
2
2
3
3
import (
4
4
"bytes"
5
- "errors"
6
5
"fmt"
7
-
8
- "github.com/cilium/ebpf/internal/unix "
6
+ "io"
7
+ "strings "
9
8
)
10
9
11
- // ErrorWithLog returns an error that includes logs from the
12
- // kernel verifier.
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.
13
14
//
14
- // logErr should be the error returned by the syscall that generated
15
- // the log. It is used to check for truncation of the output.
16
- func ErrorWithLog (err error , log []byte , logErr error ) error {
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
+
17
19
// Convert verifier log C string by truncating it on the first 0 byte
18
20
// and trimming trailing whitespace before interpreting as a Go string.
21
+ truncated := false
19
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
+
20
29
log = log [:i ]
30
+ } else if len (log ) > 0 {
31
+ // No null byte? Dodgy!
32
+ truncated = true
21
33
}
22
- logStr := string (bytes .Trim (log , "\t \r \n " ))
23
34
24
- if errors .Is (logErr , unix .ENOSPC ) {
25
- logStr += " (truncated...)"
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 )))
26
42
}
27
43
28
- return & VerifierError {err , logStr }
44
+ return & VerifierError {err , lines , truncated }
29
45
}
30
46
31
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.
32
50
type VerifierError struct {
33
- cause error
34
- log string
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
35
57
}
36
58
37
59
func (le * VerifierError ) Unwrap () error {
38
- return le .cause
60
+ return le .Cause
39
61
}
40
62
41
63
func (le * VerifierError ) Error () string {
42
- if le .log == "" {
43
- return le .cause .Error ()
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
+ output := 0
71
+ switch n := len (log ); n {
72
+ case 0 :
73
+ return le .Cause .Error ()
74
+
75
+ case 1 , 2 :
76
+ output = n
77
+
78
+ default :
79
+ output = 1
80
+ if strings .HasPrefix (log [n - 1 ], "\t " ) || le .Truncated {
81
+ // Add one more line of context if the last line starts with a tab,
82
+ // or if it has been truncated.
83
+ // For example:
84
+ // [13] STRUCT drm_rect size=16 vlen=4
85
+ // \tx1 type_id=2
86
+ output ++
87
+ }
88
+ }
89
+
90
+ var b strings.Builder
91
+ fmt .Fprintf (& b , "%s: " , le .Cause .Error ())
92
+
93
+ lines := log [len (log )- output :]
94
+ for i , line := range lines {
95
+ b .WriteString (strings .TrimSpace (line ))
96
+ if i != len (lines )- 1 {
97
+ b .WriteString (": " )
98
+ }
99
+ }
100
+
101
+ omitted := len (le .Log ) - len (lines )
102
+ if omitted == 0 && ! le .Truncated {
103
+ return b .String ()
104
+ }
105
+
106
+ b .WriteString (" (" )
107
+ if le .Truncated {
108
+ b .WriteString ("truncated" )
109
+ }
110
+
111
+ if omitted > 0 {
112
+ if le .Truncated {
113
+ b .WriteString (", " )
114
+ }
115
+ fmt .Fprintf (& b , "%d line(s) omitted" , omitted )
44
116
}
117
+ b .WriteString (")" )
118
+
119
+ return b .String ()
120
+ }
121
+
122
+ // Format the error.
123
+ //
124
+ // Understood verbs are %s and %v, which are equivalent to calling Error(). %v
125
+ // allows outputting additional information using the following flags:
126
+ //
127
+ // + Output the first <width> lines, or all lines if no width is given.
128
+ // - Output the last <width> lines, or all lines if no width is given.
129
+ //
130
+ // Use width to specify how many lines to output. Use the '-' flag to output
131
+ // lines from the end of the log instead of the beginning.
132
+ func (le * VerifierError ) Format (f fmt.State , verb rune ) {
133
+ switch verb {
134
+ case 's' :
135
+ _ , _ = io .WriteString (f , le .Error ())
136
+
137
+ case 'v' :
138
+ n , haveWidth := f .Width ()
139
+ if ! haveWidth || n > len (le .Log ) {
140
+ n = len (le .Log )
141
+ }
142
+
143
+ if ! f .Flag ('+' ) && ! f .Flag ('-' ) {
144
+ if haveWidth {
145
+ _ , _ = io .WriteString (f , "%!v(BADWIDTH)" )
146
+ return
147
+ }
45
148
46
- return fmt .Sprintf ("%s: %s" , le .cause , le .log )
149
+ _ , _ = io .WriteString (f , le .Error ())
150
+ return
151
+ }
152
+
153
+ if f .Flag ('+' ) && f .Flag ('-' ) {
154
+ _ , _ = io .WriteString (f , "%!v(BADFLAG)" )
155
+ return
156
+ }
157
+
158
+ fmt .Fprintf (f , "%s:" , le .Cause .Error ())
159
+
160
+ omitted := len (le .Log ) - n
161
+ lines := le .Log [:n ]
162
+ if f .Flag ('-' ) {
163
+ // Print last instead of first lines.
164
+ lines = le .Log [len (le .Log )- n :]
165
+ if omitted > 0 {
166
+ fmt .Fprintf (f , "\n \t (%d line(s) omitted)" , omitted )
167
+ }
168
+ }
169
+
170
+ for _ , line := range lines {
171
+ fmt .Fprintf (f , "\n \t %s" , line )
172
+ }
173
+
174
+ if ! f .Flag ('-' ) {
175
+ if omitted > 0 {
176
+ fmt .Fprintf (f , "\n \t (%d line(s) omitted)" , omitted )
177
+ }
178
+ }
179
+
180
+ if le .Truncated {
181
+ fmt .Fprintf (f , "\n \t (truncated)" )
182
+ }
183
+
184
+ default :
185
+ fmt .Fprintf (f , "%%!%c(BADVERB)" , verb )
186
+ }
47
187
}
0 commit comments