| // Copyright 2023 Google Inc. All Rights Reserved. | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // http://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| // Package stackdump provides wrappers for runtime.Stack and runtime.Callers | |
| // with uniform support for skipping caller frames. | |
| // | |
| // ⚠ Unlike the functions in the runtime package, these may allocate a | |
| // non-trivial quantity of memory: use them with care. ⚠ | |
| package stackdump | |
| import ( | |
| "bytes" | |
| "runtime" | |
| ) | |
| // runtimeStackSelfFrames is 1 if runtime.Stack includes the call to | |
| // runtime.Stack itself or 0 if it does not. | |
| // | |
| // As of 2016-04-27, the gccgo compiler includes runtime.Stack but the gc | |
| // compiler does not. | |
| var runtimeStackSelfFrames = func() int { | |
| for n := 1 << 10; n < 1<<20; n *= 2 { | |
| buf := make([]byte, n) | |
| n := runtime.Stack(buf, false) | |
| if bytes.Contains(buf[:n], []byte("runtime.Stack")) { | |
| return 1 | |
| } else if n < len(buf) || bytes.Count(buf, []byte("\n")) >= 3 { | |
| return 0 | |
| } | |
| } | |
| return 0 | |
| }() | |
| // Stack is a stack dump for a single goroutine. | |
| type Stack struct { | |
| // Text is a representation of the stack dump in a human-readable format. | |
| Text []byte | |
| // PC is a representation of the stack dump using raw program counter values. | |
| PC []uintptr | |
| } | |
| func (s Stack) String() string { return string(s.Text) } | |
| // Caller returns the Stack dump for the calling goroutine, starting skipDepth | |
| // frames before the caller of Caller. (Caller(0) provides a dump starting at | |
| // the caller of this function.) | |
| func Caller(skipDepth int) Stack { | |
| return Stack{ | |
| Text: CallerText(skipDepth + 1), | |
| PC: CallerPC(skipDepth + 1), | |
| } | |
| } | |
| // CallerText returns a textual dump of the stack starting skipDepth frames before | |
| // the caller. (CallerText(0) provides a dump starting at the caller of this | |
| // function.) | |
| func CallerText(skipDepth int) []byte { | |
| for n := 1 << 10; ; n *= 2 { | |
| buf := make([]byte, n) | |
| n := runtime.Stack(buf, false) | |
| if n < len(buf) { | |
| return pruneFrames(skipDepth+1+runtimeStackSelfFrames, buf[:n]) | |
| } | |
| } | |
| } | |
| // CallerPC returns a dump of the program counters of the stack starting | |
| // skipDepth frames before the caller. (CallerPC(0) provides a dump starting at | |
| // the caller of this function.) | |
| func CallerPC(skipDepth int) []uintptr { | |
| for n := 1 << 8; ; n *= 2 { | |
| buf := make([]uintptr, n) | |
| n := runtime.Callers(skipDepth+2, buf) | |
| if n < len(buf) { | |
| return buf[:n] | |
| } | |
| } | |
| } | |
| // pruneFrames removes the topmost skipDepth frames of the first goroutine in a | |
| // textual stack dump. It overwrites the passed-in slice. | |
| // | |
| // If there are fewer than skipDepth frames in the first goroutine's stack, | |
| // pruneFrames prunes it to an empty stack and leaves the remaining contents | |
| // intact. | |
| func pruneFrames(skipDepth int, stack []byte) []byte { | |
| headerLen := 0 | |
| for i, c := range stack { | |
| if c == '\n' { | |
| headerLen = i + 1 | |
| break | |
| } | |
| } | |
| if headerLen == 0 { | |
| return stack // No header line - not a well-formed stack trace. | |
| } | |
| skipLen := headerLen | |
| skipNewlines := skipDepth * 2 | |
| for ; skipLen < len(stack) && skipNewlines > 0; skipLen++ { | |
| c := stack[skipLen] | |
| if c != '\n' { | |
| continue | |
| } | |
| skipNewlines-- | |
| skipLen++ | |
| if skipNewlines == 0 || skipLen == len(stack) || stack[skipLen] == '\n' { | |
| break | |
| } | |
| } | |
| pruned := stack[skipLen-headerLen:] | |
| copy(pruned, stack[:headerLen]) | |
| return pruned | |
| } |