github.com/aclements/go-misc@v0.0.0-20240129233631-2f6ede80790c/findtypes/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 findtypes compares checkmarks failures with the types in a
     6  // binary to find likely matches.
     7  //
     8  // findtypes deduces the likely pointer/scalar map from the output of
     9  // a checkmarks failure and compares it against the pointer/scalar
    10  // maps of all types in a binary. The output is a scored and ranked
    11  // list of the most closely matching types, along with their
    12  // pointer/scalar maps.
    13  package main
    14  
    15  import (
    16  	"bufio"
    17  	"debug/dwarf"
    18  	"debug/elf"
    19  	"flag"
    20  	"fmt"
    21  	"io"
    22  	"log"
    23  	"math/big"
    24  	"os"
    25  	"regexp"
    26  	"sort"
    27  	"strconv"
    28  )
    29  
    30  const ptrSize = 8 // TODO: Get from DWARF.
    31  
    32  func main() {
    33  	flag.Usage = func() {
    34  		fmt.Fprintf(os.Stderr, "Usage: %s failure binary\n", os.Args[0])
    35  	}
    36  	flag.Parse()
    37  	if flag.NArg() != 2 {
    38  		flag.Usage()
    39  		os.Exit(1)
    40  	}
    41  	failPath, binPath := flag.Arg(0), flag.Arg(1)
    42  
    43  	// Parse greyobject failure.
    44  	failFile, err := os.Open(failPath)
    45  	if err != nil {
    46  		log.Fatal(err)
    47  	}
    48  	failure := parseGreyobject(failFile)
    49  	failFile.Close()
    50  	if failure.words == nil {
    51  		log.Fatal("failed to parse failure message in %s", failPath)
    52  	}
    53  	fmt.Print("failure:")
    54  	for i, known := range failure.words {
    55  		if i%32 == 0 {
    56  			fmt.Printf("\n\t")
    57  		} else if i%16 == 0 {
    58  			fmt.Printf(" ")
    59  		}
    60  		switch known {
    61  		case 0:
    62  			fmt.Print("S")
    63  		case 1:
    64  			fmt.Print("P")
    65  		case 2:
    66  			fmt.Print("?")
    67  		}
    68  	}
    69  	fmt.Println()
    70  
    71  	// Parse binary.
    72  	f, err := elf.Open(binPath)
    73  	if err != nil {
    74  		log.Fatal(err)
    75  	}
    76  	defer f.Close()
    77  	d, err := f.DWARF()
    78  	if err != nil {
    79  		log.Fatal(err)
    80  	}
    81  
    82  	// Find all of the types.
    83  	type comparison struct {
    84  		ti    *typeInfo
    85  		score float64
    86  	}
    87  	var results []comparison
    88  	r := d.Reader()
    89  	for {
    90  		ent, err := r.Next()
    91  		if err != nil {
    92  			log.Fatal(err)
    93  		}
    94  		if ent == nil {
    95  			break
    96  		}
    97  
    98  		if ent.Tag != dwarf.TagTypedef {
    99  			continue
   100  		}
   101  
   102  		name, ok := ent.Val(dwarf.AttrName).(string)
   103  		if !ok {
   104  			continue
   105  		}
   106  		base, ok := ent.Val(dwarf.AttrType).(dwarf.Offset)
   107  		if !ok {
   108  			log.Printf("type %s has unknown underlying type", name)
   109  			continue
   110  		}
   111  
   112  		typ, err := d.Type(base)
   113  		if err != nil {
   114  			log.Fatal(err)
   115  		}
   116  		ti := &typeInfo{name: name, words: int(typ.Size()+ptrSize-1) / ptrSize}
   117  		ti.processType(typ, 0)
   118  		if ti.incomplete {
   119  			log.Printf("ignoring incomplete type %s", ti.name)
   120  			continue
   121  		}
   122  
   123  		score := failure.compare(ti)
   124  		results = append(results, comparison{ti, score})
   125  	}
   126  
   127  	// Print results.
   128  	sort.Slice(results, func(i, j int) bool {
   129  		return results[i].score < results[j].score
   130  	})
   131  	if len(results) > 10 {
   132  		results = results[len(results)-10:]
   133  	}
   134  	for _, c := range results {
   135  		fmt.Print(c.score, " ", c.ti.name)
   136  		failure.printCompare(c.ti)
   137  	}
   138  }
   139  
   140  type typeInfo struct {
   141  	name       string
   142  	ptr        big.Int
   143  	words      int
   144  	incomplete bool
   145  }
   146  
   147  func (t *typeInfo) processType(typ dwarf.Type, offset int) {
   148  	switch typ := typ.(type) {
   149  	case *dwarf.ArrayType:
   150  		if typ.Count < 0 || typ.StrideBitSize > 0 {
   151  			t.incomplete = true
   152  			return
   153  		}
   154  		for i := 0; i < int(typ.Count); i++ {
   155  			// TODO: Alignment?
   156  			t.processType(typ.Type, offset+i*int(typ.Type.Size()))
   157  		}
   158  
   159  	case *dwarf.StructType:
   160  		if typ.Kind == "union" {
   161  			t.incomplete = true
   162  			log.Printf("encountered union")
   163  			return
   164  		}
   165  		if typ.Incomplete {
   166  			t.incomplete = true
   167  			return
   168  		}
   169  		for _, f := range typ.Field {
   170  			if f.BitSize != 0 {
   171  				t.incomplete = true
   172  				log.Printf("encountered bit field")
   173  				return
   174  			}
   175  			t.processType(f.Type, offset+int(f.ByteOffset))
   176  		}
   177  
   178  	case *dwarf.BoolType, *dwarf.CharType, *dwarf.ComplexType,
   179  		*dwarf.EnumType, *dwarf.FloatType, *dwarf.IntType,
   180  		*dwarf.UcharType, *dwarf.UintType:
   181  		// Nothing
   182  
   183  	case *dwarf.PtrType:
   184  		if typ.Size() != ptrSize {
   185  			log.Fatalf("funny PtrSize size: %d", typ.Size())
   186  		}
   187  		if offset%ptrSize != 0 {
   188  			log.Fatal("unaligned pointer")
   189  		}
   190  		t.ptr.SetBit(&t.ptr, offset/ptrSize, 1)
   191  
   192  	case *dwarf.FuncType:
   193  		// Size is -1.
   194  		if offset%ptrSize != 0 {
   195  			log.Fatal("unaligned pointer")
   196  		}
   197  		t.ptr.SetBit(&t.ptr, offset/ptrSize, 1)
   198  
   199  	case *dwarf.QualType:
   200  		t.processType(typ.Type, offset)
   201  
   202  	case *dwarf.TypedefType:
   203  		t.processType(typ.Type, offset)
   204  
   205  	case *dwarf.UnspecifiedType:
   206  		t.incomplete = true
   207  		log.Printf("encountered UnspecifiedType")
   208  
   209  	case *dwarf.VoidType:
   210  		t.incomplete = true
   211  		log.Printf("encountered VoidType")
   212  	}
   213  }
   214  
   215  type greyobjectFailure struct {
   216  	words []int // 0 scalar, 1 pointer, 2 unknown
   217  }
   218  
   219  var (
   220  	spanRe = regexp.MustCompile(`base=.* s\.elemsize=(\d+)`)
   221  	baseRe = regexp.MustCompile(`\*\(base\+(\d+)\) = (0x[0-9a-f]+)( <==)?$`)
   222  )
   223  
   224  func parseGreyobject(r io.Reader) *greyobjectFailure {
   225  	var failure greyobjectFailure
   226  	scanner := bufio.NewScanner(r)
   227  	for scanner.Scan() {
   228  		l := scanner.Text()
   229  
   230  		subs := spanRe.FindStringSubmatch(l)
   231  		if subs != nil {
   232  			elemsize, _ := strconv.Atoi(subs[1])
   233  			failure.words = make([]int, elemsize/ptrSize)
   234  			for i := range failure.words {
   235  				failure.words[i] = 2
   236  			}
   237  			continue
   238  		}
   239  
   240  		subs = baseRe.FindStringSubmatch(l)
   241  		if subs == nil {
   242  			continue
   243  		}
   244  
   245  		offset, _ := strconv.ParseInt(subs[1], 0, 64)
   246  		val, _ := strconv.ParseInt(subs[2], 0, 64)
   247  
   248  		// TODO: This only recognizes heap pointers. Maybe
   249  		// look at the binary to figure out reasonable global
   250  		// pointers?
   251  		known := 2
   252  		if val>>32 == 0xc4 {
   253  			known = 1
   254  		} else if val != 0 {
   255  			known = 0
   256  		}
   257  
   258  		failure.words[offset/ptrSize] = known
   259  	}
   260  	if err := scanner.Err(); err != nil {
   261  		log.Fatal("reading greyobject output:", err)
   262  	}
   263  	return &failure
   264  }
   265  
   266  func (f *greyobjectFailure) compare(ti *typeInfo) float64 {
   267  	score, denom := 0.0, 0.0
   268  	for i, known := range f.words {
   269  		if known == 2 {
   270  			continue
   271  		}
   272  		denom++
   273  		if ti.words < i {
   274  			score -= 1
   275  		} else if int(ti.ptr.Bit(i)) == known {
   276  			score += 1
   277  		} else {
   278  			score -= 1
   279  		}
   280  	}
   281  	if ti.words > len(f.words) {
   282  		score -= float64(ti.words - len(f.words))
   283  	}
   284  	return score / denom
   285  }
   286  
   287  func (f *greyobjectFailure) printCompare(ti *typeInfo) {
   288  	l := ti.words
   289  	if len(f.words) > l {
   290  		l = len(f.words)
   291  	}
   292  	for i := 0; i < l; i++ {
   293  		if i%32 == 0 {
   294  			fmt.Printf("\n\t")
   295  		} else if i%16 == 0 {
   296  			fmt.Printf(" ")
   297  		}
   298  
   299  		have := int(ti.ptr.Bit(i))
   300  
   301  		var want int
   302  		if i < len(f.words) {
   303  			want = f.words[i]
   304  		} else {
   305  			want = 1 - have
   306  		}
   307  
   308  		switch {
   309  		case want == 2:
   310  			fmt.Print("?")
   311  		case have == want:
   312  			if have == 0 {
   313  				fmt.Print("S")
   314  			} else {
   315  				fmt.Print("P")
   316  			}
   317  		case have != want:
   318  			if have == 0 {
   319  				fmt.Print("s")
   320  			} else {
   321  				fmt.Print("p")
   322  			}
   323  		}
   324  	}
   325  	fmt.Println()
   326  }