File: errors.go

package info (click to toggle)
golang-github-cilium-ebpf 0.17.3%2Bds1-1
  • links: PTS, VCS
  • area: main
  • in suites: experimental
  • size: 4,684 kB
  • sloc: ansic: 1,259; makefile: 127; python: 113; awk: 29; sh: 24
file content (179 lines) | stat: -rw-r--r-- 4,527 bytes parent folder | download
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
package internal

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

// ErrorWithLog wraps err in a VerifierError that includes the parsed verifier
// log buffer.
//
// 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.
func ErrorWithLog(source string, 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.
	if i := bytes.IndexByte(log, 0); i != -1 {
		log = log[:i]
	}

	log = bytes.Trim(log, whitespace)
	if len(log) == 0 {
		return &VerifierError{source, err, nil}
	}

	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{source, err, lines}
}

// 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 {
	source string
	// The error which caused this error.
	Cause error
	// The verifier output split into lines.
	Log []string
}

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]
	}

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

	n := len(log)
	if n == 0 {
		return b.String()
	}

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

	for _, line := range lines {
		b.WriteString(": ")
		b.WriteString(strings.TrimSpace(line))
	}

	omitted := len(le.Log) - len(lines)
	if omitted > 0 {
		fmt.Fprintf(&b, " (%d line(s) omitted)", omitted)
	}

	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:
//
//	%+<width>v: Output the first <width> lines, or all lines if no width is given.
//	%-<width>v: 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: %s:", le.source, 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)
			}
		}

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