summaryrefslogtreecommitdiff
path: root/vendor/github.com/cilium/ebpf/internal/errors.go
blob: b5ccdd7d0531ad9382b9e9a6140d3c3572e1f328 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
package internal

import (
	"bytes"
	"fmt"
	"io"
	"strings"
)

// ErrorWithLog returns an error which includes logs from the kernel verifier.
//
// The default error output is a summary of the full log. The latter can be
// accessed via VerifierError.Log or by formatting the error, see Format.
//
// A set of heuristics is used to determine whether the log has been truncated.
func ErrorWithLog(err error, log []byte) *VerifierError {
	const whitespace = "\t\r\v\n "

	// Convert verifier log C string by truncating it on the first 0 byte
	// and trimming trailing whitespace before interpreting as a Go string.
	truncated := false
	if i := bytes.IndexByte(log, 0); i != -1 {
		if i == len(log)-1 && !bytes.HasSuffix(log[:i], []byte{'\n'}) {
			// The null byte is at the end of the buffer and it's not preceded
			// by a newline character. Most likely the buffer was too short.
			truncated = true
		}

		log = log[:i]
	} else if len(log) > 0 {
		// No null byte? Dodgy!
		truncated = true
	}

	log = bytes.Trim(log, whitespace)
	logLines := bytes.Split(log, []byte{'\n'})
	lines := make([]string, 0, len(logLines))
	for _, line := range logLines {
		// Don't remove leading white space on individual lines. We rely on it
		// when outputting logs.
		lines = append(lines, string(bytes.TrimRight(line, whitespace)))
	}

	return &VerifierError{err, lines, truncated}
}

// VerifierError includes information from the eBPF verifier.
//
// It summarises the log output, see Format if you want to output the full contents.
type VerifierError struct {
	// The error which caused this error.
	Cause error
	// The verifier output split into lines.
	Log []string
	// Whether the log output is truncated, based on several heuristics.
	Truncated bool
}

func (le *VerifierError) Unwrap() error {
	return le.Cause
}

func (le *VerifierError) Error() string {
	log := le.Log
	if n := len(log); n > 0 && strings.HasPrefix(log[n-1], "processed ") {
		// Get rid of "processed 39 insns (limit 1000000) ..." from summary.
		log = log[:n-1]
	}

	n := len(log)
	if n == 0 {
		return le.Cause.Error()
	}

	lines := log[n-1:]
	if n >= 2 && (includePreviousLine(log[n-1]) || le.Truncated) {
		// Add one more line of context if it aids understanding the error.
		lines = log[n-2:]
	}

	var b strings.Builder
	fmt.Fprintf(&b, "%s: ", le.Cause.Error())

	for i, line := range lines {
		b.WriteString(strings.TrimSpace(line))
		if i != len(lines)-1 {
			b.WriteString(": ")
		}
	}

	omitted := len(le.Log) - len(lines)
	if omitted == 0 && !le.Truncated {
		return b.String()
	}

	b.WriteString(" (")
	if le.Truncated {
		b.WriteString("truncated")
	}

	if omitted > 0 {
		if le.Truncated {
			b.WriteString(", ")
		}
		fmt.Fprintf(&b, "%d line(s) omitted", omitted)
	}
	b.WriteString(")")

	return b.String()
}

// includePreviousLine returns true if the given line likely is better
// understood with additional context from the preceding line.
func includePreviousLine(line string) bool {
	// We need to find a good trade off between understandable error messages
	// and too much complexity here. Checking the string prefix is ok, requiring
	// regular expressions to do it is probably overkill.

	if strings.HasPrefix(line, "\t") {
		// [13] STRUCT drm_rect size=16 vlen=4
		// \tx1 type_id=2
		return true
	}

	if len(line) >= 2 && line[0] == 'R' && line[1] >= '0' && line[1] <= '9' {
		// 0: (95) exit
		// R0 !read_ok
		return true
	}

	if strings.HasPrefix(line, "invalid bpf_context access") {
		// 0: (79) r6 = *(u64 *)(r1 +0)
		// func '__x64_sys_recvfrom' arg0 type FWD is not a struct
		// invalid bpf_context access off=0 size=8
		return true
	}

	return false
}

// Format the error.
//
// Understood verbs are %s and %v, which are equivalent to calling Error(). %v
// allows outputting additional information using the following flags:
//
//     +   Output the first <width> lines, or all lines if no width is given.
//     -   Output the last <width> lines, or all lines if no width is given.
//
// Use width to specify how many lines to output. Use the '-' flag to output
// lines from the end of the log instead of the beginning.
func (le *VerifierError) Format(f fmt.State, verb rune) {
	switch verb {
	case 's':
		_, _ = io.WriteString(f, le.Error())

	case 'v':
		n, haveWidth := f.Width()
		if !haveWidth || n > len(le.Log) {
			n = len(le.Log)
		}

		if !f.Flag('+') && !f.Flag('-') {
			if haveWidth {
				_, _ = io.WriteString(f, "%!v(BADWIDTH)")
				return
			}

			_, _ = io.WriteString(f, le.Error())
			return
		}

		if f.Flag('+') && f.Flag('-') {
			_, _ = io.WriteString(f, "%!v(BADFLAG)")
			return
		}

		fmt.Fprintf(f, "%s:", le.Cause.Error())

		omitted := len(le.Log) - n
		lines := le.Log[:n]
		if f.Flag('-') {
			// Print last instead of first lines.
			lines = le.Log[len(le.Log)-n:]
			if omitted > 0 {
				fmt.Fprintf(f, "\n\t(%d line(s) omitted)", omitted)
			}
		}

		for _, line := range lines {
			fmt.Fprintf(f, "\n\t%s", line)
		}

		if !f.Flag('-') {
			if omitted > 0 {
				fmt.Fprintf(f, "\n\t(%d line(s) omitted)", omitted)
			}
		}

		if le.Truncated {
			fmt.Fprintf(f, "\n\t(truncated)")
		}

	default:
		fmt.Fprintf(f, "%%!%c(BADVERB)", verb)
	}
}