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