mirror of
				https://github.com/go-delve/delve.git
				synced 2025-10-31 10:47:27 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			655 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			655 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package api
 | |
| 
 | |
| import (
 | |
| 	"bytes"
 | |
| 	"fmt"
 | |
| 	"io"
 | |
| 	"math"
 | |
| 	"reflect"
 | |
| 	"strconv"
 | |
| 	"strings"
 | |
| 	"text/tabwriter"
 | |
| )
 | |
| 
 | |
| 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"
 | |
| )
 | |
| 
 | |
| type prettyFlags uint8
 | |
| 
 | |
| const (
 | |
| 	prettyTop prettyFlags = 1 << iota
 | |
| 	prettyNewlines
 | |
| 	prettyIncludeType
 | |
| 	prettyShortenType
 | |
| )
 | |
| 
 | |
| func (flags prettyFlags) top() bool         { return flags&prettyTop != 0 }
 | |
| func (flags prettyFlags) includeType() bool { return flags&prettyIncludeType != 0 }
 | |
| func (flags prettyFlags) newlines() bool    { return flags&prettyNewlines != 0 }
 | |
| func (flags prettyFlags) shortenType() bool { return flags&prettyShortenType != 0 }
 | |
| 
 | |
| func (flags prettyFlags) set(flag prettyFlags, v bool) prettyFlags {
 | |
| 	if v {
 | |
| 		return flags | flag
 | |
| 	} else {
 | |
| 		return flags &^ flag
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // SinglelineString returns a representation of v on a single line.
 | |
| func (v *Variable) SinglelineString() string {
 | |
| 	var buf bytes.Buffer
 | |
| 	v.writeTo(&buf, prettyTop|prettyIncludeType, "", "")
 | |
| 	return buf.String()
 | |
| }
 | |
| 
 | |
| // SinglelineStringWithShortTypes returns a representation of v on a single line, with types shortened.
 | |
| func (v *Variable) SinglelineStringWithShortTypes() string {
 | |
| 	var buf bytes.Buffer
 | |
| 	v.writeTo(&buf, prettyTop|prettyIncludeType|prettyShortenType, "", "")
 | |
| 	return buf.String()
 | |
| }
 | |
| 
 | |
| // SinglelineStringFormatted returns a representation of v on a single line, using the format specified by fmtstr.
 | |
| func (v *Variable) SinglelineStringFormatted(fmtstr string) string {
 | |
| 	var buf bytes.Buffer
 | |
| 	v.writeTo(&buf, prettyTop|prettyIncludeType, "", fmtstr)
 | |
| 	return buf.String()
 | |
| }
 | |
| 
 | |
| // MultilineString returns a representation of v on multiple lines.
 | |
| func (v *Variable) MultilineString(indent, fmtstr string) string {
 | |
| 	var buf bytes.Buffer
 | |
| 	v.writeTo(&buf, prettyTop|prettyNewlines|prettyIncludeType, indent, fmtstr)
 | |
| 	return buf.String()
 | |
| }
 | |
| 
 | |
| func (v *Variable) typeStr(flags prettyFlags) string {
 | |
| 	if flags.shortenType() {
 | |
| 		return ShortenType(v.Type)
 | |
| 	}
 | |
| 
 | |
| 	return v.Type
 | |
| }
 | |
| 
 | |
| func (v *Variable) writeTo(buf io.Writer, flags prettyFlags, indent, fmtstr string) {
 | |
| 	if v.Unreadable != "" {
 | |
| 		fmt.Fprintf(buf, "(unreadable %s)", v.Unreadable)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	if !flags.top() && v.Addr == 0 && v.Value == "" {
 | |
| 		if flags.includeType() && v.Type != "void" {
 | |
| 			fmt.Fprintf(buf, "%s nil", v.typeStr(flags))
 | |
| 		} else {
 | |
| 			fmt.Fprint(buf, "nil")
 | |
| 		}
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	switch v.Kind {
 | |
| 	case reflect.Slice:
 | |
| 		v.writeSliceTo(buf, flags, indent, fmtstr)
 | |
| 	case reflect.Array:
 | |
| 		v.writeArrayTo(buf, flags, indent, fmtstr)
 | |
| 	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 {
 | |
| 			v.writePointerTo(buf, flags)
 | |
| 		} else {
 | |
| 			if flags.top() && flags.newlines() && v.Children[0].Addr != 0 {
 | |
| 				v.writePointerTo(buf, flags)
 | |
| 				fmt.Fprint(buf, "\n")
 | |
| 			}
 | |
| 			fmt.Fprint(buf, "*")
 | |
| 			v.Children[0].writeTo(buf, flags.set(prettyTop, false), indent, fmtstr)
 | |
| 		}
 | |
| 	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.Chan:
 | |
| 		if flags.newlines() {
 | |
| 			v.writeStructTo(buf, flags, indent, fmtstr)
 | |
| 		} else {
 | |
| 			if len(v.Children) == 0 {
 | |
| 				fmt.Fprintf(buf, "%s nil", v.typeStr(flags))
 | |
| 			} else {
 | |
| 				fmt.Fprintf(buf, "%s %s/%s", v.typeStr(flags), v.Children[0].Value, v.Children[1].Value)
 | |
| 			}
 | |
| 		}
 | |
| 	case reflect.Struct:
 | |
| 		if v.Value != "" {
 | |
| 			fmt.Fprintf(buf, "%s(%s)", v.typeStr(flags), v.Value)
 | |
| 			flags = flags.set(prettyIncludeType, false)
 | |
| 		}
 | |
| 		v.writeStructTo(buf, flags, indent, fmtstr)
 | |
| 	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 flags.includeType() {
 | |
| 			if v.Children[0].Kind == reflect.Invalid {
 | |
| 				fmt.Fprintf(buf, "%s ", v.typeStr(flags))
 | |
| 				if v.Children[0].Addr == 0 {
 | |
| 					fmt.Fprint(buf, "nil")
 | |
| 					return
 | |
| 				}
 | |
| 			} else {
 | |
| 				fmt.Fprintf(buf, "%s(%s) ", v.typeStr(flags), 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, flags.set(prettyTop, false).set(prettyIncludeType, !flags.includeType()), indent, fmtstr)
 | |
| 			}
 | |
| 		} else if data.OnlyAddr {
 | |
| 			if strings.Contains(v.Type, "/") {
 | |
| 				fmt.Fprintf(buf, "*(*%q)(%#x)", v.typeStr(flags), v.Addr)
 | |
| 			} else {
 | |
| 				fmt.Fprintf(buf, "*(*%s)(%#x)", v.typeStr(flags), v.Addr)
 | |
| 			}
 | |
| 		} else {
 | |
| 			v.Children[0].writeTo(buf, flags.set(prettyTop, false).set(prettyIncludeType, !flags.includeType()), indent, fmtstr)
 | |
| 		}
 | |
| 	case reflect.Map:
 | |
| 		v.writeMapTo(buf, flags, indent, fmtstr)
 | |
| 	case reflect.Func:
 | |
| 		if v.Value == "" {
 | |
| 			fmt.Fprint(buf, "nil")
 | |
| 		} else {
 | |
| 			fmt.Fprintf(buf, "%s", v.Value)
 | |
| 			if flags.newlines() && len(v.Children) > 0 {
 | |
| 				fmt.Fprintf(buf, " {\n")
 | |
| 				for i := range v.Children {
 | |
| 					fmt.Fprintf(buf, "%s%s%s %s = ", indent, indentString, v.Children[i].Name, v.Children[i].typeStr(flags))
 | |
| 					v.Children[i].writeTo(buf, flags.set(prettyTop, false).set(prettyIncludeType, false), indent+indentString, fmtstr)
 | |
| 					fmt.Fprintf(buf, "\n")
 | |
| 				}
 | |
| 				fmt.Fprintf(buf, "%s}", indent)
 | |
| 			}
 | |
| 		}
 | |
| 	default:
 | |
| 		v.writeBasicType(buf, fmtstr)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (v *Variable) writePointerTo(buf io.Writer, flags prettyFlags) {
 | |
| 	if strings.Contains(v.Type, "/") {
 | |
| 		fmt.Fprintf(buf, "(%q)(%#x)", v.typeStr(flags), v.Children[0].Addr)
 | |
| 	} else {
 | |
| 		fmt.Fprintf(buf, "(%s)(%#x)", v.typeStr(flags), v.Children[0].Addr)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (v *Variable) writeBasicType(buf io.Writer, fmtstr string) {
 | |
| 	if v.Value == "" && v.Kind != reflect.String {
 | |
| 		fmt.Fprintf(buf, "(unknown %s)", v.Kind)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	switch v.Kind {
 | |
| 	case reflect.Bool:
 | |
| 		if fmtstr == "" {
 | |
| 			buf.Write([]byte(v.Value))
 | |
| 			return
 | |
| 		}
 | |
| 		var b bool = v.Value == "true"
 | |
| 		fmt.Fprintf(buf, fmtstr, b)
 | |
| 
 | |
| 	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
 | |
| 		if fmtstr == "" {
 | |
| 			buf.Write([]byte(v.Value))
 | |
| 			return
 | |
| 		}
 | |
| 		n, _ := strconv.ParseInt(ExtractIntValue(v.Value), 10, 64)
 | |
| 		fmt.Fprintf(buf, fmtstr, n)
 | |
| 
 | |
| 	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
 | |
| 		if fmtstr == "" {
 | |
| 			buf.Write([]byte(v.Value))
 | |
| 			return
 | |
| 		}
 | |
| 		n, _ := strconv.ParseUint(ExtractIntValue(v.Value), 10, 64)
 | |
| 		fmt.Fprintf(buf, fmtstr, n)
 | |
| 
 | |
| 	case reflect.Float32, reflect.Float64:
 | |
| 		if fmtstr == "" {
 | |
| 			buf.Write([]byte(v.Value))
 | |
| 			return
 | |
| 		}
 | |
| 		x, _ := strconv.ParseFloat(v.Value, 64)
 | |
| 		fmt.Fprintf(buf, fmtstr, x)
 | |
| 
 | |
| 	case reflect.Complex64, reflect.Complex128:
 | |
| 		if fmtstr == "" {
 | |
| 			fmt.Fprintf(buf, "(%s + %si)", v.Children[0].Value, v.Children[1].Value)
 | |
| 			return
 | |
| 		}
 | |
| 		real, _ := strconv.ParseFloat(v.Children[0].Value, 64)
 | |
| 		imag, _ := strconv.ParseFloat(v.Children[1].Value, 64)
 | |
| 		var x complex128 = complex(real, imag)
 | |
| 		fmt.Fprintf(buf, fmtstr, x)
 | |
| 
 | |
| 	case reflect.String:
 | |
| 		if fmtstr == "" {
 | |
| 			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)
 | |
| 			return
 | |
| 		}
 | |
| 		fmt.Fprintf(buf, fmtstr, v.Value)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func ExtractIntValue(s string) string {
 | |
| 	if s == "" || s[len(s)-1] != ')' {
 | |
| 		return s
 | |
| 	}
 | |
| 	open := strings.LastIndex(s, "(")
 | |
| 	if open < 0 {
 | |
| 		return s
 | |
| 	}
 | |
| 	return s[open+1 : len(s)-1]
 | |
| }
 | |
| 
 | |
| func (v *Variable) writeSliceTo(buf io.Writer, flags prettyFlags, indent, fmtstr string) {
 | |
| 	if flags.includeType() {
 | |
| 		fmt.Fprintf(buf, "%s len: %d, cap: %d, ", v.typeStr(flags), v.Len, v.Cap)
 | |
| 	}
 | |
| 	if v.Base == 0 && len(v.Children) == 0 {
 | |
| 		fmt.Fprintf(buf, "nil")
 | |
| 		return
 | |
| 	}
 | |
| 	v.writeSliceOrArrayTo(buf, flags, indent, fmtstr)
 | |
| }
 | |
| 
 | |
| func (v *Variable) writeArrayTo(buf io.Writer, flags prettyFlags, indent, fmtstr string) {
 | |
| 	if flags.includeType() {
 | |
| 		fmt.Fprintf(buf, "%s ", v.typeStr(flags))
 | |
| 	}
 | |
| 	v.writeSliceOrArrayTo(buf, flags, indent, fmtstr)
 | |
| }
 | |
| 
 | |
| func (v *Variable) writeStructTo(buf io.Writer, flags prettyFlags, indent, fmtstr string) {
 | |
| 	if int(v.Len) != len(v.Children) && len(v.Children) == 0 {
 | |
| 		if strings.Contains(v.Type, "/") {
 | |
| 			fmt.Fprintf(buf, "(*%q)(%#x)", v.typeStr(flags), v.Addr)
 | |
| 		} else {
 | |
| 			fmt.Fprintf(buf, "(*%s)(%#x)", v.typeStr(flags), v.Addr)
 | |
| 		}
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	if flags.includeType() {
 | |
| 		fmt.Fprintf(buf, "%s ", v.typeStr(flags))
 | |
| 	}
 | |
| 
 | |
| 	nl := v.shouldNewlineStruct(flags.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, prettyIncludeType.set(prettyNewlines, nl), indent+indentString, fmtstr)
 | |
| 		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, flags prettyFlags, indent, fmtstr string) {
 | |
| 	if flags.includeType() {
 | |
| 		fmt.Fprintf(buf, "%s ", v.typeStr(flags))
 | |
| 	}
 | |
| 	if v.Base == 0 && len(v.Children) == 0 {
 | |
| 		fmt.Fprintf(buf, "nil")
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	nl := flags.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, 0, indent+indentString, fmtstr)
 | |
| 		fmt.Fprint(buf, ": ")
 | |
| 		value.writeTo(buf, prettyFlags(0).set(prettyNewlines, nl), indent+indentString, fmtstr)
 | |
| 		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
 | |
| 			if len(v.Children) == 0 {
 | |
| 				return kind, hasptr
 | |
| 			}
 | |
| 			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, flags prettyFlags, indent, fmtstr string) {
 | |
| 	nl := v.shouldNewlineArray(flags.newlines())
 | |
| 	fmt.Fprint(buf, "[")
 | |
| 
 | |
| 	for i := range v.Children {
 | |
| 		if nl {
 | |
| 			fmt.Fprintf(buf, "\n%s%s", indent, indentString)
 | |
| 		}
 | |
| 		v.Children[i].writeTo(buf, prettyFlags(0).set(prettyNewlines, nl), indent+indentString, fmtstr)
 | |
| 		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, "]")
 | |
| }
 | |
| 
 | |
| // PrettyExamineMemory examine the memory and format data
 | |
| //
 | |
| // `format` specifies the data format (or data type), `size` specifies size of each data,
 | |
| // like 4byte integer, 1byte character, etc. `count` specifies the number of values.
 | |
| func PrettyExamineMemory(address uintptr, memArea []byte, isLittleEndian bool, format byte, size int) string {
 | |
| 	var (
 | |
| 		cols      int
 | |
| 		colFormat string
 | |
| 		colBytes  = size
 | |
| 
 | |
| 		addrLen int
 | |
| 		addrFmt string
 | |
| 	)
 | |
| 
 | |
| 	// Different versions of golang output differently about '#'.
 | |
| 	// See https://ci.appveyor.com/project/derekparker/delve-facy3/builds/30179356.
 | |
| 	switch format {
 | |
| 	case 'b':
 | |
| 		cols = 4 // Avoid emitting rows that are too long when using binary format
 | |
| 		colFormat = fmt.Sprintf("%%0%db", colBytes*8)
 | |
| 	case 'o':
 | |
| 		cols = 8
 | |
| 		colFormat = fmt.Sprintf("0%%0%do", colBytes*3) // Always keep one leading zero for octal.
 | |
| 	case 'd':
 | |
| 		cols = 8
 | |
| 		colFormat = fmt.Sprintf("%%0%dd", colBytes*3)
 | |
| 	case 'x':
 | |
| 		cols = 8
 | |
| 		colFormat = fmt.Sprintf("0x%%0%dx", colBytes*2) // Always keep one leading '0x' for hex.
 | |
| 	default:
 | |
| 		return fmt.Sprintf("not supported format %q\n", string(format))
 | |
| 	}
 | |
| 	colFormat += "\t"
 | |
| 
 | |
| 	l := len(memArea)
 | |
| 	rows := l / (cols * colBytes)
 | |
| 	if l%(cols*colBytes) != 0 {
 | |
| 		rows++
 | |
| 	}
 | |
| 
 | |
| 	// Avoid the lens of two adjacent address are different, so always use the last addr's len to format.
 | |
| 	if l != 0 {
 | |
| 		addrLen = len(fmt.Sprintf("%x", uint64(address)+uint64(l)))
 | |
| 	}
 | |
| 	addrFmt = "0x%0" + strconv.Itoa(addrLen) + "x:\t"
 | |
| 
 | |
| 	var b strings.Builder
 | |
| 	w := tabwriter.NewWriter(&b, 0, 0, 3, ' ', 0)
 | |
| 
 | |
| 	for i := 0; i < rows; i++ {
 | |
| 		fmt.Fprintf(w, addrFmt, address)
 | |
| 
 | |
| 		for j := 0; j < cols; j++ {
 | |
| 			offset := i*(cols*colBytes) + j*colBytes
 | |
| 			if offset+colBytes <= len(memArea) {
 | |
| 				n := byteArrayToUInt64(memArea[offset:offset+colBytes], isLittleEndian)
 | |
| 				fmt.Fprintf(w, colFormat, n)
 | |
| 			}
 | |
| 		}
 | |
| 		fmt.Fprintln(w, "")
 | |
| 		address += uintptr(cols * colBytes)
 | |
| 	}
 | |
| 	w.Flush()
 | |
| 	return b.String()
 | |
| }
 | |
| 
 | |
| func byteArrayToUInt64(buf []byte, isLittleEndian bool) uint64 {
 | |
| 	var n uint64
 | |
| 	if isLittleEndian {
 | |
| 		for i := len(buf) - 1; i >= 0; i-- {
 | |
| 			n = n<<8 + uint64(buf[i])
 | |
| 		}
 | |
| 	} else {
 | |
| 		for i := 0; i < len(buf); i++ {
 | |
| 			n = n<<8 + uint64(buf[i])
 | |
| 		}
 | |
| 	}
 | |
| 	return n
 | |
| }
 | |
| 
 | |
| const stacktraceTruncatedMessage = "(truncated)"
 | |
| 
 | |
| func digits(n int) int {
 | |
| 	if n <= 0 {
 | |
| 		return 1
 | |
| 	}
 | |
| 	return int(math.Floor(math.Log10(float64(n)))) + 1
 | |
| }
 | |
| 
 | |
| type StackTraceColors struct {
 | |
| 	FunctionColor string
 | |
| 	BasenameColor string
 | |
| 	NormalColor   string
 | |
| }
 | |
| 
 | |
| func PrintStack(formatPath func(string) string, out io.Writer, stack []Stackframe, ind string, offsets bool, stc StackTraceColors, include func(Stackframe) bool) {
 | |
| 	if len(stack) == 0 {
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	extranl := offsets
 | |
| 	for i := range stack {
 | |
| 		if extranl {
 | |
| 			break
 | |
| 		}
 | |
| 		extranl = extranl || (len(stack[i].Defers) > 0) || (len(stack[i].Arguments) > 0) || (len(stack[i].Locals) > 0)
 | |
| 	}
 | |
| 
 | |
| 	fileLine := func(file string, line int) string {
 | |
| 		file = formatPath(file)
 | |
| 		if stc.BasenameColor == "" {
 | |
| 			return fmt.Sprintf("%s:%d", file, line)
 | |
| 		}
 | |
| 		slash := strings.LastIndex(file, "/")
 | |
| 		if slash < 0 {
 | |
| 			slash = 0
 | |
| 		} else {
 | |
| 			slash++
 | |
| 		}
 | |
| 		return fmt.Sprintf("%s%s%s:%d%s", file[:slash], stc.BasenameColor, file[slash:], line, stc.NormalColor)
 | |
| 	}
 | |
| 
 | |
| 	d := digits(len(stack) - 1)
 | |
| 	fmtstr := "%s%" + strconv.Itoa(d) + "d  0x%016x in " + stc.FunctionColor + "%s" + stc.NormalColor + "\n"
 | |
| 	s := ind + strings.Repeat(" ", d+2+len(ind))
 | |
| 
 | |
| 	for i := range stack {
 | |
| 		if !include(stack[i]) {
 | |
| 			continue
 | |
| 		}
 | |
| 		if stack[i].Err != "" {
 | |
| 			fmt.Fprintf(out, "%serror: %s\n", s, stack[i].Err)
 | |
| 			continue
 | |
| 		}
 | |
| 		fmt.Fprintf(out, fmtstr, ind, i, stack[i].PC, stack[i].Function.Name())
 | |
| 		fmt.Fprintf(out, "%sat %s\n", s, fileLine(stack[i].File, stack[i].Line))
 | |
| 
 | |
| 		if offsets {
 | |
| 			fmt.Fprintf(out, "%sframe: %+#x frame pointer %+#x\n", s, stack[i].FrameOffset, stack[i].FramePointerOffset)
 | |
| 		}
 | |
| 
 | |
| 		for j, d := range stack[i].Defers {
 | |
| 			deferHeader := fmt.Sprintf("%s    defer %d: ", s, j+1)
 | |
| 			s2 := strings.Repeat(" ", len(deferHeader))
 | |
| 			if d.Unreadable != "" {
 | |
| 				fmt.Fprintf(out, "%s(unreadable defer: %s)\n", deferHeader, d.Unreadable)
 | |
| 				continue
 | |
| 			}
 | |
| 			fmt.Fprintf(out, "%s%#016x in %s%s%s\n", deferHeader, d.DeferredLoc.PC, stc.FunctionColor, d.DeferredLoc.Function.Name(), stc.NormalColor)
 | |
| 			fmt.Fprintf(out, "%sat %s:%d\n", s2, formatPath(d.DeferredLoc.File), d.DeferredLoc.Line)
 | |
| 			fmt.Fprintf(out, "%sdeferred by %s at %s\n", s2, d.DeferLoc.Function.Name(), fileLine(d.DeferLoc.File, d.DeferLoc.Line))
 | |
| 		}
 | |
| 
 | |
| 		for j := range stack[i].Arguments {
 | |
| 			fmt.Fprintf(out, "%s    %s = %s\n", s, stack[i].Arguments[j].Name, stack[i].Arguments[j].SinglelineString())
 | |
| 		}
 | |
| 		for j := range stack[i].Locals {
 | |
| 			fmt.Fprintf(out, "%s    %s = %s\n", s, stack[i].Locals[j].Name, stack[i].Locals[j].SinglelineString())
 | |
| 		}
 | |
| 
 | |
| 		if extranl {
 | |
| 			fmt.Fprintln(out)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if len(stack) > 0 && !stack[len(stack)-1].Bottom {
 | |
| 		fmt.Fprintf(out, "%s"+stacktraceTruncatedMessage+"\n", ind)
 | |
| 	}
 | |
| }
 | 
