errors.go 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. package internal
  2. import (
  3. "bytes"
  4. "fmt"
  5. "io"
  6. "strings"
  7. )
  8. // ErrorWithLog wraps err in a VerifierError that includes the parsed verifier
  9. // log buffer.
  10. //
  11. // The default error output is a summary of the full log. The latter can be
  12. // accessed via VerifierError.Log or by formatting the error, see Format.
  13. func ErrorWithLog(source string, err error, log []byte, truncated bool) *VerifierError {
  14. const whitespace = "\t\r\v\n "
  15. // Convert verifier log C string by truncating it on the first 0 byte
  16. // and trimming trailing whitespace before interpreting as a Go string.
  17. if i := bytes.IndexByte(log, 0); i != -1 {
  18. log = log[:i]
  19. }
  20. log = bytes.Trim(log, whitespace)
  21. if len(log) == 0 {
  22. return &VerifierError{source, err, nil, truncated}
  23. }
  24. logLines := bytes.Split(log, []byte{'\n'})
  25. lines := make([]string, 0, len(logLines))
  26. for _, line := range logLines {
  27. // Don't remove leading white space on individual lines. We rely on it
  28. // when outputting logs.
  29. lines = append(lines, string(bytes.TrimRight(line, whitespace)))
  30. }
  31. return &VerifierError{source, err, lines, truncated}
  32. }
  33. // VerifierError includes information from the eBPF verifier.
  34. //
  35. // It summarises the log output, see Format if you want to output the full contents.
  36. type VerifierError struct {
  37. source string
  38. // The error which caused this error.
  39. Cause error
  40. // The verifier output split into lines.
  41. Log []string
  42. // Whether the log output is truncated, based on several heuristics.
  43. Truncated bool
  44. }
  45. func (le *VerifierError) Unwrap() error {
  46. return le.Cause
  47. }
  48. func (le *VerifierError) Error() string {
  49. log := le.Log
  50. if n := len(log); n > 0 && strings.HasPrefix(log[n-1], "processed ") {
  51. // Get rid of "processed 39 insns (limit 1000000) ..." from summary.
  52. log = log[:n-1]
  53. }
  54. var b strings.Builder
  55. fmt.Fprintf(&b, "%s: %s", le.source, le.Cause.Error())
  56. n := len(log)
  57. if n == 0 {
  58. return b.String()
  59. }
  60. lines := log[n-1:]
  61. if n >= 2 && (includePreviousLine(log[n-1]) || le.Truncated) {
  62. // Add one more line of context if it aids understanding the error.
  63. lines = log[n-2:]
  64. }
  65. for _, line := range lines {
  66. b.WriteString(": ")
  67. b.WriteString(strings.TrimSpace(line))
  68. }
  69. omitted := len(le.Log) - len(lines)
  70. if omitted == 0 && !le.Truncated {
  71. return b.String()
  72. }
  73. b.WriteString(" (")
  74. if le.Truncated {
  75. b.WriteString("truncated")
  76. }
  77. if omitted > 0 {
  78. if le.Truncated {
  79. b.WriteString(", ")
  80. }
  81. fmt.Fprintf(&b, "%d line(s) omitted", omitted)
  82. }
  83. b.WriteString(")")
  84. return b.String()
  85. }
  86. // includePreviousLine returns true if the given line likely is better
  87. // understood with additional context from the preceding line.
  88. func includePreviousLine(line string) bool {
  89. // We need to find a good trade off between understandable error messages
  90. // and too much complexity here. Checking the string prefix is ok, requiring
  91. // regular expressions to do it is probably overkill.
  92. if strings.HasPrefix(line, "\t") {
  93. // [13] STRUCT drm_rect size=16 vlen=4
  94. // \tx1 type_id=2
  95. return true
  96. }
  97. if len(line) >= 2 && line[0] == 'R' && line[1] >= '0' && line[1] <= '9' {
  98. // 0: (95) exit
  99. // R0 !read_ok
  100. return true
  101. }
  102. if strings.HasPrefix(line, "invalid bpf_context access") {
  103. // 0: (79) r6 = *(u64 *)(r1 +0)
  104. // func '__x64_sys_recvfrom' arg0 type FWD is not a struct
  105. // invalid bpf_context access off=0 size=8
  106. return true
  107. }
  108. return false
  109. }
  110. // Format the error.
  111. //
  112. // Understood verbs are %s and %v, which are equivalent to calling Error(). %v
  113. // allows outputting additional information using the following flags:
  114. //
  115. // %+<width>v: Output the first <width> lines, or all lines if no width is given.
  116. // %-<width>v: Output the last <width> lines, or all lines if no width is given.
  117. //
  118. // Use width to specify how many lines to output. Use the '-' flag to output
  119. // lines from the end of the log instead of the beginning.
  120. func (le *VerifierError) Format(f fmt.State, verb rune) {
  121. switch verb {
  122. case 's':
  123. _, _ = io.WriteString(f, le.Error())
  124. case 'v':
  125. n, haveWidth := f.Width()
  126. if !haveWidth || n > len(le.Log) {
  127. n = len(le.Log)
  128. }
  129. if !f.Flag('+') && !f.Flag('-') {
  130. if haveWidth {
  131. _, _ = io.WriteString(f, "%!v(BADWIDTH)")
  132. return
  133. }
  134. _, _ = io.WriteString(f, le.Error())
  135. return
  136. }
  137. if f.Flag('+') && f.Flag('-') {
  138. _, _ = io.WriteString(f, "%!v(BADFLAG)")
  139. return
  140. }
  141. fmt.Fprintf(f, "%s: %s:", le.source, le.Cause.Error())
  142. omitted := len(le.Log) - n
  143. lines := le.Log[:n]
  144. if f.Flag('-') {
  145. // Print last instead of first lines.
  146. lines = le.Log[len(le.Log)-n:]
  147. if omitted > 0 {
  148. fmt.Fprintf(f, "\n\t(%d line(s) omitted)", omitted)
  149. }
  150. }
  151. for _, line := range lines {
  152. fmt.Fprintf(f, "\n\t%s", line)
  153. }
  154. if !f.Flag('-') {
  155. if omitted > 0 {
  156. fmt.Fprintf(f, "\n\t(%d line(s) omitted)", omitted)
  157. }
  158. }
  159. if le.Truncated {
  160. fmt.Fprintf(f, "\n\t(truncated)")
  161. }
  162. default:
  163. fmt.Fprintf(f, "%%!%c(BADVERB)", verb)
  164. }
  165. }