gocore: update for Go 1.24 This change drops support for old versions of Go and adds numerous updates to support Go 1.24. - Correct DWARF variable parsing via location lists and complete interpretation of DWARF expressions (with the help of a couple Delve packages). - Fixed handling of sigtrampgo and unwinding out of the signal handler. - Improved handling of frames that aren't in safe points by conservatively finding live pointers through DWARF types of live local variables. - Partial support for values live in registers (much more to do here, and composites are not supported). - Completely overhauled Process initialization to better track dependencies. - Clean up of "page table" data structure to be better encapsulated and more explicitly about the heap. - Fix for a field in a runtime.special moving from a uint16 to a uintptr. - Overhaul of gocore.Stats -> gocore.Statistic. - Update go.mod to go1.23 for iterators. - Update some dependencies. - Adds TestObject from go.dev/cl/634756 (thanks aktau@!). The main milestone this CL reaches is that the main.Large value in the core test is consistently found and correctly typed. There is still a lot left to do. - Anything interpreting map structure is completely wrong with the change to Swiss Tables. - Full support for roots that are in registers. (This matters, because otherwise values that are only in a register in the crash context are completely missed.) - Full support for values that are partially in registers (composites). - Tons of cleanup. - A better abstraction over arches (so much stuff is hard-coded for amd64). Change-Id: I9c671c15128b72ee118b3623aa403477b91f5ce6 Reviewed-on: https://go-review.googlesource.com/c/debug/+/635227 LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Nicolas Hillegeer <aktau@google.com> Auto-Submit: Nicolas Hillegeer <aktau@google.com> Commit-Queue: Nicolas Hillegeer <aktau@google.com>
diff --git a/cmd/viewcore/html.go b/cmd/viewcore/html.go index 58c7824..1b16c14 100644 --- a/cmd/viewcore/html.go +++ b/cmd/viewcore/html.go
@@ -194,11 +194,11 @@ tableStyle(w) fmt.Fprintf(w, "<table>\n") fmt.Fprintf(w, "<tr><th align=left>category</th><th align=left>bytes</th><th align=left>percent</th></tr>\n") - all := c.Stats().Size - var p func(*gocore.Stats, string) - p = func(s *gocore.Stats, prefix string) { - fmt.Fprintf(w, "<tr><td>%s%s</td><td align=right>%d</td><td align=right>%.2f</td></tr>\n", prefix, s.Name, s.Size, float64(s.Size)/float64(all)*100) - for _, c := range s.Children { + all := c.Stats().Value + var p func(*gocore.Statistic, string) + p = func(s *gocore.Statistic, prefix string) { + fmt.Fprintf(w, "<tr><td>%s%s</td><td align=right>%d</td><td align=right>%.2f</td></tr>\n", prefix, s.Name, s.Value, float64(s.Value)/float64(all)*100) + for c := range s.Children() { p(c, prefix+"..") } }
diff --git a/cmd/viewcore/main.go b/cmd/viewcore/main.go index 9f0d999..f1106de 100644 --- a/cmd/viewcore/main.go +++ b/cmd/viewcore/main.go
@@ -512,9 +512,9 @@ exitf("%v\n", err) } t := tabwriter.NewWriter(os.Stdout, 0, 8, 1, ' ', tabwriter.AlignRight) - all := c.Stats().Size - var printStat func(*gocore.Stats, string) - printStat = func(s *gocore.Stats, indent string) { + all := c.Stats().Value + var printStat func(*gocore.Statistic, string) + printStat = func(s *gocore.Statistic, indent string) { comment := "" switch s.Name { case "bss": @@ -526,8 +526,8 @@ case "released": comment = "(given back to the OS)" } - fmt.Fprintf(t, "%s\t%d\t%6.2f%%\t %s\n", fmt.Sprintf("%-20s", indent+s.Name), s.Size, float64(s.Size)*100/float64(all), comment) - for _, c := range s.Children { + fmt.Fprintf(t, "%s\t%d\t%6.2f%%\t %s\n", fmt.Sprintf("%-20s", indent+s.Name), s.Value, float64(s.Value)*100/float64(all), comment) + for c := range s.Children() { printStat(c, indent+" ") } }
diff --git a/go.mod b/go.mod index 2af987b..9e03bc5 100644 --- a/go.mod +++ b/go.mod
@@ -1,16 +1,17 @@ module golang.org/x/debug -go 1.18 +go 1.23 require ( github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e - github.com/spf13/cobra v0.0.3 - github.com/spf13/pflag v1.0.3 + github.com/go-delve/delve v1.23.1 + github.com/spf13/cobra v1.7.0 + github.com/spf13/pflag v1.0.5 golang.org/x/sys v0.28.0 ) require ( github.com/chzyer/logex v1.1.10 // indirect github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 // indirect - github.com/inconshreveable/mousetrap v1.0.0 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect )
diff --git a/go.sum b/go.sum index 5ab7155..11c30c1 100644 --- a/go.sum +++ b/go.sum
@@ -4,11 +4,17 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= -github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8= -github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= -github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= -github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/go-delve/delve v1.23.1 h1:MtZ13ppptttkqSuvVnwJ5CPhIAzDiOwRrYuCk3ES7fU= +github.com/go-delve/delve v1.23.1/go.mod h1:S3SLuEE2mn7wipKilTvk1p9HdTMnXXElcEpiZ+VcuqU= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= +github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/internal/core/process.go b/internal/core/process.go index 74400e4..22bb8b5 100644 --- a/internal/core/process.go +++ b/internal/core/process.go
@@ -51,6 +51,7 @@ symErr error // an error encountered while reading symbols dwarf *dwarf.Data // debugging info (could be nil) dwarfErr error // an error encountered while reading DWARF + dwarfLoc []byte // .debug_loc section warnings []string // warnings generated during loading } @@ -176,6 +177,10 @@ return p.dwarf, p.dwarfErr } +func (p *Process) DWARFLoc() ([]byte, error) { + return p.dwarfLoc, p.dwarfErr +} + // Symbols returns a mapping from name to inferior address, along with // any error encountered during reading the symbol information. // (There may be both an error and some returned symbols.) @@ -274,6 +279,14 @@ if dwarfErr != nil { dwarfErr = fmt.Errorf("error reading DWARF info from %s: %v", exeFile.Name(), dwarfErr) } + var dwarfLoc []byte + if locSection := exeElf.Section(".debug_loc"); locSection != nil { + var err error + dwarfLoc, err = locSection.Data() + if err != nil && dwarfErr == nil { + dwarfErr = fmt.Errorf("error reading DWARF location list section from %s: %v", exeFile.Name(), err) + } + } // Sort then merge mappings, just to clean up a bit. mappings := mem.mappings @@ -363,6 +376,7 @@ symErr: symErr, dwarf: dwarf, dwarfErr: dwarfErr, + dwarfLoc: dwarfLoc, warnings: warnings, } @@ -673,39 +687,40 @@ t.pid = uint64(meta.byteOrder.Uint32(desc[32 : 32+4])) // 112 = offsetof(prstatus_t, pr_reg), 216 = sizeof(elf_gregset_t) reg := desc[112 : 112+216] - for i := 0; i < len(reg); i += 8 { - t.regs = append(t.regs, meta.byteOrder.Uint64(reg[i:])) + i := 0 + readReg := func(name string) uint64 { + value := meta.byteOrder.Uint64(reg[i:]) + t.regs = append(t.regs, Register{Name: name, Value: value}) + i += 8 + return value } - // Registers are: - // 0: r15 - // 1: r14 - // 2: r13 - // 3: r12 - // 4: rbp - // 5: rbx - // 6: r11 - // 7: r10 - // 8: r9 - // 9: r8 - // 10: rax - // 11: rcx - // 12: rdx - // 13: rsi - // 14: rdi - // 15: orig_rax - // 16: rip - // 17: cs - // 18: eflags - // 19: rsp - // 20: ss - // 21: fs_base - // 22: gs_base - // 23: ds - // 24: es - // 25: fs - // 26: gs - t.pc = Address(t.regs[16]) - t.sp = Address(t.regs[19]) + readReg("r15") + readReg("r14") + readReg("r13") + readReg("r12") + readReg("rbp") + readReg("rbx") + readReg("r11") + readReg("r10") + readReg("r9") + readReg("r8") + readReg("rax") + readReg("rcx") + readReg("rdx") + readReg("rsi") + readReg("rdi") + readReg("orig_rax") + t.pc = Address(readReg("rip")) + readReg("cs") + readReg("eflags") + t.sp = Address(readReg("rsp")) + readReg("ss") + readReg("fs_base") + readReg("gs_base") + readReg("ds") + readReg("es") + readReg("fs") + readReg("gs") // TODO: NT_FPREGSET for floating-point registers. //
diff --git a/internal/core/thread.go b/internal/core/thread.go index 8c7517e..61c1af1 100644 --- a/internal/core/thread.go +++ b/internal/core/thread.go
@@ -6,10 +6,15 @@ // A Thread represents an operating system thread. type Thread struct { - pid uint64 // thread/process ID - regs []uint64 // set depends on arch - pc Address // program counter - sp Address // stack pointer + pid uint64 // thread/process ID + regs []Register // set depends on arch + pc Address // program counter + sp Address // stack pointer +} + +type Register struct { + Name string + Value uint64 } func (t *Thread) Pid() uint64 { @@ -18,9 +23,7 @@ // Regs returns the set of register values for the thread. // What registers go where is architecture-dependent. -// TODO: document for each architecture. -// TODO: do this in some arch-independent way? -func (t *Thread) Regs() []uint64 { +func (t *Thread) Regs() []Register { return t.regs }
diff --git a/internal/gocore/dominator_test.go b/internal/gocore/dominator_test.go index 699169e..2f9c85d 100644 --- a/internal/gocore/dominator_test.go +++ b/internal/gocore/dominator_test.go
@@ -8,27 +8,9 @@ import ( "fmt" - "os" "testing" ) -func TestLT(t *testing.T) { - p := loadExample(t) - lt := runLT(p) - checkDominator(t, lt) - if false { - lt.dot(os.Stdout) - } -} - -func TestDominators(t *testing.T) { - p := loadExample(t) - d := p.calculateDominators() - if size := d.size[pseudoRoot]; size < 100<<10 { - t.Errorf("total size of objects is only %v bytes, should be >100KiB", size) - } -} - func checkDominator(t *testing.T, d ltDom) bool { t.Helper() // Build pointer-y graph.
diff --git a/internal/gocore/dwarf.go b/internal/gocore/dwarf.go index e8f9497..53e711a 100644 --- a/internal/gocore/dwarf.go +++ b/internal/gocore/dwarf.go
@@ -6,13 +6,16 @@ import ( "debug/dwarf" + "errors" "fmt" "reflect" - "regexp" - "sort" "strings" "golang.org/x/debug/internal/core" + + "github.com/go-delve/delve/pkg/dwarf/loclist" + "github.com/go-delve/delve/pkg/dwarf/op" + "github.com/go-delve/delve/pkg/dwarf/regnum" ) const ( @@ -20,8 +23,12 @@ ) // read DWARF types from core dump. -func (p *Process) readDWARFTypes() { - d, _ := p.proc.DWARF() +func readDWARFTypes(p *core.Process) (map[dwarf.Type]*Type, error) { + d, err := p.DWARF() + if err != nil { + return nil, fmt.Errorf("failed to read DWARF: %v", err) + } + dwarfMap := make(map[dwarf.Type]*Type) // Make one of our own Types for each dwarf type. r := d.Reader() @@ -37,35 +44,33 @@ if err != nil { continue } - t := &Type{Name: gocoreName(dt), Size: dwarfSize(dt, p.proc.PtrSize())} + t := &Type{Name: gocoreName(dt), Size: dwarfSize(dt, p.PtrSize())} if goKind, ok := e.Val(AttrGoKind).(int64); ok { t.goKind = reflect.Kind(goKind) } - p.dwarfMap[dt] = t + dwarfMap[dt] = t types = append(types, t) } } - p.runtimeNameMap = map[string][]*Type{} - // Fill in fields of types. Postponed until now so we're sure // we have all the Types allocated and available. - for dt, t := range p.dwarfMap { + for dt, t := range dwarfMap { switch x := dt.(type) { case *dwarf.ArrayType: t.Kind = KindArray - t.Elem = p.dwarfMap[x.Type] + t.Elem = dwarfMap[x.Type] t.Count = x.Count case *dwarf.PtrType: t.Kind = KindPtr // unsafe.Pointer has a void base type. if _, ok := x.Type.(*dwarf.VoidType); !ok { - t.Elem = p.dwarfMap[x.Type] + t.Elem = dwarfMap[x.Type] } case *dwarf.StructType: t.Kind = KindStruct for _, f := range x.Field { - fType := p.dwarfMap[f.Type] + fType := dwarfMap[f.Type] if fType == nil { // Weird case: arrays of size 0 in structs, like // Sysinfo_t.X_f. Synthesize a type so things later don't @@ -75,24 +80,14 @@ Name: f.Type.String(), Kind: KindArray, Count: arr.Count, - Elem: p.dwarfMap[arr.Type], + Elem: dwarfMap[arr.Type], } } else { - panic(fmt.Sprintf( + return nil, fmt.Errorf( "found a nil ftype for field %s.%s, type %s (%s) on ", - x.StructName, f.Name, f.Type, reflect.TypeOf(f.Type))) + x.StructName, f.Name, f.Type, reflect.TypeOf(f.Type)) } } - - // Work around issue 21094. There's no guarantee that the - // pointer type is in the DWARF, so just invent a Type. - if strings.HasPrefix(t.Name, "sudog<") && f.Name == "elem" && - strings.Count(t.Name, "*")+1 != strings.Count(gocoreName(f.Type), "*") { - ptrName := "*" + gocoreName(f.Type) - fType = &Type{Name: ptrName, Kind: KindPtr, Size: p.proc.PtrSize(), Elem: fType} - p.runtimeNameMap[ptrName] = []*Type{fType} - } - t.Fields = append(t.Fields, Field{Name: f.Name, Type: fType, Off: f.ByteOffset}) } case *dwarf.BoolType: @@ -110,7 +105,7 @@ case *dwarf.TypedefType: // handle these types in the loop below default: - panic(fmt.Sprintf("unknown type %s %T", dt, dt)) + return nil, fmt.Errorf("unknown type %s %T", dt, dt) } } @@ -132,7 +127,7 @@ } // Copy info from base types into typedefs. - for dt, t := range p.dwarfMap { + for dt, t := range dwarfMap { tt, ok := dt.(*dwarf.TypedefType) if !ok { continue @@ -146,7 +141,7 @@ } break } - bt := p.dwarfMap[base] + bt := dwarfMap[base] // Copy type info from base. Everything except the name. name := t.Name @@ -169,59 +164,7 @@ t.Fields = nil } } - - // Make a runtime name -> Type map for existing DWARF types. - for dt, t := range p.dwarfMap { - name := runtimeName(dt) - p.runtimeNameMap[name] = append(p.runtimeNameMap[name], t) - } - - // Construct the runtime.specialfinalizer type. It won't be found - // in DWARF before 1.10 because it does not appear in the type of any variable. - // type specialfinalizer struct { - // special special - // fn *funcval - // nret uintptr - // fint *_type - // ot *ptrtype - // } - if p.runtimeNameMap["runtime.specialfinalizer"] == nil { - special := p.findType("runtime.special") - p.runtimeNameMap["runtime.specialfinalizer"] = []*Type{ - &Type{ - Name: "runtime.specialfinalizer", - Size: special.Size + 4*p.proc.PtrSize(), - Kind: KindStruct, - Fields: []Field{ - Field{ - Name: "special", - Off: 0, - Type: special, - }, - Field{ - Name: "fn", - Off: special.Size, - Type: p.findType("*runtime.funcval"), - }, - Field{ - Name: "nret", - Off: special.Size + p.proc.PtrSize(), - Type: p.findType("uintptr"), - }, - Field{ - Name: "fint", - Off: special.Size + 2*p.proc.PtrSize(), - Type: p.findType("*runtime._type"), - }, - Field{ - Name: "fn", - Off: special.Size + 3*p.proc.PtrSize(), - Type: p.findType("*runtime.ptrtype"), - }, - }, - }, - } - } + return dwarfMap, nil } func isNonGoCU(e *dwarf.Entry) bool { @@ -298,104 +241,14 @@ } } -// Generate the name the runtime uses for a dwarf type. The DWARF generator -// and the runtime use slightly different names for the same underlying type. -func runtimeName(dt dwarf.Type) string { - switch x := dt.(type) { - case *dwarf.PtrType: - if _, ok := x.Type.(*dwarf.VoidType); ok { - return "unsafe.Pointer" - } - return "*" + runtimeName(x.Type) - case *dwarf.ArrayType: - return fmt.Sprintf("[%d]%s", x.Count, runtimeName(x.Type)) - case *dwarf.StructType: - if !strings.HasPrefix(x.StructName, "struct {") { - // This is a named type, return that name. - return stripPackagePath(x.StructName) - } - // Figure out which fields have anonymous names. - var anon []bool - for _, f := range strings.Split(x.StructName[8:len(x.StructName)-1], ";") { - f = strings.TrimSpace(f) - anon = append(anon, !strings.Contains(f, " ")) - // TODO: this isn't perfect. If the field type has a space in it, - // then this logic doesn't work. Need to search for keyword for - // field type, like "interface", "struct", ... - } - // Make sure anon is long enough. This probably never triggers. - for len(anon) < len(x.Field) { - anon = append(anon, false) - } - - // Build runtime name from the DWARF fields. - s := "struct {" - first := true - for _, f := range x.Field { - if !first { - s += ";" - } - name := f.Name - if i := strings.Index(name, "."); i >= 0 { - name = name[i+1:] - } - if anon[0] { - s += fmt.Sprintf(" %s", runtimeName(f.Type)) - } else { - s += fmt.Sprintf(" %s %s", name, runtimeName(f.Type)) - } - first = false - anon = anon[1:] - } - s += " }" - return s - default: - return stripPackagePath(dt.String()) +func readRuntimeConstants(p *core.Process) (map[string]int64, error) { + d, err := p.DWARF() + if err != nil { + return nil, fmt.Errorf("failed to read DWARF: %v", err) } -} - -var pathRegexp = regexp.MustCompile(`([\w.-]+/)+\w+`) - -func stripPackagePath(name string) string { - // The runtime uses just package names. DWARF uses whole package paths. - // To convert from the latter to the former, get rid of the package paths. - // Examples: - // text/template.Template -> template.Template - // map[string]compress/gzip.Writer -> map[string]gzip.Writer - return pathRegexp.ReplaceAllStringFunc(name, func(path string) string { - return path[strings.LastIndex(path, "/")+1:] - }) -} - -// readRuntimeConstants populates the p.rtConstants map. -func (p *Process) readRuntimeConstants() { - p.rtConstants = map[string]int64{} - - // Hardcoded values for Go 1.9. - // (Go did not have constants in DWARF before 1.10.) - m := p.rtConstants - m["_MSpanDead"] = 0 - m["_MSpanInUse"] = 1 - m["_MSpanManual"] = 2 - m["_MSpanFree"] = 3 - m["_Gidle"] = 0 - m["_Grunnable"] = 1 - m["_Grunning"] = 2 - m["_Gsyscall"] = 3 - m["_Gwaiting"] = 4 - m["_Gdead"] = 6 - m["_Gscan"] = 0x1000 - m["_PCDATA_StackMapIndex"] = 0 - m["_FUNCDATA_LocalsPointerMaps"] = 1 - m["_FUNCDATA_ArgsPointerMaps"] = 0 - m["tflagExtraStar"] = 1 << 1 - m["kindGCProg"] = 1 << 6 - m["kindDirectIface"] = 1 << 5 - m["_PageSize"] = 1 << 13 - m["_KindSpecialFinalizer"] = 1 + consts := map[string]int64{} // From 1.10, these constants are recorded in DWARF records. - d, _ := p.proc.DWARF() r := d.Reader() for e, err := r.Next(); e != nil && err == nil; e, err = r.Next() { if e.Tag != dwarf.TagConstant { @@ -414,8 +267,9 @@ if c == nil { continue } - p.rtConstants[name] = c.Val.(int64) + consts[name] = c.Val.(int64) } + return consts, nil } const ( @@ -425,8 +279,12 @@ _DW_OP_consts = 0x11 ) -func (p *Process) readGlobals() { - d, _ := p.proc.DWARF() +func readGlobals(p *core.Process, dwarfTypeMap map[dwarf.Type]*Type) ([]*Root, error) { + d, err := p.DWARF() + if err != nil { + return nil, fmt.Errorf("failed to read DWARF: %v", err) + } + var roots []*Root r := d.Reader() for e, err := r.Next(); e != nil && err == nil; e, err = r.Next() { if isNonGoCU(e) { @@ -450,13 +308,13 @@ continue } var a core.Address - if p.proc.PtrSize() == 8 { - a = core.Address(p.proc.ByteOrder().Uint64(loc[1:])) + if p.PtrSize() == 8 { + a = core.Address(p.ByteOrder().Uint64(loc[1:])) } else { - a = core.Address(p.proc.ByteOrder().Uint32(loc[1:])) + a = core.Address(p.ByteOrder().Uint32(loc[1:])) } - a = a.Add(int64(p.proc.StaticBase())) - if !p.proc.Writeable(a) { + a = a.Add(int64(p.StaticBase())) + if !p.Writeable(a) { // Read-only globals can't have heap pointers. // TODO: keep roots around anyway? continue @@ -467,7 +325,7 @@ } dt, err := d.Type(f.Val.(dwarf.Offset)) if err != nil { - panic(err) + return nil, err } if _, ok := dt.(*dwarf.UnspecifiedType); ok { continue // Ignore markers like data/edata. @@ -476,24 +334,34 @@ if nf == nil { continue } - p.globals = append(p.globals, &Root{ + roots = append(roots, &Root{ Name: nf.Val.(string), Addr: a, - Type: p.dwarfMap[dt], + Type: dwarfTypeMap[dt], Frame: nil, }) } + return roots, nil } -func (p *Process) readStackVars() { - type Var struct { - name string - off int64 - typ *Type +type dwarfVar struct { + lowPC, highPC core.Address + name string + instr []byte + typ *Type +} + +func readDWARFVars(p *core.Process, fns *funcTab, dwarfTypeMap map[dwarf.Type]*Type) (map[*Func][]dwarfVar, error) { + d, err := p.DWARF() + if err != nil { + return nil, fmt.Errorf("failed to read DWARF: %v", err) } - vars := map[*Func][]Var{} + dLocSec, err := p.DWARFLoc() + if err != nil { + return nil, fmt.Errorf("failed to read DWARF: %v", err) + } + vars := make(map[*Func][]dwarfVar) var curfn *Func - d, _ := p.proc.DWARF() r := d.Reader() for e, err := r.Next(); e != nil && err == nil; e, err = r.Next() { if isNonGoCU(e) { @@ -507,18 +375,18 @@ if lowpc == nil || highpc == nil { continue } - min := core.Address(lowpc.Val.(uint64)) - max := core.Address(highpc.Val.(uint64)) - f := p.funcTab.find(min) + min := core.Address(lowpc.Val.(uint64) + p.StaticBase()) + max := core.Address(highpc.Val.(uint64) + p.StaticBase()) + f := fns.find(min) if f == nil { // some func Go doesn't know about. C? curfn = nil } else { if f.entry != min { - panic("dwarf and runtime don't agree about start of " + f.name) + return nil, errors.New("dwarf and runtime don't agree about start of " + f.name) } - if p.funcTab.find(max-1) != f { - panic("function ranges don't match for " + f.name) + if fns.find(max-1) != f { + return nil, errors.New("function ranges don't match for " + f.name) } curfn = f } @@ -531,102 +399,61 @@ if aloc == nil { continue } - if aloc.Class != dwarf.ClassExprLoc { - // TODO: handle ClassLocListPtr here. - // As of go 1.11, locals are encoded this way. - // Until we fix this TODO, viewcore will not be able to - // show local variables. + if aloc.Class != dwarf.ClassLocListPtr { continue } - // Interpret locations of the form - // DW_OP_call_frame_cfa - // DW_OP_consts <off> - // DW_OP_plus - // (with possibly missing DW_OP_consts & DW_OP_plus for the zero offset.) - // TODO: handle other possible locations (e.g. register locations). - loc := aloc.Val.([]byte) - if len(loc) == 0 || loc[0] != _DW_OP_call_frame_cfa { - continue - } - loc = loc[1:] - var off int64 - if len(loc) != 0 && loc[0] == _DW_OP_consts { - loc = loc[1:] - var s uint - for len(loc) > 0 { - b := loc[0] - loc = loc[1:] - off += int64(b&0x7f) << s - s += 7 - if b&0x80 == 0 { - break - } - } - off = off << (64 - s) >> (64 - s) - if len(loc) == 0 || loc[0] != _DW_OP_plus { - continue - } - loc = loc[1:] - } - if len(loc) != 0 { - continue // more stuff we don't recognize - } + + // Read attributes for some high-level information. f := e.AttrField(dwarf.AttrType) if f == nil { continue } dt, err := d.Type(f.Val.(dwarf.Offset)) if err != nil { - panic(err) + return nil, err } nf := e.AttrField(dwarf.AttrName) if nf == nil { continue } name := nf.Val.(string) - vars[curfn] = append(vars[curfn], Var{name: name, off: off, typ: p.dwarfMap[dt]}) - } - // Get roots from goroutine stacks. - for _, g := range p.goroutines { - for _, f := range g.frames { - // Start with all pointer slots as unnamed. - unnamed := map[core.Address]bool{} - for a := range f.Live { - unnamed[a] = true + // Read the location list. + locListOff := aloc.Val.(int64) + dr := loclist.NewDwarf2Reader(dLocSec, int(p.PtrSize())) + dr.Seek(int(locListOff)) + var base uint64 + var e loclist.Entry + for dr.Next(&e) { + if e.BaseAddressSelection() { + base = e.HighPC + p.StaticBase() + continue } - // Emit roots for DWARF entries. - for _, v := range vars[f.f] { - r := &Root{ - Name: v.name, - Addr: f.max.Add(v.off), - Type: v.typ, - Frame: f, - } - f.roots = append(f.roots, r) - // Remove this variable from the set of unnamed pointers. - for a := r.Addr; a < r.Addr.Add(r.Type.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 { - r := &Root{ - Name: "unk", - Addr: a, - Type: p.findType("unsafe.Pointer"), - Frame: f, - } - f.roots = append(f.roots, r) - } + vars[curfn] = append(vars[curfn], dwarfVar{ + lowPC: core.Address(e.LowPC + base), + highPC: core.Address(e.HighPC + base), + instr: e.Instr, + name: name, + typ: dwarfTypeMap[dt], + }) } } + return vars, nil +} + +func hardwareRegs2DWARF(hregs []core.Register) []*op.DwarfRegister { + n := regnum.AMD64MaxRegNum() + dregs := make([]*op.DwarfRegister, n) + for _, hreg := range hregs { + dwn, ok := regnum.AMD64NameToDwarf[hreg.Name] + if !ok { + continue + } + dreg := op.DwarfRegisterFromUint64(hreg.Value) + dreg.FillBytes() + dregs[dwn] = dreg + } + return dregs } /* Dwarf encoding notes
diff --git a/internal/gocore/gocore_test.go b/internal/gocore/gocore_test.go index ec60ad9..a8bd3e4 100644 --- a/internal/gocore/gocore_test.go +++ b/internal/gocore/gocore_test.go
@@ -7,16 +7,12 @@ package gocore import ( - "archive/zip" "bytes" "errors" "fmt" - "io" "os" "os/exec" - "path" "path/filepath" - "reflect" "runtime" "strings" "testing" @@ -39,52 +35,6 @@ return p } -// loadExample loads a simple core file which resulted from running the -// following program on linux/amd64 with go 1.9.0 (the earliest supported runtime): -// -// package main -// -// func main() { -// _ = *(*int)(nil) -// } -func loadExample(t *testing.T) *Process { - t.Helper() - if runtime.GOOS == "android" { - t.Skip("skipping test on android") - } - return loadCore(t, "testdata/core", "testdata", "") -} - -func loadExampleVersion(t *testing.T, version string) *Process { - t.Helper() - if runtime.GOOS == "android" { - t.Skip("skipping test on android") - } - if version == "1.9" { - version = "" - } - var file string - var base string - if strings.HasSuffix(version, ".zip") { - // Make temporary directory. - dir, err := os.MkdirTemp("", strings.TrimSuffix(version, ".zip")+"_") - if err != nil { - t.Fatalf("can't make temp directory: %s", err) - } - defer os.RemoveAll(dir) - - // Unpack test into directory. - unzip(t, filepath.Join("testdata", version), dir) - - file = filepath.Join(dir, "tmp", "coretest", "core") - base = dir - } else { - file = fmt.Sprintf("testdata/core%s", version) - base = "testdata" - } - return loadCore(t, file, base, "") -} - // loadExampleGenerated generates a core from a binary built with // runtime.GOROOT(). func loadExampleGenerated(t *testing.T, buildFlags ...string) *Process { @@ -264,202 +214,6 @@ return "", b, fmt.Errorf("did not find core file in %+v", names) } -// unzip unpacks the zip file name into the directory dir. -func unzip(t *testing.T, name, dir string) { - t.Helper() - r, err := zip.OpenReader(name) - if err != nil { - t.Fatalf("can't read zip file %s: %s", name, err) - } - for _, f := range r.File { - rf, err := f.Open() - if err != nil { - t.Fatalf("can't read entry %s: %s", f.Name, err) - } - err = os.MkdirAll(path.Dir(filepath.Join(dir, f.Name)), 0777) - if err != nil { - t.Fatalf("can't make directory: %s", err) - } - wf, err := os.Create(filepath.Join(dir, f.Name)) - if err != nil { - t.Fatalf("can't write entry %s: %s", f.Name, err) - } - _, err = io.Copy(wf, rf) - if err != nil { - t.Fatalf("can't copy %s: %s", f.Name, err) - } - err = rf.Close() - if err != nil { - t.Fatalf("can't close reader %s: %s", f.Name, err) - } - err = wf.Close() - if err != nil { - t.Fatalf("can't close writer %s: %s", f.Name, err) - } - } -} - -func TestObjects(t *testing.T) { - p := loadExample(t) - n := 0 - p.ForEachObject(func(x Object) bool { - n++ - return true - }) - if n != 104 { - t.Errorf("#objects = %d, want 104", n) - } -} - -func TestRoots(t *testing.T) { - p := loadExample(t) - n := 0 - p.ForEachRoot(func(r *Root) bool { - n++ - return true - }) - if n != 257 { - t.Errorf("#roots = %d, want 257", n) - } -} - -// TestConfig checks the configuration accessors. -func TestConfig(t *testing.T) { - p := loadExample(t) - if v := p.BuildVersion(); v != "go1.9" { - t.Errorf("version=%s, wanted go1.9", v) - } - if n := p.Stats().Size; n != 2732032 { - t.Errorf("all stats=%d, want 2732032", n) - } -} - -func TestFindFunc(t *testing.T) { - p := loadExample(t) - a := core.Address(0x404000) - f := p.FindFunc(a) - if f == nil { - t.Errorf("can't find function at %x", a) - return - } - if n := f.Name(); n != "runtime.recvDirect" { - t.Errorf("funcname(%x)=%s, want runtime.recvDirect", a, n) - } -} - -func TestTypes(t *testing.T) { - p := loadExample(t) - // Check the type of a few objects. - for _, s := range [...]struct { - addr core.Address - size int64 - kind Kind - name string - repeat int64 - }{ - {0xc420000480, 384, KindStruct, "runtime.g", 1}, - {0xc42000a020, 32, KindPtr, "*runtime.g", 4}, - {0xc420082000, 96, KindStruct, "hchan<bool>", 1}, - {0xc420062000, 64, KindStruct, "runtime._defer", 1}, - } { - x, i := p.FindObject(s.addr) - if x == 0 { - t.Errorf("can't find object at %x", s.addr) - continue - } - if i != 0 { - t.Errorf("offset(%x)=%d, want 0", s.addr, i) - } - if p.Size(x) != s.size { - t.Errorf("size(%x)=%d, want %d", s.addr, p.Size(x), s.size) - } - typ, repeat := p.Type(x) - if typ.Kind != s.kind { - t.Errorf("kind(%x)=%s, want %s", s.addr, typ.Kind, s.kind) - } - if typ.Name != s.name { - t.Errorf("name(%x)=%s, want %s", s.addr, typ.Name, s.name) - } - if repeat != s.repeat { - t.Errorf("repeat(%x)=%d, want %d", s.addr, repeat, s.repeat) - } - - y, i := p.FindObject(s.addr + 1) - if y != x { - t.Errorf("can't find object at %x", s.addr+1) - } - if i != 1 { - t.Errorf("offset(%x)=%d, want i", s.addr, i) - } - } -} - -func TestReverse(t *testing.T) { - p := loadExample(t) - - // Build the pointer map. - // m[x]=y means address x has a pointer to address y. - m1 := map[core.Address]core.Address{} - p.ForEachObject(func(x Object) bool { - p.ForEachPtr(x, func(i int64, y Object, j int64) bool { - m1[p.Addr(x).Add(i)] = p.Addr(y).Add(j) - return true - }) - return true - }) - p.ForEachRoot(func(r *Root) bool { - p.ForEachRootPtr(r, func(i int64, y Object, j int64) bool { - m1[r.Addr.Add(i)] = p.Addr(y).Add(j) - return true - }) - return true - }) - - // Build the same, with reverse entries. - m2 := map[core.Address]core.Address{} - p.ForEachObject(func(y Object) bool { - p.ForEachReversePtr(y, func(x Object, r *Root, i, j int64) bool { - if r != nil { - m2[r.Addr.Add(i)] = p.Addr(y).Add(j) - } else { - m2[p.Addr(x).Add(i)] = p.Addr(y).Add(j) - } - return true - }) - return true - }) - - if !reflect.DeepEqual(m1, m2) { - t.Errorf("forward and reverse edges don't match") - } -} - -func TestDynamicType(t *testing.T) { - p := loadExample(t) - for _, g := range p.Globals() { - if g.Name == "runtime.indexError" { - d := p.DynamicType(g.Type, g.Addr) - if d.Name != "runtime.errorString" { - t.Errorf("dynamic type wrong: got %s want runtime.errorString", d.Name) - } - } - } -} - -// getStat returns the first (depth first) stat in the hierarchy which matches -// name, nil otherwise. -func getStat(stat *Stats, name string) *Stats { - if stat.Name == name { - return stat - } - for _, child := range stat.Children { - if found := getStat(child, name); found != nil { - return found - } - } - return nil -} - func checkProcess(t *testing.T, p *Process) { t.Helper() if gs := p.Goroutines(); len(gs) == 0 { @@ -467,8 +221,8 @@ } const heapName = "heap" - heapStat := getStat(p.Stats(), heapName) - if heapStat == nil || heapStat.Size == 0 { + heapStat := p.Stats().Sub(heapName) + if heapStat == nil || heapStat.Value == 0 { t.Errorf("stat[%q].Size == 0, want >0", heapName) } @@ -479,25 +233,6 @@ } func TestVersions(t *testing.T) { - versions := []string{ - "1.10", - "1.11", - "1.12.zip", - "1.13.zip", - "1.13.3.zip", - "1.14.zip", - "1.16.zip", - "1.17.zip", - "1.18.zip", - "1.19.zip", - } - for _, ver := range versions { - t.Run(ver, func(t *testing.T) { - p := loadExampleVersion(t, ver) - checkProcess(t, p) - }) - } - t.Run("goroot", func(t *testing.T) { for _, buildFlags := range [][]string{ nil, @@ -511,69 +246,52 @@ }) } -func loadZipCore(t *testing.T, name string) *Process { - t.Helper() - if runtime.GOOS == "android" { - t.Skip("skipping test on android") - } - // Make temporary directory. - dir, err := os.MkdirTemp("", name+"_") - if err != nil { - t.Fatalf("can't make temp directory: %s", err) - } - defer os.RemoveAll(dir) - - // Unpack bin file and core file into directory. - unzip(t, filepath.Join("testdata", name+".zip"), dir) - - exe := filepath.Join(dir, name) - file := filepath.Join(dir, "core") - return loadCore(t, file, dir, exe) +func TestObjects(t *testing.T) { + t.Run("goroot", func(t *testing.T) { + for _, buildFlags := range [][]string{ + nil, + {"-buildmode=pie"}, + } { + t.Run(strings.Join(buildFlags, ","), func(t *testing.T) { + p := loadExampleGenerated(t, buildFlags...) + n := 0 + const largeObjectThreshold = 32768 + large := 0 // Number of objects larger than (or equal to largeObjectThreshold) + p.ForEachObject(func(x Object) bool { + siz := p.Size(x) + t.Logf("%s size=%d", typeName(p, x), p.Size(x)) + if siz >= largeObjectThreshold { + large++ + } + n++ + return true + }) + if n < 10 { + t.Errorf("#objects = %d, want >10", n) + } + if large != 1 { + t.Errorf("expected exactly one object larger than %d, found %d", largeObjectThreshold, large) + } + }) + } + }) } -func TestRuntimeTypes(t *testing.T) { - p := loadZipCore(t, "runtimetype") - // Check the type of a few objects. - for _, s := range [...]struct { - addr core.Address - size int64 - kind Kind - name string - repeat int64 - }{ - {0xc00018e000, 16, KindStruct, "example.com/m/path-a/pkg.T1", 1}, - {0xc00018e010, 16, KindStruct, "example.com/m/path-a/pkg.T2", 1}, - {0xc000190000, 32, KindStruct, "example.com/m/path-b/pkg.T1", 1}, - {0xc000190020, 32, KindStruct, "example.com/m/path-b/pkg.T2", 1}, - } { - x, i := p.FindObject(s.addr) - if x == 0 { - t.Errorf("can't find object at %x", s.addr) - continue - } - if i != 0 { - t.Errorf("offset(%x)=%d, want 0", s.addr, i) - } - if p.Size(x) != s.size { - t.Errorf("size(%x)=%d, want %d", s.addr, p.Size(x), s.size) - } - typ, repeat := p.Type(x) - if typ.Kind != s.kind { - t.Errorf("kind(%x)=%s, want %s", s.addr, typ.Kind, s.kind) - } - if typ.Name != s.name { - t.Errorf("name(%x)=%s, want %s", s.addr, typ.Name, s.name) - } - if repeat != s.repeat { - t.Errorf("repeat(%x)=%d, want %d", s.addr, repeat, s.repeat) - } - - y, i := p.FindObject(s.addr + 1) - if y != x { - t.Errorf("can't find object at %x", s.addr+1) - } - if i != 1 { - t.Errorf("offset(%x)=%d, want i", s.addr, i) +// typeName returns a string representing the type of this object. +func typeName(c *Process, x Object) string { + size := c.Size(x) + typ, repeat := c.Type(x) + if typ == nil { + return fmt.Sprintf("unk%d", size) + } + name := typ.String() + n := size / typ.Size + if n > 1 { + if repeat < n { + name = fmt.Sprintf("[%d+%d?]%s", repeat, n-repeat, name) + } else { + name = fmt.Sprintf("[%d]%s", repeat, name) } } + return name }
diff --git a/internal/gocore/goroutine.go b/internal/gocore/goroutine.go index 2c6dbdd..053e274 100644 --- a/internal/gocore/goroutine.go +++ b/internal/gocore/goroutine.go
@@ -13,6 +13,9 @@ stackSize int64 // current stack allocation frames []*Frame + // Registers containing live pointers. + regRoots []*Root + // TODO: defers, in-progress panics }
diff --git a/internal/gocore/module.go b/internal/gocore/module.go index 9429a8d..3d09d29 100644 --- a/internal/gocore/module.go +++ b/internal/gocore/module.go
@@ -15,23 +15,23 @@ type module struct { r region // inferior region holding a runtime.moduledata types, etypes core.Address // range that holds all the runtime._type data in this module - p *Process // The parent process of this module. } -func (p *Process) readModules() { - // Note: the cast is necessary for cores generated by Go 1.9, where - // runtime.moduledata is just an unsafe.Pointer. - ms := p.rtGlobals["modulesSlice"].Cast("*[]*runtime.moduledata").Deref() +func readModules(rtTypeByName map[string]*Type, rtConsts map[string]int64, rtGlobals map[string]region) ([]*module, *funcTab, error) { + ms := rtGlobals["modulesSlice"].Deref() n := ms.SliceLen() + var modules []*module + var fnTab funcTab for i := int64(0); i < n; i++ { md := ms.SliceIndex(i).Deref() - p.modules = append(p.modules, p.readModule(md)) + modules = append(modules, readModule(md, &fnTab, rtTypeByName, rtConsts)) } - p.funcTab.sort() + fnTab.sort() + return modules, &fnTab, nil } -func (p *Process) readModule(r region) *module { - m := &module{p: p, r: r} +func readModule(r region, fns *funcTab, rtTypeByName map[string]*Type, rtConsts map[string]int64) *module { + m := &module{r: r} m.types = core.Address(r.Field("types").Uintptr()) m.etypes = core.Address(r.Field("etypes").Uintptr()) @@ -62,17 +62,17 @@ // funcoff changed type, but had the same meaning. funcoff = int64(ft.Field("funcoff").Uintptr()) } - fr := pcln.SliceIndex(funcoff).Cast("runtime._func") + fr := pcln.SliceIndex(funcoff).Cast(rtTypeByName["runtime._func"]) var f *Func if havePCtab { - f = m.readFunc(fr, pctab, funcnametab) + f = m.readFunc(fr, pctab, funcnametab, rtConsts) } else { - f = m.readFunc(fr, pcln, pcln) + f = m.readFunc(fr, pcln, pcln, rtConsts) } if f.entry != min { panic(fmt.Errorf("entry %x and min %x don't match for %s", f.entry, min, f.name)) } - p.funcTab.add(min, max, f) + fns.add(min, max, f) } return m @@ -81,7 +81,7 @@ // readFunc parses a runtime._func and returns a *Func. // r must have type runtime._func. // pcln must have type []byte and represent the module's pcln table region. -func (m *module) readFunc(r region, pctab region, funcnametab region) *Func { +func (m *module) readFunc(r region, pctab region, funcnametab region, rtConsts map[string]int64) *Func { f := &Func{module: m, r: r} var nameOff int32 switch { @@ -98,7 +98,7 @@ f.entry = core.Address(r.Field("entry").Uintptr()) nameOff = r.Field("nameoff").Int32() } - f.name = r.p.proc.ReadCString(funcnametab.SliceIndex(int64(nameOff)).a) + f.name = r.p.ReadCString(funcnametab.SliceIndex(int64(nameOff)).a) pcsp := r.Field("pcsp") var pcspIdx int64 if pcsp.typ.Kind == KindUint { @@ -107,7 +107,7 @@ } else { pcspIdx = int64(pcsp.Int32()) } - f.frameSize.read(r.p.proc, pctab.SliceIndex(pcspIdx).a) + f.frameSize.read(r.p, pctab.SliceIndex(pcspIdx).a) // Parse pcdata and funcdata, which are laid out beyond the end of the _func. // In 1.16, npcdata changed to be a uint32 from an int32. @@ -123,43 +123,26 @@ a := nfd.a.Add(nfd.typ.Size) for i := uint32(0); i < n; i++ { - f.pcdata = append(f.pcdata, r.p.proc.ReadInt32(a)) + f.pcdata = append(f.pcdata, r.p.ReadInt32(a)) a = a.Add(4) } - is118OrGreater := m.r.HasField("gofunc") - if !is118OrGreater { - // Since 1.18, funcdata no longer needs to be aligned. - a = a.Align(r.p.proc.PtrSize()) - } - - if nfd.typ.Size == 1 { // go 1.12 and beyond, this is a uint8 - n = uint32(nfd.Uint8()) - } else { // go 1.11 and earlier, this is an int32 - n = uint32(nfd.Int32()) - } + n = uint32(nfd.Uint8()) for i := uint32(0); i < n; i++ { - if is118OrGreater { - // Since 1.18, funcdata contains offsets from go.func.*. - off := r.p.proc.ReadUint32(a) - if off == ^uint32(0) { - // No entry. - f.funcdata = append(f.funcdata, 0) - } else { - f.funcdata = append(f.funcdata, core.Address(m.r.Field("gofunc").Uintptr()+uint64(off))) - } - a = a.Add(4) + // Since 1.18, funcdata contains offsets from go.func.*. + off := r.p.ReadUint32(a) + if off == ^uint32(0) { + // No entry. + f.funcdata = append(f.funcdata, 0) } else { - // Prior to 1.18, funcdata contains pointers directly - // to the data. - f.funcdata = append(f.funcdata, r.p.proc.ReadPtr(a)) - a = a.Add(r.p.proc.PtrSize()) + f.funcdata = append(f.funcdata, core.Address(m.r.Field("gofunc").Uintptr()+uint64(off))) } + a = a.Add(4) } // Read pcln tables we need. - if stackmap := int(r.p.rtConstants["_PCDATA_StackMapIndex"]); stackmap < len(f.pcdata) { - f.stackMap.read(r.p.proc, pctab.SliceIndex(int64(f.pcdata[stackmap])).a) + if stackmap := int(rtConsts["_PCDATA_StackMapIndex"]); stackmap < len(f.pcdata) { + f.stackMap.read(r.p, pctab.SliceIndex(int64(f.pcdata[stackmap])).a) } else { f.stackMap.setEmpty() }
diff --git a/internal/gocore/object.go b/internal/gocore/object.go index 481a04d..b0e2704 100644 --- a/internal/gocore/object.go +++ b/internal/gocore/object.go
@@ -5,6 +5,7 @@ package gocore import ( + "iter" "math/bits" "strings" @@ -29,7 +30,7 @@ // Function to call when we find a new pointer. add := func(x core.Address) { - h := p.findHeapInfo(x) + h := p.heap.get(x) if h == nil { // not in heap or not in a valid span // Invalid spans can happen with intra-stack pointers. return @@ -37,7 +38,7 @@ // Round down to object start. x = h.base.Add(x.Sub(h.base) / h.size * h.size) // Object start may map to a different info. Reload heap info. - h = p.findHeapInfo(x) + h = p.heap.get(x) // Find mark bit b := uint64(x) % heapInfoSize / 8 if h.mark&(uint64(1)<<b) != 0 { // already found @@ -61,6 +62,9 @@ // Not a huge deal given that we'll just ignore outright bad pointers, but // we may accidentally mark some objects as live erroneously. for _, g := range p.goroutines { + for _, r := range g.regRoots { + add(core.Address(r.RegValue)) + } for _, f := range g.frames { for a := range f.Live { add(p.proc.ReadPtr(a)) @@ -115,7 +119,7 @@ // Initialize firstIdx fields in the heapInfo, for fast object index lookups. n = 0 p.ForEachObject(func(x Object) bool { - h := p.findHeapInfo(p.Addr(x)) + h := p.heap.get(p.Addr(x)) if h.firstIdx == -1 { h.firstIdx = n } @@ -127,24 +131,26 @@ } // Update stats to include the live/garbage distinction. - alloc := p.Stats().Child("heap").Child("in use spans").Child("alloc") - alloc.Children = []*Stats{ - &Stats{"live", live, nil}, - &Stats{"garbage", alloc.Size - live, nil}, - } + allocSize := p.Stats().Sub("heap", "in use spans", "alloc").Value + p.Stats().Sub("heap", "in use spans").setChild( + groupStat("alloc", + leafStat("live", live), + leafStat("garbage", allocSize-live), + ), + ) } // isPtrFromHeap reports whether the inferior at address a contains a pointer. // a must be somewhere in the heap. func (p *Process) isPtrFromHeap(a core.Address) bool { - return p.findHeapInfo(a).IsPtr(a, p.proc.PtrSize()) + return p.heap.get(a).isPtr(a, p.proc.PtrSize()) } // IsPtr reports whether the inferior at address a contains a pointer. func (p *Process) IsPtr(a core.Address) bool { - h := p.findHeapInfo(a) + h := p.heap.get(a) if h != nil { - return h.IsPtr(a, p.proc.PtrSize()) + return h.isPtr(a, p.proc.PtrSize()) } for _, m := range p.modules { for _, s := range [2]string{"data", "bss"} { @@ -169,7 +175,7 @@ // Returns 0,0 if a doesn't point to a live heap object. func (p *Process) FindObject(a core.Address) (Object, int64) { // Round down to the start of an object. - h := p.findHeapInfo(a) + h := p.heap.get(a) if h == nil { // Not in Go heap, or in a span // that doesn't hold Go objects (freed, stacks, ...) @@ -177,7 +183,7 @@ } x := h.base.Add(a.Sub(h.base) / h.size * h.size) // Check if object is marked. - h = p.findHeapInfo(x) + h = p.heap.get(x) if h.mark>>(uint64(x)%heapInfoSize/8)&1 == 0 { // free or garbage return 0, 0 } @@ -189,25 +195,21 @@ if x == 0 { return -1, 0 } - h := p.findHeapInfo(core.Address(x)) + h := p.heap.get(core.Address(x)) return h.firstIdx + bits.OnesCount64(h.mark&(uint64(1)<<(uint64(x)%heapInfoSize/8)-1)), off } // ForEachObject calls fn with each object in the Go heap. // If fn returns false, ForEachObject returns immediately. func (p *Process) ForEachObject(fn func(x Object) bool) { - for _, k := range p.pages { - pt := p.pageTable[k] - for i := range pt { - h := &pt[i] - m := h.mark - for m != 0 { - j := bits.TrailingZeros64(m) - m &= m - 1 - x := Object(k)*pageTableSize*heapInfoSize + Object(i)*heapInfoSize + Object(j)*8 - if !fn(x) { - return - } + for a, h := range p.heap.all() { + m := h.mark + for m != 0 { + j := bits.TrailingZeros64(m) + m &= m - 1 + x := Object(a + core.Address(j*8)) + if !fn(x) { + return } } } @@ -222,6 +224,11 @@ } } for _, g := range p.goroutines { + for _, r := range g.regRoots { + if !fn(r) { + return + } + } for _, f := range g.frames { for _, r := range f.roots { if !fn(r) { @@ -239,7 +246,7 @@ // Size returns the size of x in bytes. func (p *Process) Size(x Object) int64 { - return p.findHeapInfo(core.Address(x)).size + return p.heap.get(core.Address(x)).size } // Type returns the type and repeat count for the object x. @@ -306,6 +313,15 @@ off += p.proc.PtrSize() fallthrough case KindPtr, KindString, KindSlice, KindFunc: + if t.Kind == KindPtr && r.Addr == 0 { + dst, off2 := p.FindObject(core.Address(r.RegValue)) + if dst != 0 { + if !fn(off, dst, off2) { + return false + } + } + break + } a := r.Addr.Add(off) if r.Frame == nil || r.Frame.Live[a] { dst, off2 := p.FindObject(p.proc.ReadPtr(a)) @@ -346,7 +362,7 @@ ptr [2]uint64 } -func (h *heapInfo) IsPtr(a core.Address, ptrSize int64) bool { +func (h *heapInfo) isPtr(a core.Address, ptrSize int64) bool { if ptrSize == 8 { i := uint(a%heapInfoSize) / 8 return h.ptr[0]>>i&1 != 0 @@ -355,30 +371,45 @@ return h.ptr[i/64]>>(i%64)&1 != 0 } -// setHeapPtr records that the memory at heap address a contains a pointer. -func (p *Process) setHeapPtr(a core.Address) { - h := p.allocHeapInfo(a) - if p.proc.PtrSize() == 8 { - i := uint(a%heapInfoSize) / 8 - h.ptr[0] |= uint64(1) << i - return - } - i := a % heapInfoSize / 4 - h.ptr[i/64] |= uint64(1) << (i % 64) +type heapTableID uint64 + +func (id heapTableID) addr() core.Address { + return core.Address(id * heapInfoSize * heapTableSize) +} + +type heapTable struct { + table map[heapTableID]*heapTableEntry + entries []heapTableID + ptrSize uint64 +} + +func heapTableIndex(a core.Address) (heapTableID, uint64) { + return heapTableID(a / heapInfoSize / heapTableSize), uint64(a / heapInfoSize % heapTableSize) } // Heap info structures cover 9 bits of address. // A page table entry covers 20 bits of address (1MB). -const pageTableSize = 1 << 11 +const heapTableSize = 1 << 11 -type pageTableEntry [pageTableSize]heapInfo +type heapTableEntry [heapTableSize]heapInfo -// findHeapInfo finds the heapInfo structure for a. -// Returns nil if a is not a heap address. -func (p *Process) findHeapInfo(a core.Address) *heapInfo { - k := a / heapInfoSize / pageTableSize - i := a / heapInfoSize % pageTableSize - t := p.pageTable[k] +func (ht *heapTable) setIsPointer(a core.Address) { + h := ht.getOrCreate(a) + ptrSize := ht.ptrSize + if ptrSize == 8 { + i := uint64(a%heapInfoSize) / 8 + h.ptr[0] |= uint64(1) << i + return + } + i := uint64(a%heapInfoSize) / 4 + h.ptr[i/64] |= uint64(1) << (i % 64) +} + +// get returns the heap info for an address. Returns nil +// if a is not a heap address. +func (ht *heapTable) get(a core.Address) *heapInfo { + k, i := heapTableIndex(a) + t := ht.table[k] if t == nil { return nil } @@ -389,19 +420,33 @@ return h } -// Same as findHeapInfo, but allocates the heapInfo if it -// hasn't been allocated yet. -func (p *Process) allocHeapInfo(a core.Address) *heapInfo { - k := a / heapInfoSize / pageTableSize - i := a / heapInfoSize % pageTableSize - t := p.pageTable[k] +// getOrCreate returns the heap info for an address, or if nil, +// creates it, marking it as a heap address. +func (ht *heapTable) getOrCreate(a core.Address) *heapInfo { + k, i := heapTableIndex(a) + t := ht.table[k] if t == nil { - t = new(pageTableEntry) - for j := 0; j < pageTableSize; j++ { + t = new(heapTableEntry) + for j := 0; j < heapTableSize; j++ { t[j].firstIdx = -1 } - p.pageTable[k] = t - p.pages = append(p.pages, k) + ht.table[k] = t + ht.entries = append(ht.entries, k) } return &t[i] } + +func (ht *heapTable) all() iter.Seq2[core.Address, *heapInfo] { + return func(yield func(core.Address, *heapInfo) bool) { + for _, k := range ht.entries { + e := ht.table[k] + for i := range e { + h := &e[i] + a := k.addr() + core.Address(uint64(i)*heapInfoSize) + if !yield(a, h) { + return + } + } + } + } +}
diff --git a/internal/gocore/process.go b/internal/gocore/process.go index e934358..7b2777c 100644 --- a/internal/gocore/process.go +++ b/internal/gocore/process.go
@@ -6,60 +6,56 @@ import ( "debug/dwarf" + "encoding/binary" + "errors" "fmt" + "iter" "math/bits" + "sort" "strings" "sync" "golang.org/x/debug/internal/core" + + "github.com/go-delve/delve/pkg/dwarf/op" + "github.com/go-delve/delve/pkg/dwarf/regnum" ) // A Process represents the state of a Go process that core dumped. type Process struct { - proc *core.Process + proc *core.Process + buildVersion string - // data structure for fast object finding - // The key to these maps is the object address divided by - // pageTableSize * heapInfoSize. - pageTable map[core.Address]*pageTableEntry - pages []core.Address // deterministic ordering of keys of pageTable + // Index of heap objects and pointers. + heap *heapTable // number of live objects nObj int goroutines []*Goroutine - // runtime info - rtGlobals map[string]region - rtConstants map[string]int64 + // Runtime info for easier lookup. + rtGlobals map[string]region + rtConsts map[string]int64 // A module is a loadable unit. Most Go programs have 1, programs // which load plugins will have more. modules []*module - // address -> function mapping - funcTab funcTab + // Maps core.Address to functions. + funcTab *funcTab - // map from dwarf type to *Type - dwarfMap map[dwarf.Type]*Type + // Fundamental type mappings extracted from the core. + dwarfTypeMap map[dwarf.Type]*Type + rtTypeByName map[string]*Type - // map from address of runtime._type to *Type - runtimeMap map[core.Address]*Type + // rtTypeMap maps a core.Address to a *Type. Constructed incrementally, on-demand. + rtTypeMap map[core.Address]*Type - // map from runtime type name to the set of *Type with that name - // Used to find candidates to put in the runtimeMap map. - runtimeNameMap map[string][]*Type + // Memory usage breakdown. + stats *Statistic - // memory usage by category - stats *Stats - - buildVersion string - - // This is a Go 1.17 process, or higher. This field is used for - // differences in behavior that otherwise can't be detected via the - // type system. - is117OrGreater bool - + // Global roots. globals []*Root // Types of each object, indexed by object index. @@ -78,6 +74,78 @@ rootIdx []*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, err = readDWARFTypes(proc) + if err != nil { + return nil, err + } + p.rtTypeByName = make(map[string]*Type) + for dt, t := range p.dwarfTypeMap { + name := gocoreName(dt) + if _, ok := p.rtTypeByName[name]; ok { + // If a runtime type matches more than one DWARF type, + // pick one arbitrarily. + // + // This looks mostly harmless. DWARF has some redundant entries. + // For example, [32]uint8 appears twice. + // + // TODO(mknyszek): Investigate the reason for this duplication. + continue + } + p.rtTypeByName[name] = t + } + p.rtConsts, err = readRuntimeConstants(proc) + if err != nil { + return nil, err + } + p.globals, err = readGlobals(proc, 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.") { + p.rtGlobals[g.Name[8:]] = region{p: proc, a: g.Addr, typ: g.Type} + } + } + + // Read all the data that depend on runtime globals. + 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. + dwarfVars, err := readDWARFVars(proc, p.funcTab, p.dwarfTypeMap) + if err != nil { + return nil, err + } + + // Read goroutines. + p.goroutines, err = readGoroutines(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 @@ -88,7 +156,7 @@ } // Stats returns a breakdown of the program's memory use by category. -func (p *Process) Stats() *Stats { +func (p *Process) Stats() *Statistic { return p.stats } @@ -115,197 +183,59 @@ } func (p *Process) tryFindType(name string) *Type { - s := p.runtimeNameMap[name] - if len(s) == 0 { - return nil - } - return s[0] -} - -// Core takes a loaded core file and extracts Go information from it. -func Core(proc *core.Process) (p *Process, err error) { - // Make sure we have DWARF info. - if _, err := proc.DWARF(); err != nil { - return nil, fmt.Errorf("error reading dwarf: %w", err) - } - - // Guard against failures of proc.Read* routines. - /* - defer func() { - e := recover() - if e == nil { - return - } - p = nil - if x, ok := e.(error); ok { - err = x - return - } - panic(e) // Not an error, re-panic it. - }() - */ - - p = &Process{ - proc: proc, - runtimeMap: map[core.Address]*Type{}, - dwarfMap: map[dwarf.Type]*Type{}, - } - - // Initialize everything that just depends on DWARF. - p.readDWARFTypes() - p.readRuntimeConstants() - p.readGlobals() - - // Find runtime globals we care about. Initialize regions for them. - p.rtGlobals = map[string]region{} - for _, g := range p.globals { - if strings.HasPrefix(g.Name, "runtime.") { - p.rtGlobals[g.Name[8:]] = region{p: p, a: g.Addr, typ: g.Type} - } - } - - // Read all the data that depend on runtime globals. - p.buildVersion = p.rtGlobals["buildVersion"].String() - - // runtime._type varint name length encoding, and mheap curArena - // counting changed behavior in 1.17 without explicitly related type - // changes, making the difference difficult to detect. As a workaround, - // we check on the version explicitly. - // - // Go 1.17 added runtime._func.flag, so use that as a sentinel for this - // version. - p.is117OrGreater = p.findType("runtime._func").HasField("flag") - - p.readModules() - p.readHeap() - p.readGs() - p.readStackVars() // needs to be after readGs. - p.markObjects() // needs to be after readGlobals, readStackVars. - - return p, nil + return p.rtTypeByName[name] } // arena is a summary of the size of components of a heapArena. type arena struct { - heapMin core.Address - heapMax core.Address - - // Optional. - bitmapMin core.Address - bitmapMax core.Address - + heapMin core.Address + heapMax core.Address spanTableMin core.Address spanTableMax core.Address } -func (p *Process) getArenaBaseOffset() int64 { - if x, ok := p.rtConstants["arenaBaseOffsetUintptr"]; ok { // go1.15+ - // arenaBaseOffset changed sign in 1.15. Callers treat this - // value as it was specified in 1.14, so we negate it here. - return -x - } - return p.rtConstants["arenaBaseOffset"] -} - -func (p *Process) readHeap() { - ptrSize := p.proc.PtrSize() - p.pageTable = map[core.Address]*pageTableEntry{} +func readHeap(p *Process) (*heapTable, *Statistic, error) { mheap := p.rtGlobals["mheap_"] + var arenas []arena + arenaSize := p.rtConsts["heapArenaBytes"] + if arenaSize%heapInfoSize != 0 { + panic("arenaSize not a multiple of heapInfoSize") + } + arenaBaseOffset := -p.rtConsts["arenaBaseOffsetUintptr"] + if p.proc.PtrSize() == 4 && arenaBaseOffset != 0 { + panic("arenaBaseOffset must be 0 for 32-bit inferior") + } - if mheap.HasField("spans") { - // go 1.9 or 1.10. There is a single arena. - arenas = append(arenas, p.readArena19(mheap)) - } else { - // go 1.11+. Has multiple arenas. - arenaSize := p.rtConstants["heapArenaBytes"] - if arenaSize%heapInfoSize != 0 { - panic("arenaSize not a multiple of heapInfoSize") + level1Table := mheap.Field("arenas") + level1size := level1Table.ArrayLen() + for level1 := int64(0); level1 < level1size; level1++ { + ptr := level1Table.ArrayIndex(level1) + if ptr.Address() == 0 { + continue } - arenaBaseOffset := p.getArenaBaseOffset() - if 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) + level2table := ptr.Deref() + level2size := level2table.ArrayLen() + for level2 := int64(0); level2 < level2size; level2++ { + ptr = level2table.ArrayIndex(level2) 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() + a := ptr.Deref() - min := core.Address(arenaSize*(level2+level1*level2size) - arenaBaseOffset) - max := min.Add(arenaSize) + min := core.Address(arenaSize*(level2+level1*level2size) - arenaBaseOffset) + max := min.Add(arenaSize) - arenas = append(arenas, p.readArena(a, min, max)) - } + arenas = append(arenas, readArena(a, min, max)) } } - - p.readSpans(mheap, arenas) -} - -// Read the global arena. Go 1.9 or 1.10 only, which has a single arena. Record -// heap pointers and return the arena size summary. -func (p *Process) readArena19(mheap region) arena { - ptrSize := p.proc.PtrSize() - logPtrSize := p.proc.LogPtrSize() - - arenaStart := core.Address(mheap.Field("arena_start").Uintptr()) - arenaUsed := core.Address(mheap.Field("arena_used").Uintptr()) - arenaEnd := core.Address(mheap.Field("arena_end").Uintptr()) - bitmapEnd := core.Address(mheap.Field("bitmap").Uintptr()) - bitmapStart := bitmapEnd.Add(-int64(mheap.Field("bitmap_mapped").Uintptr())) - spanTableStart := mheap.Field("spans").SlicePtr().Address() - spanTableEnd := spanTableStart.Add(mheap.Field("spans").SliceCap() * ptrSize) - - // Copy pointer bits to heap info. - // Note that the pointer bits are stored backwards. - for a := arenaStart; a < arenaUsed; a = a.Add(ptrSize) { - off := a.Sub(arenaStart) >> logPtrSize - if p.proc.ReadUint8(bitmapEnd.Add(-(off>>2)-1))>>uint(off&3)&1 != 0 { - p.setHeapPtr(a) - } - } - - return arena{ - heapMin: arenaStart, - heapMax: arenaEnd, - bitmapMin: bitmapStart, - bitmapMax: bitmapEnd, - spanTableMin: spanTableStart, - spanTableMax: spanTableEnd, - } + 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 (p *Process) readArena(a region, min, max core.Address) arena { - ptrSize := p.proc.PtrSize() - - // Bitmap reading handled in readSpans. - // Read in all the pointer locations from the arenas, where they lived before Go 1.22. - // After Go 1.22, the pointer locations are scattered among more locations; they're - // processed during readSpans instead. - var bitmap region - if a.HasField("bitmap") { - bitmap = a.Field("bitmap") - if oneBitBitmap := a.HasField("noMorePtrs"); oneBitBitmap { // Starting in go 1.20 - p.readOneBitBitmap(bitmap, min) - } else { - p.readMultiBitBitmap(bitmap, min) - } - } - +func readArena(a region, min, max core.Address) arena { + ptrSize := a.p.PtrSize() spans := a.Field("spans") arena := arena{ heapMin: min, @@ -313,73 +243,50 @@ spanTableMin: spans.a, spanTableMax: spans.a.Add(spans.ArrayLen() * ptrSize), } - if bitmap.a != 0 { - arena.bitmapMin = bitmap.a - arena.bitmapMax = bitmap.a.Add(bitmap.ArrayLen()) - } return arena } -// Read a one-bit bitmap (Go 1.20+), recording the heap pointers. -func (p *Process) readOneBitBitmap(bitmap region, min core.Address) { - ptrSize := p.proc.PtrSize() - n := bitmap.ArrayLen() - for i := int64(0); i < n; i++ { - // The array uses 1 bit per word of heap. See mbitmap.go for - // more information. - m := bitmap.ArrayIndex(i).Uintptr() - bits := 8 * ptrSize - for j := int64(0); j < bits; j++ { - if m>>uint(j)&1 != 0 { - p.setHeapPtr(min.Add((i*bits + j) * ptrSize)) - } - } - } -} +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... -// Read a multi-bit bitmap (Go 1.11-1.20), recording the heap pointers. -func (p *Process) readMultiBitBitmap(bitmap region, min core.Address) { - ptrSize := p.proc.PtrSize() - n := bitmap.ArrayLen() - for i := int64(0); i < n; i++ { - // The nth byte is composed of 4 object bits and 4 live/dead - // bits. We ignore the 4 live/dead bits, which are on the - // high order side of the byte. - // - // See mbitmap.go for more information on the format of - // the bitmap field of heapArena. - m := bitmap.ArrayIndex(i).Uint8() - for j := int64(0); j < 4; j++ { - if m>>uint(j)&1 != 0 { - p.setHeapPtr(min.Add((i*4 + j) * ptrSize)) - } - } + // The main goal of this function is to initialize this data structure. + heap := &heapTable{ + table: make(map[heapTableID]*heapTableEntry), + ptrSize: uint64(p.proc.PtrSize()), } -} -func (p *Process) readSpans(mheap region, arenas []arena) { - ptrSize := p.proc.PtrSize() - var all int64 - var text int64 - var readOnly int64 - var heap int64 - var spanTable int64 - var bitmap int64 - var data int64 - var bss int64 // also includes mmap'd regions + // ... 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() - all += size + stats.all += size switch m.Perm() { case core.Read: - readOnly += size + stats.readOnly += size case core.Read | core.Exec: - text += size + 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. - data += size + stats.data += size break } attribute := func(x, y core.Address, p *int64) { @@ -391,59 +298,36 @@ } } for _, a := range arenas { - attribute(a.heapMin, a.heapMax, &heap) - attribute(a.bitmapMin, a.bitmapMax, &bitmap) - attribute(a.spanTableMin, a.spanTableMax, &spanTable) + attribute(a.spanTableMin, a.spanTableMax, &stats.spanTable) } // Any other anonymous mapping is bss. // TODO: how to distinguish original bss from anonymous mmap? - bss += size + stats.bss += size case core.Exec: // Ignore --xp mappings, like Linux's vsyscall=xonly. - all -= size // Make the total match again. + stats.all -= size // Make the total match again. default: - panic("weird mapping " + m.Perm().String()) + return nil, nil, errors.New("weird mapping " + m.Perm().String()) } } - if !p.is117OrGreater && mheap.HasField("curArena") { - // 1.13.3 and up have curArena. Subtract unallocated space in - // the current arena from the heap. - // - // As of 1.17, the runtime does this automatically - // (https://go.dev/cl/270537). - ca := mheap.Field("curArena") - unused := int64(ca.Field("end").Uintptr() - ca.Field("base").Uintptr()) - heap -= unused - all -= unused - } - pageSize := p.rtConstants["_PageSize"] + pageSize := p.rtConsts["_PageSize"] - // Span types - spanInUse := uint8(p.rtConstants["_MSpanInUse"]) - spanManual := uint8(p.rtConstants["_MSpanManual"]) - spanDead := uint8(p.rtConstants["_MSpanDead"]) - spanFree := uint8(p.rtConstants["_MSpanFree"]) + // Span types. + spanInUse := uint8(p.rtConsts["mSpanInUse"]) + spanManual := uint8(p.rtConsts["mSpanManual"]) + spanDead := uint8(p.rtConsts["mSpanDead"]) // Malloc header constants (go 1.22+) - minSizeForMallocHeader := int64(p.rtConstants["minSizeForMallocHeader"]) - mallocHeaderSize := int64(p.rtConstants["mallocHeaderSize"]) - maxSmallSize := int64(p.rtConstants["maxSmallSize"]) + minSizeForMallocHeader := int64(p.rtConsts["minSizeForMallocHeader"]) + mallocHeaderSize := int64(p.rtConsts["mallocHeaderSize"]) + maxSmallSize := int64(p.rtConsts["maxSmallSize"]) - abiType := p.tryFindType("abi.Type") // Non-nil expected for Go 1.22+. + abiType := p.tryFindType("internal/abi.Type") // Process spans. if pageSize%heapInfoSize != 0 { - panic(fmt.Sprintf("page size not a multiple of %d", heapInfoSize)) + return nil, nil, fmt.Errorf("page size not a multiple of %d", heapInfoSize) } allspans := mheap.Field("allspans") - var freeSpanSize int64 - var releasedSpanSize int64 - var manualSpanSize int64 - var inUseSpanSize int64 - var allocSize int64 - var freeSize int64 - var spanRoundSize int64 - var manualAllocSize int64 - var manualFreeSize int64 n := allspans.SliceLen() for i := int64(0); i < n; i++ { s := allspans.SliceIndex(i).Deref() @@ -471,7 +355,7 @@ } switch st.Uint8() { case spanInUse: - inUseSpanSize += spanSize + stats.inUseSpanSize += spanSize nelems := s.Field("nelems") var n int64 if nelems.IsUint16() { // go1.22+ @@ -498,16 +382,16 @@ } for i := int64(0); i < n; i++ { if alloc[i] { - allocSize += elemSize + stats.allocSize += elemSize } else { - freeSize += elemSize + stats.freeSize += elemSize } } - spanRoundSize += spanSize - n*elemSize + stats.spanRoundSize += spanSize - n*elemSize // initialize heap info records for all inuse spans. for a := min; a < max; a += heapInfoSize { - h := p.allocHeapInfo(a) + h := heap.getOrCreate(a) h.base = min h.size = elemSize } @@ -515,11 +399,11 @@ // 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.rtConstants["_KindSpecialFinalizer"]) { + if sp.Field("kind").Uint8() != uint8(p.rtConsts["_KindSpecialFinalizer"]) { // All other specials (just profile records) can't point into the heap. continue } - obj := min.Add(int64(sp.Field("offset").Uint16())) + obj := min.Add(int64(sp.Field("offset").Uint64())) p.globals = append(p.globals, &Root{ Name: fmt.Sprintf("finalizer for %x", obj), @@ -533,24 +417,19 @@ // the corresponding finalizer data, so we punt on that thorny // issue for now. } - // Check if we're in Go 1.22+. If not, then we're done. Otherwise, - // we need to discover all the heap pointers in the span here. - if !s.HasField("largeType") || !s.HasField("spanclass") { - continue - } if noscan := s.Field("spanclass").Uint8()&1 != 0; noscan { // No pointers. continue } if elemSize <= minSizeForMallocHeader { // Heap bits in span. - bitmapSize := spanSize / ptrSize / 8 + 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 { - p.setHeapPtr(min.Add(ptrSize * (i*8 + j))) + heap.setIsPointer(min.Add(int64(heap.ptrSize) * (i*8 + j))) } } } @@ -573,15 +452,15 @@ if typeAddr == 0 { continue } - typ := region{p: p, a: typeAddr, typ: abiType} - nptrs := int64(typ.Field("PtrBytes").Uintptr()) / ptrSize - if typ.Field("Kind_").Uint8()&uint8(p.rtConstants["kindGCProg"]) != 0 { + typ := region{p: p.proc, a: typeAddr, typ: abiType} + nptrs := int64(typ.Field("PtrBytes").Uintptr()) / int64(heap.ptrSize) + if typ.Field("Kind_").Uint8()&uint8(p.rtConsts["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 { - p.setHeapPtr(min.Add(off + mallocHeaderSize + i*ptrSize)) + heap.setIsPointer(min.Add(off + mallocHeaderSize + i*int64(heap.ptrSize))) } } } @@ -593,153 +472,125 @@ // 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()) / ptrSize - if typ.Field("Kind_").Uint8()&uint8(p.rtConstants["kindGCProg"]) != 0 { + nptrs := int64(typ.Field("PtrBytes").Uintptr()) / int64(heap.ptrSize) + if typ.Field("Kind_").Uint8()&uint8(p.rtConsts["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 { - p.setHeapPtr(min.Add(i * ptrSize)) + heap.setIsPointer(min.Add(i * int64(heap.ptrSize))) } } } - case spanFree: - freeSpanSize += spanSize - if s.HasField("npreleased") { // go 1.11 and earlier - nReleased := int64(s.Field("npreleased").Uintptr()) - releasedSpanSize += nReleased * pageSize - } else { // go 1.12 and beyond - if s.Field("scavenged").Bool() { - releasedSpanSize += spanSize - } - } case spanDead: // These are just deallocated span descriptors. They use no heap. case spanManual: - manualSpanSize += spanSize - manualAllocSize += spanSize - for x := core.Address(s.Field("manualFreeList").Cast("uintptr").Uintptr()); x != 0; x = p.proc.ReadPtr(x) { - manualAllocSize -= elemSize - manualFreeSize += elemSize + stats.manualSpanSize += spanSize + stats.manualAllocSize += spanSize + for x := core.Address(s.Field("manualFreeList").Cast(p.findType("uintptr")).Uintptr()); x != 0; x = p.proc.ReadPtr(x) { + stats.manualAllocSize -= elemSize + stats.manualFreeSize += elemSize } } } - if mheap.HasField("pages") { // go1.14+ - // 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") - arenaBaseOffset := p.getArenaBaseOffset() - pallocChunkBytes := p.rtConstants["pallocChunkBytes"] - pallocChunksL1Bits := p.rtConstants["pallocChunksL1Bits"] - pallocChunksL2Bits := p.rtConstants["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") - if baseField.IsStruct() { // go 1.15+ - baseField = baseField.Field("a") - } - base := core.Address(baseField.Uintptr()) - limitField := r.Field("limit") - if limitField.IsStruct() { // go 1.15+ - limitField = limitField.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++ { - 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++ { - 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") - freeSpanSize += int64(bits.OnesCount64(pcache.Field("cache").Uint64())) * pageSize - releasedSpanSize += int64(bits.OnesCount64(pcache.Field("scav").Uint64())) * pageSize - } - } - p.stats = &Stats{"all", all, []*Stats{ - &Stats{"text", text, nil}, - &Stats{"readonly", readOnly, nil}, - &Stats{"data", data, nil}, - &Stats{"bss", bss, nil}, - &Stats{"heap", heap, []*Stats{ - &Stats{"in use spans", inUseSpanSize, []*Stats{ - &Stats{"alloc", allocSize, nil}, - &Stats{"free", freeSize, nil}, - &Stats{"round", spanRoundSize, nil}, - }}, - &Stats{"manual spans", manualSpanSize, []*Stats{ - &Stats{"alloc", manualAllocSize, nil}, - &Stats{"free", manualFreeSize, nil}, - }}, - &Stats{"free spans", freeSpanSize, []*Stats{ - &Stats{"retained", freeSpanSize - releasedSpanSize, nil}, - &Stats{"released", releasedSpanSize, nil}, - }}, - }}, - &Stats{"ptr bitmap", bitmap, nil}, - &Stats{"span table", spanTable, nil}, - }} - - var check func(*Stats) - check = func(s *Stats) { - if len(s.Children) == 0 { - return - } - var sum int64 - for _, c := range s.Children { - sum += c.Size - } - if sum != s.Size { - panic(fmt.Sprintf("check failed for %s: %d vs %d", s.Name, s.Size, sum)) - } - for _, c := range s.Children { - check(c) + // 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["pallocChunkBytes"] + pallocChunksL1Bits := p.rtConsts["pallocChunksL1Bits"] + pallocChunksL2Bits := p.rtConsts["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 + } } } - check(p.stats) + + // 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 (p *Process) readGs() { - // TODO: figure out how to "flush" running Gs. +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 := p.readG(r) + g, err := readGoroutine(p, r, dwarfVars) + if err != nil { + return nil, fmt.Errorf("reading goroutine: %v", err) + } if g == nil { continue } - p.goroutines = append(p.goroutines, g) + goroutines = append(goroutines, g) } + return goroutines, nil } -func (p *Process) readG(r region) *Goroutine { +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()) @@ -756,41 +607,51 @@ } } } - st := r.Field("atomicstatus") - if st.IsStruct() && st.HasField("value") { // go1.20+ - st = st.Field("value") - } + st := r.Field("atomicstatus").Field("value") status := st.Uint32() - status &^= uint32(p.rtConstants["_Gscan"]) + status &^= uint32(p.rtConsts["_Gscan"]) var sp, pc core.Address switch status { - case uint32(p.rtConstants["_Gidle"]): - return g - case uint32(p.rtConstants["_Grunnable"]), uint32(p.rtConstants["_Gwaiting"]): + case uint32(p.rtConsts["_Gidle"]): + return g, nil + case uint32(p.rtConsts["_Grunnable"]), uint32(p.rtConsts["_Gwaiting"]): sched := r.Field("sched") sp = core.Address(sched.Field("sp").Uintptr()) pc = core.Address(sched.Field("pc").Uintptr()) - case uint32(p.rtConstants["_Grunning"]): + case uint32(p.rtConsts["_Grunning"]): sp = osT.SP() pc = osT.PC() // TODO: back up to the calling frame? - case uint32(p.rtConstants["_Gsyscall"]): + case uint32(p.rtConsts["_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.rtConstants["_Gdead"]): - return nil + case uint32(p.rtConsts["_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 + 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. + isCrashFrame := false for { - f, err := p.readFrame(sp, pc) + f, err := readFrame(p, sp, pc) if err != nil { - fmt.Printf("warning: giving up on backtrace: %v\n", err) + 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" { @@ -801,16 +662,193 @@ } 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 + } + conservativeLiveness := isCrashFrame && len(f.Live) == 0 + + // 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 addr == 0 { + // TODO(mknyszek): Handle composites via pieces returned by the stack program. + continue + } + if addr != 0 && len(pieces) == 1 && v.typ.Kind == KindPtr { + r := &Root{ + Name: v.name, + RegName: regnum.AMD64ToName(pieces[0].Val), + RegValue: core.Address(addr), + Type: v.typ, + Frame: f, + } + g.regRoots = append(g.regRoots, r) + } else { + r := &Root{ + Name: v.name, + Addr: core.Address(addr), + Type: v.typ, + Frame: f, + } + f.roots = append(f.roots, r) + + if conservativeLiveness { + // This is a frame that is likely not at a safe point, so the liveness + // map is almost certainly empty. If it's not, then great, we can use + // that for precise information, but otherwise, let's be conservative + // and populate liveness data from the DWARF. + r.Type.forEachPointer(0, p.proc.PtrSize(), func(off int64) { + f.Live[r.Addr.Add(off)] = true + }) + } + + // Remove this variable from the set of unnamed pointers. + for a := r.Addr; a < r.Addr.Add(r.Type.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 { + r := &Root{ + Name: "unk", + Addr: a, + Type: p.findType("unsafe.Pointer"), + Frame: f, + } + f.roots = append(f.roots, r) + } + + // 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 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 := p.proc.ReadPtr(sp.Add(16)) // 3rd arg - //ctxt is a *ucontext + + // ctxt is a *ucontext mctxt := ctxt.Add(5 * 8) // mctxt is a *mcontext - sp = p.proc.ReadPtr(mctxt.Add(15 * 8)) - pc = p.proc.ReadPtr(mctxt.Add(16 * 8)) // 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) + + isCrashFrame = true } else { sp = f.max pc = core.Address(p.proc.ReadUintptr(sp - 8)) // TODO:amd64 only @@ -826,10 +864,10 @@ pc = core.Address(sched.Field("pc").Uintptr()) } } - return g + return g, nil } -func (p *Process) readFrame(sp, pc core.Address) (*Frame, error) { +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) @@ -845,13 +883,13 @@ // Find live ptrs in locals live := map[core.Address]bool{} - if x := int(p.rtConstants["_FUNCDATA_LocalsPointerMaps"]); x < len(f.funcdata) { + if x := int(p.rtConsts["_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, a: addr, typ: p.findType("runtime.stackmap")} + locals := region{p: p.proc, a: addr, typ: p.findType("runtime.stackmap")} n := locals.Field("n").Int32() // # of bitmaps nbit := locals.Field("nbit").Int32() // # of bits per bitmap idx, err := f.stackMap.find(off) @@ -874,10 +912,10 @@ } } // Same for args - if x := int(p.rtConstants["_FUNCDATA_ArgsPointerMaps"]); x < len(f.funcdata) { + if x := int(p.rtConsts["_FUNCDATA_ArgsPointerMaps"]); x < len(f.funcdata) { addr := f.funcdata[x] if addr != 0 { - args := region{p: p, a: addr, typ: p.findType("runtime.stackmap")} + args := region{p: p.proc, a: addr, typ: p.findType("runtime.stackmap")} n := args.Field("n").Int32() // # of bitmaps nbit := args.Field("nbit").Int32() // # of bits per bitmap idx, err := f.stackMap.find(off) @@ -909,17 +947,61 @@ // by category. // We maintain the invariant that, if there are children, // Size == sum(c.Size for c in Children). -type Stats struct { - Name string - Size int64 - Children []*Stats +type Statistic struct { + Name string + Value int64 + + children map[string]*Statistic } -func (s *Stats) Child(name string) *Stats { - for _, c := range s.Children { - if c.Name == name { - return c +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 nil + 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 + } + } + } }
diff --git a/internal/gocore/region.go b/internal/gocore/region.go index df09865..b4ef594 100644 --- a/internal/gocore/region.go +++ b/internal/gocore/region.go
@@ -11,7 +11,7 @@ // Note that it is the type of the thing in the region, // not the type of the reference to the region. type region struct { - p *Process + p *core.Process a core.Address typ *Type } @@ -21,28 +21,28 @@ if r.typ.Kind != KindPtr { panic("can't ask for the Address of a non-pointer " + r.typ.Name) } - return r.p.proc.ReadPtr(r.a) + return r.p.ReadPtr(r.a) } // Int returns the int value stored in r. func (r region) Int() int64 { - if r.typ.Kind != KindInt || r.typ.Size != r.p.proc.PtrSize() { + if r.typ.Kind != KindInt || r.typ.Size != r.p.PtrSize() { panic("not an int: " + r.typ.Name) } - return r.p.proc.ReadInt(r.a) + return r.p.ReadInt(r.a) } // Uintptr returns the uintptr value stored in r. func (r region) Uintptr() uint64 { - if r.typ.Kind != KindUint || r.typ.Size != r.p.proc.PtrSize() { + if r.typ.Kind != KindUint || r.typ.Size != r.p.PtrSize() { panic("not a uintptr: " + r.typ.Name) } - return r.p.proc.ReadUintptr(r.a) + return r.p.ReadUintptr(r.a) } // Cast the region to the given type. -func (r region) Cast(typ string) region { - return region{p: r.p, a: r.a, typ: r.p.findType(typ)} +func (r region) Cast(typ *Type) region { + return region{p: r.p, a: r.a, typ: typ} } // Deref loads from a pointer. r must contain a pointer. @@ -53,7 +53,7 @@ if r.typ.Elem == nil { panic("can't deref unsafe.Pointer") } - p := r.p.proc.ReadPtr(r.a) + p := r.p.ReadPtr(r.a) return region{p: r.p, a: p, typ: r.typ.Elem} } @@ -63,7 +63,7 @@ if r.typ.Kind != KindUint || r.typ.Size != 8 { panic("bad uint64 type " + r.typ.Name) } - return r.p.proc.ReadUint64(r.a) + return r.p.ReadUint64(r.a) } // Uint32 returns the uint32 value stored in r. @@ -72,7 +72,7 @@ if r.typ.Kind != KindUint || r.typ.Size != 4 { panic("bad uint32 type " + r.typ.Name) } - return r.p.proc.ReadUint32(r.a) + return r.p.ReadUint32(r.a) } // Int32 returns the int32 value stored in r. @@ -81,7 +81,7 @@ if r.typ.Kind != KindInt || r.typ.Size != 4 { panic("bad int32 type " + r.typ.Name) } - return r.p.proc.ReadInt32(r.a) + return r.p.ReadInt32(r.a) } // Uint16 returns the uint16 value stored in r. @@ -90,7 +90,7 @@ if r.typ.Kind != KindUint || r.typ.Size != 2 { panic("bad uint16 type " + r.typ.Name) } - return r.p.proc.ReadUint16(r.a) + return r.p.ReadUint16(r.a) } // Uint8 returns the uint8 value stored in r. @@ -99,7 +99,7 @@ if r.typ.Kind != KindUint || r.typ.Size != 1 { panic("bad uint8 type " + r.typ.Name) } - return r.p.proc.ReadUint8(r.a) + return r.p.ReadUint8(r.a) } // Bool returns the bool value stored in r. @@ -108,7 +108,7 @@ if r.typ.Kind != KindBool { panic("bad bool type " + r.typ.Name) } - return r.p.proc.ReadUint8(r.a) != 0 + return r.p.ReadUint8(r.a) != 0 } // String returns the value of the string stored in r. @@ -116,10 +116,10 @@ if r.typ.Kind != KindString { panic("bad string type " + r.typ.Name) } - p := r.p.proc.ReadPtr(r.a) - n := r.p.proc.ReadUintptr(r.a.Add(r.p.proc.PtrSize())) + p := r.p.ReadPtr(r.a) + n := r.p.ReadUintptr(r.a.Add(r.p.PtrSize())) b := make([]byte, n) - r.p.proc.ReadAt(b, p) + r.p.ReadAt(b, p) return string(b) } @@ -129,7 +129,7 @@ if r.typ.Kind != KindSlice { panic("can't index a non-slice") } - p := r.p.proc.ReadPtr(r.a) + p := r.p.ReadPtr(r.a) return region{p: r.p, a: p.Add(n * r.typ.Elem.Size), typ: r.typ.Elem} } @@ -138,7 +138,7 @@ if r.typ.Kind != KindSlice { panic("can't Ptr a non-slice") } - return region{p: r.p, a: r.a, typ: &Type{Name: "*" + r.typ.Name[2:], Size: r.p.proc.PtrSize(), Kind: KindPtr, Elem: r.typ.Elem}} + return region{p: r.p, a: r.a, typ: &Type{Name: "*" + r.typ.Name[2:], Size: r.p.PtrSize(), Kind: KindPtr, Elem: r.typ.Elem}} } // SliceLen returns the length of a slice. r must contain a slice. @@ -146,7 +146,7 @@ if r.typ.Kind != KindSlice { panic("can't len a non-slice") } - return r.p.proc.ReadInt(r.a.Add(r.p.proc.PtrSize())) + return r.p.ReadInt(r.a.Add(r.p.PtrSize())) } // SliceCap returns the capacity of a slice. r must contain a slice. @@ -154,7 +154,7 @@ if r.typ.Kind != KindSlice { panic("can't cap a non-slice") } - return r.p.proc.ReadInt(r.a.Add(2 * r.p.proc.PtrSize())) + return r.p.ReadInt(r.a.Add(2 * r.p.PtrSize())) } // Field returns the part of r which contains the field f.
diff --git a/internal/gocore/root.go b/internal/gocore/root.go index 2c7aade..f44dea0 100644 --- a/internal/gocore/root.go +++ b/internal/gocore/root.go
@@ -10,9 +10,11 @@ // A Root is an area of memory that might have pointers into the Go heap. type Root struct { - Name string - Addr core.Address - Type *Type // always non-nil + Name string + Addr core.Address + RegName string + RegValue core.Address // set if Addr == 0 + Type *Type // always non-nil // Frame, if non-nil, points to the frame in which this root lives. // Roots with non-nil Frame fields refer to local variables on a stack. // A stack root might be a large type, with some of its fields live and
diff --git a/internal/gocore/testdata/1.12.zip b/internal/gocore/testdata/1.12.zip deleted file mode 100644 index 4d2a74a..0000000 --- a/internal/gocore/testdata/1.12.zip +++ /dev/null Binary files differ
diff --git a/internal/gocore/testdata/1.13.3.zip b/internal/gocore/testdata/1.13.3.zip deleted file mode 100644 index 5e13ea6..0000000 --- a/internal/gocore/testdata/1.13.3.zip +++ /dev/null Binary files differ
diff --git a/internal/gocore/testdata/1.13.zip b/internal/gocore/testdata/1.13.zip deleted file mode 100644 index 332944d..0000000 --- a/internal/gocore/testdata/1.13.zip +++ /dev/null Binary files differ
diff --git a/internal/gocore/testdata/1.14.zip b/internal/gocore/testdata/1.14.zip deleted file mode 100644 index 5f225ce..0000000 --- a/internal/gocore/testdata/1.14.zip +++ /dev/null Binary files differ
diff --git a/internal/gocore/testdata/1.16.zip b/internal/gocore/testdata/1.16.zip deleted file mode 100644 index 05fb11a..0000000 --- a/internal/gocore/testdata/1.16.zip +++ /dev/null Binary files differ
diff --git a/internal/gocore/testdata/1.17.zip b/internal/gocore/testdata/1.17.zip deleted file mode 100644 index f96ee79..0000000 --- a/internal/gocore/testdata/1.17.zip +++ /dev/null Binary files differ
diff --git a/internal/gocore/testdata/1.18.zip b/internal/gocore/testdata/1.18.zip deleted file mode 100644 index 105a586..0000000 --- a/internal/gocore/testdata/1.18.zip +++ /dev/null Binary files differ
diff --git a/internal/gocore/testdata/1.19.zip b/internal/gocore/testdata/1.19.zip deleted file mode 100644 index cb02b84..0000000 --- a/internal/gocore/testdata/1.19.zip +++ /dev/null Binary files differ
diff --git a/internal/gocore/testdata/core b/internal/gocore/testdata/core deleted file mode 100644 index 55318de..0000000 --- a/internal/gocore/testdata/core +++ /dev/null Binary files differ
diff --git a/internal/gocore/testdata/core1.10 b/internal/gocore/testdata/core1.10 deleted file mode 100644 index 903c176..0000000 --- a/internal/gocore/testdata/core1.10 +++ /dev/null Binary files differ
diff --git a/internal/gocore/testdata/core1.11 b/internal/gocore/testdata/core1.11 deleted file mode 100644 index e020b01..0000000 --- a/internal/gocore/testdata/core1.11 +++ /dev/null Binary files differ
diff --git a/internal/gocore/testdata/coretest/test.go b/internal/gocore/testdata/coretest/test.go index d3ebaf9..d78fa55 100644 --- a/internal/gocore/testdata/coretest/test.go +++ b/internal/gocore/testdata/coretest/test.go
@@ -1,5 +1,9 @@ package main +import ( + "runtime" +) + // Large is an object that (since Go 1.22) is allocated in a span that has a // non-nil largeType field. Meaning it must be (>maxSmallSize-mallocHeaderSize). // At the time of writing this is (32768 - 8). @@ -19,4 +23,5 @@ o.arr[14] = 0xDE // Prevent a future smart compiler from allocating o directly on pings stack. _ = *(*int)(nil) + runtime.KeepAlive(&o) }
diff --git a/internal/gocore/testdata/runtimetype.zip b/internal/gocore/testdata/runtimetype.zip deleted file mode 100644 index f6a988a..0000000 --- a/internal/gocore/testdata/runtimetype.zip +++ /dev/null Binary files differ
diff --git a/internal/gocore/testdata/tmp/test b/internal/gocore/testdata/tmp/test deleted file mode 100755 index f3353b2..0000000 --- a/internal/gocore/testdata/tmp/test +++ /dev/null Binary files differ
diff --git a/internal/gocore/testdata/tmp/test1.10 b/internal/gocore/testdata/tmp/test1.10 deleted file mode 100755 index 6839de6..0000000 --- a/internal/gocore/testdata/tmp/test1.10 +++ /dev/null Binary files differ
diff --git a/internal/gocore/testdata/tmp/test1.11 b/internal/gocore/testdata/tmp/test1.11 deleted file mode 100755 index 0da844f..0000000 --- a/internal/gocore/testdata/tmp/test1.11 +++ /dev/null Binary files differ
diff --git a/internal/gocore/type.go b/internal/gocore/type.go index b3a4b27..c983e64 100644 --- a/internal/gocore/type.go +++ b/internal/gocore/type.go
@@ -123,116 +123,74 @@ // return the number of bytes of the variable int and its value, // which means the length of a name. func readNameLen(p *Process, a core.Address) (int64, int64) { - if p.is117OrGreater { - v := 0 - for i := 0; ; i++ { - x := p.proc.ReadUint8(a.Add(int64(i + 1))) - v += int(x&0x7f) << (7 * i) - if x&0x80 == 0 { - return int64(i + 1), int64(v) - } + v := 0 + for i := 0; ; i++ { + x := p.proc.ReadUint8(a.Add(int64(i + 1))) + v += int(x&0x7f) << (7 * i) + if x&0x80 == 0 { + return int64(i + 1), int64(v) } - } else { - n1 := p.proc.ReadUint8(a.Add(1)) - n2 := p.proc.ReadUint8(a.Add(2)) - n := uint16(n1)<<8 + uint16(n2) - return 2, int64(n) } } // runtimeType is a thin wrapper around a runtime._type (AKA abi.Type) region // that abstracts over the name changes seen in Go 1.21. type runtimeType struct { - reg region - hasAbiType bool // True if Go 1.21+ + reg region } // findRuntimeType finds either abi.Type (Go 1.21+) or runtime._type. func (p *Process) findRuntimeType(a core.Address) runtimeType { - typ := runtimeType{ - reg: region{p: p, a: a, typ: p.tryFindType("abi.Type")}, - hasAbiType: true, + return runtimeType{ + reg: region{p: p.proc, a: a, typ: p.tryFindType("internal/abi.Type")}, } - if typ.reg.typ == nil { - typ.reg.typ = p.findType("runtime._type") - typ.hasAbiType = false - } - return typ } // Size_ is either abi.Type.Size_ or runtime._type.Size_. func (r runtimeType) Size_() int64 { - if !r.hasAbiType { - return int64(r.reg.Field("size").Uintptr()) - } return int64(r.reg.Field("Size_").Uintptr()) } // TFlag is either abi.Type.TFlag or runtime._type.TFlag. func (r runtimeType) TFlag() uint8 { - if !r.hasAbiType { - return r.reg.Field("tflag").Uint8() - } return r.reg.Field("TFlag").Uint8() } // Str is either abi.Type.Str or runtime._type.Str. func (r runtimeType) Str() int64 { - if !r.hasAbiType { - return int64(r.reg.Field("str").Int32()) - } return int64(r.reg.Field("Str").Int32()) } // PtrBytes is either abi.Type.PtrBytes or runtime._type.PtrBytes. func (r runtimeType) PtrBytes() int64 { - if !r.hasAbiType { - return int64(r.reg.Field("ptrdata").Uintptr()) - } return int64(r.reg.Field("PtrBytes").Uintptr()) } // Kind_ is either abi.Type.Kind_ or runtime._type.Kind_. func (r runtimeType) Kind_() uint8 { - if !r.hasAbiType { - return r.reg.Field("kind").Uint8() - } return r.reg.Field("Kind_").Uint8() } // GCData is either abi.Type.GCData or runtime._type.GCData. func (r runtimeType) GCData() core.Address { - if !r.hasAbiType { - return r.reg.Field("gcdata").Address() - } return r.reg.Field("GCData").Address() } // runtimeItab is a thin wrapper around a abi.ITab (used to be runtime.itab). It // abstracts over name/package changes in Go 1.21. type runtimeItab struct { - typ *Type - hasAbiITab bool // True if Go 1.21+ + typ *Type } // findItab finds either abi.ITab (Go 1.21+) or runtime.itab. func (p *Process) findItab() runtimeItab { - typ := runtimeItab{ - typ: p.tryFindType("abi.ITab"), - hasAbiITab: true, + return runtimeItab{ + typ: p.tryFindType("internal/abi.ITab"), } - if typ.typ == nil { - typ.typ = p.findType("runtime.itab") - typ.hasAbiITab = false - } - return typ } // Type is the field representing either abi.ITab.Type or runtime.itab._type. func (r runtimeItab) Type() *Field { - if !r.hasAbiITab { - return r.typ.field("_type") - } return r.typ.field("Type") } @@ -241,7 +199,7 @@ // If "d" is 0, just return *Type and not to do the interface disambiguation. // Guaranteed to return a non-nil *Type. func (p *Process) runtimeType2Type(a core.Address, d core.Address) *Type { - if t := p.runtimeMap[a]; t != nil { + if t := p.rtTypeMap[a]; t != nil { return t } @@ -266,7 +224,7 @@ b := make([]byte, n) p.proc.ReadAt(b, x.Add(i+1)) name = string(b) - if r.TFlag()&uint8(p.rtConstants["tflagExtraStar"]) != 0 { + if r.TFlag()&uint8(p.rtConsts["tflagExtraStar"]) != 0 { name = name[1:] } } else { @@ -280,7 +238,7 @@ ptrSize := p.proc.PtrSize() nptrs := int64(r.PtrBytes()) / ptrSize var ptrs []int64 - if r.Kind_()&uint8(p.rtConstants["kindGCProg"]) == 0 { + if r.Kind_()&uint8(p.rtConsts["kindGCProg"]) == 0 { gcdata := r.GCData() for i := int64(0); i < nptrs; i++ { if p.proc.ReadUint8(gcdata.Add(i/8))>>uint(i%8)&1 != 0 { @@ -294,43 +252,11 @@ // Find a Type that matches this type. // (The matched type will be one constructed from DWARF info.) // It must match name, size, and pointer bits. - var candidates []*Type - for _, t := range p.runtimeNameMap[name] { - if size == t.Size && equal(ptrs, t.ptrs()) { - candidates = append(candidates, t) - } + t := p.rtTypeByName[name] + if t != nil && (size != t.Size || !equal(ptrs, t.ptrs())) { + t = nil } - // There may be multiple candidates, when they are the pointers to the same type name, - // in the same package name, but in the different package paths. eg. path-1/pkg.Foo and path-2/pkg.Foo. - // Match the object size may be a proper choice, just for try best, since we have no other choices. - // If the interface is of type T, for direct interfaces, that pointer points to a T.Elem. - if d != 0 && len(candidates) > 1 && !ifaceIndir(a, p) { - ptr := p.proc.ReadPtr(d) - obj, off := p.FindObject(ptr) - // only useful while it point to the head of an object, - // otherwise, the GC object size should bigger than the size of the type. - if obj != 0 && off == 0 { - sz := p.Size(obj) - var tmp []*Type - for _, t := range candidates { - if t.Elem != nil && t.Elem.Size == sz { - tmp = append(tmp, t) - } - } - if len(tmp) > 0 { - candidates = tmp - } - } - } - var t *Type - if len(candidates) > 0 { - // If a runtime type matches more than one DWARF type, - // pick one arbitrarily. - // This looks mostly harmless. DWARF has some redundant entries. - // For example, [32]uint8 appears twice. - // TODO: investigate the reason for this duplication. - t = candidates[0] - } else { + if t == nil { // There's no corresponding DWARF type. Make our own. t = &Type{Name: name, Size: size, Kind: KindStruct} n := t.Size / ptrSize @@ -360,9 +286,12 @@ // TODO: tail of <ptrSize data. } } - // Memoize. - p.runtimeMap[a] = t + // Memoize. + if p.rtTypeMap == nil { + p.rtTypeMap = make(map[core.Address]*Type) + } + p.rtTypeMap[a] = t return t } @@ -476,181 +405,186 @@ // typeHeap tries to label all the heap objects with types. func (p *Process) typeHeap() { - p.initTypeHeap.Do(func() { - // Type info for the start of each object. a.k.a. "0 offset" typings. - p.types = make([]typeInfo, p.nObj) + p.initTypeHeap.Do(p.doTypeHeap) +} - // Type info for the interior of objects, a.k.a. ">0 offset" typings. - // Type information is arranged in chunks. Chunks are stored in an - // arbitrary order, and are guaranteed to not overlap. If types are - // equal, chunks are also guaranteed not to abut. - // Interior typings are kept separate because they hopefully are rare. - // TODO: They aren't really that rare. On some large heaps I tried - // ~50% of objects have an interior pointer into them. - // Keyed by object index. - interior := map[int][]typeChunk{} +func (p *Process) doTypeHeap() { + // Type info for the start of each object. a.k.a. "0 offset" typings. + p.types = make([]typeInfo, p.nObj) - // Typings we know about but haven't scanned yet. - type workRecord struct { - a core.Address - t *Type - r int64 + // Type info for the interior of objects, a.k.a. ">0 offset" typings. + // Type information is arranged in chunks. Chunks are stored in an + // arbitrary order, and are guaranteed to not overlap. If types are + // equal, chunks are also guaranteed not to abut. + // Interior typings are kept separate because they hopefully are rare. + // TODO: They aren't really that rare. On some large heaps I tried + // ~50% of objects have an interior pointer into them. + // Keyed by object index. + interior := map[int][]typeChunk{} + + // Typings we know about but haven't scanned yet. + type workRecord struct { + a core.Address + t *Type + r int64 + } + var work []workRecord + + // add records the fact that we know the object at address a has + // r copies of type t. + add := func(a core.Address, t *Type, r int64) { + if a == 0 { // nil pointer + return } - var work []workRecord - - // add records the fact that we know the object at address a has - // r copies of type t. - add := func(a core.Address, t *Type, r int64) { - if a == 0 { // nil pointer - return + i, off := p.findObjectIndex(a) + if i < 0 { // pointer doesn't point to an object in the Go heap + return + } + if off == 0 { + // We have a 0-offset typing. Replace existing 0-offset typing + // if the new one is larger. + ot := p.types[i].t + or := p.types[i].r + if ot == nil || r*t.Size > or*ot.Size { + if t == ot { + // Scan just the new section. + work = append(work, workRecord{ + a: a.Add(or * ot.Size), + t: t, + r: r - or, + }) + } else { + // Rescan the whole typing using the updated type. + work = append(work, workRecord{ + a: a, + t: t, + r: r, + }) + } + p.types[i].t = t + p.types[i].r = r } - i, off := p.findObjectIndex(a) - if i < 0 { // pointer doesn't point to an object in the Go heap - return - } - if off == 0 { - // We have a 0-offset typing. Replace existing 0-offset typing - // if the new one is larger. - ot := p.types[i].t - or := p.types[i].r - if ot == nil || r*t.Size > or*ot.Size { - if t == ot { - // Scan just the new section. - work = append(work, workRecord{ - a: a.Add(or * ot.Size), - t: t, - r: r - or, - }) - } else { - // Rescan the whole typing using the updated type. - work = append(work, workRecord{ - a: a, - t: t, - r: r, - }) - } - p.types[i].t = t - p.types[i].r = r - } - return - } + return + } - // Add an interior typing to object #i. - c := typeChunk{off: off, t: t, r: r} + // Add an interior typing to object #i. + c := typeChunk{off: off, t: t, r: r} - // Merge the given typing into the chunks we already know. - // TODO: this could be O(n) per insert if there are lots of internal pointers. - chunks := interior[i] - newchunks := chunks[:0] - addWork := true - for _, d := range chunks { - if c.max() <= d.min() || c.min() >= d.max() { - // c does not overlap with d. - if c.t == d.t && (c.max() == d.min() || c.min() == d.max()) { - // c and d abut and share the same base type. Merge them. - c = c.merge(d) - continue - } - // Keep existing chunk d. - // Overwrites chunks slice, but we're only merging chunks so it - // can't overwrite to-be-processed elements. - newchunks = append(newchunks, d) - continue - } - // There is some overlap. There are a few possibilities: - // 1) One is completely contained in the other. - // 2) Both are slices of a larger underlying array. - // 3) Some unsafe trickery has happened. Non-containing overlap - // can only happen in safe Go via case 2. - if c.min() >= d.min() && c.max() <= d.max() { - // 1a: c is contained within the existing chunk d. - // Note that there can be a type mismatch between c and d, - // but we don't care. We use the larger chunk regardless. - c = d - addWork = false // We've already scanned all of c. - continue - } - if d.min() >= c.min() && d.max() <= c.max() { - // 1b: existing chunk d is completely covered by c. - continue - } - if c.t == d.t && c.matchingAlignment(d) { - // Union two regions of the same base type. Case 2 above. + // Merge the given typing into the chunks we already know. + // TODO: this could be O(n) per insert if there are lots of internal pointers. + chunks := interior[i] + newchunks := chunks[:0] + addWork := true + for _, d := range chunks { + if c.max() <= d.min() || c.min() >= d.max() { + // c does not overlap with d. + if c.t == d.t && (c.max() == d.min() || c.min() == d.max()) { + // c and d abut and share the same base type. Merge them. c = c.merge(d) continue } - if c.size() < d.size() { - // Keep the larger of the two chunks. - c = d - addWork = false - } - } - // Add new chunk to list of chunks for object. - newchunks = append(newchunks, c) - interior[i] = newchunks - // Also arrange to scan the new chunk. Note that if we merged - // with an existing chunk (or chunks), those will get rescanned. - // Duplicate work, but that's ok. TODO: but could be expensive. - if addWork { - work = append(work, workRecord{ - a: a.Add(c.off - off), - t: c.t, - r: c.r, - }) - } - } - - // Get typings starting at roots. - fr := &frameReader{p: p} - p.ForEachRoot(func(r *Root) bool { - if r.Frame != nil { - fr.live = r.Frame.Live - p.typeObject(r.Addr, r.Type, fr, add) - } else { - p.typeObject(r.Addr, r.Type, p.proc, add) - } - return true - }) - - // Propagate typings through the heap. - for len(work) > 0 { - c := work[len(work)-1] - work = work[:len(work)-1] - switch c.t.Kind { - case KindBool, KindInt, KindUint, KindFloat, KindComplex: - // Don't do O(n) function calls for big primitive slices + // Keep existing chunk d. + // Overwrites chunks slice, but we're only merging chunks so it + // can't overwrite to-be-processed elements. + newchunks = append(newchunks, d) continue } - for i := int64(0); i < c.r; i++ { - p.typeObject(c.a.Add(i*c.t.Size), c.t, p.proc, add) + // There is some overlap. There are a few possibilities: + // 1) One is completely contained in the other. + // 2) Both are slices of a larger underlying array. + // 3) Some unsafe trickery has happened. Non-containing overlap + // can only happen in safe Go via case 2. + if c.min() >= d.min() && c.max() <= d.max() { + // 1a: c is contained within the existing chunk d. + // Note that there can be a type mismatch between c and d, + // but we don't care. We use the larger chunk regardless. + c = d + addWork = false // We've already scanned all of c. + continue + } + if d.min() >= c.min() && d.max() <= c.max() { + // 1b: existing chunk d is completely covered by c. + continue + } + if c.t == d.t && c.matchingAlignment(d) { + // Union two regions of the same base type. Case 2 above. + c = c.merge(d) + continue + } + if c.size() < d.size() { + // Keep the larger of the two chunks. + c = d + addWork = false } } + // Add new chunk to list of chunks for object. + newchunks = append(newchunks, c) + interior[i] = newchunks + // Also arrange to scan the new chunk. Note that if we merged + // with an existing chunk (or chunks), those will get rescanned. + // Duplicate work, but that's ok. TODO: but could be expensive. + if addWork { + work = append(work, workRecord{ + a: a.Add(c.off - off), + t: c.t, + r: c.r, + }) + } + } - // Merge any interior typings with the 0-offset typing. - for i, chunks := range interior { - t := p.types[i].t - r := p.types[i].r - if t == nil { - continue // We have no type info at offset 0. - } - for _, c := range chunks { - if c.max() <= r*t.Size { - // c is completely contained in the 0-offset typing. Ignore it. - continue - } - if c.min() <= r*t.Size { - // Typings overlap or abut. Extend if we can. - if c.t == t && c.min()%t.Size == 0 { - r = c.max() / t.Size - p.types[i].r = r - } - continue - } - // Note: at this point we throw away any interior typings that weren't - // merged with the 0-offset typing. TODO: make more use of this info. - } + // Get typings starting at roots. + fr := &frameReader{p: p} + p.ForEachRoot(func(r *Root) bool { + if r.Frame != nil { + fr.live = r.Frame.Live + p.typeObject(r.Addr, r.Type, fr, add) + } else if r.Addr == 0 && r.Type.Kind == KindPtr && r.Type.Elem != nil { + p.typeObject(r.RegValue, r.Type.Elem, fr, add) + } else { + p.typeObject(r.Addr, r.Type, p.proc, add) } + return true }) + + // Propagate typings through the heap. + for len(work) > 0 { + c := work[len(work)-1] + work = work[:len(work)-1] + switch c.t.Kind { + case KindBool, KindInt, KindUint, KindFloat, KindComplex: + // Don't do O(n) function calls for big primitive slices + continue + } + for i := int64(0); i < c.r; i++ { + p.typeObject(c.a.Add(i*c.t.Size), c.t, p.proc, add) + } + } + + // Merge any interior typings with the 0-offset typing. + for i, chunks := range interior { + t := p.types[i].t + r := p.types[i].r + if t == nil { + continue // We have no type info at offset 0. + } + for _, c := range chunks { + if c.max() <= r*t.Size { + // c is completely contained in the 0-offset typing. Ignore it. + continue + } + if c.min() <= r*t.Size { + // Typings overlap or abut. Extend if we can. + if c.t == t && c.min()%t.Size == 0 { + r = c.max() / t.Size + p.types[i].r = r + } + continue + } + // Note: at this point we throw away any interior typings that weren't + // merged with the 0-offset typing. TODO: make more use of this info. + } + } + } type reader interface { @@ -689,11 +623,7 @@ } else { typeName = matches[1] + "." + matches[3] } - s := p.runtimeNameMap[typeName] - if len(s) > 0 { - // TODO: filter with the object size when there are multiple candidates. - return s[0] - } + return p.rtTypeByName[typeName] } return nil } @@ -701,7 +631,7 @@ // ifaceIndir reports whether t is stored indirectly in an interface value. func ifaceIndir(t core.Address, p *Process) bool { typr := p.findRuntimeType(t) - return typr.Kind_()&uint8(p.rtConstants["kindDirectIface"]) == 0 + return typr.Kind_()&uint8(p.rtConsts["kindDirectIface"]) == 0 } // typeObject takes an address and a type for the data at that address. @@ -849,3 +779,27 @@ panic(fmt.Sprintf("unknown type kind %s\n", t.Kind)) } } + +// forEachPointer iterates over each pointer in the type, emitting the offset of the +// pointer in the type. +func (t *Type) forEachPointer(baseOffset, ptrSize int64, yield func(off int64)) { + switch t.Kind { + case KindBool, KindInt, KindUint, KindFloat, KindComplex: + // Nothing to do + case KindEface, KindIface: + yield(ptrSize) + case KindString, KindSlice, KindPtr, KindFunc: + yield(baseOffset) + case KindArray: + n := t.Elem.Size + for i := int64(0); i < t.Count; i++ { + t.Elem.forEachPointer(baseOffset+i*n, ptrSize, yield) + } + case KindStruct: + for _, f := range t.Fields { + f.Type.forEachPointer(baseOffset+f.Off, ptrSize, yield) + } + default: + panic(fmt.Sprintf("unknown type kind %s\n", t.Kind)) + } +}