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 }