mirror of
1
Fork 0
gotosocial/vendor/github.com/cilium/ebpf/internal/errors.go

207 lines
5.1 KiB
Go

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