LEFT | RIGHT |
(no file at all) | |
1 // Copyright 2012 The Go Authors. All rights reserved. | 1 // Copyright 2012 The Go Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style | 2 // Use of this source code is governed by a BSD-style |
3 // license that can be found in the LICENSE file. | 3 // license that can be found in the LICENSE file. |
4 | 4 |
5 // Objdump is a minimal simulation of the GNU objdump tool, | 5 // Objdump disassembles executable files. |
6 // just enough to support pprof. | |
7 // | 6 // |
8 // Usage: | 7 // Usage: |
| 8 // |
| 9 // go tool objdump [-s symregexp] binary |
| 10 // |
| 11 // Objdump prints a disassembly of all text symbols (code) in the binary. |
| 12 // If the -s option is present, objdump only disassembles |
| 13 // symbols with names matching the regular expression. |
| 14 // |
| 15 // Alternate usage: |
| 16 // |
9 // go tool objdump binary start end | 17 // go tool objdump binary start end |
10 // | 18 // |
11 // Objdump disassembles the binary starting at the start address and | 19 // In this mode, objdump disassembles the binary starting at the start address a
nd |
12 // stopping at the end address. The start and end addresses are program | 20 // stopping at the end address. The start and end addresses are program |
13 // counters written in hexadecimal with optional leading 0x prefix. | 21 // counters written in hexadecimal with optional leading 0x prefix. |
14 // | 22 // In this mode, objdump prints a sequence of stanzas of the form: |
15 // It prints a sequence of stanzas of the form: | |
16 // | 23 // |
17 // file:line | 24 // file:line |
18 // address: assembly | 25 // address: assembly |
19 // address: assembly | 26 // address: assembly |
20 // ... | 27 // ... |
21 // | 28 // |
22 // Each stanza gives the disassembly for a contiguous range of addresses | 29 // Each stanza gives the disassembly for a contiguous range of addresses |
23 // all mapped to the same original source file and line number. | 30 // all mapped to the same original source file and line number. |
24 // | 31 // This mode is intended for use by pprof. |
25 // The disassembler is missing (golang.org/issue/7452) but will be added | 32 // |
| 33 // The ARM disassembler is missing (golang.org/issue/7452) but will be added |
26 // before the Go 1.3 release. | 34 // before the Go 1.3 release. |
27 // | |
28 // This tool is intended for use only by pprof; its interface may change or | |
29 // it may be deleted entirely in future releases. | |
30 package main | 35 package main |
31 | 36 |
32 import ( | 37 import ( |
33 "bufio" | 38 "bufio" |
| 39 "bytes" |
34 "debug/elf" | 40 "debug/elf" |
35 "debug/gosym" | 41 "debug/gosym" |
36 "debug/macho" | 42 "debug/macho" |
37 "debug/pe" | 43 "debug/pe" |
38 "flag" | 44 "flag" |
39 "fmt" | 45 "fmt" |
| 46 "io" |
40 "log" | 47 "log" |
41 "os" | 48 "os" |
| 49 "regexp" |
| 50 "sort" |
42 "strconv" | 51 "strconv" |
43 "strings" | 52 "strings" |
| 53 "text/tabwriter" |
44 ) | 54 ) |
45 | 55 |
46 func printUsage(w *os.File) { | 56 var symregexp = flag.String("s", "", "only dump symbols matching this regexp") |
47 » fmt.Fprintf(w, "usage: objdump binary start end\n") | 57 var symRE *regexp.Regexp |
48 » fmt.Fprintf(w, "disassembles binary from start PC to end PC.\n") | |
49 » fmt.Fprintf(w, "start and end are hexadecimal numbers with optional lead
ing 0x prefix.\n") | |
50 } | |
51 | 58 |
52 func usage() { | 59 func usage() { |
53 » printUsage(os.Stderr) | 60 » fmt.Fprintf(os.Stderr, "usage: go tool objdump [-s symregexp] binary [st
art end]\n\n") |
| 61 » flag.PrintDefaults() |
54 os.Exit(2) | 62 os.Exit(2) |
55 } | 63 } |
| 64 |
| 65 type lookupFunc func(addr uint64) (sym string, base uint64) |
| 66 type disasmFunc func(code []byte, pc uint64, lookup lookupFunc) (text string, si
ze int) |
56 | 67 |
57 func main() { | 68 func main() { |
58 log.SetFlags(0) | 69 log.SetFlags(0) |
59 log.SetPrefix("objdump: ") | 70 log.SetPrefix("objdump: ") |
60 | 71 |
61 flag.Usage = usage | 72 flag.Usage = usage |
62 flag.Parse() | 73 flag.Parse() |
63 » if flag.NArg() != 3 { | 74 » if flag.NArg() != 1 && flag.NArg() != 3 { |
64 usage() | 75 usage() |
65 } | 76 } |
66 | 77 |
| 78 if *symregexp != "" { |
| 79 re, err := regexp.Compile(*symregexp) |
| 80 if err != nil { |
| 81 log.Fatalf("invalid -s regexp: %v", err) |
| 82 } |
| 83 symRE = re |
| 84 } |
| 85 |
67 f, err := os.Open(flag.Arg(0)) | 86 f, err := os.Open(flag.Arg(0)) |
68 if err != nil { | 87 if err != nil { |
69 log.Fatal(err) | 88 log.Fatal(err) |
70 } | 89 } |
71 | 90 |
72 textStart, textData, symtab, pclntab, err := loadTables(f) | 91 textStart, textData, symtab, pclntab, err := loadTables(f) |
73 if err != nil { | 92 if err != nil { |
74 log.Fatalf("reading %s: %v", flag.Arg(0), err) | 93 log.Fatalf("reading %s: %v", flag.Arg(0), err) |
| 94 } |
| 95 |
| 96 syms, goarch, err := loadSymbols(f) |
| 97 if err != nil { |
| 98 log.Fatalf("reading %s: %v", flag.Arg(0), err) |
| 99 } |
| 100 |
| 101 // Filter out section symbols, overwriting syms in place. |
| 102 keep := syms[:0] |
| 103 for _, sym := range syms { |
| 104 switch sym.Name { |
| 105 case "text", "_text", "etext", "_etext": |
| 106 // drop |
| 107 default: |
| 108 keep = append(keep, sym) |
| 109 } |
| 110 } |
| 111 syms = keep |
| 112 |
| 113 disasm := disasms[goarch] |
| 114 if disasm == nil { |
| 115 log.Fatalf("reading %s: unknown architecture", flag.Arg(0)) |
| 116 } |
| 117 |
| 118 lookup := func(addr uint64) (string, uint64) { |
| 119 i := sort.Search(len(syms), func(i int) bool { return syms[i].Ad
dr > addr }) |
| 120 if i > 0 { |
| 121 s := syms[i-1] |
| 122 if s.Addr <= addr && addr < s.Addr+uint64(s.Size) && s.N
ame != "etext" && s.Name != "_etext" { |
| 123 return s.Name, s.Addr |
| 124 } |
| 125 } |
| 126 return "", 0 |
75 } | 127 } |
76 | 128 |
77 pcln := gosym.NewLineTable(pclntab, textStart) | 129 pcln := gosym.NewLineTable(pclntab, textStart) |
78 tab, err := gosym.NewTable(symtab, pcln) | 130 tab, err := gosym.NewTable(symtab, pcln) |
79 if err != nil { | 131 if err != nil { |
80 log.Fatalf("reading %s: %v", flag.Arg(0), err) | 132 log.Fatalf("reading %s: %v", flag.Arg(0), err) |
81 } | 133 } |
82 | 134 |
| 135 if flag.NArg() == 1 { |
| 136 // disassembly of entire object - our format |
| 137 dump(tab, lookup, disasm, syms, textData, textStart) |
| 138 os.Exit(exitCode) |
| 139 } |
| 140 |
| 141 // disassembly of specific piece of object - gnu objdump format for ppro
f |
| 142 gnuDump(tab, lookup, disasm, textData, textStart) |
| 143 os.Exit(exitCode) |
| 144 } |
| 145 |
| 146 // base returns the final element in the path. |
| 147 // It works on both Windows and Unix paths. |
| 148 func base(path string) string { |
| 149 path = path[strings.LastIndex(path, "/")+1:] |
| 150 path = path[strings.LastIndex(path, `\`)+1:] |
| 151 return path |
| 152 } |
| 153 |
| 154 func dump(tab *gosym.Table, lookup lookupFunc, disasm disasmFunc, syms []Sym, te
xtData []byte, textStart uint64) { |
| 155 stdout := bufio.NewWriter(os.Stdout) |
| 156 defer stdout.Flush() |
| 157 |
| 158 printed := false |
| 159 for _, sym := range syms { |
| 160 if sym.Code != 'T' || sym.Size == 0 || sym.Name == "_text" || sy
m.Name == "text" || sym.Addr < textStart || symRE != nil && !symRE.MatchString(s
ym.Name) { |
| 161 continue |
| 162 } |
| 163 if sym.Addr >= textStart+uint64(len(textData)) || sym.Addr+uint6
4(sym.Size) > textStart+uint64(len(textData)) { |
| 164 break |
| 165 } |
| 166 if printed { |
| 167 fmt.Fprintf(stdout, "\n") |
| 168 } else { |
| 169 printed = true |
| 170 } |
| 171 file, _, _ := tab.PCToLine(sym.Addr) |
| 172 fmt.Fprintf(stdout, "TEXT %s(SB) %s\n", sym.Name, file) |
| 173 tw := tabwriter.NewWriter(stdout, 1, 8, 1, '\t', 0) |
| 174 start := sym.Addr |
| 175 end := sym.Addr + uint64(sym.Size) |
| 176 for pc := start; pc < end; { |
| 177 i := pc - textStart |
| 178 text, size := disasm(textData[i:end-textStart], pc, look
up) |
| 179 file, line, _ := tab.PCToLine(pc) |
| 180 fmt.Fprintf(tw, "\t%s:%d\t%#x\t%x\t%s\n", base(file), li
ne, pc, textData[i:i+uint64(size)], text) |
| 181 pc += uint64(size) |
| 182 } |
| 183 tw.Flush() |
| 184 } |
| 185 } |
| 186 |
| 187 func disasm_386(code []byte, pc uint64, lookup lookupFunc) (string, int) { |
| 188 return disasm_x86(code, pc, lookup, 32) |
| 189 } |
| 190 |
| 191 func disasm_amd64(code []byte, pc uint64, lookup lookupFunc) (string, int) { |
| 192 return disasm_x86(code, pc, lookup, 64) |
| 193 } |
| 194 |
| 195 func disasm_x86(code []byte, pc uint64, lookup lookupFunc, arch int) (string, in
t) { |
| 196 inst, err := x86_Decode(code, 64) |
| 197 var text string |
| 198 size := inst.Len |
| 199 if err != nil || size == 0 || inst.Op == 0 { |
| 200 size = 1 |
| 201 text = "?" |
| 202 } else { |
| 203 text = x86_plan9Syntax(inst, pc, lookup) |
| 204 } |
| 205 return text, size |
| 206 } |
| 207 |
| 208 func disasm_arm(code []byte, pc uint64, lookup lookupFunc) (string, int) { |
| 209 /* |
| 210 inst, size, err := arm_Decode(code, 64) |
| 211 var text string |
| 212 if err != nil || size == 0 || inst.Op == 0 { |
| 213 size = 1 |
| 214 text = "?" |
| 215 } else { |
| 216 text = arm_plan9Syntax(inst, pc, lookup) |
| 217 } |
| 218 return text, size |
| 219 */ |
| 220 return "?", 4 |
| 221 } |
| 222 |
| 223 var disasms = map[string]disasmFunc{ |
| 224 "386": disasm_386, |
| 225 "amd64": disasm_amd64, |
| 226 "arm": disasm_arm, |
| 227 } |
| 228 |
| 229 func gnuDump(tab *gosym.Table, lookup lookupFunc, disasm disasmFunc, textData []
byte, textStart uint64) { |
83 start, err := strconv.ParseUint(strings.TrimPrefix(flag.Arg(1), "0x"), 1
6, 64) | 230 start, err := strconv.ParseUint(strings.TrimPrefix(flag.Arg(1), "0x"), 1
6, 64) |
84 if err != nil { | 231 if err != nil { |
85 log.Fatalf("invalid start PC: %v", err) | 232 log.Fatalf("invalid start PC: %v", err) |
86 } | 233 } |
87 end, err := strconv.ParseUint(strings.TrimPrefix(flag.Arg(2), "0x"), 16,
64) | 234 end, err := strconv.ParseUint(strings.TrimPrefix(flag.Arg(2), "0x"), 16,
64) |
88 if err != nil { | 235 if err != nil { |
89 log.Fatalf("invalid end PC: %v", err) | 236 log.Fatalf("invalid end PC: %v", err) |
90 } | 237 } |
91 | 238 |
92 stdout := bufio.NewWriter(os.Stdout) | 239 stdout := bufio.NewWriter(os.Stdout) |
| 240 defer stdout.Flush() |
93 | 241 |
94 // For now, find spans of same PC/line/fn and | 242 // For now, find spans of same PC/line/fn and |
95 // emit them as having dummy instructions. | 243 // emit them as having dummy instructions. |
96 var ( | 244 var ( |
97 spanPC uint64 | 245 spanPC uint64 |
98 spanFile string | 246 spanFile string |
99 spanLine int | 247 spanLine int |
100 spanFn *gosym.Func | 248 spanFn *gosym.Func |
101 ) | 249 ) |
102 | 250 |
103 flush := func(endPC uint64) { | 251 flush := func(endPC uint64) { |
104 if spanPC == 0 { | 252 if spanPC == 0 { |
105 return | 253 return |
106 } | 254 } |
107 fmt.Fprintf(stdout, "%s:%d\n", spanFile, spanLine) | 255 fmt.Fprintf(stdout, "%s:%d\n", spanFile, spanLine) |
108 » » for pc := spanPC; pc < endPC; pc++ { | 256 » » for pc := spanPC; pc < endPC; { |
109 » » » // TODO(rsc): Disassemble instructions here. | 257 » » » text, size := disasm(textData[pc-textStart:], pc, lookup
) |
110 » » » if textStart <= pc && pc-textStart < uint64(len(textData
)) { | 258 » » » fmt.Fprintf(stdout, " %x: %s\n", pc, text) |
111 » » » » fmt.Fprintf(stdout, " %x: byte %#x\n", pc, textD
ata[pc-textStart]) | 259 » » » pc += uint64(size) |
112 » » » } else { | |
113 » » » » fmt.Fprintf(stdout, " %x: ?\n", pc) | |
114 » » » } | |
115 } | 260 } |
116 spanPC = 0 | 261 spanPC = 0 |
117 } | 262 } |
118 | 263 |
119 for pc := start; pc < end; pc++ { | 264 for pc := start; pc < end; pc++ { |
120 file, line, fn := tab.PCToLine(pc) | 265 file, line, fn := tab.PCToLine(pc) |
121 if file != spanFile || line != spanLine || fn != spanFn { | 266 if file != spanFile || line != spanLine || fn != spanFn { |
122 flush(pc) | 267 flush(pc) |
123 spanPC, spanFile, spanLine, spanFn = pc, file, line, fn | 268 spanPC, spanFile, spanLine, spanFn = pc, file, line, fn |
124 } | 269 } |
125 } | 270 } |
126 flush(end) | 271 flush(end) |
127 | |
128 stdout.Flush() | |
129 } | 272 } |
130 | 273 |
131 func loadTables(f *os.File) (textStart uint64, textData, symtab, pclntab []byte,
err error) { | 274 func loadTables(f *os.File) (textStart uint64, textData, symtab, pclntab []byte,
err error) { |
132 if obj, err := elf.NewFile(f); err == nil { | 275 if obj, err := elf.NewFile(f); err == nil { |
133 if sect := obj.Section(".text"); sect != nil { | 276 if sect := obj.Section(".text"); sect != nil { |
134 textStart = sect.Addr | 277 textStart = sect.Addr |
135 textData, _ = sect.Data() | 278 textData, _ = sect.Data() |
136 } | 279 } |
137 if sect := obj.Section(".gosymtab"); sect != nil { | 280 if sect := obj.Section(".gosymtab"); sect != nil { |
138 if symtab, err = sect.Data(); err != nil { | 281 if symtab, err = sect.Data(); err != nil { |
(...skipping 71 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
210 if ssym.SectionNumber != esym.SectionNumber { | 353 if ssym.SectionNumber != esym.SectionNumber { |
211 return nil, fmt.Errorf("%s and %s symbols must be in the same se
ction", sname, ename) | 354 return nil, fmt.Errorf("%s and %s symbols must be in the same se
ction", sname, ename) |
212 } | 355 } |
213 sect := f.Sections[ssym.SectionNumber-1] | 356 sect := f.Sections[ssym.SectionNumber-1] |
214 data, err := sect.Data() | 357 data, err := sect.Data() |
215 if err != nil { | 358 if err != nil { |
216 return nil, err | 359 return nil, err |
217 } | 360 } |
218 return data[ssym.Value:esym.Value], nil | 361 return data[ssym.Value:esym.Value], nil |
219 } | 362 } |
| 363 |
| 364 // TODO(rsc): This code is taken from cmd/nm. Arrange some way to share the code
. |
| 365 |
| 366 var exitCode = 0 |
| 367 |
| 368 func errorf(format string, args ...interface{}) { |
| 369 log.Printf(format, args...) |
| 370 exitCode = 1 |
| 371 } |
| 372 |
| 373 func loadSymbols(f *os.File) (syms []Sym, goarch string, err error) { |
| 374 f.Seek(0, 0) |
| 375 buf := make([]byte, 16) |
| 376 io.ReadFull(f, buf) |
| 377 f.Seek(0, 0) |
| 378 |
| 379 for _, p := range parsers { |
| 380 if bytes.HasPrefix(buf, p.prefix) { |
| 381 syms, goarch = p.parse(f) |
| 382 sort.Sort(byAddr(syms)) |
| 383 return |
| 384 } |
| 385 } |
| 386 err = fmt.Errorf("unknown file format") |
| 387 return |
| 388 } |
| 389 |
| 390 type Sym struct { |
| 391 Addr uint64 |
| 392 Size int64 |
| 393 Code rune |
| 394 Name string |
| 395 Type string |
| 396 } |
| 397 |
| 398 var parsers = []struct { |
| 399 prefix []byte |
| 400 parse func(*os.File) ([]Sym, string) |
| 401 }{ |
| 402 {[]byte("\x7FELF"), elfSymbols}, |
| 403 {[]byte("\xFE\xED\xFA\xCE"), machoSymbols}, |
| 404 {[]byte("\xFE\xED\xFA\xCF"), machoSymbols}, |
| 405 {[]byte("\xCE\xFA\xED\xFE"), machoSymbols}, |
| 406 {[]byte("\xCF\xFA\xED\xFE"), machoSymbols}, |
| 407 {[]byte("MZ"), peSymbols}, |
| 408 {[]byte("\x00\x00\x01\xEB"), plan9Symbols}, // 386 |
| 409 {[]byte("\x00\x00\x04\x07"), plan9Symbols}, // mips |
| 410 {[]byte("\x00\x00\x06\x47"), plan9Symbols}, // arm |
| 411 {[]byte("\x00\x00\x8A\x97"), plan9Symbols}, // amd64 |
| 412 } |
| 413 |
| 414 type byAddr []Sym |
| 415 |
| 416 func (x byAddr) Len() int { return len(x) } |
| 417 func (x byAddr) Swap(i, j int) { x[i], x[j] = x[j], x[i] } |
| 418 func (x byAddr) Less(i, j int) bool { return x[i].Addr < x[j].Addr } |
LEFT | RIGHT |