delve/service/api/prettyprint.go
chainhelen a5d9dbee79
pkg,service: add cmd examinemem(x) for examining memory. (#1814)
According to #1800 #1584 #1038, `dlv` should enable the user to dive into
memory. User can print binary data in specific memory address range.
But not support for sepecific variable name or structures temporarily.(Because
I have no idea that modify `print` command.)

Close #1584.
2020-02-13 09:29:21 -08:00

408 lines
9.6 KiB
Go

package api
import (
"bytes"
"fmt"
"io"
"reflect"
"strconv"
"strings"
)
const (
// strings longer than this will cause slices, arrays and structs to be printed on multiple lines when newlines is enabled
maxShortStringLen = 7
// string used for one indentation level (when printing on multiple lines)
indentString = "\t"
)
// SinglelineString returns a representation of v on a single line.
func (v *Variable) SinglelineString() string {
var buf bytes.Buffer
v.writeTo(&buf, true, false, true, "")
return buf.String()
}
// MultilineString returns a representation of v on multiple lines.
func (v *Variable) MultilineString(indent string) string {
var buf bytes.Buffer
v.writeTo(&buf, true, true, true, indent)
return buf.String()
}
func (v *Variable) writeTo(buf io.Writer, top, newlines, includeType bool, indent string) {
if v.Unreadable != "" {
fmt.Fprintf(buf, "(unreadable %s)", v.Unreadable)
return
}
if !top && v.Addr == 0 && v.Value == "" {
if includeType && v.Type != "void" {
fmt.Fprintf(buf, "%s nil", v.Type)
} else {
fmt.Fprint(buf, "nil")
}
return
}
switch v.Kind {
case reflect.Slice:
v.writeSliceTo(buf, newlines, includeType, indent)
case reflect.Array:
v.writeArrayTo(buf, newlines, includeType, indent)
case reflect.Ptr:
if v.Type == "" || len(v.Children) == 0 {
fmt.Fprint(buf, "nil")
} else if v.Children[0].OnlyAddr && v.Children[0].Addr != 0 {
if strings.Contains(v.Type, "/") {
fmt.Fprintf(buf, "(%q)(%#x)", v.Type, v.Children[0].Addr)
} else {
fmt.Fprintf(buf, "(%s)(%#x)", v.Type, v.Children[0].Addr)
}
} else {
fmt.Fprint(buf, "*")
v.Children[0].writeTo(buf, false, newlines, includeType, indent)
}
case reflect.UnsafePointer:
if len(v.Children) == 0 {
fmt.Fprintf(buf, "unsafe.Pointer(nil)")
} else {
fmt.Fprintf(buf, "unsafe.Pointer(%#x)", v.Children[0].Addr)
}
case reflect.String:
v.writeStringTo(buf)
case reflect.Chan:
if newlines {
v.writeStructTo(buf, newlines, includeType, indent)
} else {
if len(v.Children) == 0 {
fmt.Fprintf(buf, "%s nil", v.Type)
} else {
fmt.Fprintf(buf, "%s %s/%s", v.Type, v.Children[0].Value, v.Children[1].Value)
}
}
case reflect.Struct:
v.writeStructTo(buf, newlines, includeType, indent)
case reflect.Interface:
if v.Addr == 0 {
// an escaped interface variable that points to nil, this shouldn't
// happen in normal code but can happen if the variable is out of scope.
fmt.Fprintf(buf, "nil")
return
}
if includeType {
if v.Children[0].Kind == reflect.Invalid {
fmt.Fprintf(buf, "%s ", v.Type)
if v.Children[0].Addr == 0 {
fmt.Fprint(buf, "nil")
return
}
} else {
fmt.Fprintf(buf, "%s(%s) ", v.Type, v.Children[0].Type)
}
}
data := v.Children[0]
if data.Kind == reflect.Ptr {
if len(data.Children) == 0 {
fmt.Fprint(buf, "...")
} else if data.Children[0].Addr == 0 {
fmt.Fprint(buf, "nil")
} else if data.Children[0].OnlyAddr {
fmt.Fprintf(buf, "0x%x", v.Children[0].Addr)
} else {
v.Children[0].writeTo(buf, false, newlines, !includeType, indent)
}
} else if data.OnlyAddr {
if strings.Contains(v.Type, "/") {
fmt.Fprintf(buf, "*(*%q)(%#x)", v.Type, v.Addr)
} else {
fmt.Fprintf(buf, "*(*%s)(%#x)", v.Type, v.Addr)
}
} else {
v.Children[0].writeTo(buf, false, newlines, !includeType, indent)
}
case reflect.Map:
v.writeMapTo(buf, newlines, includeType, indent)
case reflect.Func:
if v.Value == "" {
fmt.Fprint(buf, "nil")
} else {
fmt.Fprintf(buf, "%s", v.Value)
}
case reflect.Complex64, reflect.Complex128:
fmt.Fprintf(buf, "(%s + %si)", v.Children[0].Value, v.Children[1].Value)
default:
if v.Value != "" {
buf.Write([]byte(v.Value))
} else {
fmt.Fprintf(buf, "(unknown %s)", v.Kind)
}
}
}
func (v *Variable) writeStringTo(buf io.Writer) {
s := v.Value
if len(s) != int(v.Len) {
s = fmt.Sprintf("%s...+%d more", s, int(v.Len)-len(s))
}
fmt.Fprintf(buf, "%q", s)
}
func (v *Variable) writeSliceTo(buf io.Writer, newlines, includeType bool, indent string) {
if includeType {
fmt.Fprintf(buf, "%s len: %d, cap: %d, ", v.Type, v.Len, v.Cap)
}
if v.Base == 0 && len(v.Children) == 0 {
fmt.Fprintf(buf, "nil")
return
}
v.writeSliceOrArrayTo(buf, newlines, indent)
}
func (v *Variable) writeArrayTo(buf io.Writer, newlines, includeType bool, indent string) {
if includeType {
fmt.Fprintf(buf, "%s ", v.Type)
}
v.writeSliceOrArrayTo(buf, newlines, indent)
}
func (v *Variable) writeStructTo(buf io.Writer, newlines, includeType bool, indent string) {
if int(v.Len) != len(v.Children) && len(v.Children) == 0 {
if strings.Contains(v.Type, "/") {
fmt.Fprintf(buf, "(*%q)(%#x)", v.Type, v.Addr)
} else {
fmt.Fprintf(buf, "(*%s)(%#x)", v.Type, v.Addr)
}
return
}
if includeType {
fmt.Fprintf(buf, "%s ", v.Type)
}
nl := v.shouldNewlineStruct(newlines)
fmt.Fprint(buf, "{")
for i := range v.Children {
if nl {
fmt.Fprintf(buf, "\n%s%s", indent, indentString)
}
fmt.Fprintf(buf, "%s: ", v.Children[i].Name)
v.Children[i].writeTo(buf, false, nl, true, indent+indentString)
if i != len(v.Children)-1 || nl {
fmt.Fprint(buf, ",")
if !nl {
fmt.Fprint(buf, " ")
}
}
}
if len(v.Children) != int(v.Len) {
if nl {
fmt.Fprintf(buf, "\n%s%s", indent, indentString)
} else {
fmt.Fprint(buf, ",")
}
fmt.Fprintf(buf, "...+%d more", int(v.Len)-len(v.Children))
}
fmt.Fprint(buf, "}")
}
func (v *Variable) writeMapTo(buf io.Writer, newlines, includeType bool, indent string) {
if includeType {
fmt.Fprintf(buf, "%s ", v.Type)
}
if v.Base == 0 && len(v.Children) == 0 {
fmt.Fprintf(buf, "nil")
return
}
nl := newlines && (len(v.Children) > 0)
fmt.Fprint(buf, "[")
for i := 0; i < len(v.Children); i += 2 {
key := &v.Children[i]
value := &v.Children[i+1]
if nl {
fmt.Fprintf(buf, "\n%s%s", indent, indentString)
}
key.writeTo(buf, false, false, false, indent+indentString)
fmt.Fprint(buf, ": ")
value.writeTo(buf, false, nl, false, indent+indentString)
if i != len(v.Children)-1 || nl {
fmt.Fprint(buf, ", ")
}
}
if len(v.Children)/2 != int(v.Len) {
if len(v.Children) != 0 {
if nl {
fmt.Fprintf(buf, "\n%s%s", indent, indentString)
} else {
fmt.Fprint(buf, ",")
}
fmt.Fprintf(buf, "...+%d more", int(v.Len)-(len(v.Children)/2))
} else {
fmt.Fprint(buf, "...")
}
}
if nl {
fmt.Fprintf(buf, "\n%s", indent)
}
fmt.Fprint(buf, "]")
}
func (v *Variable) shouldNewlineArray(newlines bool) bool {
if !newlines || len(v.Children) == 0 {
return false
}
kind, hasptr := (&v.Children[0]).recursiveKind()
switch kind {
case reflect.Slice, reflect.Array, reflect.Struct, reflect.Map, reflect.Interface:
return true
case reflect.String:
if hasptr {
return true
}
for i := range v.Children {
if len(v.Children[i].Value) > maxShortStringLen {
return true
}
}
return false
default:
return false
}
}
func (v *Variable) recursiveKind() (reflect.Kind, bool) {
hasptr := false
var kind reflect.Kind
for {
kind = v.Kind
if kind == reflect.Ptr {
hasptr = true
v = &(v.Children[0])
} else {
break
}
}
return kind, hasptr
}
func (v *Variable) shouldNewlineStruct(newlines bool) bool {
if !newlines || len(v.Children) == 0 {
return false
}
for i := range v.Children {
kind, hasptr := (&v.Children[i]).recursiveKind()
switch kind {
case reflect.Slice, reflect.Array, reflect.Struct, reflect.Map, reflect.Interface:
return true
case reflect.String:
if hasptr {
return true
}
if len(v.Children[i].Value) > maxShortStringLen {
return true
}
}
}
return false
}
func (v *Variable) writeSliceOrArrayTo(buf io.Writer, newlines bool, indent string) {
nl := v.shouldNewlineArray(newlines)
fmt.Fprint(buf, "[")
for i := range v.Children {
if nl {
fmt.Fprintf(buf, "\n%s%s", indent, indentString)
}
v.Children[i].writeTo(buf, false, nl, false, indent+indentString)
if i != len(v.Children)-1 || nl {
fmt.Fprint(buf, ",")
}
}
if len(v.Children) != int(v.Len) {
if len(v.Children) != 0 {
if nl {
fmt.Fprintf(buf, "\n%s%s", indent, indentString)
} else {
fmt.Fprint(buf, ",")
}
fmt.Fprintf(buf, "...+%d more", int(v.Len)-len(v.Children))
} else {
fmt.Fprint(buf, "...")
}
}
if nl {
fmt.Fprintf(buf, "\n%s", indent)
}
fmt.Fprint(buf, "]")
}
func PrettyExamineMemory(address uintptr, memArea []byte, format byte) string {
cols := 8
// Avoid emitting rows that are too long when using binary format
if format == 'b' {
cols = 4
}
l := len(memArea)
rows := l / cols
if l%cols != 0 {
rows++
}
var colFormat string
// Leading zero and occupy 8 for binary
if format == 'b' {
colFormat = " %#08b"
} else {
var maxColCharNum int
for i := 0; i < rows; i++ {
for j := 0; j < cols && i*cols+j < l; j++ {
curColCharNum := len(fmt.Sprintf("%#"+string(format), memArea[i*cols+j]))
if curColCharNum > maxColCharNum {
maxColCharNum = curColCharNum
}
}
}
colFormat = " %#-" + strconv.Itoa(maxColCharNum) + string(format)
}
lines := ""
for i := 0; i < rows; i++ {
lines += fmt.Sprintf("%#x:", address)
for j := 0; j < cols && i*cols+j < l; j++ {
curOutput := fmt.Sprintf(colFormat, memArea[i*cols+j])
// Diffrent versions of golang output differently if binary.
// See https://ci.appveyor.com/project/derekparker/delve-facy3/builds/30179356.
// Remove prefix `0b` if binary in some versions of golang because it is not graceful.
if format == 'b' && strings.Contains(curOutput, "0b") {
curOutput = " " + curOutput[6:]
}
lines += curOutput
}
lines += "\n"
address += uintptr(cols)
}
return lines
}