【问题标题】:runtime.Callers print different program counters depending on where its run fromruntime.Callers 根据运行的位置打印不同的程序计数器
【发布时间】:2020-09-30 10:42:47
【问题描述】:

我有下面这段代码,它根据运行的位置打印不同的程序计数器值。

代码:

package main

import (
    "fmt"
    "runtime"
)

func foo() {
    bar()
}

func bar() {
    pcs := make([]uintptr, 10)
    _ = runtime.Callers(0, pcs)
    for _, pc := range pcs {
        fmt.Printf("Value of pc %+v\n", runtime.FuncForPC(pc).Name())
    }
}

func main() {
    foo()
}
  1. 使用go run 或编译后的二进制文件运行时,会打印(main.bar 缺失)
Value of pc runtime.Callers
Value of pc runtime.Callers
Value of pc main.main
Value of pc main.foo
Value of pc runtime.main
Value of pc runtime.goexit
  1. 从 Visual Studio Code 运行代码时(仅在 debug 模式下运行正常)
Value of pc runtime.Callers
Value of pc main.bar
Value of pc main.foo
Value of pc main.main
Value of pc runtime.main
Value of pc runtime.goexit
  1. Playground 中运行时,(foobar,两者都缺失)
Value of pc runtime.Callers
Value of pc runtime.Callers
Value of pc main.main
Value of pc main.main
Value of pc runtime.main
Value of pc runtime.goexit

我正在使用一个框架 (logrus),它依赖于 PC 的顺序来执行一些操作(记录文件名)。
由于 PC 值会根据其运行位置不断变化,因此它可以在调试模式下工作,但在使用 go run 或编译后的二进制文件运行时会失败。

知道是什么导致 PC 加载不同吗?是否有任何正在启动的配置或优化?

【问题讨论】:

  • 这可能是由于函数内联,请参阅 stackoverflow.com/questions/63785981/… 了解这如何影响堆栈跟踪以及如何确认。
  • @Marc 我添加了go:noinline,但它仍然没有显示bar 方法。任何其他可能对这里有所帮助的配置?
  • 无意冒犯,但您可能添加错误或没有重新编译您的二进制文件。您也不应该仅仅为了解决日志库中的错误而这样做。
  • @Marc 没问题,我也怀疑我的实现:) 有和没有go:noinline 是有区别的。唯一的区别是,现在GoPlayground 和使用go run 的输出是一样的。但是bar 没有出现的问题仍然存在。奇怪的是,这在几周前还有效,我们刚刚开始注意到filenames 没有被logrus 记录,并将问题深入到此PC 行为play.golang.org/p/h9fOdZbvHTe

标签: go runtime logrus


【解决方案1】:

runtime.Callers() 的文档说明:

要将这些 PC 转换为符号信息,例如函数名称和行号,请​​使用 CallersFrames。 CallersFrames 考虑内联函数并将返回程序计数器调整为调用程序计数器。不鼓励直接迭代返回的 PC 切片,就像在任何返回的 PC 上使用 FuncForPC 一样,因为这些不能考虑内联或返回程序计数器调整。

Doc 建议使用runtime.CallersFrames() 从知道并解释函数内联的原始计数器获取函数信息,例如:

pcs := make([]uintptr, 10)
n := runtime.Callers(0, pcs)
pcs = pcs[:n]

frames := runtime.CallersFrames(pcs)
for {
    frame, more := frames.Next()
    if !more {
        break
    }
    fmt.Println("Function:", frame.Function)
}

无论您如何调用/运行它都应该输出(在Go Playground 上尝试):

Function: runtime.Callers
Function: main.bar
Function: main.foo
Function: main.main
Function: runtime.main

【讨论】:

  • 不幸的是,logrus 框架内部使用runtime.Callers API 来解决这个问题。那么使用此 API 调用使其工作的任何解决方法?调试模式如何实现这一点,可能会跳过一些优化步骤?
  • @BandiKishore 可能在调试模式下运行时,函数内联被禁用,因此您可以检查/跟踪每个函数调用。调试模式不是关于性能,而是关于可追溯性。
  • 如果logrus 使用runtime.Callers() 而不使用runtime.CallersFrames(),它真的需要更新。
  • 嗯,所以我想我唯一的选择就是向logrus repo 提出一个错误。对于常规调用堆栈,它使用runtime.CallersFrames(),但在单例初始化期间使用此API 找出logrus packagegithub.com/sirupsen/logrus/blob/master/entry.go#L177
  • @BandiKishore 是的,你应该在那里创建一个问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-01-20
  • 1970-01-01
  • 1970-01-01
  • 2013-10-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多