github.com/aclements/go-misc@v0.0.0-20240129233631-2f6ede80790c/ptype/main.go (about)

     1  // Copyright 2017 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // Command ptype prints Go types from a binary using DWARF info.
     6  //
     7  // ptype binary <types...> prints types matching the given regexps in
     8  // binary in (approximate) Go syntax. If no types are named, ptype
     9  // prints all named types.
    10  //
    11  // The printed types are annotated with information about sizes, field
    12  // offsets, and gaps between fields.
    13  //
    14  // The printed types are as close as possible to Go type syntax, but
    15  // aren't guaranteed to be legal Go code (e.g., unions have no Go
    16  // equivalent). ptype backs out high-level Go types such as maps and
    17  // channels where possible.
    18  //
    19  // For example, ptype ptype runtime.mcache prints:
    20  //
    21  //     type runtime.mcache struct {
    22  //             // 1200 byte struct
    23  //             next_sample int32                         // offset 0
    24  //             // 4 byte gap
    25  //             local_scan uintptr                        // offset 8
    26  //             tiny uintptr                              // offset 16
    27  //             tinyoffset uintptr                        // offset 24
    28  //             local_tinyallocs uintptr                  // offset 32
    29  //             alloc [67]*mspan                          // offset 40
    30  //             stackcache [4]struct {                    // offset 576
    31  //                     list runtime.gclinkptr            // offset 576 + 16*i
    32  //                     size uintptr                      // offset 576 + 16*i + 8
    33  //             }
    34  //             local_nlookup uintptr                     // offset 640
    35  //             local_largefree uintptr                   // offset 648
    36  //             local_nlargefree uintptr                  // offset 656
    37  //             local_nsmallfree [67]uintptr              // offset 664
    38  //     }
    39  package main
    40  
    41  import (
    42  	"debug/dwarf"
    43  	"debug/elf"
    44  	"flag"
    45  	"fmt"
    46  	"log"
    47  	"os"
    48  	"regexp"
    49  	"strings"
    50  	"unicode/utf8"
    51  )
    52  
    53  func main() {
    54  	flag.Usage = func() {
    55  		fmt.Fprintf(os.Stderr, "Usage: %s binary <type-regexp...>\n", os.Args[0])
    56  	}
    57  	flag.Parse()
    58  	if flag.NArg() < 1 {
    59  		flag.Usage()
    60  		os.Exit(2)
    61  	}
    62  	binPath := flag.Arg(0)
    63  
    64  	// Parse type regexp args.
    65  	regexps := []*regexp.Regexp{}
    66  	for _, tre := range flag.Args()[1:] {
    67  		re, err := regexp.Compile("^" + tre)
    68  		if err != nil {
    69  			fmt.Fprintf(os.Stderr, "bad regexp %q: %s", tre, err)
    70  			os.Exit(1)
    71  		}
    72  		regexps = append(regexps, re)
    73  	}
    74  	if len(regexps) == 0 {
    75  		regexps = append(regexps, regexp.MustCompile(".*"))
    76  	}
    77  
    78  	// Parse binary.
    79  	f, err := elf.Open(binPath)
    80  	if err != nil {
    81  		log.Fatal(err)
    82  	}
    83  	defer f.Close()
    84  	d, err := f.DWARF()
    85  	if err != nil {
    86  		log.Fatal(err)
    87  	}
    88  
    89  	// Find all of the named types.
    90  	r := d.Reader()
    91  	for {
    92  		ent, err := r.Next()
    93  		if err != nil {
    94  			log.Fatal(err)
    95  		}
    96  		if ent == nil {
    97  			break
    98  		}
    99  
   100  		if ent.Tag != dwarf.TagTypedef {
   101  			continue
   102  		}
   103  
   104  		name, ok := ent.Val(dwarf.AttrName).(string)
   105  		if !ok {
   106  			continue
   107  		}
   108  
   109  		// Do we want this type?
   110  		matched := false
   111  		for _, re := range regexps {
   112  			if re.MatchString(name) {
   113  				matched = true
   114  				break
   115  			}
   116  		}
   117  		if isBuiltinName(name) || !matched {
   118  			r.SkipChildren()
   119  			continue
   120  		}
   121  
   122  		// Print the type.
   123  		base, ok := ent.Val(dwarf.AttrType).(dwarf.Offset)
   124  		if !ok {
   125  			log.Printf("type %s has unknown underlying type", name)
   126  			continue
   127  		}
   128  
   129  		typ, err := d.Type(base)
   130  		if err != nil {
   131  			log.Fatal(err)
   132  		}
   133  
   134  		pkg := ""
   135  		if i := strings.LastIndex(name, "."); i >= 0 {
   136  			pkg = name[:i+1]
   137  		}
   138  
   139  		p := &typePrinter{pkg: pkg}
   140  		p.fmt("type %s ", name)
   141  		p.printType(typ)
   142  		p.fmt("\n\n")
   143  
   144  		r.SkipChildren()
   145  	}
   146  }
   147  
   148  func isBuiltinName(typeName string) bool {
   149  	switch typeName {
   150  	case "string":
   151  		return true
   152  	}
   153  	return strings.HasPrefix(typeName, "map[") ||
   154  		strings.HasPrefix(typeName, "func(") ||
   155  		strings.HasPrefix(typeName, "chan ") ||
   156  		strings.HasPrefix(typeName, "chan<- ") ||
   157  		strings.HasPrefix(typeName, "<-chan ")
   158  }
   159  
   160  type typePrinter struct {
   161  	offset []int64
   162  	depth  int
   163  	nameOk int
   164  	pkg    string
   165  
   166  	// pos is the current character position on this line.
   167  	pos int
   168  
   169  	// lineComment is a comment to print at the end of this line.
   170  	lineComment string
   171  }
   172  
   173  func (p *typePrinter) fmt(f string, args ...interface{}) {
   174  	b := fmt.Sprintf(f, args...)
   175  	if strings.IndexAny(b, "\n\t") < 0 {
   176  		fmt.Printf("%s", b)
   177  		p.pos += utf8.RuneCountInString(b)
   178  		return
   179  	}
   180  	lines := strings.Split(b, "\n")
   181  	for i, line := range lines {
   182  		hasNL := i < len(lines)-1
   183  		if p.lineComment == "" && hasNL {
   184  			// Fast path for complete lines with no comment.
   185  			fmt.Printf("%s\n", line)
   186  			p.pos = 0
   187  			continue
   188  		}
   189  
   190  		for _, r := range line {
   191  			if r == '\t' {
   192  				p.pos = (p.pos + 8) &^ 7
   193  			} else {
   194  				p.pos++
   195  			}
   196  		}
   197  		fmt.Printf("%s", line)
   198  		if hasNL {
   199  			if p.lineComment != "" {
   200  				space := 50 - p.pos
   201  				if space < 1 {
   202  					space = 1
   203  				}
   204  				fmt.Printf("%*s// %s", space, "", p.lineComment)
   205  				p.lineComment = ""
   206  			}
   207  			fmt.Printf("\n")
   208  			p.pos = 0
   209  		}
   210  	}
   211  }
   212  
   213  func (p *typePrinter) setLineComment(f string, args ...interface{}) {
   214  	if p.lineComment != "" {
   215  		panic("multiple line comments")
   216  	}
   217  	p.lineComment = fmt.Sprintf(f, args...)
   218  }
   219  
   220  func (p *typePrinter) stripPkg(name string) string {
   221  	if p.pkg != "" && strings.HasPrefix(name, p.pkg) {
   222  		return name[len(p.pkg):]
   223  	}
   224  	return name
   225  }
   226  
   227  func (p *typePrinter) printType(typ dwarf.Type) {
   228  	if p.offset == nil {
   229  		p.offset = []int64{0}
   230  	}
   231  
   232  	if p.nameOk > 0 && typ.Common().Name != "" {
   233  		p.fmt("%s", p.stripPkg(typ.Common().Name))
   234  		p.offset[len(p.offset)-1] += typ.Size()
   235  		return
   236  	}
   237  
   238  	switch typ := typ.(type) {
   239  	case *dwarf.ArrayType:
   240  		if typ.Count < 0 {
   241  			p.fmt("[incomplete]")
   242  		} else {
   243  			p.fmt("[%d]", typ.Count)
   244  		}
   245  		if typ.StrideBitSize > 0 {
   246  			p.fmt("/* %d bit element */", typ.StrideBitSize)
   247  		}
   248  		origOffset := p.offset
   249  		p.offset = append(p.offset, typ.Type.Size(), 0)
   250  		p.printType(typ.Type)
   251  		p.offset = origOffset
   252  
   253  	case *dwarf.StructType:
   254  		if typ.StructName != "" && (p.nameOk > 0 || isBuiltinName(typ.StructName)) {
   255  			p.fmt("%s", p.stripPkg(typ.StructName))
   256  			break
   257  		}
   258  
   259  		if strings.HasPrefix(typ.StructName, "[]") {
   260  			p.fmt("[]")
   261  			elem := typ.Field[0].Type
   262  			origOffset := p.offset
   263  			p.offset = append(p.offset, elem.Size(), 0)
   264  			p.printType(elem)
   265  			p.offset = origOffset
   266  			break
   267  		}
   268  
   269  		if typ.StructName == "runtime.eface" {
   270  			p.fmt("interface{}")
   271  			break
   272  		} else if typ.StructName == "runtime.iface" {
   273  			p.fmt("interface{ ... }")
   274  			break
   275  		}
   276  
   277  		isUnion := typ.Kind == "union"
   278  		p.fmt("%s {", typ.Kind)
   279  		if typ.Incomplete {
   280  			p.fmt(" incomplete }")
   281  			break
   282  		}
   283  		p.depth++
   284  		indent := "\n" + strings.Repeat("\t", p.depth)
   285  		p.fmt("%s// %d byte %s", indent, typ.Size(), typ.Kind)
   286  		startOffset := p.offset[len(p.offset)-1]
   287  		var prevEnd int64
   288  		for i, f := range typ.Field {
   289  			p.fmt(indent)
   290  			// TODO: Bit offsets?
   291  			if !isUnion {
   292  				offset := startOffset + f.ByteOffset
   293  				if i > 0 && prevEnd < offset {
   294  					p.fmt("// %d byte gap", offset-prevEnd)
   295  					p.fmt(indent)
   296  				}
   297  				p.offset[len(p.offset)-1] = offset
   298  				p.setLineComment("offset %s", p.strOffset())
   299  				if f.Type.Size() < 0 {
   300  					// Who knows. Give up.
   301  					// TODO: This happens for funcs.
   302  					prevEnd = (1 << 31) - 1
   303  				} else {
   304  					prevEnd = offset + f.Type.Size()
   305  				}
   306  			}
   307  			p.fmt("%s ", f.Name)
   308  			p.printType(f.Type)
   309  			if f.BitSize != 0 {
   310  				p.fmt(" : %d", f.BitSize)
   311  			}
   312  		}
   313  		p.offset[len(p.offset)-1] = startOffset
   314  		p.depth--
   315  		p.fmt("\n%s}", strings.Repeat("\t", p.depth))
   316  
   317  	case *dwarf.EnumType:
   318  		p.fmt("enum") // TODO
   319  
   320  	case *dwarf.BoolType, *dwarf.CharType, *dwarf.ComplexType, *dwarf.FloatType, *dwarf.IntType, *dwarf.UcharType, *dwarf.UintType:
   321  		// Basic types.
   322  		p.fmt("%s", typ.String())
   323  
   324  	case *dwarf.PtrType:
   325  		if _, ok := typ.Type.(*dwarf.VoidType); ok {
   326  			// *void is unsafe.Pointer
   327  			p.fmt("unsafe.Pointer")
   328  			break
   329  		}
   330  		origOffset := p.offset
   331  		p.offset = []int64{0}
   332  		p.nameOk++
   333  		p.fmt("*")
   334  		p.printType(typ.Type)
   335  		p.nameOk--
   336  		p.offset = origOffset
   337  
   338  	case *dwarf.FuncType:
   339  		// TODO: Expand ourselves so we can clean up argument
   340  		// types, etc.
   341  		p.fmt(typ.String())
   342  
   343  	case *dwarf.QualType:
   344  		p.fmt("/* %s */ ", typ.Qual)
   345  		p.printType(typ.Type)
   346  
   347  	case *dwarf.TypedefType:
   348  		n := typ.Common().Name
   349  		if isBuiltinName(n) {
   350  			// TODO: Make Go-ifying optional.
   351  			//
   352  			// TODO: Expand map types ourselves if
   353  			// possible so we can clean up the type names.
   354  			p.fmt("%s", n)
   355  			return
   356  		}
   357  
   358  		real := typ.Type
   359  		for {
   360  			if real2, ok := real.(*dwarf.TypedefType); ok {
   361  				real = real2.Type
   362  			} else {
   363  				break
   364  			}
   365  		}
   366  		if str, ok := real.(*dwarf.StructType); ok {
   367  			switch str.StructName {
   368  			case "runtime.iface", "runtime.eface":
   369  				// Named interface type.
   370  				p.fmt(p.stripPkg(n))
   371  				return
   372  			}
   373  		}
   374  
   375  		// TODO: If it's "type x map..." or similar, we never
   376  		// see the "map[...]" style name and only see that x's
   377  		// underlying type is a pointer to a struct named
   378  		// "hash<...>".
   379  
   380  		p.fmt("/* %s */ ", p.stripPkg(n))
   381  		p.printType(real)
   382  
   383  	case *dwarf.UnspecifiedType:
   384  		p.fmt("unspecified")
   385  
   386  	case *dwarf.VoidType:
   387  		p.fmt("void")
   388  	}
   389  
   390  	p.offset[len(p.offset)-1] += typ.Size()
   391  }
   392  
   393  func (p *typePrinter) strOffset() string {
   394  	buf := fmt.Sprintf("%d", p.offset[0])
   395  	for i, idx := 1, 'i'; i < len(p.offset); i, idx = i+2, idx+1 {
   396  		buf += fmt.Sprintf(" + %d*%c", p.offset[i], idx)
   397  		if p.offset[i+1] != 0 {
   398  			buf += fmt.Sprintf(" + %d", p.offset[i+1])
   399  		}
   400  	}
   401  	return buf
   402  }