errors.go 5.1 KB

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