英文:
Go prints different function name from program counters returned from runtime.Callers() if I use errors package
问题
code1:
package main
import (
"fmt"
"github.com/pkg/errors"
"runtime"
)
func main() {
ptrs, _ := Test1("arg1", "arg2")
for _, pc := range *ptrs {
f := runtime.FuncForPC(pc)
if f == nil {
continue
}
filename, line := f.FileLine(pc)
fmt.Printf("func name: %s, entry: %x, filename: %s, line: %d\n", f.Name(), f.Entry(), filename, line)
}
}
func Test0(args ...interface{}) *[]uintptr {
const depth = 32
var pcs [depth]uintptr
n := runtime.Callers(0, pcs[:])
st := pcs[0:n]
return &st
}
func Test1(args ...interface{}) (*[]uintptr, error) {
return Test0(args...), errors.New("Test")
}
code2:
package main
import (
"fmt"
"runtime"
)
func main() {
ptrs, _ := Test1("arg1", "arg2")
for _, pc := range *ptrs {
f := runtime.FuncForPC(pc)
if f == nil {
continue
}
filename, line := f.FileLine(pc)
fmt.Printf("func name: %s, entry: %x, filename: %s, line: %d\n", f.Name(), f.Entry(), filename, line)
}
}
func Test0(args ...interface{}) *[]uintptr {
const depth = 32
var pcs [depth]uintptr
n := runtime.Callers(0, pcs[:])
st := pcs[0:n]
return &st
}
func Test1(args ...interface{}) (*[]uintptr, error) {
return Test0(args...), nil//, errors.New("Test")
}
唯一不同的部分是Test1()
的错误返回值。在code1中,它返回errors.New("Test")
,而在code2中,它返回nil
。在main
函数的FOR循环中,程序打印函数名,结果是不同的:
code1的结果:
func name: runtime.Callers, entry: 6a20c0, filename: C:/Program Files/Go/src/runtime/extern.go, line: 247
func name: runtime.Callers, entry: 6a20c0, filename: C:/Program Files/Go/src/runtime/extern.go, line: 247
func name: main.Test1, entry: 6a2180, filename: D:/develop/go-testbed/cmd/slicetest/slicetest.go, line: 30
func name: main.main, entry: 6a1ea0, filename: D:/develop/go-testbed/cmd/slicetest/slicetest.go, line: 11
func name: runtime.main, entry: 6476a0, filename: C:/Program Files/Go/src/runtime/proc.go, line: 259
func name: runtime.goexit, entry: 66eee0, filename: C:/Program Files/Go/src/runtime/asm_amd64.s, line: 1595
code2的结果:
func name: runtime.Callers, entry: b30020, filename: C:/Program Files/Go/src/runtime/extern.go, line: 247
func name: runtime.Callers, entry: b30020, filename: C:/Program Files/Go/src/runtime/extern.go, line: 247
func name: main.main, entry: b2fe00, filename: D:/develop/go-testbed/cmd/slicetest/slicetest.go, line: 10
func name: main.main, entry: b2fe00, filename: D:/develop/go-testbed/cmd/slicetest/slicetest.go, line: 9
func name: runtime.main, entry: ad6fc0, filename: C:/Program Files/Go/src/runtime/proc.go, line: 259
func name: runtime.goexit, entry: afe580, filename: C:/Program Files/Go/src/runtime/asm_amd64.s, line: 1595
如你所见,code1打印了正确的函数名main.Test1
,但code2没有。我阅读了运行时调用栈根据运行位置打印不同的程序计数器,它建议使用runtime.CallersFrames()
,但我认为这不是问题,因为code1和code2都没有使用它。我还看到了errors.New()
函数,但我没有找到什么特别之处。它通过caller()
函数创建调用栈,这与我的代码中的Test0()
几乎相同--实际上,我从caller()
复制了Test0()
的内容。
有人能告诉我是什么原因导致了这种差异吗?
Go版本:go1.19 windows/amd64
操作系统:Windows 11
github.com/pkg/errors
的版本:v0.9.1
如果我在Goland IDE的调试模式下运行它们,这两个代码都会打印出相同的结果。
谢谢!
英文:
code1:
package main
import (
"fmt"
"github.com/pkg/errors"
"runtime"
)
func main() {
ptrs, _ := Test1("arg1", "arg2")
for _, pc := range *ptrs {
f := runtime.FuncForPC(pc)
if f == nil {
continue
}
filename, line := f.FileLine(pc)
fmt.Printf("func name: %s, entry: %x, filename: %s, line: %d\n", f.Name(), f.Entry(), filename, line)
}
}
func Test0(args ...interface{}) *[]uintptr {
const depth = 32
var pcs [depth]uintptr
n := runtime.Callers(0, pcs[:])
st := pcs[0:n]
return &st
}
func Test1(args ...interface{}) (*[]uintptr, error) {
return Test0(args...), errors.New("Test")
}
code2:
package main
import (
"fmt"
"runtime"
)
func main() {
ptrs, _ := Test1("arg1", "arg2")
for _, pc := range *ptrs {
f := runtime.FuncForPC(pc)
if f == nil {
continue
}
filename, line := f.FileLine(pc)
fmt.Printf("func name: %s, entry: %x, filename: %s, line: %d\n", f.Name(), f.Entry(), filename, line)
}
}
func Test0(args ...interface{}) *[]uintptr {
const depth = 32
var pcs [depth]uintptr
n := runtime.Callers(0, pcs[:])
st := pcs[0:n]
return &st
}
func Test1(args ...interface{}) (*[]uintptr, error) {
return Test0(args...), nil//, errors.New("Test")
}
The only different part is returning error value of Test1()
. In the code1 it returns erors.New("Test")
, in the code2 it returns nil
. In the FOR loop of main
function, the program prints function name, the results are different:
result of code1:
func name: runtime.Callers, entry: 6a20c0, filename: C:/Program Files/Go/src/runtime/extern.go, line: 247
func name: runtime.Callers, entry: 6a20c0, filename: C:/Program Files/Go/src/runtime/extern.go, line: 247
func name: main.Test1, entry: 6a2180, filename: D:/develop/go-testbed/cmd/slicetest/slicetest.go, line: 30
func name: main.main, entry: 6a1ea0, filename: D:/develop/go-testbed/cmd/slicetest/slicetest.go, line: 11
func name: runtime.main, entry: 6476a0, filename: C:/Program Files/Go/src/runtime/proc.go, line: 259
func name: runtime.goexit, entry: 66eee0, filename: C:/Program Files/Go/src/runtime/asm_amd64.s, line: 1595
result of code2
func name: runtime.Callers, entry: b30020, filename: C:/Program Files/Go/src/runtime/extern.go, line: 247
func name: runtime.Callers, entry: b30020, filename: C:/Program Files/Go/src/runtime/extern.go, line: 247
func name: main.main, entry: b2fe00, filename: D:/develop/go-testbed/cmd/slicetest/slicetest.go, line: 10
func name: main.main, entry: b2fe00, filename: D:/develop/go-testbed/cmd/slicetest/slicetest.go, line: 9
func name: runtime.main, entry: ad6fc0, filename: C:/Program Files/Go/src/runtime/proc.go, line: 259
func name: runtime.goexit, entry: afe580, filename: C:/Program Files/Go/src/runtime/asm_amd64.s, line: 1595
As you see, code1 printed the correct function name, main.Test1
but code2 didn't.
I read runtime.Callers print different program counters depending on where its run from which recommands to use runtime.CallersFrames()
but I thinks it's not the problem because both code1 and code2 do not use it. I also see the errors.New()
function but I didn't find something special. It creates callstack by caller()
function which is almost same as Test0()
in my code -- actually, I copied the contents of Test0()
from caller()
.
Can someone tell me which causes the difference?
Go version: go1.19 windows/amd64
OS: windonws 11
version of github.com/pkg/errors
: v0.9.1
Both of codes print the same result with main.Test1
if I run them in debug mode of Goland IDE.
Thank you!
答案1
得分: 2
根据runtime.Callers的文档:
要将这些PC(程序计数器)转换为符号信息,如函数名和行号,请使用CallersFrames。CallersFrames会考虑内联函数并将返回的程序计数器调整为调用程序计数器。直接迭代返回的PC切片是不鼓励的,同样不鼓励在任何返回的PC上使用FuncForPC,因为它们无法考虑内联或返回程序计数器的调整。
(强调是我的)你不能自己尝试解释runtime.Callers的结果,因为你无法正确地做到这一点。你错误的尝试清楚地证明了文档是正确的。
这与github.com/pkg/errors(已停止维护,并且已被包错误中的新内容取代)完全无关。这是编译器优化代码(在调试时不同)的结果。
经验法则:阅读你使用的函数的文档,并按照文档的指示操作。这是“有时候在实验室里花3个月可以节省你在图书馆里6个小时”的一个例子。
英文:
From the documentation of runtime.Callers
> To translate these PCs into symbolic information such as function names and line numbers, use CallersFrames. CallersFrames accounts for inlined functions and adjusts the return program counters into call program counters. Iterating over the returned slice of PCs directly is discouraged, as is using FuncForPC on any of the returned PCs, since these cannot account for inlining or return program counter adjustment.
(emph mine)
You must not try to interpret the result of runtime.Callers yourself as you cannot do it properly. Your flawed attempt clearly demonsrates that the documentation is right.
This has literally nothing to do with github.com/pkg/errors (which is unmaintained and superseeded by new stuff in package errors anyway). It is a result of the compiler optimizing your code (differently while debugging).
Rule of thumb: Read the documentation of the functions you use and do what the documentation tells you. This is an instance of "Sometimes 3 month in the lab can spare you 6 hours in the library".
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论