搞了大半晚上,终于搞清楚了go的函数调用栈以及打印函数调用栈的相关函数,先简单记录一下结论
go的函数调用栈相关函数
func Callers(skip int, pc []uintptr) int
- 该函数会将你这条goroutine函数调用链上的程序计数器pc返回到切片pc中(就是函数第二个参数)
- 如果有协程,则这条链是从go关键字后面的函数开始到调用runtime.Callers所在的函数为止的
- skip参数表示想要跳过的堆栈帧数,0表示Callers函数本身的栈帧,1表示调用Callers函数的栈帧
- Callers返回值是这条函数调用链上的函数栈帧数
- 搭配
runtime.CallersFrames
函数使用(见下面例子),返回一个runtime.Frame类型的结构体迭代器,遍历迭代器frame,可以得到当前函数的调用者的函数名(frame.Function)、调用者调用当前函数的代码所处行号(frame.Line)、调用者函数起始地址(frame.Entry)、调用者函数所在文件(frame.File) - 注意上一条!!!是打印当前函数的调用者的!!!!!!!!!!
package main
import (
"fmt"
"runtime"
"time"
)
func A() {
B()
}
func B() {
pc := make([]uintptr, 100)
n := runtime.Callers(0, pc)
frames := runtime.CallersFrames(pc[:n])
for i := 0; true; i++ {
frame, more := frames.Next()
fmt.Printf("file: %s, line: %d, function: %s, Address: %v\n",
frame.File, frame.Line, frame.Function, frame.Entry)
if !more {
break
}
}
}
func main() {
fmt.Printf("Callers address: %d\n", runtime.Callers)
fmt.Printf("A address: %d\n", A)
fmt.Printf("B address: %d\n", B)
go A()
time.Sleep(1 * time.Second) // 这行要有
}
// 输出:
// Callers address: 1871424
// A address: 2424160
// B address: 2424224
// file: D:/Go/src/runtime/extern.go, line: 247, function: runtime.Callers, Address: 2424224
// file: D:/Go_WorkSpace/test.go, line: 15, function: main.B, Address: 2424224
// file: D:/Go_WorkSpace/test.go, line: 10, function: main.A, Address: 2424160
// file: D:/Go/src/runtime/asm_amd64.s, line: 1594, function: runtime.goexit, Address: 2221344
对比一下就可以发现,file: D:/Go_WorkSpace/test.go, line: 15, function: main.B, Address: 6487328 这个输出打印的是调用Callers函数的调用者的信息,同理file: D:/Go_WorkSpace/test.go, line: 10, function: main.A, Address: 6487264 打印的是调用函数B的调用者的信息
函数调用栈好文及资料
其他关于函数调用栈和栈帧的详细内容之后有空再记录吧,先记录一下相关资料