cmd/viewcore/html.go
func serveHTML(c *gocore.Process, port int, async bool) {
	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")
	})

	if port <= 0 {
		port = 8080
	}
	fmt.Printf("start serving on http://localhost:%d\n", port)

	httpAddr := fmt.Sprintf(":%d", port)
	if async {
		go http.ListenAndServe(httpAddr, nil)
		return
	}
	http.ListenAndServe(httpAddr, nil)
}
func serveHTML(c *gocore.Process, port int, async bool) {
	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")
	})

	if port <= 0 {
		port = 8080
	}
	fmt.Printf("start serving on http://localhost:%d\n", port)

	httpAddr := fmt.Sprintf(":%d", port)
	if async {
		go http.ListenAndServe(httpAddr, nil)
		return
	}
	http.ListenAndServe(httpAddr, nil)
}
// serveHTML starts and serves a webserver on the port.
// If async is true, it returns immediately after starting the server.