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 }