Files
sing-box-extended-mirror/experimental/libbox/internal/runtimeinfo/goroutine_badlinkname.go
T

173 lines
3.7 KiB
Go

//go:build badlinkname
package runtimeinfo
import (
"bytes"
"cmp"
"reflect"
"runtime"
"slices"
"strconv"
"sync"
"sync/atomic"
"unsafe"
)
// Mirrors runtime.g of the pinned Go toolchain up to goid; the offsets of gopc and startpc are
// discovered by probeGoroutine.
type runtimeG struct {
stackLo uintptr
stackHi uintptr
_ [2]uintptr
_ [3]unsafe.Pointer
_ [6]uintptr
_ [4]uintptr
_ unsafe.Pointer
atomicstatus uint32
_ uint32
goid uint64
}
//go:linkname allgs runtime.allgs
var allgs []*runtimeG
const probeScanBytes = 512
var (
gopcOffset uintptr
startpcOffset uintptr
probeDone chan bool
)
func (gp *runtimeG) word(offset uintptr) uintptr {
return *(*uintptr)(unsafe.Add(unsafe.Pointer(gp), offset))
}
func (gp *runtimeG) gopc() uintptr {
return gp.word(gopcOffset)
}
func (gp *runtimeG) startpc() uintptr {
return gp.word(startpcOffset)
}
const (
statusDead = 6
statusScan = 0x1000
)
var statusNames = map[uint32]string{
0: "idle",
1: "runnable",
2: "running",
3: "syscall",
4: "waiting",
6: "dead",
8: "copystack",
9: "preempted",
}
var (
layoutOnce sync.Once
layoutVerified bool
)
func probeLayout() {
probeDone = make(chan bool, 1)
go probeGoroutine()
layoutVerified = <-probeDone
}
func probeGoroutine() {
id := currentGoroutineID()
var self *runtimeG
for _, gp := range allgs {
if gp.goid == id {
self = gp
break
}
}
if self == nil {
probeDone <- false
return
}
creatorName := functionName(reflect.ValueOf(probeLayout).Pointer())
selfName := functionName(reflect.ValueOf(probeGoroutine).Pointer())
goidOffset := unsafe.Offsetof(self.goid)
for offset := goidOffset + 8; offset < goidOffset+probeScanBytes; offset += 8 {
name := functionName(self.word(offset))
if gopcOffset == 0 && name == creatorName {
gopcOffset = offset
} else if startpcOffset == 0 && name == selfName {
startpcOffset = offset
}
}
probeDone <- gopcOffset != 0 && startpcOffset != 0
}
func functionName(pc uintptr) string {
function := runtime.FuncForPC(pc)
if function == nil {
return "unknown"
}
return function.Name()
}
func currentGoroutineID() uint64 {
var stackBuffer [64]byte
n := runtime.Stack(stackBuffer[:], false)
fields := bytes.Fields(stackBuffer[:n])
if len(fields) < 2 {
return 0
}
id, _ := strconv.ParseUint(string(fields[1]), 10, 64)
return id
}
func collectGoroutines() *GoroutineReport {
layoutOnce.Do(probeLayout)
if !layoutVerified {
return nil
}
report := &GoroutineReport{ByStatus: make(map[string]int)}
groups := make(map[string]*GoroutineGroup)
for _, gp := range allgs {
status := atomic.LoadUint32(&gp.atomicstatus) &^ statusScan
stackSize := uint64(gp.stackHi - gp.stackLo)
if status == statusDead {
report.Dead++
if gp.stackLo != 0 {
report.DeadStackBytes += stackSize
}
continue
}
report.Total++
report.StackBytes += stackSize
statusName, known := statusNames[status]
if !known {
statusName = strconv.Itoa(int(status))
}
report.ByStatus[statusName]++
name := functionName(gp.startpc())
group, found := groups[name]
if !found {
group = &GoroutineGroup{Function: name, CreatedBy: functionName(gp.gopc()), MinStackBytes: stackSize}
groups[name] = group
}
group.Count++
group.StackBytes += stackSize
group.MaxStackBytes = max(group.MaxStackBytes, stackSize)
group.MinStackBytes = min(group.MinStackBytes, stackSize)
}
report.ByFunction = make([]GoroutineGroup, 0, len(groups))
for _, group := range groups {
report.ByFunction = append(report.ByFunction, *group)
}
slices.SortFunc(report.ByFunction, func(a, b GoroutineGroup) int {
return cmp.Compare(b.StackBytes, a.StackBytes)
})
return report
}