| // Copyright 2017 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package gocore |
| |
| import ( |
| "debug/dwarf" |
| "encoding/binary" |
| "errors" |
| "fmt" |
| "iter" |
| "math/bits" |
| "sort" |
| "strings" |
| "sync" |
| |
| "golang.org/x/debug/internal/core" |
| |
| "golang.org/x/debug/third_party/delve/dwarf/op" |
| "golang.org/x/debug/third_party/delve/dwarf/regnum" |
| ) |
| |
| // A Process represents the state of a Go process that core dumped. |
| type Process struct { |
| proc *core.Process |
| buildVersion string |
| |
| // Index of heap objects and pointers. |
| heap *heapTable |
| |
| // number of live objects |
| nObj int |
| |
| goroutines []*Goroutine |
| |
| // Runtime info for easier lookup. |
| rtGlobals map[string]region |
| rtConsts constsMap |
| |
| // A module is a loadable unit. Most Go programs have 1, programs |
| // which load plugins will have more. |
| modules []*module |
| |
| // Maps core.Address to functions. |
| funcTab *funcTab |
| |
| // DWARF variables in each function. |
| dwarfVars map[*Func][]dwarfVar |
| |
| // Fundamental type mappings extracted from the core. |
| dwarfTypeMap map[dwarf.Type]*Type |
| rtTypeByName map[string]*Type // Core runtime types only, from DWARF. |
| rtTypeMap map[core.Address]*Type |
| |
| // Memory usage breakdown. |
| stats *Statistic |
| |
| // Global roots. |
| globals []*Root |
| |
| // Types of each object, indexed by object index. |
| initTypeHeap sync.Once |
| types []typeInfo |
| |
| // Reverse edges. |
| // The reverse edges for object #i are redge[ridx[i]:ridx[i+1]]. |
| // A "reverse edge" for object #i is a location in memory where a pointer |
| // to object #i lives. |
| initReverseEdges sync.Once |
| redge []reverseEdge |
| ridx []int64 |
| // Sorted list of all roots, sorted by id. |
| rootIdx []*Root |
| nRoots int |
| } |
| |
| type reverseEdge struct { |
| addr core.Address // 0 if root != nil and vice versa. |
| root *Root // Roots do not always have well-defined addresses. |
| rOff int64 // Offset of pointer in root. |
| } |
| |
| // Core takes a loaded core file and extracts Go information from it. |
| func Core(proc *core.Process) (p *Process, err error) { |
| p = &Process{proc: proc} |
| |
| // Initialize everything that just depends on DWARF. |
| p.dwarfTypeMap, p.rtTypeMap, err = readDWARFTypes(proc) |
| if err != nil { |
| return nil, err |
| } |
| p.rtTypeByName = make(map[string]*Type) |
| for dt, t := range p.dwarfTypeMap { |
| // Make an index of some low-level types we'll need unambiguous access to. |
| if name := gocoreName(dt); strings.HasPrefix(name, "runtime.") || strings.HasPrefix(name, "internal/abi.") || strings.HasPrefix(name, "unsafe.") || !strings.Contains(name, ".") { |
| // Sometimes there's duplicate types in the DWARF. That's fine, they're always the same. |
| p.rtTypeByName[name] = t |
| } |
| } |
| p.rtConsts, err = readConstants(proc) |
| if err != nil { |
| return nil, err |
| } |
| p.globals, err = readGlobals(proc, &p.nRoots, p.dwarfTypeMap) |
| if err != nil { |
| return nil, err |
| } |
| |
| // Find runtime globals we care about. Initialize regions for them. |
| p.rtGlobals = make(map[string]region) |
| for _, g := range p.globals { |
| if strings.HasPrefix(g.Name, "runtime.") { |
| if g.pieces[0].kind != addrPiece || len(g.pieces) > 1 { |
| panic("global is unexpectedly stored in pieces") |
| } |
| p.rtGlobals[g.Name[8:]] = region{p: proc, a: core.Address(g.pieces[0].value), typ: g.Type} |
| } |
| } |
| |
| // Read the build version. |
| // |
| // TODO(mknyszek): Check it and reject unsupported versions. Do this once we |
| // feel confident which versions it actually works for. |
| p.buildVersion = p.rtGlobals["buildVersion"].String() |
| |
| // Read modules and function data. |
| p.modules, p.funcTab, err = readModules(p.rtTypeByName, p.rtConsts, p.rtGlobals) |
| if err != nil { |
| return nil, err |
| } |
| |
| // Initialize the heap data structures. |
| p.heap, p.stats, err = readHeap(p) |
| if err != nil { |
| return nil, err |
| } |
| |
| // Read stack and register variables from DWARF. |
| p.dwarfVars, err = readDWARFVars(proc, p.funcTab, p.dwarfTypeMap) |
| if err != nil { |
| return nil, err |
| } |
| |
| // Read goroutines. |
| p.goroutines, err = readGoroutines(p, p.dwarfVars) |
| if err != nil { |
| return nil, err |
| } |
| |
| p.markObjects() // needs to be after readGlobals, readGs. |
| return p, nil |
| } |
| |
| // Process returns the core.Process used to construct this Process. |
| func (p *Process) Process() *core.Process { |
| return p.proc |
| } |
| |
| func (p *Process) Goroutines() []*Goroutine { |
| return p.goroutines |
| } |
| |
| // Stats returns a breakdown of the program's memory use by category. |
| func (p *Process) Stats() *Statistic { |
| return p.stats |
| } |
| |
| // BuildVersion returns the Go version that was used to build the inferior binary. |
| func (p *Process) BuildVersion() string { |
| return p.buildVersion |
| } |
| |
| func (p *Process) Globals() []*Root { |
| return p.globals |
| } |
| |
| // FindFunc returns the function which contains the code at address pc, if any. |
| func (p *Process) FindFunc(pc core.Address) *Func { |
| return p.funcTab.find(pc) |
| } |
| |
| // arena is a summary of the size of components of a heapArena. |
| type arena struct { |
| heapMin core.Address |
| heapMax core.Address |
| spanTableMin core.Address |
| spanTableMax core.Address |
| } |
| |
| func readHeap(p *Process) (*heapTable, *Statistic, error) { |
| mheap := p.rtGlobals["mheap_"] |
| |
| var arenas []arena |
| arenaSize := p.rtConsts.get("runtime.heapArenaBytes") |
| if arenaSize%heapInfoSize != 0 { |
| panic("arenaSize not a multiple of heapInfoSize") |
| } |
| arenaBaseOffset := -p.rtConsts.get("runtime.arenaBaseOffsetUintptr") |
| if p.proc.PtrSize() == 4 && arenaBaseOffset != 0 { |
| panic("arenaBaseOffset must be 0 for 32-bit inferior") |
| } |
| |
| level1Table := mheap.Field("arenas") |
| level1size := level1Table.ArrayLen() |
| for level1 := int64(0); level1 < level1size; level1++ { |
| ptr := level1Table.ArrayIndex(level1) |
| if ptr.Address() == 0 { |
| continue |
| } |
| level2table := ptr.Deref() |
| level2size := level2table.ArrayLen() |
| for level2 := int64(0); level2 < level2size; level2++ { |
| ptr = level2table.ArrayIndex(level2) |
| if ptr.Address() == 0 { |
| continue |
| } |
| a := ptr.Deref() |
| |
| min := core.Address(arenaSize*(level2+level1*level2size) - arenaBaseOffset) |
| max := min.Add(arenaSize) |
| |
| arenas = append(arenas, readArena(a, min, max)) |
| } |
| } |
| return readHeap0(p, mheap, arenas, arenaBaseOffset) |
| } |
| |
| // Read a single heapArena. Go 1.11+, which has multiple areans. Record heap |
| // pointers and return the arena size summary. |
| func readArena(a region, min, max core.Address) arena { |
| ptrSize := a.p.PtrSize() |
| spans := a.Field("spans") |
| arena := arena{ |
| heapMin: min, |
| heapMax: max, |
| spanTableMin: spans.a, |
| spanTableMax: spans.a.Add(spans.ArrayLen() * ptrSize), |
| } |
| return arena |
| } |
| |
| func readHeap0(p *Process, mheap region, arenas []arena, arenaBaseOffset int64) (*heapTable, *Statistic, error) { |
| // TODO(mknyszek): Break up this function into heapTable setup and statistics collection, |
| // at the very least... |
| |
| // The main goal of this function is to initialize this data structure. |
| heap := &heapTable{ |
| table: make(map[heapTableID]*heapTableEntry), |
| ptrSize: uint64(p.proc.PtrSize()), |
| } |
| |
| // ... But while we're here, we'll be collecting stats. |
| var stats struct { |
| all int64 |
| text int64 |
| readOnly int64 |
| spanTable int64 |
| data int64 |
| bss int64 |
| freeSpanSize int64 |
| releasedSpanSize int64 |
| manualSpanSize int64 |
| inUseSpanSize int64 |
| allocSize int64 |
| freeSize int64 |
| spanRoundSize int64 |
| manualAllocSize int64 |
| manualFreeSize int64 |
| } |
| for _, m := range p.proc.Mappings() { |
| size := m.Size() |
| stats.all += size |
| switch m.Perm() { |
| case core.Read: |
| stats.readOnly += size |
| case core.Read | core.Exec: |
| stats.text += size |
| case core.Read | core.Write: |
| if m.CopyOnWrite() { |
| // Check if m.file == text's file? That could distinguish |
| // data segment from mmapped file. |
| stats.data += size |
| break |
| } |
| attribute := func(x, y core.Address, p *int64) { |
| a := x.Max(m.Min()) |
| b := y.Min(m.Max()) |
| if a < b { |
| *p += b.Sub(a) |
| size -= b.Sub(a) |
| } |
| } |
| for _, a := range arenas { |
| attribute(a.spanTableMin, a.spanTableMax, &stats.spanTable) |
| } |
| // Any other anonymous mapping is bss. |
| // TODO: how to distinguish original bss from anonymous mmap? |
| stats.bss += size |
| case core.Exec: // Ignore --xp mappings, like Linux's vsyscall=xonly. |
| stats.all -= size // Make the total match again. |
| default: |
| return nil, nil, errors.New("weird mapping " + m.Perm().String()) |
| } |
| } |
| pageSize := p.rtConsts.get("runtime._PageSize") |
| |
| // Span types. |
| spanInUse := uint8(p.rtConsts.get("runtime.mSpanInUse")) |
| spanManual := uint8(p.rtConsts.get("runtime.mSpanManual")) |
| spanDead := uint8(p.rtConsts.get("runtime.mSpanDead")) |
| |
| // Malloc header constants (go 1.22+) |
| minSizeForMallocHeader := int64(p.rtConsts.get("runtime.minSizeForMallocHeader")) |
| mallocHeaderSize := int64(p.rtConsts.get("runtime.mallocHeaderSize")) |
| maxSmallSize := int64(p.rtConsts.get("runtime.maxSmallSize")) |
| |
| abiType := p.rtTypeByName["internal/abi.Type"] |
| |
| // Process spans. |
| if pageSize%heapInfoSize != 0 { |
| return nil, nil, fmt.Errorf("page size not a multiple of %d", heapInfoSize) |
| } |
| allspans := mheap.Field("allspans") |
| n := allspans.SliceLen() |
| for i := int64(0); i < n; i++ { |
| s := allspans.SliceIndex(i).Deref() |
| min := core.Address(s.Field("startAddr").Uintptr()) |
| elemSize := int64(s.Field("elemsize").Uintptr()) |
| nPages := int64(s.Field("npages").Uintptr()) |
| spanSize := nPages * pageSize |
| max := min.Add(spanSize) |
| for a := min; a != max; a = a.Add(pageSize) { |
| if !p.proc.Readable(a) { |
| // Sometimes allocated but not yet touched pages or |
| // MADV_DONTNEEDed pages are not written |
| // to the core file. Don't count these pages toward |
| // space usage (otherwise it can look like the heap |
| // is larger than the total memory used). |
| spanSize -= pageSize |
| } |
| } |
| st := s.Field("state") |
| if st.IsStruct() && st.HasField("s") { // go1.14+ |
| st = st.Field("s") |
| } |
| if st.IsStruct() && st.HasField("value") { // go1.20+ |
| st = st.Field("value") |
| } |
| switch st.Uint8() { |
| case spanInUse: |
| stats.inUseSpanSize += spanSize |
| nelems := s.Field("nelems") |
| var n int64 |
| if nelems.IsUint16() { // go1.22+ |
| n = int64(nelems.Uint16()) |
| } else { |
| n = int64(nelems.Uintptr()) |
| } |
| // An object is allocated if it is marked as |
| // allocated or it is below freeindex. |
| x := s.Field("allocBits").Address() |
| alloc := make([]bool, n) |
| for i := int64(0); i < n; i++ { |
| alloc[i] = p.proc.ReadUint8(x.Add(i/8))>>uint(i%8)&1 != 0 |
| } |
| freeindex := s.Field("freeindex") |
| var k int64 |
| if freeindex.IsUint16() { // go1.22+ |
| k = int64(freeindex.Uint16()) |
| } else { |
| k = int64(freeindex.Uintptr()) |
| } |
| for i := int64(0); i < k; i++ { |
| alloc[i] = true |
| } |
| for i := int64(0); i < n; i++ { |
| if alloc[i] { |
| stats.allocSize += elemSize |
| } else { |
| stats.freeSize += elemSize |
| } |
| } |
| stats.spanRoundSize += spanSize - n*elemSize |
| |
| // initialize heap info records for all inuse spans. |
| for a := min; a < max; a += heapInfoSize { |
| h := heap.getOrCreate(a) |
| h.base = min |
| h.size = elemSize |
| } |
| |
| // Process special records. |
| for sp := s.Field("specials"); sp.Address() != 0; sp = sp.Field("next") { |
| sp = sp.Deref() // *special to special |
| if sp.Field("kind").Uint8() != uint8(p.rtConsts.get("runtime._KindSpecialFinalizer")) { |
| // All other specials (just profile records) can't point into the heap. |
| continue |
| } |
| offField := sp.Field("offset") |
| var off int64 |
| if offField.typ.Size == p.proc.PtrSize() { |
| // Go 1.24+ |
| off = int64(offField.Uintptr()) |
| } else { |
| // Go 1.23 and below. |
| off = int64(offField.Uint16()) |
| } |
| obj := min.Add(off) |
| typ := p.rtTypeByName["runtime.specialfinalizer"] |
| p.globals = append(p.globals, p.makeMemRoot(fmt.Sprintf("finalizer for %x", obj), typ, nil, sp.a)) |
| // TODO: these aren't really "globals", as they |
| // are kept alive by the object they reference being alive. |
| // But we have no way of adding edges from an object to |
| // the corresponding finalizer data, so we punt on that thorny |
| // issue for now. |
| } |
| if noscan := s.Field("spanclass").Uint8()&1 != 0; noscan { |
| // No pointers. |
| continue |
| } |
| if elemSize <= minSizeForMallocHeader { |
| // Heap bits in span. |
| bitmapSize := spanSize / int64(heap.ptrSize) / 8 |
| bitmapAddr := min.Add(spanSize - bitmapSize) |
| for i := int64(0); i < bitmapSize; i++ { |
| bits := p.proc.ReadUint8(bitmapAddr.Add(int64(i))) |
| for j := int64(0); j < 8; j++ { |
| if bits&(uint8(1)<<j) != 0 { |
| heap.setIsPointer(min.Add(int64(heap.ptrSize) * (i*8 + j))) |
| } |
| } |
| } |
| } else if elemSize <= maxSmallSize-mallocHeaderSize { |
| // Allocation headers. |
| // |
| // These will always point to real abi.Type values that, once allocated, |
| // are never freed, so it's safe to observe them even if the object is |
| // dead. We may note down pointers that are invalid if the object is not |
| // allocated (or live) but that's no different from reading stale bits |
| // out of the bitmap in older Go versions. |
| for e, off := 0, int64(0); int64(e) < n; e, off = e+1, off+elemSize { |
| // We need to be careful to only check space that's actually marked |
| // allocated, otherwise it can contain junk, including an invalid |
| // header. |
| if !alloc[e] { |
| continue |
| } |
| typeAddr := p.proc.ReadPtr(min.Add(off)) |
| if typeAddr == 0 { |
| continue |
| } |
| typ := region{p: p.proc, a: typeAddr, typ: abiType} |
| nptrs := int64(typ.Field("PtrBytes").Uintptr()) / int64(heap.ptrSize) |
| kindGCProg, hasGCProgs := p.rtConsts.find("internal/abi.KindGCProg") |
| if hasGCProgs && typ.Field("Kind_").Uint8()&uint8(kindGCProg) != 0 { |
| panic("unexpected GC prog on small allocation") |
| } |
| gcdata := typ.Field("GCData").Address() |
| for i := int64(0); i < nptrs; i++ { |
| if p.proc.ReadUint8(gcdata.Add(i/8))>>uint(i%8)&1 != 0 { |
| heap.setIsPointer(min.Add(off + mallocHeaderSize + i*int64(heap.ptrSize))) |
| } |
| } |
| } |
| } else { |
| // Large object (header in span). |
| // |
| // These will either point to a real type or a "dummy" type whose storage |
| // is not valid if the object is dead. However, because large objects are |
| // 1:1 with spans, we can be certain largeType is valid as long as the span |
| // is in use. |
| typ := s.Field("largeType").Deref() |
| nptrs := int64(typ.Field("PtrBytes").Uintptr()) / int64(heap.ptrSize) |
| kindGCProg, hasGCProgs := p.rtConsts.find("internal/abi.KindGCProg") |
| if hasGCProgs && typ.Field("Kind_").Uint8()&uint8(kindGCProg) != 0 { |
| panic("large object's GCProg was not unrolled") |
| } |
| gcdata := typ.Field("GCData").Address() |
| for i := int64(0); i < nptrs; i++ { |
| if p.proc.ReadUint8(gcdata.Add(i/8))>>uint(i%8)&1 != 0 { |
| heap.setIsPointer(min.Add(i * int64(heap.ptrSize))) |
| } |
| } |
| } |
| case spanDead: |
| // These are just deallocated span descriptors. They use no heap. |
| case spanManual: |
| stats.manualSpanSize += spanSize |
| stats.manualAllocSize += spanSize |
| for x := core.Address(s.Field("manualFreeList").Cast(p.rtTypeByName["uintptr"]).Uintptr()); x != 0; x = p.proc.ReadPtr(x) { |
| stats.manualAllocSize -= elemSize |
| stats.manualFreeSize += elemSize |
| } |
| } |
| } |
| |
| // There are no longer "free" mspans to represent unused pages. |
| // Instead, there are just holes in the pagemap into which we can allocate. |
| // Look through the page allocator and count the total free space. |
| // Also keep track of how much has been scavenged. |
| pages := mheap.Field("pages") |
| chunks := pages.Field("chunks") |
| pallocChunkBytes := p.rtConsts.get("runtime.pallocChunkBytes") |
| pallocChunksL1Bits := p.rtConsts.get("runtime.pallocChunksL1Bits") |
| pallocChunksL2Bits := p.rtConsts.get("runtime.pallocChunksL2Bits") |
| inuse := pages.Field("inUse") |
| ranges := inuse.Field("ranges") |
| for i := int64(0); i < ranges.SliceLen(); i++ { |
| r := ranges.SliceIndex(i) |
| baseField := r.Field("base").Field("a") |
| base := core.Address(baseField.Uintptr()) |
| limitField := r.Field("limit").Field("a") |
| limit := core.Address(limitField.Uintptr()) |
| chunkBase := (int64(base) + arenaBaseOffset) / pallocChunkBytes |
| chunkLimit := (int64(limit) + arenaBaseOffset) / pallocChunkBytes |
| for chunkIdx := chunkBase; chunkIdx < chunkLimit; chunkIdx++ { |
| var l1, l2 int64 |
| if pallocChunksL1Bits == 0 { |
| l2 = chunkIdx |
| } else { |
| l1 = chunkIdx >> uint(pallocChunksL2Bits) |
| l2 = chunkIdx & (1<<uint(pallocChunksL2Bits) - 1) |
| } |
| chunk := chunks.ArrayIndex(l1).Deref().ArrayIndex(l2) |
| // Count the free bits in this chunk. |
| alloc := chunk.Field("pallocBits") |
| for i := int64(0); i < pallocChunkBytes/pageSize/64; i++ { |
| stats.freeSpanSize += int64(bits.OnesCount64(^alloc.ArrayIndex(i).Uint64())) * pageSize |
| } |
| // Count the scavenged bits in this chunk. |
| scavenged := chunk.Field("scavenged") |
| for i := int64(0); i < pallocChunkBytes/pageSize/64; i++ { |
| stats.releasedSpanSize += int64(bits.OnesCount64(scavenged.ArrayIndex(i).Uint64())) * pageSize |
| } |
| } |
| } |
| |
| // Also count pages in the page cache for each P. |
| allp := p.rtGlobals["allp"] |
| for i := int64(0); i < allp.SliceLen(); i++ { |
| pcache := allp.SliceIndex(i).Deref().Field("pcache") |
| stats.freeSpanSize += int64(bits.OnesCount64(pcache.Field("cache").Uint64())) * pageSize |
| stats.releasedSpanSize += int64(bits.OnesCount64(pcache.Field("scav").Uint64())) * pageSize |
| } |
| |
| // Create stats. |
| // |
| // TODO(mknyszek): Double-check that our own computations of the group stats match the sums here. |
| return heap, groupStat("all", |
| leafStat("text", stats.text), |
| leafStat("readonly", stats.readOnly), |
| leafStat("data", stats.data), |
| leafStat("bss", stats.bss), |
| groupStat("heap", |
| groupStat("in use spans", |
| leafStat("alloc", stats.allocSize), |
| leafStat("free", stats.freeSize), |
| leafStat("round", stats.spanRoundSize), |
| ), |
| groupStat("manual spans", |
| leafStat("alloc", stats.manualAllocSize), |
| leafStat("free", stats.manualFreeSize), |
| ), |
| groupStat("free spans", |
| leafStat("retained", stats.freeSpanSize-stats.releasedSpanSize), |
| leafStat("released", stats.releasedSpanSize), |
| ), |
| ), |
| leafStat("span table", stats.spanTable), |
| ), nil |
| } |
| |
| func readGoroutines(p *Process, dwarfVars map[*Func][]dwarfVar) ([]*Goroutine, error) { |
| allgs := p.rtGlobals["allgs"] |
| n := allgs.SliceLen() |
| var goroutines []*Goroutine |
| for i := int64(0); i < n; i++ { |
| r := allgs.SliceIndex(i).Deref() |
| g, err := readGoroutine(p, r, dwarfVars) |
| if err != nil { |
| return nil, fmt.Errorf("reading goroutine: %v", err) |
| } |
| if g == nil { |
| continue |
| } |
| goroutines = append(goroutines, g) |
| } |
| return goroutines, nil |
| } |
| |
| func readGoroutine(p *Process, r region, dwarfVars map[*Func][]dwarfVar) (*Goroutine, error) { |
| // Set up register descriptors for DWARF stack programs to be executed. |
| g := &Goroutine{r: r} |
| stk := r.Field("stack") |
| g.stackSize = int64(stk.Field("hi").Uintptr() - stk.Field("lo").Uintptr()) |
| |
| var osT *core.Thread // os thread working on behalf of this G (if any). |
| mp := r.Field("m") |
| if mp.Address() != 0 { |
| m := mp.Deref() |
| pid := m.Field("procid").Uint64() |
| // TODO check that m.curg points to g? |
| for _, t := range p.proc.Threads() { |
| if t.Pid() == pid { |
| osT = t |
| } |
| } |
| } |
| st := r.Field("atomicstatus").Field("value") |
| status := st.Uint32() |
| status &^= uint32(p.rtConsts.get("runtime._Gscan")) |
| var sp, pc core.Address |
| switch status { |
| case uint32(p.rtConsts.get("runtime._Gidle")): |
| return g, nil |
| case uint32(p.rtConsts.get("runtime._Grunnable")), uint32(p.rtConsts.get("runtime._Gwaiting")): |
| sched := r.Field("sched") |
| sp = core.Address(sched.Field("sp").Uintptr()) |
| pc = core.Address(sched.Field("pc").Uintptr()) |
| case uint32(p.rtConsts.get("runtime._Grunning")): |
| sp = osT.SP() |
| pc = osT.PC() |
| // TODO: back up to the calling frame? |
| case uint32(p.rtConsts.get("runtime._Gsyscall")): |
| sp = core.Address(r.Field("syscallsp").Uintptr()) |
| pc = core.Address(r.Field("syscallpc").Uintptr()) |
| // TODO: or should we use the osT registers? |
| case uint32(p.rtConsts.get("runtime._Gdead")): |
| return nil, nil |
| // TODO: copystack, others? |
| default: |
| // Unknown state. We can't read the frames, so just bail now. |
| // TODO: make this switch complete and then panic here. |
| // TODO: or just return nil? |
| return g, nil |
| } |
| |
| // Set up register context. |
| var dregs []*op.DwarfRegister |
| if osT != nil { |
| dregs = hardwareRegs2DWARF(osT.Regs()) |
| } else { |
| dregs = hardwareRegs2DWARF(nil) |
| } |
| regs := op.NewDwarfRegisters(p.proc.StaticBase(), dregs, binary.LittleEndian, regnum.AMD64_Rip, regnum.AMD64_Rsp, regnum.AMD64_Rbp, 0) |
| |
| // Read all the frames. |
| for { |
| f, err := readFrame(p, sp, pc) |
| if err != nil { |
| goid := r.Field("goid").Uint64() |
| fmt.Printf("warning: giving up on backtrace for %d after %d frames: %v\n", goid, len(g.frames), err) |
| break |
| } |
| if f.f.name == "runtime.goexit" { |
| break |
| } |
| if len(g.frames) > 0 { |
| g.frames[len(g.frames)-1].parent = f |
| } |
| g.frames = append(g.frames, f) |
| |
| regs.CFA = int64(f.max) |
| regs.FrameBase = int64(f.max) |
| |
| // Start with all pointer slots as unnamed. |
| unnamed := map[core.Address]bool{} |
| for a := range f.Live { |
| unnamed[a] = true |
| } |
| |
| // Emit roots for DWARF entries. |
| for _, v := range dwarfVars[f.f] { |
| if !(v.lowPC <= f.pc && f.pc < v.highPC) { |
| continue |
| } |
| addr, pieces, err := op.ExecuteStackProgram(*regs, v.instr, int(p.proc.PtrSize()), func(buf []byte, addr uint64) (int, error) { |
| p.proc.ReadAt(buf, core.Address(addr)) |
| return len(buf), nil |
| }) |
| if err != nil { |
| return nil, fmt.Errorf("failed to execute DWARF stack program for variable %s: %v", v.name, err) |
| } |
| if len(pieces) != 0 { |
| // The variable is "de-structured" and broken up into multiple pieces. |
| var rps []rootPiece |
| off := int64(0) |
| for _, p := range pieces { |
| rp := rootPiece{size: int64(p.Size), off: off} |
| switch p.Kind { |
| case op.AddrPiece: |
| // Remove this variable from the set of unnamed pointers. |
| delete(unnamed, core.Address(p.Val)) |
| |
| rp.kind = addrPiece |
| rp.value = p.Val |
| case op.RegPiece: |
| rp.kind = regPiece |
| rp.value = regs.Uint64Val(p.Val) |
| case op.ImmPiece: |
| rp.kind = immPiece |
| rp.value = p.Val |
| } |
| off += int64(p.Size) |
| } |
| f.roots = append(f.roots, p.makeCompositeRoot(v.name, v.typ, f, rps)) |
| } else if addr != 0 && len(pieces) == 0 { |
| // Simple contiguous stack location. |
| f.roots = append(f.roots, p.makeMemRoot(v.name, v.typ, f, core.Address(addr))) |
| |
| // Remove this variable from the set of unnamed pointers. |
| for a := core.Address(addr); a < core.Address(addr).Add(v.typ.Size); a = a.Add(p.proc.PtrSize()) { |
| delete(unnamed, a) |
| } |
| } |
| } |
| |
| // Emit roots for unnamed pointer slots in the frame. |
| // Make deterministic by sorting first. |
| s := make([]core.Address, 0, len(unnamed)) |
| for a := range unnamed { |
| s = append(s, a) |
| } |
| sort.Slice(s, func(i, j int) bool { return s[i] < s[j] }) |
| for _, a := range s { |
| typ := p.rtTypeByName["unsafe.Pointer"] |
| f.roots = append(f.roots, p.makeMemRoot("unk", typ, f, a)) |
| } |
| |
| // Figure out how to unwind to the next frame. |
| if f.f.name == "runtime.sigtrampgo" { |
| if osT == nil { |
| // No thread attached to a goroutine in sigtrampgo? |
| break |
| } |
| var ctxt core.Address |
| for _, v := range dwarfVars[f.f] { |
| if v.name != "ctx" { |
| continue |
| } |
| if !(v.lowPC <= f.pc && f.pc < v.highPC) { |
| continue |
| } |
| addr, pieces, err := op.ExecuteStackProgram(*regs, v.instr, int(p.proc.PtrSize()), func(buf []byte, addr uint64) (int, error) { |
| p.proc.ReadAt(buf, core.Address(addr)) |
| return len(buf), nil |
| }) |
| if err != nil { |
| continue |
| } |
| if addr == 0 { |
| continue |
| } |
| // If the stack program indicates that there's just a single value |
| // in a single register, ExecuteStackProgram will return that register's |
| // contents. Otherwise, if it returns an address, it's referring to a |
| // location on the stack, which is one indirection from what we actually |
| // want. |
| if addr != 0 && len(pieces) == 1 && v.typ.Kind == KindPtr { |
| ctxt = core.Address(addr) |
| } else { |
| ctxt = p.proc.ReadPtr(core.Address(addr)) |
| } |
| } |
| if ctxt == 0 { |
| // No way to unwind further. |
| break |
| } |
| // Continue traceback at location where the signal |
| // interrupted normal execution. |
| |
| // ctxt is a *ucontext |
| mctxt := ctxt.Add(5 * 8) |
| // mctxt is a *mcontext |
| // TODO: totally arch-dependent! |
| |
| // Read new register context out of mcontext, before we continue. |
| // |
| // type mcontext struct { |
| // r8 uint64 |
| // r9 uint64 |
| // r10 uint64 |
| // r11 uint64 |
| // r12 uint64 |
| // r13 uint64 |
| // r14 uint64 |
| // r15 uint64 |
| // rdi uint64 |
| // rsi uint64 |
| // rbp uint64 |
| // rbx uint64 |
| // rdx uint64 |
| // rax uint64 |
| // rcx uint64 |
| // rsp uint64 |
| // rip uint64 |
| // eflags uint64 |
| // cs uint16 |
| // gs uint16 |
| // fs uint16 |
| // __pad0 uint16 |
| // err uint64 |
| // trapno uint64 |
| // oldmask uint64 |
| // cr2 uint64 |
| // fpstate uint64 // pointer |
| // __reserved1 [8]uint64 |
| // } |
| var hregs []core.Register |
| i := int64(0) |
| readReg := func(name string) uint64 { |
| value := p.proc.ReadUint64(mctxt.Add(i)) |
| hregs = append(hregs, core.Register{Name: name, Value: value}) |
| i += 8 |
| return value |
| } |
| readReg("r8") |
| readReg("r9") |
| readReg("r10") |
| readReg("r11") |
| readReg("r12") |
| readReg("r13") |
| readReg("r14") |
| readReg("r15") |
| readReg("rdi") |
| readReg("rsi") |
| readReg("rbp") |
| readReg("rbx") |
| readReg("rdx") |
| readReg("rax") |
| readReg("rcx") |
| sp = core.Address(readReg("rsp")) |
| pc = core.Address(readReg("rip")) |
| readReg("eflags") |
| readReg("cs") |
| readReg("gs") |
| readReg("fs") |
| |
| // Update register state. |
| dregs := hardwareRegs2DWARF(hregs) |
| regs = op.NewDwarfRegisters(p.proc.StaticBase(), dregs, binary.LittleEndian, regnum.AMD64_Rip, regnum.AMD64_Rsp, regnum.AMD64_Rbp, 0) |
| } else { |
| sp = f.max |
| pc = core.Address(p.proc.ReadUintptr(sp - 8)) // TODO:amd64 only |
| } |
| if pc == 0 { |
| // TODO: when would this happen? |
| break |
| } |
| if f.f.name == "runtime.systemstack" { |
| // switch over to goroutine stack |
| sched := r.Field("sched") |
| sp = core.Address(sched.Field("sp").Uintptr()) |
| pc = core.Address(sched.Field("pc").Uintptr()) |
| } |
| } |
| return g, nil |
| } |
| |
| func readFrame(p *Process, sp, pc core.Address) (*Frame, error) { |
| f := p.funcTab.find(pc) |
| if f == nil { |
| return nil, fmt.Errorf("cannot find func for pc=%#x", pc) |
| } |
| off := pc.Sub(f.entry) |
| size, err := f.frameSize.find(off) |
| if err != nil { |
| return nil, fmt.Errorf("cannot read frame size at pc=%#x: %v", pc, err) |
| } |
| size += p.proc.PtrSize() // TODO: on amd64, the pushed return address |
| |
| frame := &Frame{f: f, pc: pc, min: sp, max: sp.Add(size)} |
| |
| // Find live ptrs in locals |
| live := map[core.Address]bool{} |
| if x := int(p.rtConsts.get("internal/abi.FUNCDATA_LocalsPointerMaps")); x < len(f.funcdata) { |
| addr := f.funcdata[x] |
| // TODO: Ideally we should have the same frame size check as |
| // runtime.getStackSize to detect errors when we are missing |
| // the stackmap. |
| if addr != 0 { |
| locals := region{p: p.proc, a: addr, typ: p.rtTypeByName["runtime.stackmap"]} |
| n := locals.Field("n").Int32() // # of bitmaps |
| nbit := locals.Field("nbit").Int32() // # of bits per bitmap |
| idx, err := f.stackMap.find(off) |
| if err != nil { |
| return nil, fmt.Errorf("cannot read stack map at pc=%#x: %v", pc, err) |
| } |
| if idx < -1 { |
| return nil, fmt.Errorf("cannot read stack map at pc=%#x: invalid stack map index %d", pc, idx) |
| } |
| if idx == -1 { |
| idx = 0 |
| } |
| if idx < int64(n) { |
| bits := locals.Field("bytedata").a.Add(int64(nbit+7) / 8 * idx) |
| base := frame.max.Add(-16).Add(-int64(nbit) * p.proc.PtrSize()) |
| // TODO: -16 for amd64. Return address and parent's frame pointer |
| for i := int64(0); i < int64(nbit); i++ { |
| if p.proc.ReadUint8(bits.Add(i/8))>>uint(i&7)&1 != 0 { |
| live[base.Add(i*p.proc.PtrSize())] = true |
| } |
| } |
| } |
| } |
| } |
| // Same for args |
| if x := int(p.rtConsts.get("internal/abi.FUNCDATA_ArgsPointerMaps")); x < len(f.funcdata) { |
| addr := f.funcdata[x] |
| if addr != 0 { |
| args := region{p: p.proc, a: addr, typ: p.rtTypeByName["runtime.stackmap"]} |
| n := args.Field("n").Int32() // # of bitmaps |
| nbit := args.Field("nbit").Int32() // # of bits per bitmap |
| idx, err := f.stackMap.find(off) |
| if err != nil { |
| return nil, fmt.Errorf("cannot read stack map at pc=%#x: %v", pc, err) |
| } |
| if idx < -1 { |
| return nil, fmt.Errorf("cannot read stack map at pc=%#x: invalid stack map index %d", pc, idx) |
| } |
| if idx == -1 { |
| idx = 0 |
| } |
| if idx < int64(n) { |
| bits := args.Field("bytedata").a.Add((int64(nbit+7) / 8) * idx) |
| base := frame.max |
| // TODO: add to base for LR archs. |
| for i := int64(0); i < int64(nbit); i++ { |
| if p.proc.ReadUint8(bits.Add(i/8))>>uint(i&7)&1 != 0 { |
| live[base.Add(i*p.proc.PtrSize())] = true |
| } |
| } |
| } |
| } |
| } |
| frame.Live = live |
| |
| return frame, nil |
| } |
| |
| // A Stats struct is the node of a tree representing the entire memory |
| // usage of the Go program. Children of a node break its usage down |
| // by category. |
| // We maintain the invariant that, if there are children, |
| // Size == sum(c.Size for c in Children). |
| type Statistic struct { |
| Name string |
| Value int64 |
| |
| children map[string]*Statistic |
| } |
| |
| func leafStat(name string, value int64) *Statistic { |
| return &Statistic{Name: name, Value: value} |
| } |
| |
| func groupStat(name string, children ...*Statistic) *Statistic { |
| var cmap map[string]*Statistic |
| var value int64 |
| if len(children) != 0 { |
| cmap = make(map[string]*Statistic) |
| for _, child := range children { |
| cmap[child.Name] = child |
| value += child.Value |
| } |
| } |
| return &Statistic{ |
| Name: name, |
| Value: value, |
| children: cmap, |
| } |
| } |
| |
| func (s *Statistic) Sub(chain ...string) *Statistic { |
| for _, name := range chain { |
| if s == nil { |
| return nil |
| } |
| s = s.children[name] |
| } |
| return s |
| } |
| |
| func (s *Statistic) setChild(child *Statistic) { |
| if len(s.children) == 0 { |
| panic("cannot add children to leaf statistic") |
| } |
| if oldChild, ok := s.children[child.Name]; ok { |
| s.Value -= oldChild.Value |
| } |
| s.children[child.Name] = child |
| s.Value += child.Value |
| } |
| |
| func (s *Statistic) Children() iter.Seq[*Statistic] { |
| return func(yield func(*Statistic) bool) { |
| for _, child := range s.children { |
| if !yield(child) { |
| return |
| } |
| } |
| } |
| } |