cmd: add coreview tool

Add a sample application to use the gocore package.

Change-Id: I717cdd626531a0d9252c5aac5bcd1acdf357c0a1
Reviewed-on: https://go-review.googlesource.com/92418
Reviewed-by: Peter Weinberger <pjw@google.com>
diff --git a/cmd/viewcore/html.go b/cmd/viewcore/html.go
new file mode 100644
index 0000000..f24223d
--- /dev/null
+++ b/cmd/viewcore/html.go
@@ -0,0 +1,408 @@
+// Copyright 2018 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+	"fmt"
+	"html"
+	"math"
+	"net/http"
+	"strconv"
+
+	"golang.org/x/debug/core"
+	"golang.org/x/debug/gocore"
+)
+
+func serveHTML(c *gocore.Process) {
+	http.HandleFunc("/object", func(w http.ResponseWriter, r *http.Request) {
+		objs, ok := r.URL.Query()["o"]
+		if !ok || len(objs) != 1 {
+			fmt.Fprintf(w, "wrong or missing o= object specification")
+			return
+		}
+		obj, err := strconv.ParseInt(objs[0], 16, 64)
+		if err != nil {
+			fmt.Fprintf(w, "unparseable o= object specification: %s", err)
+			return
+		}
+		a := core.Address(obj)
+		x, _ := c.FindObject(a)
+		if x == 0 {
+			fmt.Fprintf(w, "can't find object at %x", a)
+			return
+		}
+		addr := c.Addr(x)
+		size := c.Size(x)
+		typ, repeat := c.Type(x)
+
+		tableStyle(w)
+		fmt.Fprintf(w, "<h1>object %x</h1>\n", a)
+		fmt.Fprintf(w, "<h3>%s</h3>\n", html.EscapeString(typeName(c, x)))
+		fmt.Fprintf(w, "<h3>%d bytes</h3>\n", size)
+
+		if typ != nil && repeat == 1 && typ.String() == "runtime.g" {
+			found := false
+			for _, g := range c.Goroutines() {
+				if g.Addr() == addr {
+					found = true
+					break
+				}
+			}
+			if found {
+				fmt.Fprintf(w, "<h3><a href=\"goroutine?g=%x\">goroutine stack</a></h3>\n", addr)
+			}
+		}
+
+		fmt.Fprintf(w, "<table>\n")
+		fmt.Fprintf(w, "<tr><th align=left>field</th><th align=left colspan=\"2\">type</th><th align=left>value</th></tr>\n")
+		var end int64
+		if typ != nil {
+			n := size / typ.Size
+			if n > 1 {
+				for i := int64(0); i < n; i++ {
+					htmlObject(w, c, fmt.Sprintf("[%d]", i), addr.Add(i*typ.Size), typ, nil)
+				}
+			} else {
+				htmlObject(w, c, "", addr, typ, nil)
+			}
+			end = n * typ.Size
+		}
+		for i := end; i < size; i += c.Process().PtrSize() {
+			fmt.Fprintf(w, "<tr><td>f%d</td><td colspan=\"2\">?</td>", i)
+			if c.IsPtr(addr.Add(i)) {
+				fmt.Fprintf(w, "<td>%s</td>", htmlPointer(c, c.Process().ReadPtr(addr.Add(i))))
+			} else {
+				fmt.Fprintf(w, "<td><pre>")
+				for j := int64(0); j < c.Process().PtrSize(); j++ {
+					fmt.Fprintf(w, "%02x ", c.Process().ReadUint8(addr.Add(i+j)))
+				}
+				fmt.Fprintf(w, "</pre></td><td><pre>")
+				for j := int64(0); j < c.Process().PtrSize(); j++ {
+					r := c.Process().ReadUint8(addr.Add(i + j))
+					if r >= 32 && r <= 126 {
+						fmt.Fprintf(w, "%s", html.EscapeString(string(rune(r))))
+					} else {
+						fmt.Fprintf(w, ".")
+					}
+				}
+				fmt.Fprintf(w, "</pre></td>")
+			}
+			fmt.Fprintf(w, "</tr>\n")
+		}
+		fmt.Fprintf(w, "</table>\n")
+		fmt.Fprintf(w, "<h3>references to this object</h3>\n")
+		nrev := 0
+		c.ForEachReversePtr(x, func(z gocore.Object, r *gocore.Root, i, j int64) bool {
+			if nrev == 10 {
+				fmt.Fprintf(w, "...additional references elided...<br/>\n")
+				return false
+			}
+			if r != nil {
+				fmt.Fprintf(w, "%s%s", r.Name, typeFieldName(r.Type, i))
+			} else {
+				t, r := c.Type(z)
+				if t == nil {
+					fmt.Fprintf(w, "%s", htmlPointer(c, c.Addr(z).Add(i)))
+				} else {
+					idx := ""
+					if r > 1 {
+						idx = fmt.Sprintf("[%d]", i/t.Size)
+						i %= t.Size
+					}
+					fmt.Fprintf(w, "%s%s%s", htmlPointer(c, c.Addr(z)), idx, typeFieldName(t, i))
+				}
+			}
+			fmt.Fprintf(w, " → %s<br/>\n", htmlPointer(c, a.Add(j)))
+			nrev++
+			return true
+		})
+	})
+	http.HandleFunc("/goroutines", func(w http.ResponseWriter, r *http.Request) {
+		fmt.Fprintf(w, "<h1>goroutines</h1>\n")
+		tableStyle(w)
+		fmt.Fprintf(w, "<table>\n")
+		fmt.Fprintf(w, "<tr><th align=left>goroutine</th><th align=left>top of stack</th></tr>\n")
+		for _, g := range c.Goroutines() {
+			fmt.Fprintf(w, "<tr><td><a href=\"goroutine?g=%x\">%x</a></td><td>%s</td></tr>\n", g.Addr(), g.Addr(), g.Frames()[0].Func().Name())
+		}
+		fmt.Fprintf(w, "</table>\n")
+		// TODO: export goroutine state (runnable, running, syscall, ...) and print it here.
+	})
+	http.HandleFunc("/goroutine", func(w http.ResponseWriter, r *http.Request) {
+		gs, ok := r.URL.Query()["g"]
+		if !ok || len(gs) != 1 {
+			fmt.Fprintf(w, "wrong or missing g= goroutine specification")
+			return
+		}
+		addr, err := strconv.ParseInt(gs[0], 16, 64)
+		if err != nil {
+			fmt.Fprintf(w, "unparseable g= goroutine specification: %s\n", err)
+			return
+		}
+		a := core.Address(addr)
+		var g *gocore.Goroutine
+		for _, x := range c.Goroutines() {
+			if x.Addr() == a {
+				g = x
+				break
+			}
+		}
+		if g == nil {
+			fmt.Fprintf(w, "goroutine %x not found\n", a)
+			return
+		}
+
+		tableStyle(w)
+		fmt.Fprintf(w, "<h1>goroutine %x</h1>\n", g.Addr())
+		fmt.Fprintf(w, "<h3>%s</h3>\n", htmlPointer(c, g.Addr()))
+		fmt.Fprintf(w, "<h3>%d bytes of stack</h3>\n", g.Stack())
+		for _, f := range g.Frames() {
+			fmt.Fprintf(w, "<h3>%s+%d</h3>\n", f.Func().Name(), f.PC().Sub(f.Func().Entry()))
+			// TODO: convert fn+off to file+lineno.
+			fmt.Fprintf(w, "<table>\n")
+			fmt.Fprintf(w, "<tr><th align=left>field</th><th align=left colspan=\"2\">type</th><th align=left>value</th></tr>\n")
+			for _, r := range f.Roots() {
+				htmlObject(w, c, r.Name, r.Addr, r.Type, f.Live)
+			}
+			fmt.Fprintf(w, "</table>\n")
+		}
+	})
+	http.HandleFunc("/globals", func(w http.ResponseWriter, r *http.Request) {
+		fmt.Fprintf(w, "<h1>globals</h1>\n")
+		tableStyle(w)
+		fmt.Fprintf(w, "<table>\n")
+		fmt.Fprintf(w, "<tr><th align=left>field</th><th align=left colspan=\"2\">type</th><th align=left>value</th></tr>\n")
+		for _, r := range c.Globals() {
+			htmlObject(w, c, r.Name, r.Addr, r.Type, nil)
+		}
+		fmt.Fprintf(w, "</table>\n")
+	})
+	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+		fmt.Fprintf(w, "<h1>core dump viewer</h1>\n")
+		fmt.Fprintf(w, "%s<br/>\n", c.Process().Arch())
+		fmt.Fprintf(w, "%s<br/>\n", c.BuildVersion())
+		fmt.Fprintf(w, "<a href=\"goroutines\">goroutines</a><br/>\n")
+		fmt.Fprintf(w, "<a href=\"globals\">globals</a><br/>\n")
+		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 {
+				p(c, prefix+"..")
+			}
+		}
+		p(c.Stats(), "")
+		fmt.Fprintf(w, "</table>\n")
+	})
+	fmt.Println("serving on :8080")
+	http.ListenAndServe(":8080", nil)
+}
+
+func htmlObject(w http.ResponseWriter, c *gocore.Process, name string, a core.Address, t *gocore.Type, live map[core.Address]bool) {
+	switch t.Kind {
+	case gocore.KindBool:
+		v := c.Process().ReadUint8(a) != 0
+		fmt.Fprintf(w, "<tr><td>%s</td><td colspan=\"2\">%s</td><td>%t</td></tr>\n", name, html.EscapeString(t.String()), v)
+	case gocore.KindInt:
+		var v int64
+		switch t.Size {
+		case 1:
+			v = int64(c.Process().ReadInt8(a))
+		case 2:
+			v = int64(c.Process().ReadInt16(a))
+		case 4:
+			v = int64(c.Process().ReadInt32(a))
+		case 8:
+			v = c.Process().ReadInt64(a)
+		}
+		fmt.Fprintf(w, "<tr><td>%s</td><td colspan=\"2\">%s</td><td>%d</td></tr>\n", name, html.EscapeString(t.String()), v)
+	case gocore.KindUint:
+		var v uint64
+		switch t.Size {
+		case 1:
+			v = uint64(c.Process().ReadUint8(a))
+		case 2:
+			v = uint64(c.Process().ReadUint16(a))
+		case 4:
+			v = uint64(c.Process().ReadUint32(a))
+		case 8:
+			v = c.Process().ReadUint64(a)
+		}
+		fmt.Fprintf(w, "<tr><td>%s</td><td colspan=\"2\">%s</td><td>%d</td></tr>\n", name, html.EscapeString(t.String()), v)
+	case gocore.KindFloat:
+		var v float64
+		switch t.Size {
+		case 4:
+			v = float64(math.Float32frombits(c.Process().ReadUint32(a)))
+		case 8:
+			v = math.Float64frombits(c.Process().ReadUint64(a))
+		}
+		fmt.Fprintf(w, "<tr><td>%s</td><td colspan=\"2\">%s</td><td>%f</td></tr>\n", name, html.EscapeString(t.String()), v)
+	case gocore.KindComplex:
+		var v complex128
+		switch t.Size {
+		case 8:
+			v = complex128(complex(
+				math.Float32frombits(c.Process().ReadUint32(a)),
+				math.Float32frombits(c.Process().ReadUint32(a.Add(4)))))
+
+		case 16:
+			v = complex(
+				math.Float64frombits(c.Process().ReadUint64(a)),
+				math.Float64frombits(c.Process().ReadUint64(a.Add(8))))
+		}
+		fmt.Fprintf(w, "<tr><td>%s</td><td colspan=\"2\">%s</td><td>%f</td></tr>\n", name, html.EscapeString(t.String()), v)
+	case gocore.KindEface:
+		fmt.Fprintf(w, "<tr><td rowspan=\"2\">%s</td><td rowspan=\"2\">interface{}</td><td>*runtime._type</td><td>%s</td>", name, htmlPointerAt(c, a, live))
+		if live == nil || live[a] {
+			dt := c.DynamicType(t, a)
+			if dt != nil {
+				fmt.Fprintf(w, "<td>%s</td>", dt.Name)
+			}
+		}
+		fmt.Fprintf(w, "</tr>\n")
+		fmt.Fprintf(w, "<tr><td>unsafe.Pointer</td><td>%s</td></tr>\n", htmlPointerAt(c, a.Add(c.Process().PtrSize()), live))
+	case gocore.KindIface:
+		fmt.Fprintf(w, "<tr><td rowspan=\"2\">%s</td><td rowspan=\"2\">interface{...}</td><td>*runtime.itab</td><td>%s</td>", name, htmlPointerAt(c, a, live))
+		if live == nil || live[a] {
+			dt := c.DynamicType(t, a)
+			if dt != nil {
+				fmt.Fprintf(w, "<td>%s</td>", dt.Name)
+			}
+		}
+		fmt.Fprintf(w, "</tr>\n")
+		fmt.Fprintf(w, "<tr><td>unsafe.Pointer</td><td>%s</td></tr>\n", htmlPointerAt(c, a.Add(c.Process().PtrSize()), live))
+	case gocore.KindPtr:
+		fmt.Fprintf(w, "<tr><td>%s</td><td colspan=\"2\">%s</td><td>%s</td></tr>\n", name, html.EscapeString(t.String()), htmlPointerAt(c, a, live))
+	case gocore.KindFunc:
+		fmt.Fprintf(w, "<tr><td>%s</td><td colspan=\"2\">%s</td><td>%s</td>", name, html.EscapeString(t.String()), htmlPointerAt(c, a, live))
+		if fn := c.Process().ReadPtr(a); fn != 0 {
+			pc := c.Process().ReadPtr(fn)
+			if f := c.FindFunc(pc); f != nil && f.Entry() == pc {
+				fmt.Fprintf(w, "<td>%s</td>", f.Name())
+			}
+		}
+		fmt.Fprintf(w, "</tr>\n")
+	case gocore.KindString:
+		n := c.Process().ReadInt(a.Add(c.Process().PtrSize()))
+		fmt.Fprintf(w, "<tr><td rowspan=\"2\">%s</td><td rowspan=\"2\">string</td><td>*uint8</td><td>%s</td>", name, htmlPointerAt(c, a, live))
+		if live == nil || live[a] {
+			if n > 0 {
+				n2 := n
+				ddd := ""
+				if n > 100 {
+					n2 = 100
+					ddd = "..."
+				}
+				b := make([]byte, n2)
+				c.Process().ReadAt(b, c.Process().ReadPtr(a))
+				fmt.Fprintf(w, "<td rowspan=\"2\">\"%s\"%s</td>", html.EscapeString(string(b)), ddd)
+			} else {
+				fmt.Fprintf(w, "<td rowspan=\"2\">\"\"</td>")
+			}
+		}
+		fmt.Fprintf(w, "</tr>\n")
+		fmt.Fprintf(w, "<tr><td>int</td><td>%d</td></tr>\n", n)
+	case gocore.KindSlice:
+		fmt.Fprintf(w, "<tr><td rowspan=\"3\">%s</td><td rowspan=\"3\">%s</td><td>*%s</td><td>%s</td></tr>\n", name, t, t.Elem, htmlPointerAt(c, a, live))
+		fmt.Fprintf(w, "<tr><td>int</td><td>%d</td></tr>\n", c.Process().ReadInt(a.Add(c.Process().PtrSize())))
+		fmt.Fprintf(w, "<tr><td>int</td><td>%d</td></tr>\n", c.Process().ReadInt(a.Add(c.Process().PtrSize()*2)))
+	case gocore.KindArray:
+		s := t.Elem.Size
+		n := t.Count
+		if n*s > 16384 {
+			n = (16384 + s - 1) / s
+		}
+		for i := int64(0); i < n; i++ {
+			htmlObject(w, c, fmt.Sprintf("%s[%d]", name, i), a.Add(i*s), t.Elem, live)
+		}
+		if n*s != t.Size {
+			fmt.Fprintf(w, "<tr><td>...</td><td>...</td><td>...</td></tr>\n")
+		}
+	case gocore.KindStruct:
+		for _, f := range t.Fields {
+			htmlObject(w, c, name+"."+f.Name, a.Add(f.Off), f.Type, live)
+		}
+	}
+}
+
+func htmlPointer(c *gocore.Process, a core.Address) string {
+	if a == 0 {
+		return "nil"
+	}
+	x, i := c.FindObject(a)
+	if x == 0 {
+		return fmt.Sprintf("%x", a)
+	}
+	s := fmt.Sprintf("<a href=\"/object?o=%x\">object %x</a>", c.Addr(x), c.Addr(x))
+	if i == 0 {
+		return s
+	}
+	t, r := c.Type(x)
+	if t == nil {
+		return fmt.Sprintf("%s+%d", s, i)
+	}
+	idx := ""
+	if r > 1 {
+		idx = fmt.Sprintf("[%d]", i/t.Size)
+		i %= t.Size
+	}
+	return fmt.Sprintf("%s%s%s", s, idx, typeFieldName(t, i))
+}
+
+func htmlPointerAt(c *gocore.Process, a core.Address, live map[core.Address]bool) string {
+	if live != nil && !live[a] {
+		return "<text style=\"color:LightGray\">dead</text>"
+	}
+	return htmlPointer(c, c.Process().ReadPtr(a))
+}
+
+func tableStyle(w http.ResponseWriter) {
+	fmt.Fprintf(w, "<style>\n")
+	fmt.Fprintf(w, "table, th, td {\n")
+	fmt.Fprintf(w, "    border: 1px solid black;\n")
+	fmt.Fprintf(w, "    border-collapse: collapse;\n")
+	fmt.Fprintf(w, "    align: left;\n")
+	fmt.Fprintf(w, "}\n")
+	fmt.Fprintf(w, "table, th, td {\n")
+	fmt.Fprintf(w, "    padding: 2px;\n")
+	fmt.Fprintf(w, "}\n")
+	fmt.Fprintf(w, "tr:hover {background-color: #f5f5f5}\n")
+	fmt.Fprintf(w, "</style>\n")
+
+}
+
+// Returns the name of the field at offset off in x.
+func objField(c *gocore.Process, x gocore.Object, off int64) string {
+	t, r := c.Type(x)
+	if t == nil {
+		return fmt.Sprintf("f%d", off)
+	}
+	s := ""
+	if r > 1 {
+		s = fmt.Sprintf("[%d]", off/t.Size)
+		off %= t.Size
+	}
+	return s + typeFieldName(t, off)
+}
+
+func objRegion(c *gocore.Process, x gocore.Object, off int64) string {
+	t, r := c.Type(x)
+	if t == nil {
+		return fmt.Sprintf("f%d", off)
+	}
+	if off == 0 {
+		return ""
+	}
+	s := ""
+	if r > 1 {
+		s = fmt.Sprintf("[%d]", off/t.Size)
+		off %= t.Size
+	}
+	return s + typeFieldName(t, off)
+}
diff --git a/cmd/viewcore/main.go b/cmd/viewcore/main.go
new file mode 100644
index 0000000..1d94673
--- /dev/null
+++ b/cmd/viewcore/main.go
@@ -0,0 +1,477 @@
+// Copyright 2018 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// The coreview tool is a command-line tool for exploring the state of a Go process
+// that has dumped core.
+// Run "coreview help" for a list of commands.
+package main
+
+import (
+	"flag"
+	"fmt"
+	"os"
+	"runtime/pprof"
+	"sort"
+	"strconv"
+	"text/tabwriter"
+
+	"golang.org/x/debug/core"
+	"golang.org/x/debug/gocore"
+)
+
+func usage() {
+	fmt.Println(`
+Usage:
+
+        coreview command corefile
+
+The commands are:
+
+        help: print this message
+    overview: print a few overall statistics
+    mappings: print virtual memory mappings
+  goroutines: list goroutines
+   histogram: print histogram of heap memory use by Go type
+   breakdown: print memory use by class
+     objects: print a list of all live objects
+    objgraph: dump object graph to the file tmp.dot
+   reachable: find path from root to an object
+        html: start an http server on :8080 for browsing core file data
+
+Flags applicable to all commands:
+`)
+	flag.PrintDefaults()
+}
+
+func main() {
+	base := flag.String("base", "", "root directory to find core dump file references")
+	prof := flag.String("prof", "", "write cpu profile of coreview to this file (for coreview's developers)")
+	flag.Parse()
+
+	// Extract command.
+	args := flag.Args()
+	if len(args) < 1 {
+		fmt.Fprintf(os.Stderr, "%s: no command specified\n", os.Args[0])
+		usage()
+		os.Exit(2)
+	}
+	cmd := args[0]
+	if cmd == "help" {
+		usage()
+		return
+	}
+
+	if *prof != "" {
+		f, err := os.Create(*prof)
+		if err != nil {
+			fmt.Fprintf(os.Stderr, "can't open profile file: %s\n", err)
+			os.Exit(2)
+		}
+		pprof.StartCPUProfile(f)
+		defer pprof.StopCPUProfile()
+	}
+
+	var flags gocore.Flags
+	switch cmd {
+	default:
+		fmt.Fprintf(os.Stderr, "%s: unknown command %s\n", os.Args[0], cmd)
+		fmt.Fprintf(os.Stderr, "Run 'coreview help' for usage.\n")
+		os.Exit(2)
+	case "overview":
+	case "mappings":
+	case "goroutines":
+	case "histogram":
+		flags = gocore.FlagTypes
+	case "breakdown":
+	case "objgraph":
+		flags = gocore.FlagTypes
+	case "objects":
+		flags = gocore.FlagTypes
+	case "reachable":
+		flags = gocore.FlagTypes | gocore.FlagReverse
+	case "html":
+		flags = gocore.FlagTypes | gocore.FlagReverse
+	}
+
+	// All commands other than "help" need a core file.
+	if len(args) < 2 {
+		fmt.Fprintf(os.Stderr, "%s: no core dump specified for command %s\n", os.Args[0], cmd)
+		fmt.Fprintf(os.Stderr, "Run 'coreview help' for usage.\n")
+		os.Exit(2)
+	}
+	file := args[1]
+	p, err := core.Core(file, *base)
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "%v\n", err)
+		os.Exit(1)
+	}
+	for _, w := range p.Warnings() {
+		fmt.Fprintf(os.Stderr, "WARNING: %s\n", w)
+	}
+	c, err := gocore.Core(p, flags)
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "%v\n", err)
+		os.Exit(1)
+	}
+
+	switch cmd {
+	default:
+		fmt.Fprintf(os.Stderr, "%s: unknown command %s\n", os.Args[0], cmd)
+		fmt.Fprintf(os.Stderr, "Run '%s help' for usage.\n", os.Args[0])
+		os.Exit(2)
+
+	case "overview":
+		t := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0)
+		fmt.Fprintf(t, "arch\t%s\n", p.Arch())
+		fmt.Fprintf(t, "runtime\t%s\n", c.BuildVersion())
+		var total int64
+		for _, m := range p.Mappings() {
+			total += m.Max().Sub(m.Min())
+		}
+		fmt.Fprintf(t, "memory\t%.1f MB\n", float64(total)/(1<<20))
+		t.Flush()
+
+	case "mappings":
+		t := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', tabwriter.AlignRight)
+		fmt.Fprintf(t, "min\tmax\tperm\tsource\toriginal\t\n")
+		for _, m := range p.Mappings() {
+			perm := ""
+			if m.Perm()&core.Read != 0 {
+				perm += "r"
+			} else {
+				perm += "-"
+			}
+			if m.Perm()&core.Write != 0 {
+				perm += "w"
+			} else {
+				perm += "-"
+			}
+			if m.Perm()&core.Exec != 0 {
+				perm += "x"
+			} else {
+				perm += "-"
+			}
+			file, off := m.Source()
+			fmt.Fprintf(t, "%x\t%x\t%s\t%s@%x\t", m.Min(), m.Max(), perm, file, off)
+			if m.CopyOnWrite() {
+				file, off = m.OrigSource()
+				fmt.Fprintf(t, "%s@%x", file, off)
+			}
+			fmt.Fprintf(t, "\t\n")
+		}
+		t.Flush()
+
+	case "goroutines":
+		for _, g := range c.Goroutines() {
+			fmt.Printf("G stacksize=%x\n", g.Stack())
+			for _, f := range g.Frames() {
+				pc := f.PC()
+				entry := f.Func().Entry()
+				var adj string
+				switch {
+				case pc == entry:
+					adj = ""
+				case pc < entry:
+					adj = fmt.Sprintf("-%d", entry.Sub(pc))
+				default:
+					adj = fmt.Sprintf("+%d", pc.Sub(entry))
+				}
+				fmt.Printf("  %016x %016x %s%s\n", f.Min(), f.Max(), f.Func().Name(), adj)
+			}
+		}
+
+	case "histogram":
+		// Produce an object histogram (bytes per type).
+		type bucket struct {
+			name  string
+			size  int64
+			count int64
+		}
+		var buckets []*bucket
+		m := map[string]*bucket{}
+		c.ForEachObject(func(x gocore.Object) bool {
+			name := typeName(c, x)
+			b := m[name]
+			if b == nil {
+				b = &bucket{name: name, size: c.Size(x)}
+				buckets = append(buckets, b)
+				m[name] = b
+			}
+			b.count++
+			return true
+		})
+		sort.Slice(buckets, func(i, j int) bool {
+			return buckets[i].size*buckets[i].count > buckets[j].size*buckets[j].count
+		})
+		t := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', tabwriter.AlignRight)
+		fmt.Fprintf(t, "%s\t%s\t%s\t %s\n", "count", "size", "bytes", "type")
+		for _, e := range buckets {
+			fmt.Fprintf(t, "%d\t%d\t%d\t %s\n", e.count, e.size, e.count*e.size, e.name)
+		}
+		t.Flush()
+
+	case "breakdown":
+		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) {
+			comment := ""
+			switch s.Name {
+			case "bss":
+				comment = "(grab bag, includes OS thread stacks, ...)"
+			case "manual spans":
+				comment = "(Go stacks)"
+			case "retained":
+				comment = "(kept for reuse by Go)"
+			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 {
+				printStat(c, indent+"  ")
+			}
+		}
+		printStat(c.Stats(), "")
+		t.Flush()
+
+	case "objgraph":
+		// Dump object graph to output file.
+		w, err := os.Create("tmp.dot")
+		if err != nil {
+			panic(err)
+		}
+		fmt.Fprintf(w, "digraph {\n")
+		for k, r := range c.Globals() {
+			printed := false
+			c.ForEachRootPtr(r, func(i int64, y gocore.Object, j int64) bool {
+				if !printed {
+					fmt.Fprintf(w, "r%d [label=\"%s\n%s\",shape=hexagon]\n", k, r.Name, r.Type)
+					printed = true
+				}
+				fmt.Fprintf(w, "r%d -> o%x [label=\"%s\"", k, c.Addr(y), typeFieldName(r.Type, i))
+				if j != 0 {
+					fmt.Fprintf(w, " ,headlabel=\"+%d\"", j)
+				}
+				fmt.Fprintf(w, "]\n")
+				return true
+			})
+		}
+		for _, g := range c.Goroutines() {
+			last := fmt.Sprintf("o%x", g.Addr())
+			for _, f := range g.Frames() {
+				frame := fmt.Sprintf("f%x", f.Max())
+				fmt.Fprintf(w, "%s [label=\"%s\",shape=rectangle]\n", frame, f.Func().Name())
+				fmt.Fprintf(w, "%s -> %s [style=dotted]\n", last, frame)
+				last = frame
+				for _, r := range f.Roots() {
+					c.ForEachRootPtr(r, func(i int64, y gocore.Object, j int64) bool {
+						fmt.Fprintf(w, "%s -> o%x [label=\"%s%s\"", frame, c.Addr(y), r.Name, typeFieldName(r.Type, i))
+						if j != 0 {
+							fmt.Fprintf(w, " ,headlabel=\"+%d\"", j)
+						}
+						fmt.Fprintf(w, "]\n")
+						return true
+					})
+				}
+			}
+		}
+		c.ForEachObject(func(x gocore.Object) bool {
+			addr := c.Addr(x)
+			size := c.Size(x)
+			fmt.Fprintf(w, "o%x [label=\"%s\\n%d\"]\n", addr, typeName(c, x), size)
+			c.ForEachPtr(x, func(i int64, y gocore.Object, j int64) bool {
+				fmt.Fprintf(w, "o%x -> o%x [label=\"%s\"", addr, c.Addr(y), fieldName(c, x, i))
+				if j != 0 {
+					fmt.Fprintf(w, ",headlabel=\"+%d\"", j)
+				}
+				fmt.Fprintf(w, "]\n")
+				return true
+			})
+			return true
+		})
+		fmt.Fprintf(w, "}")
+		w.Close()
+
+	case "objects":
+		c.ForEachObject(func(x gocore.Object) bool {
+			fmt.Printf("%16x %s\n", c.Addr(x), typeName(c, x))
+			return true
+		})
+
+	case "reachable":
+		if len(args) < 3 {
+			fmt.Fprintf(os.Stderr, "no object address provided\n")
+			os.Exit(1)
+		}
+		n, err := strconv.ParseInt(args[2], 16, 64)
+		if err != nil {
+			fmt.Fprintf(os.Stderr, "can't parse %s as an object address\n", args[2])
+			os.Exit(1)
+		}
+		a := core.Address(n)
+		obj, _ := c.FindObject(a)
+		if obj == 0 {
+			fmt.Fprintf(os.Stderr, "can't find object at address %s\n", args[2])
+			os.Exit(1)
+		}
+
+		// Breadth-first search backwards until we reach a root.
+		type hop struct {
+			i int64         // offset in "from" object (the key in the path map) where the pointer is
+			x gocore.Object // the "to" object
+			j int64         // the offset in the "to" object
+		}
+		depth := map[gocore.Object]int{}
+		depth[obj] = 0
+		q := []gocore.Object{obj}
+		done := false
+		for !done {
+			if len(q) == 0 {
+				panic("can't find a root that can reach the object")
+			}
+			y := q[0]
+			q = q[1:]
+			c.ForEachReversePtr(y, func(x gocore.Object, r *gocore.Root, i, j int64) bool {
+				if r != nil {
+					// found it.
+					if r.Frame == nil {
+						// Print global
+						fmt.Printf("%s", r.Name)
+					} else {
+						// Print stack up to frame in question.
+						var frames []*gocore.Frame
+						for f := r.Frame.Parent(); f != nil; f = f.Parent() {
+							frames = append(frames, f)
+						}
+						for k := len(frames) - 1; k >= 0; k-- {
+							fmt.Printf("%s\n", frames[k].Func().Name())
+						}
+						// Print frame + variable in frame.
+						fmt.Printf("%s.%s", r.Frame.Func().Name(), r.Name)
+					}
+					fmt.Printf("%s → \n", typeFieldName(r.Type, i))
+
+					z := y
+					for {
+						fmt.Printf("%x %s", c.Addr(z), typeName(c, z))
+						if z == obj {
+							fmt.Println()
+							break
+						}
+						// Find an edge out of z which goes to an object
+						// closer to obj.
+						c.ForEachPtr(z, func(i int64, w gocore.Object, j int64) bool {
+							if d, ok := depth[w]; ok && d < depth[z] {
+								fmt.Printf(" %s → %s", objField(c, z, i), objRegion(c, w, j))
+								z = w
+								return false
+							}
+							return true
+						})
+						fmt.Println()
+					}
+					done = true
+					return false
+				}
+				if _, ok := depth[x]; ok {
+					// we already found a shorter path to this object.
+					return true
+				}
+				depth[x] = depth[y] + 1
+				q = append(q, x)
+				return true
+			})
+		}
+	case "html":
+		serveHTML(c)
+	}
+}
+
+// typeName returns a string representing the type of this object.
+func typeName(c *gocore.Process, x gocore.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
+}
+
+// fieldName returns the name of the field at offset off in x.
+func fieldName(c *gocore.Process, x gocore.Object, off int64) string {
+	size := c.Size(x)
+	typ, repeat := c.Type(x)
+	if typ == nil {
+		return fmt.Sprintf("f%d", off)
+	}
+	n := size / typ.Size
+	i := off / typ.Size
+	if i == 0 && repeat == 1 {
+		// Probably a singleton object, no need for array notation.
+		return typeFieldName(typ, off)
+	}
+	if i >= n {
+		// Partial space at the end of the object - the type can't be complete.
+		return fmt.Sprintf("f%d", off)
+	}
+	q := ""
+	if i >= repeat {
+		// Past the known repeat section, add a ? because we're not sure about the type.
+		q = "?"
+	}
+	return fmt.Sprintf("[%d]%s%s", i, typeFieldName(typ, off-i*typ.Size), q)
+}
+
+// typeFieldName returns the name of the field at offset off in t.
+func typeFieldName(t *gocore.Type, off int64) string {
+	switch t.Kind {
+	case gocore.KindBool, gocore.KindInt, gocore.KindUint, gocore.KindFloat:
+		return ""
+	case gocore.KindComplex:
+		if off == 0 {
+			return ".real"
+		}
+		return ".imag"
+	case gocore.KindIface, gocore.KindEface:
+		if off == 0 {
+			return ".type"
+		}
+		return ".data"
+	case gocore.KindPtr, gocore.KindFunc:
+		return ""
+	case gocore.KindString:
+		if off == 0 {
+			return ".ptr"
+		}
+		return ".len"
+	case gocore.KindSlice:
+		if off == 0 {
+			return ".ptr"
+		}
+		if off <= t.Size/2 {
+			return ".len"
+		}
+		return ".cap"
+	case gocore.KindArray:
+		s := t.Elem.Size
+		i := off / s
+		return fmt.Sprintf("[%d]%s", i, typeFieldName(t.Elem, off-i*s))
+	case gocore.KindStruct:
+		for _, f := range t.Fields {
+			if f.Off <= off && off < f.Off+f.Type.Size {
+				return "." + f.Name + typeFieldName(f.Type, off-f.Off)
+			}
+		}
+	}
+	return ".???"
+}