1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package binutils
16
17 import (
18 "bytes"
19 "debug/elf"
20 "encoding/binary"
21 "errors"
22 "fmt"
23 "math"
24 "path/filepath"
25 "reflect"
26 "regexp"
27 "runtime"
28 "strings"
29 "testing"
30
31 "github.com/google/pprof/internal/plugin"
32 )
33
34 var testAddrMap = map[int]string{
35 1000: "_Z3fooid.clone2",
36 2000: "_ZNSaIiEC1Ev.clone18",
37 3000: "_ZNSt6vectorIS_IS_IiSaIiEESaIS1_EESaIS3_EEixEm",
38 }
39
40 func functionName(level int) (name string) {
41 if name = testAddrMap[level]; name != "" {
42 return name
43 }
44 return fmt.Sprintf("fun%d", level)
45 }
46
47 func TestAddr2Liner(t *testing.T) {
48 const offset = 0x500
49
50 a := addr2Liner{rw: &mockAddr2liner{}, base: offset}
51 for i := 1; i < 8; i++ {
52 addr := i*0x1000 + offset
53 s, err := a.addrInfo(uint64(addr))
54 if err != nil {
55 t.Fatalf("addrInfo(%#x): %v", addr, err)
56 }
57 if len(s) != i {
58 t.Fatalf("addrInfo(%#x): got len==%d, want %d", addr, len(s), i)
59 }
60 for l, f := range s {
61 level := (len(s) - l) * 1000
62 want := plugin.Frame{Func: functionName(level), File: fmt.Sprintf("file%d", level), Line: level}
63
64 if f != want {
65 t.Errorf("AddrInfo(%#x)[%d]: = %+v, want %+v", addr, l, f, want)
66 }
67 }
68 }
69 s, err := a.addrInfo(0xFFFF)
70 if err != nil {
71 t.Fatalf("addrInfo(0xFFFF): %v", err)
72 }
73 if len(s) != 0 {
74 t.Fatalf("AddrInfo(0xFFFF): got len==%d, want 0", len(s))
75 }
76 a.rw.close()
77 }
78
79 type mockAddr2liner struct {
80 output []string
81 }
82
83 func (a *mockAddr2liner) write(s string) error {
84 var lines []string
85 switch s {
86 case "1000":
87 lines = []string{"_Z3fooid.clone2", "file1000:1000"}
88 case "2000":
89 lines = []string{"_ZNSaIiEC1Ev.clone18", "file2000:2000", "_Z3fooid.clone2", "file1000:1000"}
90 case "3000":
91 lines = []string{"_ZNSt6vectorIS_IS_IiSaIiEESaIS1_EESaIS3_EEixEm", "file3000:3000", "_ZNSaIiEC1Ev.clone18", "file2000:2000", "_Z3fooid.clone2", "file1000:1000"}
92 case "4000":
93 lines = []string{"fun4000", "file4000:4000", "_ZNSt6vectorIS_IS_IiSaIiEESaIS1_EESaIS3_EEixEm", "file3000:3000", "_ZNSaIiEC1Ev.clone18", "file2000:2000", "_Z3fooid.clone2", "file1000:1000"}
94 case "5000":
95 lines = []string{"fun5000", "file5000:5000", "fun4000", "file4000:4000", "_ZNSt6vectorIS_IS_IiSaIiEESaIS1_EESaIS3_EEixEm", "file3000:3000", "_ZNSaIiEC1Ev.clone18", "file2000:2000", "_Z3fooid.clone2", "file1000:1000"}
96 case "6000":
97 lines = []string{"fun6000", "file6000:6000", "fun5000", "file5000:5000", "fun4000", "file4000:4000", "_ZNSt6vectorIS_IS_IiSaIiEESaIS1_EESaIS3_EEixEm", "file3000:3000", "_ZNSaIiEC1Ev.clone18", "file2000:2000", "_Z3fooid.clone2", "file1000:1000"}
98 case "7000":
99 lines = []string{"fun7000", "file7000:7000", "fun6000", "file6000:6000", "fun5000", "file5000:5000", "fun4000", "file4000:4000", "_ZNSt6vectorIS_IS_IiSaIiEESaIS1_EESaIS3_EEixEm", "file3000:3000", "_ZNSaIiEC1Ev.clone18", "file2000:2000", "_Z3fooid.clone2", "file1000:1000"}
100 case "8000":
101 lines = []string{"fun8000", "file8000:8000", "fun7000", "file7000:7000", "fun6000", "file6000:6000", "fun5000", "file5000:5000", "fun4000", "file4000:4000", "_ZNSt6vectorIS_IS_IiSaIiEESaIS1_EESaIS3_EEixEm", "file3000:3000", "_ZNSaIiEC1Ev.clone18", "file2000:2000", "_Z3fooid.clone2", "file1000:1000"}
102 case "9000":
103 lines = []string{"fun9000", "file9000:9000", "fun8000", "file8000:8000", "fun7000", "file7000:7000", "fun6000", "file6000:6000", "fun5000", "file5000:5000", "fun4000", "file4000:4000", "_ZNSt6vectorIS_IS_IiSaIiEESaIS1_EESaIS3_EEixEm", "file3000:3000", "_ZNSaIiEC1Ev.clone18", "file2000:2000", "_Z3fooid.clone2", "file1000:1000"}
104 default:
105 lines = []string{"??", "??:0"}
106 }
107 a.output = append(a.output, "0x"+s)
108 a.output = append(a.output, lines...)
109 return nil
110 }
111
112 func (a *mockAddr2liner) readLine() (string, error) {
113 if len(a.output) == 0 {
114 return "", fmt.Errorf("end of file")
115 }
116 next := a.output[0]
117 a.output = a.output[1:]
118 return next, nil
119 }
120
121 func (a *mockAddr2liner) close() {
122 }
123
124 func TestAddr2LinerLookup(t *testing.T) {
125 for _, tc := range []struct {
126 desc string
127 nmOutput string
128 wantSymbolized map[uint64]string
129 wantUnsymbolized []uint64
130 }{
131 {
132 desc: "odd symbol count",
133 nmOutput: `
134 0x1000 T 1000 100
135 0x2000 T 2000 120
136 0x3000 T 3000 130
137 `,
138 wantSymbolized: map[uint64]string{
139 0x1000: "0x1000",
140 0x1001: "0x1000",
141 0x1FFF: "0x1000",
142 0x2000: "0x2000",
143 0x2001: "0x2000",
144 0x3000: "0x3000",
145 0x312f: "0x3000",
146 },
147 wantUnsymbolized: []uint64{0x0fff, 0x3130},
148 },
149 {
150 desc: "even symbol count",
151 nmOutput: `
152 0x1000 T 1000 100
153 0x2000 T 2000 120
154 0x3000 T 3000 130
155 0x4000 T 4000 140
156 `,
157 wantSymbolized: map[uint64]string{
158 0x1000: "0x1000",
159 0x1001: "0x1000",
160 0x1FFF: "0x1000",
161 0x2000: "0x2000",
162 0x2fff: "0x2000",
163 0x3000: "0x3000",
164 0x3fff: "0x3000",
165 0x4000: "0x4000",
166 0x413f: "0x4000",
167 },
168 wantUnsymbolized: []uint64{0x0fff, 0x4140},
169 },
170 {
171 desc: "different symbol types",
172 nmOutput: `
173 absolute_0x100 a 100
174 absolute_0x200 A 200
175 text_0x1000 t 1000 100
176 bss_0x2000 b 2000 120
177 data_0x3000 d 3000 130
178 rodata_0x4000 r 4000 140
179 weak_0x5000 v 5000 150
180 text_0x6000 T 6000 160
181 bss_0x7000 B 7000 170
182 data_0x8000 D 8000 180
183 rodata_0x9000 R 9000 190
184 weak_0xa000 V a000 1a0
185 weak_0xb000 W b000 1b0
186 `,
187 wantSymbolized: map[uint64]string{
188 0x1000: "text_0x1000",
189 0x1FFF: "text_0x1000",
190 0x2000: "bss_0x2000",
191 0x211f: "bss_0x2000",
192 0x3000: "data_0x3000",
193 0x312f: "data_0x3000",
194 0x4000: "rodata_0x4000",
195 0x413f: "rodata_0x4000",
196 0x5000: "weak_0x5000",
197 0x514f: "weak_0x5000",
198 0x6000: "text_0x6000",
199 0x6fff: "text_0x6000",
200 0x7000: "bss_0x7000",
201 0x716f: "bss_0x7000",
202 0x8000: "data_0x8000",
203 0x817f: "data_0x8000",
204 0x9000: "rodata_0x9000",
205 0x918f: "rodata_0x9000",
206 0xa000: "weak_0xa000",
207 0xa19f: "weak_0xa000",
208 0xb000: "weak_0xb000",
209 0xb1af: "weak_0xb000",
210 },
211 wantUnsymbolized: []uint64{0x100, 0x200, 0x0fff, 0x2120, 0x3130, 0x4140, 0x5150, 0x7170, 0x8180, 0x9190, 0xa1a0, 0xb1b0},
212 },
213 } {
214 t.Run(tc.desc, func(t *testing.T) {
215 a, err := parseAddr2LinerNM(0, bytes.NewBufferString(tc.nmOutput))
216 if err != nil {
217 t.Fatalf("nm parse error: %v", err)
218 }
219 for address, want := range tc.wantSymbolized {
220 if got, _ := a.addrInfo(address); !checkAddress(got, address, want) {
221 t.Errorf("%x: got %v, want %s", address, got, want)
222 }
223 }
224 for _, unknown := range tc.wantUnsymbolized {
225 if got, _ := a.addrInfo(unknown); got != nil {
226 t.Errorf("%x: got %v, want nil", unknown, got)
227 }
228 }
229 })
230 }
231 }
232
233 func checkAddress(got []plugin.Frame, address uint64, want string) bool {
234 if len(got) != 1 {
235 return false
236 }
237 return got[0].Func == want
238 }
239
240 func TestSetTools(t *testing.T) {
241
242 bu := &Binutils{}
243 bu.SetTools("")
244 bu.SetTools("")
245 }
246
247 func TestSetFastSymbolization(t *testing.T) {
248
249 bu := &Binutils{}
250 bu.SetFastSymbolization(true)
251 bu.SetFastSymbolization(false)
252 }
253
254 func skipUnlessLinuxAmd64(t *testing.T) {
255 if runtime.GOOS != "linux" || runtime.GOARCH != "amd64" {
256 t.Skip("This test only works on x86-64 Linux")
257 }
258 }
259
260 func skipUnlessDarwinAmd64(t *testing.T) {
261 if runtime.GOOS != "darwin" || runtime.GOARCH != "amd64" {
262 t.Skip("This test only works on x86-64 macOS")
263 }
264 }
265
266 func skipUnlessWindowsAmd64(t *testing.T) {
267 if runtime.GOOS != "windows" || runtime.GOARCH != "amd64" {
268 t.Skip("This test only works on x86-64 Windows")
269 }
270 }
271
272 func testDisasm(t *testing.T, intelSyntax bool) {
273 _, llvmObjdump, buObjdump := findObjdump([]string{""})
274 if !(llvmObjdump || buObjdump) {
275 t.Skip("cannot disasm: no objdump tool available")
276 }
277
278 bu := &Binutils{}
279 var testexe string
280 switch runtime.GOOS {
281 case "linux":
282 testexe = "exe_linux_64"
283 case "darwin":
284 testexe = "exe_mac_64"
285 case "windows":
286 testexe = "exe_windows_64.exe"
287 default:
288 t.Skipf("unsupported OS %q", runtime.GOOS)
289 }
290
291 insts, err := bu.Disasm(filepath.Join("testdata", testexe), 0, math.MaxUint64, intelSyntax)
292 if err != nil {
293 t.Fatalf("Disasm: unexpected error %v", err)
294 }
295 mainCount := 0
296 for _, x := range insts {
297
298 if x.Function == "main" || x.Function == "_main" {
299 mainCount++
300 }
301 }
302 if mainCount == 0 {
303 t.Error("Disasm: found no main instructions")
304 }
305 }
306
307 func TestDisasm(t *testing.T) {
308 if (runtime.GOOS != "linux" && runtime.GOOS != "darwin" && runtime.GOOS != "windows") || runtime.GOARCH != "amd64" {
309 t.Skip("This test only works on x86-64 Linux, macOS or Windows")
310 }
311 testDisasm(t, false)
312 }
313
314 func TestDisasmIntelSyntax(t *testing.T) {
315 if (runtime.GOOS != "linux" && runtime.GOOS != "darwin" && runtime.GOOS != "windows") || runtime.GOARCH != "amd64" {
316 t.Skip("This test only works on x86_64 Linux, macOS or Windows as it tests Intel asm syntax")
317 }
318 testDisasm(t, true)
319 }
320
321 func findSymbol(syms []*plugin.Sym, name string) *plugin.Sym {
322 for _, s := range syms {
323 for _, n := range s.Name {
324 if n == name {
325 return s
326 }
327 }
328 }
329 return nil
330 }
331
332 func TestObjFile(t *testing.T) {
333
334
335
336 skipUnlessLinuxAmd64(t)
337 for _, tc := range []struct {
338 desc string
339 start, limit, offset uint64
340 addr uint64
341 }{
342 {"fixed load address", 0x400000, 0x4006fc, 0, 0x40052d},
343
344
345
346 {"simulated ASLR address", 0x500000, 0x5006fc, 0, 0x50052d},
347 } {
348 t.Run(tc.desc, func(t *testing.T) {
349 bu := &Binutils{}
350 f, err := bu.Open(filepath.Join("testdata", "exe_linux_64"), tc.start, tc.limit, tc.offset, "")
351 if err != nil {
352 t.Fatalf("Open: unexpected error %v", err)
353 }
354 defer f.Close()
355 syms, err := f.Symbols(regexp.MustCompile("main"), 0)
356 if err != nil {
357 t.Fatalf("Symbols: unexpected error %v", err)
358 }
359
360 m := findSymbol(syms, "main")
361 if m == nil {
362 t.Fatalf("Symbols: did not find main")
363 }
364 addr, err := f.ObjAddr(tc.addr)
365 if err != nil {
366 t.Fatalf("ObjAddr(%x) failed: %v", tc.addr, err)
367 }
368 if addr != m.Start {
369 t.Errorf("ObjAddr(%x) got %x, want %x", tc.addr, addr, m.Start)
370 }
371 gotFrames, err := f.SourceLine(tc.addr)
372 if err != nil {
373 t.Fatalf("SourceLine: unexpected error %v", err)
374 }
375 wantFrames := []plugin.Frame{
376 {Func: "main", File: "/tmp/hello.c", Line: 3},
377 }
378 if !reflect.DeepEqual(gotFrames, wantFrames) {
379 t.Fatalf("SourceLine for main: got %v; want %v\n", gotFrames, wantFrames)
380 }
381 })
382 }
383 }
384
385 func TestMachoFiles(t *testing.T) {
386
387
388
389 skipUnlessDarwinAmd64(t)
390
391
392
393
394 for _, tc := range []struct {
395 desc string
396 file string
397 start, limit, offset uint64
398 addr uint64
399 sym string
400 expected []plugin.Frame
401 }{
402 {"normal mapping", "exe_mac_64", 0x100000000, math.MaxUint64, 0,
403 0x100000f50, "_main",
404 []plugin.Frame{
405 {Func: "main", File: "/tmp/hello.c", Line: 3},
406 }},
407 {"other mapping", "exe_mac_64", 0x200000000, math.MaxUint64, 0,
408 0x200000f50, "_main",
409 []plugin.Frame{
410 {Func: "main", File: "/tmp/hello.c", Line: 3},
411 }},
412 {"lib normal mapping", "lib_mac_64", 0, math.MaxUint64, 0,
413 0xfa0, "_bar",
414 []plugin.Frame{
415 {Func: "bar", File: "/tmp/lib.c", Line: 5},
416 }},
417 } {
418 t.Run(tc.desc, func(t *testing.T) {
419 bu := &Binutils{}
420 f, err := bu.Open(filepath.Join("testdata", tc.file), tc.start, tc.limit, tc.offset, "")
421 if err != nil {
422 t.Fatalf("Open: unexpected error %v", err)
423 }
424 t.Logf("binutils: %v", bu)
425 if runtime.GOOS == "darwin" && !bu.rep.addr2lineFound && !bu.rep.llvmSymbolizerFound {
426
427
428
429 t.Skip("couldn't find addr2line or gaddr2line")
430 }
431 defer f.Close()
432 syms, err := f.Symbols(nil, 0)
433 if err != nil {
434 t.Fatalf("Symbols: unexpected error %v", err)
435 }
436
437 m := findSymbol(syms, tc.sym)
438 if m == nil {
439 t.Fatalf("Symbols: could not find symbol %v", tc.sym)
440 }
441 gotFrames, err := f.SourceLine(tc.addr)
442 if err != nil {
443 t.Fatalf("SourceLine: unexpected error %v", err)
444 }
445 if !reflect.DeepEqual(gotFrames, tc.expected) {
446 t.Fatalf("SourceLine for main: got %v; want %v\n", gotFrames, tc.expected)
447 }
448 })
449 }
450 }
451
452 func TestLLVMSymbolizer(t *testing.T) {
453 if runtime.GOOS != "linux" {
454 t.Skip("testtdata/llvm-symbolizer has only been tested on linux")
455 }
456
457 cmd := filepath.Join("testdata", "fake-llvm-symbolizer")
458 for _, c := range []struct {
459 addr uint64
460 isData bool
461 frames []plugin.Frame
462 }{
463 {0x10, false, []plugin.Frame{
464 {Func: "Inlined_0x10", File: "foo.h", Line: 0, Column: 0},
465 {Func: "Func_0x10", File: "foo.c", Line: 2, Column: 1},
466 }},
467 {0x20, true, []plugin.Frame{
468 {Func: "foo_0x20", File: "0x20 8"},
469 }},
470 } {
471 desc := fmt.Sprintf("Code %x", c.addr)
472 if c.isData {
473 desc = fmt.Sprintf("Data %x", c.addr)
474 }
475 t.Run(desc, func(t *testing.T) {
476 symbolizer, err := newLLVMSymbolizer(cmd, "foo", 0, c.isData)
477 if err != nil {
478 t.Fatalf("newLLVMSymbolizer: unexpected error %v", err)
479 }
480 defer symbolizer.rw.close()
481
482 frames, err := symbolizer.addrInfo(c.addr)
483 if err != nil {
484 t.Fatalf("LLVM: unexpected error %v", err)
485 }
486 if !reflect.DeepEqual(frames, c.frames) {
487 t.Errorf("LLVM: expect %v; got %v\n", c.frames, frames)
488 }
489 })
490 }
491 }
492
493 func TestPEFile(t *testing.T) {
494
495
496
497 skipUnlessWindowsAmd64(t)
498 for _, tc := range []struct {
499 desc string
500 start, limit, offset uint64
501 addr uint64
502 }{
503 {"fake mapping", 0, math.MaxUint64, 0, 0x140001594},
504 {"fixed load address", 0x140000000, 0x140002000, 0, 0x140001594},
505 {"simulated ASLR address", 0x150000000, 0x150002000, 0, 0x150001594},
506 } {
507 t.Run(tc.desc, func(t *testing.T) {
508 bu := &Binutils{}
509 f, err := bu.Open(filepath.Join("testdata", "exe_windows_64.exe"), tc.start, tc.limit, tc.offset, "")
510 if err != nil {
511 t.Fatalf("Open: unexpected error %v", err)
512 }
513 defer f.Close()
514 syms, err := f.Symbols(regexp.MustCompile("main"), 0)
515 if err != nil {
516 t.Fatalf("Symbols: unexpected error %v", err)
517 }
518
519 m := findSymbol(syms, "main")
520 if m == nil {
521 t.Fatalf("Symbols: did not find main")
522 }
523 addr, err := f.ObjAddr(tc.addr)
524 if err != nil {
525 t.Fatalf("ObjAddr(%x) failed: %v", tc.addr, err)
526 }
527 if addr != m.Start {
528 t.Errorf("ObjAddr(%x) got %x, want %x", tc.addr, addr, m.Start)
529 }
530 gotFrames, err := f.SourceLine(tc.addr)
531 if err != nil {
532 t.Fatalf("SourceLine: unexpected error %v", err)
533 }
534 wantFrames := []plugin.Frame{
535 {Func: "main", File: "hello.c", Line: 3, Column: 12},
536 }
537 if !reflect.DeepEqual(gotFrames, wantFrames) {
538 t.Fatalf("SourceLine for main: got %v; want %v\n", gotFrames, wantFrames)
539 }
540 })
541 }
542 }
543
544 func TestOpenMalformedELF(t *testing.T) {
545
546
547 bu := &Binutils{}
548 _, err := bu.Open(filepath.Join("testdata", "malformed_elf"), 0, 0, 0, "")
549 if err == nil {
550 t.Fatalf("Open: unexpected success")
551 }
552
553 if !strings.Contains(err.Error(), "ELF") {
554 t.Errorf("Open: got %v, want error containing 'ELF'", err)
555 }
556 }
557
558 func TestOpenMalformedMachO(t *testing.T) {
559
560
561 bu := &Binutils{}
562 _, err := bu.Open(filepath.Join("testdata", "malformed_macho"), 0, 0, 0, "")
563 if err == nil {
564 t.Fatalf("Open: unexpected success")
565 }
566
567 if !strings.Contains(err.Error(), "Mach-O") {
568 t.Errorf("Open: got %v, want error containing 'Mach-O'", err)
569 }
570 }
571
572 func TestObjdumpVersionChecks(t *testing.T) {
573
574 type testcase struct {
575 desc string
576 os string
577 ver string
578 want bool
579 }
580
581 for _, tc := range []testcase{
582 {
583 desc: "Valid Apple LLVM version string with usable version",
584 os: "darwin",
585 ver: "Apple LLVM version 11.0.3 (clang-1103.0.32.62)\nOptimized build.",
586 want: true,
587 },
588 {
589 desc: "Valid Apple LLVM version string with unusable version",
590 os: "darwin",
591 ver: "Apple LLVM version 10.0.0 (clang-1000.11.45.5)\nOptimized build.",
592 want: false,
593 },
594 {
595 desc: "Invalid Apple LLVM version string with usable version",
596 os: "darwin",
597 ver: "Apple LLVM versions 11.0.3 (clang-1103.0.32.62)\nOptimized build.",
598 want: false,
599 },
600 {
601 desc: "Valid LLVM version string with usable version",
602 os: "linux",
603 ver: "LLVM (http://llvm.org/):\nLLVM version 9.0.1\n\nOptimized build.",
604 want: true,
605 },
606 {
607 desc: "Valid LLVM version string with unusable version",
608 os: "linux",
609 ver: "LLVM (http://llvm.org/):\nLLVM version 6.0.1\n\nOptimized build.",
610 want: false,
611 },
612 {
613 desc: "Invalid LLVM version string with usable version",
614 os: "linux",
615 ver: "LLVM (http://llvm.org/):\nLLVM versions 9.0.1\n\nOptimized build.",
616 want: false,
617 },
618 {
619 desc: "Valid LLVM objdump version string with trunk",
620 os: runtime.GOOS,
621 ver: "LLVM (http://llvm.org/):\nLLVM version custom-trunk 124ffeb592a00bfe\nOptimized build.",
622 want: true,
623 },
624 {
625 desc: "Invalid LLVM objdump version string with trunk",
626 os: runtime.GOOS,
627 ver: "LLVM (http://llvm.org/):\nLLVM version custom-trank 124ffeb592a00bfe\nOptimized build.",
628 want: false,
629 },
630 {
631 desc: "Invalid LLVM objdump version string with trunk",
632 os: runtime.GOOS,
633 ver: "LLVM (http://llvm.org/):\nllvm version custom-trunk 124ffeb592a00bfe\nOptimized build.",
634 want: false,
635 },
636 } {
637 if runtime.GOOS == tc.os {
638 if got := isLLVMObjdump(tc.ver); got != tc.want {
639 t.Errorf("%v: got %v, want %v", tc.desc, got, tc.want)
640 }
641 }
642 }
643 for _, tc := range []testcase{
644 {
645 desc: "Valid GNU objdump version string",
646 ver: "GNU objdump (GNU Binutils) 2.34\nCopyright (C) 2020 Free Software Foundation, Inc.",
647 want: true,
648 },
649 {
650 desc: "Invalid GNU objdump version string",
651 ver: "GNU nm (GNU Binutils) 2.34\nCopyright (C) 2020 Free Software Foundation, Inc.",
652 want: false,
653 },
654 } {
655 if got := isBuObjdump(tc.ver); got != tc.want {
656 t.Errorf("%v: got %v, want %v", tc.desc, got, tc.want)
657 }
658 }
659 }
660
661 func TestComputeBase(t *testing.T) {
662 realELFOpen := elfOpen
663 defer func() {
664 elfOpen = realELFOpen
665 }()
666
667 tinyExecFile := &elf.File{
668 FileHeader: elf.FileHeader{Type: elf.ET_EXEC},
669 Progs: []*elf.Prog{
670 {ProgHeader: elf.ProgHeader{Type: elf.PT_PHDR, Flags: elf.PF_R | elf.PF_X, Off: 0x40, Vaddr: 0x400040, Paddr: 0x400040, Filesz: 0x1f8, Memsz: 0x1f8, Align: 8}},
671 {ProgHeader: elf.ProgHeader{Type: elf.PT_INTERP, Flags: elf.PF_R, Off: 0x238, Vaddr: 0x400238, Paddr: 0x400238, Filesz: 0x1c, Memsz: 0x1c, Align: 1}},
672 {ProgHeader: elf.ProgHeader{Type: elf.PT_LOAD, Flags: elf.PF_R | elf.PF_X, Off: 0, Vaddr: 0, Paddr: 0, Filesz: 0xc80, Memsz: 0xc80, Align: 0x200000}},
673 {ProgHeader: elf.ProgHeader{Type: elf.PT_LOAD, Flags: elf.PF_R | elf.PF_W, Off: 0xc80, Vaddr: 0x200c80, Paddr: 0x200c80, Filesz: 0x1f0, Memsz: 0x1f0, Align: 0x200000}},
674 },
675 }
676 tinyBadBSSExecFile := &elf.File{
677 FileHeader: elf.FileHeader{Type: elf.ET_EXEC},
678 Progs: []*elf.Prog{
679 {ProgHeader: elf.ProgHeader{Type: elf.PT_PHDR, Flags: elf.PF_R | elf.PF_X, Off: 0x40, Vaddr: 0x400040, Paddr: 0x400040, Filesz: 0x1f8, Memsz: 0x1f8, Align: 8}},
680 {ProgHeader: elf.ProgHeader{Type: elf.PT_INTERP, Flags: elf.PF_R, Off: 0x238, Vaddr: 0x400238, Paddr: 0x400238, Filesz: 0x1c, Memsz: 0x1c, Align: 1}},
681 {ProgHeader: elf.ProgHeader{Type: elf.PT_LOAD, Flags: elf.PF_R | elf.PF_X, Off: 0, Vaddr: 0, Paddr: 0, Filesz: 0xc80, Memsz: 0xc80, Align: 0x200000}},
682 {ProgHeader: elf.ProgHeader{Type: elf.PT_LOAD, Flags: elf.PF_R | elf.PF_W, Off: 0xc80, Vaddr: 0x200c80, Paddr: 0x200c80, Filesz: 0x100, Memsz: 0x1f0, Align: 0x200000}},
683 {ProgHeader: elf.ProgHeader{Type: elf.PT_LOAD, Flags: elf.PF_R | elf.PF_W, Off: 0xd80, Vaddr: 0x400d80, Paddr: 0x400d80, Filesz: 0x90, Memsz: 0x90, Align: 0x200000}},
684 },
685 }
686
687 for _, tc := range []struct {
688 desc string
689 file *elf.File
690 openErr error
691 mapping *elfMapping
692 addr uint64
693 wantError bool
694 wantBase uint64
695 wantIsData bool
696 }{
697 {
698 desc: "no elf mapping, no error",
699 mapping: nil,
700 addr: 0x1000,
701 wantBase: 0,
702 wantIsData: false,
703 },
704 {
705 desc: "address outside mapping bounds means error",
706 file: &elf.File{},
707 mapping: &elfMapping{start: 0x2000, limit: 0x5000, offset: 0x1000},
708 addr: 0x1000,
709 wantError: true,
710 },
711 {
712 desc: "elf.Open failing means error",
713 file: &elf.File{FileHeader: elf.FileHeader{Type: elf.ET_EXEC}},
714 openErr: errors.New("elf.Open failed"),
715 mapping: &elfMapping{start: 0x2000, limit: 0x5000, offset: 0x1000},
716 addr: 0x4000,
717 wantError: true,
718 },
719 {
720 desc: "no loadable segments, no error",
721 file: &elf.File{FileHeader: elf.FileHeader{Type: elf.ET_EXEC}},
722 mapping: &elfMapping{start: 0x2000, limit: 0x5000, offset: 0x1000},
723 addr: 0x4000,
724 wantBase: 0,
725 wantIsData: false,
726 },
727 {
728 desc: "unsupported executable type, Get Base returns error",
729 file: &elf.File{FileHeader: elf.FileHeader{Type: elf.ET_NONE}},
730 mapping: &elfMapping{start: 0x2000, limit: 0x5000, offset: 0x1000},
731 addr: 0x4000,
732 wantError: true,
733 },
734 {
735 desc: "tiny file select executable segment by offset",
736 file: tinyExecFile,
737 mapping: &elfMapping{start: 0x5000000, limit: 0x5001000, offset: 0x0},
738 addr: 0x5000c00,
739 wantBase: 0x5000000,
740 wantIsData: false,
741 },
742 {
743 desc: "tiny file select data segment by offset",
744 file: tinyExecFile,
745 mapping: &elfMapping{start: 0x5200000, limit: 0x5201000, offset: 0x0},
746 addr: 0x5200c80,
747 wantBase: 0x5000000,
748 wantIsData: true,
749 },
750 {
751 desc: "tiny file offset outside any segment means error",
752 file: tinyExecFile,
753 mapping: &elfMapping{start: 0x5200000, limit: 0x5201000, offset: 0x0},
754 addr: 0x5200e70,
755 wantError: true,
756 },
757 {
758 desc: "tiny file with bad BSS segment selects data segment by offset in initialized section",
759 file: tinyBadBSSExecFile,
760 mapping: &elfMapping{start: 0x5200000, limit: 0x5201000, offset: 0x0},
761 addr: 0x5200d79,
762 wantBase: 0x5000000,
763 wantIsData: true,
764 },
765 {
766 desc: "tiny file with bad BSS segment with offset in uninitialized section means error",
767 file: tinyBadBSSExecFile,
768 mapping: &elfMapping{start: 0x5200000, limit: 0x5201000, offset: 0x0},
769 addr: 0x5200d80,
770 wantError: true,
771 },
772 } {
773 t.Run(tc.desc, func(t *testing.T) {
774 elfOpen = func(_ string) (*elf.File, error) {
775 return tc.file, tc.openErr
776 }
777 f := file{m: tc.mapping}
778 err := f.computeBase(tc.addr)
779 if (err != nil) != tc.wantError {
780 t.Errorf("got error %v, want any error=%v", err, tc.wantError)
781 }
782 if err != nil {
783 return
784 }
785 if f.base != tc.wantBase {
786 t.Errorf("got base %x, want %x", f.base, tc.wantBase)
787 }
788 if f.isData != tc.wantIsData {
789 t.Errorf("got isData %v, want %v", f.isData, tc.wantIsData)
790 }
791 })
792 }
793 }
794
795 func TestELFObjAddr(t *testing.T) {
796
797
798
799
800
801 name := filepath.Join("testdata", "exe_linux_64")
802
803 for _, tc := range []struct {
804 desc string
805 start, limit, offset uint64
806 wantOpenError bool
807 addr uint64
808 wantObjAddr uint64
809 wantAddrError bool
810 }{
811 {"exec mapping, good address", 0x5400000, 0x5401000, 0, false, 0x5400400, 0x400400, false},
812 {"exec mapping, address outside segment", 0x5400000, 0x5401000, 0, false, 0x5400800, 0, true},
813 {"short data mapping, good address", 0x5600e00, 0x5602000, 0xe00, false, 0x5600e10, 0x600e10, false},
814 {"short data mapping, address outside segment", 0x5600e00, 0x5602000, 0xe00, false, 0x5600e00, 0x600e00, false},
815 {"page aligned data mapping, good address", 0x5600000, 0x5602000, 0, false, 0x5601000, 0x601000, false},
816 {"page aligned data mapping, address outside segment", 0x5600000, 0x5602000, 0, false, 0x5601048, 0, true},
817 {"bad file offset, no matching segment", 0x5600000, 0x5602000, 0x2000, false, 0x5600e10, 0, true},
818 {"large mapping size, match by sample offset", 0x5600000, 0x5603000, 0, false, 0x5600e10, 0x600e10, false},
819 } {
820 t.Run(tc.desc, func(t *testing.T) {
821 b := binrep{}
822 o, err := b.openELF(name, tc.start, tc.limit, tc.offset, "")
823 if (err != nil) != tc.wantOpenError {
824 t.Errorf("openELF got error %v, want any error=%v", err, tc.wantOpenError)
825 }
826 if err != nil {
827 return
828 }
829 got, err := o.ObjAddr(tc.addr)
830 if (err != nil) != tc.wantAddrError {
831 t.Errorf("ObjAddr got error %v, want any error=%v", err, tc.wantAddrError)
832 }
833 if err != nil {
834 return
835 }
836 if got != tc.wantObjAddr {
837 t.Errorf("got ObjAddr %x; want %x\n", got, tc.wantObjAddr)
838 }
839 })
840 }
841 }
842
843 type buf struct {
844 data []byte
845 }
846
847
848 func (b *buf) write(s string) uint32 {
849 res := uint32(len(b.data))
850 b.data = append(b.data, s...)
851 b.data = append(b.data, '\x00')
852 return res
853 }
854
855
856
857
858 func fakeELFFile(t *testing.T) *elf.File {
859 var (
860 sizeHeader64 = binary.Size(elf.Header64{})
861 sizeProg64 = binary.Size(elf.Prog64{})
862 sizeSection64 = binary.Size(elf.Section64{})
863 )
864
865 const (
866 textAddr = 0xffff000010080000
867 stextAddr = 0xffff000010081000
868 )
869
870
871 var ident [16]uint8
872 ident[0] = '\x7f'
873 ident[1] = 'E'
874 ident[2] = 'L'
875 ident[3] = 'F'
876 ident[elf.EI_CLASS] = uint8(elf.ELFCLASS64)
877 ident[elf.EI_DATA] = uint8(elf.ELFDATA2LSB)
878 ident[elf.EI_VERSION] = uint8(elf.EV_CURRENT)
879 ident[elf.EI_OSABI] = uint8(elf.ELFOSABI_NONE)
880
881
882 progs := []elf.Prog64{{
883 Type: uint32(elf.PT_LOAD), Flags: uint32(elf.PF_R | elf.PF_X), Off: 0x10000, Vaddr: textAddr, Paddr: textAddr, Filesz: 0x1234567, Memsz: 0x1234567, Align: 0x10000}}
884
885 symNames := buf{}
886 syms := []elf.Sym64{
887 {},
888 {Name: symNames.write("_text"), Info: 0, Other: 0, Shndx: 0, Value: textAddr, Size: 0},
889 {Name: symNames.write("_stext"), Info: 0, Other: 0, Shndx: 0, Value: stextAddr, Size: 0},
890 }
891
892 const numSections = 5
893
894 const textSize = 16
895
896 sectionsStart := uint64(sizeHeader64 + len(progs)*sizeProg64 + numSections*sizeSection64)
897
898 secNames := buf{}
899 sections := [numSections]elf.Section64{
900 {Name: secNames.write(".head.text"), Type: uint32(elf.SHT_PROGBITS), Flags: uint64(elf.SHF_ALLOC | elf.SHF_EXECINSTR), Addr: textAddr, Off: sectionsStart, Size: textSize, Link: 0, Info: 0, Addralign: 2048, Entsize: 0},
901 {Name: secNames.write(".text"), Type: uint32(elf.SHT_PROGBITS), Flags: uint64(elf.SHF_ALLOC | elf.SHF_EXECINSTR), Addr: stextAddr, Off: sectionsStart + textSize, Size: textSize, Link: 0, Info: 0, Addralign: 2048, Entsize: 0},
902 {Name: secNames.write(".symtab"), Type: uint32(elf.SHT_SYMTAB), Flags: 0, Addr: 0, Off: sectionsStart + 2*textSize, Size: uint64(len(syms) * elf.Sym64Size), Link: 3 , Info: 0, Addralign: 8, Entsize: elf.Sym64Size},
903 {Name: secNames.write(".strtab"), Type: uint32(elf.SHT_STRTAB), Flags: 0, Addr: 0, Off: sectionsStart + 2*textSize + uint64(len(syms)*elf.Sym64Size), Size: uint64(len(symNames.data)), Link: 0, Info: 0, Addralign: 1, Entsize: 0},
904 {Name: secNames.write(".shstrtab"), Type: uint32(elf.SHT_STRTAB), Flags: 0, Addr: 0, Off: sectionsStart + 2*textSize + uint64(len(syms)*elf.Sym64Size+len(symNames.data)), Size: uint64(len(secNames.data)), Link: 0, Info: 0, Addralign: 1, Entsize: 0},
905 }
906
907 hdr := elf.Header64{
908 Ident: ident,
909 Type: uint16(elf.ET_DYN),
910 Machine: uint16(elf.EM_AARCH64),
911 Version: uint32(elf.EV_CURRENT),
912 Entry: textAddr,
913 Phoff: uint64(sizeHeader64),
914 Shoff: uint64(sizeHeader64 + len(progs)*sizeProg64),
915 Flags: 0,
916 Ehsize: uint16(sizeHeader64),
917 Phentsize: uint16(sizeProg64),
918 Phnum: uint16(len(progs)),
919 Shentsize: uint16(sizeSection64),
920 Shnum: uint16(len(sections)),
921 Shstrndx: 4,
922 }
923
924
925 var data bytes.Buffer
926 for i, b := range []interface{}{hdr, progs, sections, [textSize]byte{}, [textSize]byte{}, syms, symNames.data, secNames.data} {
927 err := binary.Write(&data, binary.LittleEndian, b)
928 if err != nil {
929 t.Fatalf("Write(%v) got err %v, want nil", i, err)
930 }
931 }
932
933
934 ef, err := elf.NewFile(bytes.NewReader(data.Bytes()))
935 if err != nil {
936 t.Fatalf("elf.NewFile got err %v, want nil", err)
937 }
938 return ef
939 }
940
941 func TestELFKernelOffset(t *testing.T) {
942 realELFOpen := elfOpen
943 defer func() {
944 elfOpen = realELFOpen
945 }()
946
947 wantAddr := uint64(0xffff000010082000)
948 elfOpen = func(_ string) (*elf.File, error) {
949 return fakeELFFile(t), nil
950 }
951
952 for _, tc := range []struct {
953 name string
954 relocationSymbol string
955 start uint64
956 }{
957 {"text", "_text", 0xffff000020080000},
958 {"stext", "_stext", 0xffff000020081000},
959 } {
960
961 b := binrep{}
962 o, err := b.openELF("vmlinux", tc.start, 0xffffffffffffffff, tc.start, tc.relocationSymbol)
963 if err != nil {
964 t.Errorf("%v: openELF got error %v, want nil", tc.name, err)
965 continue
966 }
967
968 addr, err := o.ObjAddr(0xffff000020082000)
969 if err != nil {
970 t.Errorf("%v: ObjAddr got err %v, want nil", tc.name, err)
971 continue
972 }
973 if addr != wantAddr {
974 t.Errorf("%v: ObjAddr got %x, want %x", tc.name, addr, wantAddr)
975 }
976
977 }
978 }
979
View as plain text