我有一些代码大多是从https://github.com/go-errors/errors复制而来的,该代码会生成堆栈跟踪
当我在VSCode调试器中运行它时,输出完全符合预期。文件名和函数名正确。
调试输出
C:/work/projects/go/src/errtest/customerrors.go:17 (0x4d149f)
NewDbError: dbe.stacktrace = NewStackTrace()
C:/work/projects/go/src/errtest/errortest04a.go:6 (0x4d156f)
errorTest04a: err := NewDbError("This is a db error")
C:/work/projects/go/src/errtest/errortest04.go:4 (0x4d1507)
errorTest04: errorTest04a()
C:/work/projects/go/src/errtest/main.go:4 (0x4d16b7)
main: errorTest04()
C:/Go/src/runtime/proc.go:200 (0x431bc1)
main: fn()
C:/Go/src/runtime/asm_amd64.s:1337 (0x458bc1)
goexit: BYTE $0x90 // NOP
当我构建并运行相同的代码时,输出不正确。
构建输出
C:/work/projects/go/src/errtest/customerrors.go:17 (0x4a5dbe)
errorTest04a: dbe.stacktrace = NewStackTrace()
C:/work/projects/go/src/errtest/customerrors.go:17 (0x4a5db9)
NewDbError: dbe.stacktrace = NewStackTrace()
C:/work/projects/go/src/errtest/errortest04.go:4 (0x4a5e88)
main: errorTest04a()
C:/work/projects/go/src/errtest/errortest04.go:4 (0x4a5e83)
errorTest04: errorTest04a()
C:/Go/src/runtime/proc.go:200 (0x42d5ac)
main: fn()
C:/Go/src/runtime/asm_amd64.s:1337 (0x452911)
goexit: BYTE $0x90 // NOP
问题:
为什么代码会根据是在Debug中运行还是在build中生成不同的输出?
“调试输出”中的顶部条目正确显示了最后一个调用方为函数NewDbError
和文件dbe.stacktrace = NewStackTrace()
中的行customerrors.go:17
Build Output的顶部条目显示相同的详细信息,但函数名称errorTest04a
不在文件``customerrors.go but in the file
errorTest04a.go`
Build输出的第二行显示Debug输出的第一行
构建输出中的第三行显示了错误的函数名称main
相同的代码生成不同的输出似乎很奇怪。
结束问题
我在想,也许编译会删除文件名,但是它们在那里,只是顺序不正确,而且函数名也乱七八糟。
如果任何人都可以阐明一点,将不胜感激。谢谢
这是我使用的代码
main.go
package main
func main() {
errorTest04()
}
errortest04.go
package main
func errorTest04() {
errorTest04a()
}
errortest04a.go 包主
import "fmt"
func errorTest04a() {
err := NewDbError("This is a db error")
fmt.Print(string(err.stacktrace.Stack()))
}
customerorrs.go
package main
// DbError is a custom error that holds a dberror
type DbError struct {
stacktrace *StackTrace
errmsg string
}
func (e *DbError) Error() string {
return e.errmsg
}
// NewDbError returns a new DbError
func NewDbError(e string) DbError {
dbe := DbError{errmsg: e}
dbe.stacktrace = NewStackTrace()
return dbe
}
stacktrace.go -从https://github.com/go-errors/errors
提起package main
import (
"bytes"
"fmt"
"io/ioutil"
"runtime"
"strings"
)
const maxStackDepth = 50
// StackTrace holds the stack data
type StackTrace struct {
callers []uintptr
callerslen int
frames []StackFrame
}
// Stack - returns a formatted Stack Trace
func (st *StackTrace) Stack() []byte {
buf := bytes.Buffer{}
for _, frame := range st.frames {
buf.WriteString(frame.String())
}
return buf.Bytes()
}
// NewStackTrace creates a new stack object
func NewStackTrace() *StackTrace {
st := StackTrace{}
st.callers = make([]uintptr, maxStackDepth)
st.callerslen = runtime.Callers(2, st.callers[:])
st.frames = make([]StackFrame, st.callerslen)
for i := range st.frames {
st.frames[i] = newStackFrame(st.callers[i])
}
return &st
}
// StackFrame - details of a caller. Lifted from https://github.com/go-errors/errors/blob/master/stackframe.go
type StackFrame struct {
// The path to the file containing this ProgramCounter
File string
// The LineNumber in that file
LineNumber int
// The Name of the function that contains this ProgramCounter
FnName string
// The Package that contains this function
Package string
// The underlying ProgramCounter
ProgramCounter uintptr
}
func newStackFrame(pc uintptr) StackFrame {
frame := StackFrame{ProgramCounter: pc}
if frame.Func() == nil {
return frame
}
frame.Package, frame.FnName = packageAndName(frame.Func())
// pc -1 because the program counters we use are usually return addresses,
// and we want to show the line that corresponds to the function call
// frame.File, frame.LineNumber = frame.Func().FileLine(pc)
frame.File, frame.LineNumber = frame.Func().FileLine(pc - 1)
return frame
}
// Func returns the function that contained this frame.
func (frame *StackFrame) Func() *runtime.Func {
if frame.ProgramCounter == 0 {
return nil
}
return runtime.FuncForPC(frame.ProgramCounter)
}
// SourceLine gets the line of code (from File and Line) of the original source if possible.
func (frame *StackFrame) SourceLine() (string, error) {
data, err := ioutil.ReadFile(frame.File)
// _ replace with err
if err != nil {
return "", err
}
lines := bytes.Split(data, []byte{'\n'})
if frame.LineNumber <= 0 || frame.LineNumber >= len(lines) {
return "???", nil
}
// -1 because line-numbers are 1 based, but our array is 0 based
return string(bytes.Trim(lines[frame.LineNumber-1], " \t")), nil
}
// String returns the stackframe formatted in the same way as go does
// in runtime/debug.Stack()
func (frame *StackFrame) String() string {
str := fmt.Sprintf("%s:%d (0x%x)\n", frame.File, frame.LineNumber, frame.ProgramCounter)
source, err := frame.SourceLine()
if err != nil {
return str
}
return str + fmt.Sprintf("\t%s: %s\n", frame.FnName, source)
}
func packageAndName(fn *runtime.Func) (string, string) {
name := fn.Name()
pkg := ""
// The name includes the path name to the package, which is unnecessary
// since the file name is already included. Plus, it has center dots.
// That is, we see
// runtime/debug.*T·ptrmethod
// and want
// *T.ptrmethod
// Since the package path might contains dots (e.g. code.google.com/...),
// we first remove the path prefix if there is one.
if lastslash := strings.LastIndex(name, "/"); lastslash >= 0 {
pkg += name[:lastslash] + "/"
name = name[lastslash+1:]
}
if period := strings.Index(name, "."); period >= 0 {
pkg += name[:period]
name = name[period+1:]
}
name = strings.Replace(name, "·", ".", -1)
return pkg, name
}