Home | History | Annotate | Download | only in nm
      1 // Copyright 2014 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 	"bufio"
      9 	"bytes"
     10 	"fmt"
     11 	"internal/testenv"
     12 	"io/ioutil"
     13 	"os"
     14 	"os/exec"
     15 	"path/filepath"
     16 	"runtime"
     17 	"strings"
     18 	"testing"
     19 	"text/template"
     20 )
     21 
     22 var testnmpath string // path to nm command created for testing purposes
     23 
     24 // The TestMain function creates a nm command for testing purposes and
     25 // deletes it after the tests have been run.
     26 func TestMain(m *testing.M) {
     27 	os.Exit(testMain(m))
     28 }
     29 
     30 func testMain(m *testing.M) int {
     31 	if !testenv.HasGoBuild() {
     32 		return 0
     33 	}
     34 
     35 	tmpDir, err := ioutil.TempDir("", "TestNM")
     36 	if err != nil {
     37 		fmt.Println("TempDir failed:", err)
     38 		return 2
     39 	}
     40 	defer os.RemoveAll(tmpDir)
     41 
     42 	testnmpath = filepath.Join(tmpDir, "testnm.exe")
     43 	gotool, err := testenv.GoTool()
     44 	if err != nil {
     45 		fmt.Println("GoTool failed:", err)
     46 		return 2
     47 	}
     48 	out, err := exec.Command(gotool, "build", "-o", testnmpath, "cmd/nm").CombinedOutput()
     49 	if err != nil {
     50 		fmt.Printf("go build -o %v cmd/nm: %v\n%s", testnmpath, err, string(out))
     51 		return 2
     52 	}
     53 
     54 	return m.Run()
     55 }
     56 
     57 func TestNonGoExecs(t *testing.T) {
     58 	testfiles := []string{
     59 		"elf/testdata/gcc-386-freebsd-exec",
     60 		"elf/testdata/gcc-amd64-linux-exec",
     61 		"macho/testdata/gcc-386-darwin-exec",
     62 		"macho/testdata/gcc-amd64-darwin-exec",
     63 		// "pe/testdata/gcc-amd64-mingw-exec", // no symbols!
     64 		"pe/testdata/gcc-386-mingw-exec",
     65 		"plan9obj/testdata/amd64-plan9-exec",
     66 		"plan9obj/testdata/386-plan9-exec",
     67 	}
     68 	for _, f := range testfiles {
     69 		exepath := filepath.Join(runtime.GOROOT(), "src", "debug", f)
     70 		cmd := exec.Command(testnmpath, exepath)
     71 		out, err := cmd.CombinedOutput()
     72 		if err != nil {
     73 			t.Errorf("go tool nm %v: %v\n%s", exepath, err, string(out))
     74 		}
     75 	}
     76 }
     77 
     78 func testGoExec(t *testing.T, iscgo, isexternallinker bool) {
     79 	tmpdir, err := ioutil.TempDir("", "TestGoExec")
     80 	if err != nil {
     81 		t.Fatal(err)
     82 	}
     83 	defer os.RemoveAll(tmpdir)
     84 
     85 	src := filepath.Join(tmpdir, "a.go")
     86 	file, err := os.Create(src)
     87 	if err != nil {
     88 		t.Fatal(err)
     89 	}
     90 	err = template.Must(template.New("main").Parse(testexec)).Execute(file, iscgo)
     91 	if e := file.Close(); err == nil {
     92 		err = e
     93 	}
     94 	if err != nil {
     95 		t.Fatal(err)
     96 	}
     97 
     98 	exe := filepath.Join(tmpdir, "a.exe")
     99 	args := []string{"build", "-o", exe}
    100 	if iscgo {
    101 		linkmode := "internal"
    102 		if isexternallinker {
    103 			linkmode = "external"
    104 		}
    105 		args = append(args, "-ldflags", "-linkmode="+linkmode)
    106 	}
    107 	args = append(args, src)
    108 	out, err := exec.Command(testenv.GoToolPath(t), args...).CombinedOutput()
    109 	if err != nil {
    110 		t.Fatalf("building test executable failed: %s %s", err, out)
    111 	}
    112 
    113 	out, err = exec.Command(exe).CombinedOutput()
    114 	if err != nil {
    115 		t.Fatalf("running test executable failed: %s %s", err, out)
    116 	}
    117 	names := make(map[string]string)
    118 	for _, line := range strings.Split(string(out), "\n") {
    119 		if line == "" {
    120 			continue
    121 		}
    122 		f := strings.Split(line, "=")
    123 		if len(f) != 2 {
    124 			t.Fatalf("unexpected output line: %q", line)
    125 		}
    126 		names["main."+f[0]] = f[1]
    127 	}
    128 
    129 	out, err = exec.Command(testnmpath, exe).CombinedOutput()
    130 	if err != nil {
    131 		t.Fatalf("go tool nm: %v\n%s", err, string(out))
    132 	}
    133 	scanner := bufio.NewScanner(bytes.NewBuffer(out))
    134 	dups := make(map[string]bool)
    135 	for scanner.Scan() {
    136 		f := strings.Fields(scanner.Text())
    137 		if len(f) < 3 {
    138 			continue
    139 		}
    140 		name := f[2]
    141 		if addr, found := names[name]; found {
    142 			if want, have := addr, "0x"+f[0]; have != want {
    143 				t.Errorf("want %s address for %s symbol, but have %s", want, name, have)
    144 			}
    145 			delete(names, name)
    146 		}
    147 		if _, found := dups[name]; found {
    148 			t.Errorf("duplicate name of %q is found", name)
    149 		}
    150 	}
    151 	err = scanner.Err()
    152 	if err != nil {
    153 		t.Fatalf("error reading nm output: %v", err)
    154 	}
    155 	if len(names) > 0 {
    156 		t.Errorf("executable is missing %v symbols", names)
    157 	}
    158 }
    159 
    160 func TestGoExec(t *testing.T) {
    161 	testGoExec(t, false, false)
    162 }
    163 
    164 func testGoLib(t *testing.T, iscgo bool) {
    165 	tmpdir, err := ioutil.TempDir("", "TestGoLib")
    166 	if err != nil {
    167 		t.Fatal(err)
    168 	}
    169 	defer os.RemoveAll(tmpdir)
    170 
    171 	gopath := filepath.Join(tmpdir, "gopath")
    172 	libpath := filepath.Join(gopath, "src", "mylib")
    173 
    174 	err = os.MkdirAll(libpath, 0777)
    175 	if err != nil {
    176 		t.Fatal(err)
    177 	}
    178 	src := filepath.Join(libpath, "a.go")
    179 	file, err := os.Create(src)
    180 	if err != nil {
    181 		t.Fatal(err)
    182 	}
    183 	err = template.Must(template.New("mylib").Parse(testlib)).Execute(file, iscgo)
    184 	if e := file.Close(); err == nil {
    185 		err = e
    186 	}
    187 	if err != nil {
    188 		t.Fatal(err)
    189 	}
    190 
    191 	args := []string{"install", "mylib"}
    192 	cmd := exec.Command(testenv.GoToolPath(t), args...)
    193 	cmd.Env = append(os.Environ(), "GOPATH="+gopath)
    194 	out, err := cmd.CombinedOutput()
    195 	if err != nil {
    196 		t.Fatalf("building test lib failed: %s %s", err, out)
    197 	}
    198 	pat := filepath.Join(gopath, "pkg", "*", "mylib.a")
    199 	ms, err := filepath.Glob(pat)
    200 	if err != nil {
    201 		t.Fatal(err)
    202 	}
    203 	if len(ms) == 0 {
    204 		t.Fatalf("cannot found paths for pattern %s", pat)
    205 	}
    206 	mylib := ms[0]
    207 
    208 	out, err = exec.Command(testnmpath, mylib).CombinedOutput()
    209 	if err != nil {
    210 		t.Fatalf("go tool nm: %v\n%s", err, string(out))
    211 	}
    212 	type symType struct {
    213 		Type  string
    214 		Name  string
    215 		CSym  bool
    216 		Found bool
    217 	}
    218 	var syms = []symType{
    219 		{"B", "%22%22.Testdata", false, false},
    220 		{"T", "%22%22.Testfunc", false, false},
    221 	}
    222 	if iscgo {
    223 		syms = append(syms, symType{"B", "%22%22.TestCgodata", false, false})
    224 		syms = append(syms, symType{"T", "%22%22.TestCgofunc", false, false})
    225 		if runtime.GOOS == "darwin" || (runtime.GOOS == "windows" && runtime.GOARCH == "386") {
    226 			syms = append(syms, symType{"D", "_cgodata", true, false})
    227 			syms = append(syms, symType{"T", "_cgofunc", true, false})
    228 		} else {
    229 			syms = append(syms, symType{"D", "cgodata", true, false})
    230 			syms = append(syms, symType{"T", "cgofunc", true, false})
    231 		}
    232 	}
    233 	scanner := bufio.NewScanner(bytes.NewBuffer(out))
    234 	for scanner.Scan() {
    235 		f := strings.Fields(scanner.Text())
    236 		var typ, name string
    237 		var csym bool
    238 		if iscgo {
    239 			if len(f) < 4 {
    240 				continue
    241 			}
    242 			csym = !strings.Contains(f[0], "_go_.o")
    243 			typ = f[2]
    244 			name = f[3]
    245 		} else {
    246 			if len(f) < 3 {
    247 				continue
    248 			}
    249 			typ = f[1]
    250 			name = f[2]
    251 		}
    252 		for i := range syms {
    253 			sym := &syms[i]
    254 			if sym.Type == typ && sym.Name == name && sym.CSym == csym {
    255 				if sym.Found {
    256 					t.Fatalf("duplicate symbol %s %s", sym.Type, sym.Name)
    257 				}
    258 				sym.Found = true
    259 			}
    260 		}
    261 	}
    262 	err = scanner.Err()
    263 	if err != nil {
    264 		t.Fatalf("error reading nm output: %v", err)
    265 	}
    266 	for _, sym := range syms {
    267 		if !sym.Found {
    268 			t.Errorf("cannot found symbol %s %s", sym.Type, sym.Name)
    269 		}
    270 	}
    271 }
    272 
    273 func TestGoLib(t *testing.T) {
    274 	testGoLib(t, false)
    275 }
    276 
    277 const testexec = `
    278 package main
    279 
    280 import "fmt"
    281 {{if .}}import "C"
    282 {{end}}
    283 
    284 func main() {
    285 	testfunc()
    286 }
    287 
    288 var testdata uint32
    289 
    290 func testfunc() {
    291 	fmt.Printf("main=%p\n", main)
    292 	fmt.Printf("testfunc=%p\n", testfunc)
    293 	fmt.Printf("testdata=%p\n", &testdata)
    294 }
    295 `
    296 
    297 const testlib = `
    298 package mylib
    299 
    300 {{if .}}
    301 // int cgodata = 5;
    302 // void cgofunc(void) {}
    303 import "C"
    304 
    305 var TestCgodata = C.cgodata
    306 
    307 func TestCgofunc() {
    308 	C.cgofunc()
    309 }
    310 {{end}}
    311 
    312 var Testdata uint32
    313 
    314 func Testfunc() {}
    315 `
    316