github.com/insolar/vanilla@v0.0.0-20201023172447-248fdf805322/throw/stacktrace.go (about) 1 // Copyright 2020 Insolar Network Ltd. 2 // All rights reserved. 3 // This material is licensed under the Insolar License version 1.0, 4 // available at https://github.com/insolar/assured-ledger/blob/master/LICENSE.md. 5 6 package throw 7 8 import ( 9 "bytes" 10 "io" 11 "runtime" 12 "runtime/debug" 13 "strconv" 14 "strings" 15 ) 16 17 const StackTracePrefix = "Stack trace:" 18 const stackTracePrintPrefix = StackTracePrefix + "\n" 19 20 type StackTrace interface { 21 StackTraceAsText() string 22 WriteStackTraceTo(writer io.Writer) error 23 IsFullStack() bool 24 } 25 26 // CaptureStack captures whole stack 27 // When (skipFrames) are more than stack depth then only "created by" entry will be returned 28 func CaptureStack(skipFrames int) StackTrace { 29 return stackTrace{captureStack(skipFrames+1, false), false} 30 } 31 32 // CaptureStackTop is an optimized version to capture a limited info about top-level stack entry only 33 // When (skipFrames) are more than stack depth then only "created by" entry will be returned 34 func CaptureStackTop(skipFrames int) StackTrace { 35 return stackTrace{captureStack(skipFrames+1, true), true} 36 } 37 38 func ExtractStackTop(st StackTrace, skipFrames int) StackTrace { 39 var data []byte 40 switch vv := st.(type) { 41 case nil: 42 return nil 43 case stackTrace: 44 if vv.limit { 45 return st 46 } 47 data = vv.data 48 default: 49 if !vv.IsFullStack() { 50 return st 51 } 52 data = []byte(vv.StackTraceAsText()) 53 } 54 start := indexOfFrame(data, skipFrames) 55 if start < 0 || start == 0 && skipFrames > 0 { 56 return stackTrace{data, true} 57 } 58 start += skipPanicFrame(data) 59 end := indexOfFrame(data[start:], 1) 60 if end > 0 { 61 data = data[start : start+end] 62 } else { 63 data = data[start:] 64 } 65 return stackTrace{append([]byte(nil), data...), true} 66 } 67 68 func IsInSystemPanic(skipFrames int) bool { 69 pc := make([]uintptr, 1) 70 if runtime.Callers(skipFrames+2, pc) != 1 { 71 return false 72 } 73 n := runtime.FuncForPC(pc[0]).Name() 74 return n == "runtime.preprintpanics" 75 } 76 77 type stackTrace struct { 78 data []byte 79 limit bool 80 } 81 82 func (v stackTrace) IsFullStack() bool { 83 return !v.limit 84 } 85 86 func (v stackTrace) WriteStackTraceTo(w io.Writer) error { 87 _, err := w.Write(v.data) 88 return err 89 } 90 91 func (v stackTrace) StackTraceAsText() string { 92 return string(v.data) 93 } 94 95 func (v stackTrace) LogString() string { 96 return string(v.data) 97 } 98 99 func (v stackTrace) String() string { 100 return stackTracePrintPrefix + string(v.data) 101 } 102 103 func captureStack(skipFrames int, limitFrames bool) []byte { 104 skipFrames++ 105 if limitFrames { 106 // provides a bit less info, but is 10x times faster 107 result := captureStackByCallers(skipFrames, true) 108 if len(result) > 0 { 109 return result 110 } 111 // result will be empty at stack top that we can't capture by runtime.Callers() so we will fallback 112 } 113 return captureStackByDebug(skipFrames, limitFrames) 114 } 115 116 const topFrameLimit = 1 // MUST be 1, otherwise comparison of stack vs stack top may not work properly 117 118 func captureStackByDebug(skipFrames int, limitFrames bool) []byte { 119 stackBytes := debug.Stack() 120 capacity := cap(stackBytes) 121 if i := bytes.IndexByte(stackBytes, '\n'); i > 0 { 122 stackBytes = stackBytes[i+1:] 123 } else { 124 // strange result, let be safe 125 return stackBytes 126 } 127 128 const serviceFrames = 2 // debug.Stack() + captureStackByDebug() 129 if i := indexOfFrame(stackBytes, skipFrames+serviceFrames); i > 0 { 130 stackBytes = stackBytes[i:] 131 132 if limitFrames { 133 if i := indexOfFrame(stackBytes, topFrameLimit); i > 0 { 134 stackBytes = stackBytes[:i] 135 } 136 } 137 } 138 139 stackBytes = bytes.TrimSpace(stackBytes) 140 return trimCapacity(capacity, stackBytes) 141 } 142 143 func trimCapacity(actualCapacity int, b []byte) []byte { 144 n := len(b) << 1 145 if actualCapacity > n || cap(b) > n { 146 return append(make([]byte, 0, n), b...) 147 } 148 return b 149 } 150 151 func indexOfFrame(stackBytes []byte, skipFrames int) int { 152 offset := 0 153 for ; skipFrames > 0; skipFrames-- { 154 prevOffset := offset 155 if i := bytes.Index(stackBytes[offset:], frameFileSep); i > 0 { 156 offset += i + len(frameFileSep) 157 if j := bytes.IndexByte(stackBytes[offset:], '\n'); j > 0 { 158 offset += j + 1 159 if offset == len(stackBytes) { 160 return prevOffset 161 } 162 continue 163 } 164 return prevOffset 165 } 166 return -1 167 } 168 return offset 169 } 170 171 const frameFileSeparator = "\n\t" 172 173 var frameFileSep = []byte(frameFileSeparator) 174 175 func captureStackByCallers(skipFrames int, limitFrames bool) []byte { 176 var pcs []uintptr 177 if limitFrames { 178 pcs = make([]uintptr, topFrameLimit) 179 } else { 180 pcs = make([]uintptr, 50) // maxStackDepth 181 } 182 const serviceFrames = 2 // runtime.Callers() + captureStackByCallers() 183 pcs = pcs[:runtime.Callers(skipFrames+serviceFrames, pcs)] 184 185 result := make([]byte, 0, len(pcs)<<7) 186 for i, pc := range pcs { 187 fn := runtime.FuncForPC(pc) 188 if fn == nil { 189 continue 190 } 191 fName := fn.Name() 192 if i == len(pcs)-1 && fName == "runtime.goexit" { 193 break 194 } 195 196 // imitation of debug.Stack() format 197 198 if len(result) > 0 { 199 result = append(result, '\n') 200 } 201 202 result = append(result, fName...) 203 result = append(result, frameFileSeparator...) 204 205 fName, line := fn.FileLine(pc) 206 result = append(result, fName...) 207 result = append(result, ':') 208 result = strconv.AppendInt(result, int64(line), 10) 209 } 210 211 return trimCapacity(0, result) 212 } 213 214 func TrimStackTrace(s string) string { 215 if i := strings.Index(s, "\n"+stackTracePrintPrefix); i >= 0 { 216 return s[:i] 217 } 218 return s 219 }