goruntimelogrus

runtime.Callers print different program counters depending on where its run from


I have this below code that prints different program counter values depending on where its run from.

Code:

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. When running using go run or the compiled binary, it prints (main.bar is missing)
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. When running the code from Visual Studio Code (Only in debug mode, it works fine)
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. When running in Playground, (foo, bar, both are missing)
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

I'm using a framework (logrus) which relies on the PCs order to perform some operation (logging the filename).
Since the PC values keeps changing depending on where its run from, it works in Debug Mode but fails when running using go run or the compiled binary.

Any idea what could be causing the PCs to load differently? Any config or optimization that's kicking in?


Solution

  • Documentation of runtime.Callers() states:

    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.

    Doc suggests to use runtime.CallersFrames() to obtain function information from the raw counters which knows about and accounts for function inlining, for example:

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

    This should output regardless of how you call / run it (try it on the Go Playground):

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