Home | History | Annotate | Download | only in link
      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 package main
      6 
      7 import (
      8 	"cmd/internal/objfile"
      9 	"debug/dwarf"
     10 	"internal/testenv"
     11 	"io"
     12 	"io/ioutil"
     13 	"os"
     14 	"os/exec"
     15 	"path"
     16 	"path/filepath"
     17 	"runtime"
     18 	"strings"
     19 	"testing"
     20 )
     21 
     22 func TestDWARF(t *testing.T) {
     23 	testenv.MustHaveCGO(t)
     24 	testenv.MustHaveGoBuild(t)
     25 
     26 	if runtime.GOOS == "plan9" {
     27 		t.Skip("skipping on plan9; no DWARF symbol table in executables")
     28 	}
     29 
     30 	out, err := exec.Command(testenv.GoToolPath(t), "list", "-f", "{{.Stale}}", "cmd/link").CombinedOutput()
     31 	if err != nil {
     32 		t.Fatalf("go list: %v\n%s", err, out)
     33 	}
     34 	if string(out) != "false\n" {
     35 		if os.Getenv("GOROOT_FINAL_OLD") != "" {
     36 			t.Skip("cmd/link is stale, but $GOROOT_FINAL_OLD is set")
     37 		}
     38 		t.Fatalf("cmd/link is stale - run go install cmd/link")
     39 	}
     40 
     41 	tmpDir, err := ioutil.TempDir("", "go-link-TestDWARF")
     42 	if err != nil {
     43 		t.Fatal("TempDir failed: ", err)
     44 	}
     45 	defer os.RemoveAll(tmpDir)
     46 
     47 	for _, prog := range []string{"testprog", "testprogcgo"} {
     48 		t.Run(prog, func(t *testing.T) {
     49 			exe := filepath.Join(tmpDir, prog+".exe")
     50 			dir := "../../runtime/testdata/" + prog
     51 			out, err := exec.Command(testenv.GoToolPath(t), "build", "-o", exe, dir).CombinedOutput()
     52 			if err != nil {
     53 				t.Fatalf("go build -o %v %v: %v\n%s", exe, dir, err, out)
     54 			}
     55 
     56 			f, err := objfile.Open(exe)
     57 			if err != nil {
     58 				t.Fatal(err)
     59 			}
     60 			defer f.Close()
     61 
     62 			syms, err := f.Symbols()
     63 			if err != nil {
     64 				t.Fatal(err)
     65 			}
     66 
     67 			var addr uint64
     68 			for _, sym := range syms {
     69 				if sym.Name == "main.main" {
     70 					addr = sym.Addr
     71 					break
     72 				}
     73 			}
     74 			if addr == 0 {
     75 				t.Fatal("cannot find main.main in symbols")
     76 			}
     77 
     78 			d, err := f.DWARF()
     79 			if err != nil {
     80 				t.Fatal(err)
     81 			}
     82 
     83 			// TODO: We'd like to use filepath.Join here.
     84 			// Also related: golang.org/issue/19784.
     85 			wantFile := path.Join(prog, "main.go")
     86 			wantLine := 24
     87 			r := d.Reader()
     88 			var line dwarf.LineEntry
     89 			for {
     90 				cu, err := r.Next()
     91 				if err != nil {
     92 					t.Fatal(err)
     93 				}
     94 				if cu == nil {
     95 					break
     96 				}
     97 				if cu.Tag != dwarf.TagCompileUnit {
     98 					r.SkipChildren()
     99 					continue
    100 				}
    101 				lr, err := d.LineReader(cu)
    102 				if err != nil {
    103 					t.Fatal(err)
    104 				}
    105 				for {
    106 					err := lr.Next(&line)
    107 					if err == io.EOF {
    108 						break
    109 					}
    110 					if err != nil {
    111 						t.Fatal(err)
    112 					}
    113 					if line.Address == addr {
    114 						if !strings.HasSuffix(line.File.Name, wantFile) || line.Line != wantLine {
    115 							t.Errorf("%#x is %s:%d, want %s:%d", addr, line.File.Name, line.Line, filepath.Join("...", wantFile), wantLine)
    116 						}
    117 						return
    118 					}
    119 				}
    120 			}
    121 			t.Fatalf("did not find file:line for %#x (main.main)", addr)
    122 		})
    123 	}
    124 }
    125