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 5package main 6 7import ( 8 "internal/obscuretestdata" 9 "internal/platform" 10 "internal/testenv" 11 "os" 12 "path/filepath" 13 "runtime" 14 "strings" 15 "sync" 16 "testing" 17 "text/template" 18) 19 20// TestMain executes the test binary as the nm command if 21// GO_NMTEST_IS_NM is set, and runs the tests otherwise. 22func TestMain(m *testing.M) { 23 if os.Getenv("GO_NMTEST_IS_NM") != "" { 24 main() 25 os.Exit(0) 26 } 27 28 os.Setenv("GO_NMTEST_IS_NM", "1") // Set for subprocesses to inherit. 29 os.Exit(m.Run()) 30} 31 32// nmPath returns the path to the "nm" binary to run. 33func nmPath(t testing.TB) string { 34 t.Helper() 35 testenv.MustHaveExec(t) 36 37 nmPathOnce.Do(func() { 38 nmExePath, nmPathErr = os.Executable() 39 }) 40 if nmPathErr != nil { 41 t.Fatal(nmPathErr) 42 } 43 return nmExePath 44} 45 46var ( 47 nmPathOnce sync.Once 48 nmExePath string 49 nmPathErr error 50) 51 52func TestNonGoExecs(t *testing.T) { 53 t.Parallel() 54 testfiles := []string{ 55 "debug/elf/testdata/gcc-386-freebsd-exec", 56 "debug/elf/testdata/gcc-amd64-linux-exec", 57 "debug/macho/testdata/gcc-386-darwin-exec.base64", // golang.org/issue/34986 58 "debug/macho/testdata/gcc-amd64-darwin-exec.base64", // golang.org/issue/34986 59 // "debug/pe/testdata/gcc-amd64-mingw-exec", // no symbols! 60 "debug/pe/testdata/gcc-386-mingw-exec", 61 "debug/plan9obj/testdata/amd64-plan9-exec", 62 "debug/plan9obj/testdata/386-plan9-exec", 63 "internal/xcoff/testdata/gcc-ppc64-aix-dwarf2-exec", 64 } 65 for _, f := range testfiles { 66 exepath := filepath.Join(testenv.GOROOT(t), "src", f) 67 if strings.HasSuffix(f, ".base64") { 68 tf, err := obscuretestdata.DecodeToTempFile(exepath) 69 if err != nil { 70 t.Errorf("obscuretestdata.DecodeToTempFile(%s): %v", exepath, err) 71 continue 72 } 73 defer os.Remove(tf) 74 exepath = tf 75 } 76 77 cmd := testenv.Command(t, nmPath(t), exepath) 78 out, err := cmd.CombinedOutput() 79 if err != nil { 80 t.Errorf("go tool nm %v: %v\n%s", exepath, err, string(out)) 81 } 82 } 83} 84 85func testGoExec(t *testing.T, iscgo, isexternallinker bool) { 86 t.Parallel() 87 tmpdir, err := os.MkdirTemp("", "TestGoExec") 88 if err != nil { 89 t.Fatal(err) 90 } 91 defer os.RemoveAll(tmpdir) 92 93 src := filepath.Join(tmpdir, "a.go") 94 file, err := os.Create(src) 95 if err != nil { 96 t.Fatal(err) 97 } 98 err = template.Must(template.New("main").Parse(testexec)).Execute(file, iscgo) 99 if e := file.Close(); err == nil { 100 err = e 101 } 102 if err != nil { 103 t.Fatal(err) 104 } 105 106 exe := filepath.Join(tmpdir, "a.exe") 107 args := []string{"build", "-o", exe} 108 if iscgo { 109 linkmode := "internal" 110 if isexternallinker { 111 linkmode = "external" 112 } 113 args = append(args, "-ldflags", "-linkmode="+linkmode) 114 } 115 args = append(args, src) 116 out, err := testenv.Command(t, testenv.GoToolPath(t), args...).CombinedOutput() 117 if err != nil { 118 t.Fatalf("building test executable failed: %s %s", err, out) 119 } 120 121 out, err = testenv.Command(t, exe).CombinedOutput() 122 if err != nil { 123 t.Fatalf("running test executable failed: %s %s", err, out) 124 } 125 names := make(map[string]string) 126 for _, line := range strings.Split(string(out), "\n") { 127 if line == "" { 128 continue 129 } 130 f := strings.Split(line, "=") 131 if len(f) != 2 { 132 t.Fatalf("unexpected output line: %q", line) 133 } 134 names["main."+f[0]] = f[1] 135 } 136 137 runtimeSyms := map[string]string{ 138 "runtime.text": "T", 139 "runtime.etext": "T", 140 "runtime.rodata": "R", 141 "runtime.erodata": "R", 142 "runtime.epclntab": "R", 143 "runtime.noptrdata": "D", 144 } 145 146 if runtime.GOOS == "aix" && iscgo { 147 // pclntab is moved to .data section on AIX. 148 runtimeSyms["runtime.epclntab"] = "D" 149 } 150 151 out, err = testenv.Command(t, nmPath(t), exe).CombinedOutput() 152 if err != nil { 153 t.Fatalf("go tool nm: %v\n%s", err, string(out)) 154 } 155 156 relocated := func(code string) bool { 157 if runtime.GOOS == "aix" { 158 // On AIX, .data and .bss addresses are changed by the loader. 159 // Therefore, the values returned by the exec aren't the same 160 // than the ones inside the symbol table. 161 // In case of cgo, .text symbols are also changed. 162 switch code { 163 case "T", "t", "R", "r": 164 return iscgo 165 case "D", "d", "B", "b": 166 return true 167 } 168 } 169 if platform.DefaultPIE(runtime.GOOS, runtime.GOARCH, false) { 170 // Code is always relocated if the default buildmode is PIE. 171 return true 172 } 173 return false 174 } 175 176 dups := make(map[string]bool) 177 for _, line := range strings.Split(string(out), "\n") { 178 f := strings.Fields(line) 179 if len(f) < 3 { 180 continue 181 } 182 name := f[2] 183 if addr, found := names[name]; found { 184 if want, have := addr, "0x"+f[0]; have != want { 185 if !relocated(f[1]) { 186 t.Errorf("want %s address for %s symbol, but have %s", want, name, have) 187 } 188 } 189 delete(names, name) 190 } 191 if _, found := dups[name]; found { 192 t.Errorf("duplicate name of %q is found", name) 193 } 194 if stype, found := runtimeSyms[name]; found { 195 if runtime.GOOS == "plan9" && stype == "R" { 196 // no read-only data segment symbol on Plan 9 197 stype = "D" 198 } 199 if want, have := stype, strings.ToUpper(f[1]); have != want { 200 if runtime.GOOS == "android" && name == "runtime.epclntab" && have == "D" { 201 // TODO(#58807): Figure out why this fails and fix up the test. 202 t.Logf("(ignoring on %s) want %s type for %s symbol, but have %s", runtime.GOOS, want, name, have) 203 } else { 204 t.Errorf("want %s type for %s symbol, but have %s", want, name, have) 205 } 206 } 207 delete(runtimeSyms, name) 208 } 209 } 210 if len(names) > 0 { 211 t.Errorf("executable is missing %v symbols", names) 212 } 213 if len(runtimeSyms) > 0 { 214 t.Errorf("executable is missing %v symbols", runtimeSyms) 215 } 216} 217 218func TestGoExec(t *testing.T) { 219 testGoExec(t, false, false) 220} 221 222func testGoLib(t *testing.T, iscgo bool) { 223 t.Parallel() 224 tmpdir, err := os.MkdirTemp("", "TestGoLib") 225 if err != nil { 226 t.Fatal(err) 227 } 228 defer os.RemoveAll(tmpdir) 229 230 gopath := filepath.Join(tmpdir, "gopath") 231 libpath := filepath.Join(gopath, "src", "mylib") 232 233 err = os.MkdirAll(libpath, 0777) 234 if err != nil { 235 t.Fatal(err) 236 } 237 src := filepath.Join(libpath, "a.go") 238 file, err := os.Create(src) 239 if err != nil { 240 t.Fatal(err) 241 } 242 err = template.Must(template.New("mylib").Parse(testlib)).Execute(file, iscgo) 243 if e := file.Close(); err == nil { 244 err = e 245 } 246 if err == nil { 247 err = os.WriteFile(filepath.Join(libpath, "go.mod"), []byte("module mylib\n"), 0666) 248 } 249 if err != nil { 250 t.Fatal(err) 251 } 252 253 cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-buildmode=archive", "-o", "mylib.a", ".") 254 cmd.Dir = libpath 255 cmd.Env = append(os.Environ(), "GOPATH="+gopath) 256 out, err := cmd.CombinedOutput() 257 if err != nil { 258 t.Fatalf("building test lib failed: %s %s", err, out) 259 } 260 mylib := filepath.Join(libpath, "mylib.a") 261 262 out, err = testenv.Command(t, nmPath(t), mylib).CombinedOutput() 263 if err != nil { 264 t.Fatalf("go tool nm: %v\n%s", err, string(out)) 265 } 266 type symType struct { 267 Type string 268 Name string 269 CSym bool 270 Found bool 271 } 272 var syms = []symType{ 273 {"B", "mylib.Testdata", false, false}, 274 {"T", "mylib.Testfunc", false, false}, 275 } 276 if iscgo { 277 syms = append(syms, symType{"B", "mylib.TestCgodata", false, false}) 278 syms = append(syms, symType{"T", "mylib.TestCgofunc", false, false}) 279 if runtime.GOOS == "darwin" || runtime.GOOS == "ios" || (runtime.GOOS == "windows" && runtime.GOARCH == "386") { 280 syms = append(syms, symType{"D", "_cgodata", true, false}) 281 syms = append(syms, symType{"T", "_cgofunc", true, false}) 282 } else if runtime.GOOS == "aix" { 283 syms = append(syms, symType{"D", "cgodata", true, false}) 284 syms = append(syms, symType{"T", ".cgofunc", true, false}) 285 } else { 286 syms = append(syms, symType{"D", "cgodata", true, false}) 287 syms = append(syms, symType{"T", "cgofunc", true, false}) 288 } 289 } 290 291 for _, line := range strings.Split(string(out), "\n") { 292 f := strings.Fields(line) 293 var typ, name string 294 var csym bool 295 if iscgo { 296 if len(f) < 4 { 297 continue 298 } 299 csym = !strings.Contains(f[0], "_go_.o") 300 typ = f[2] 301 name = f[3] 302 } else { 303 if len(f) < 3 { 304 continue 305 } 306 typ = f[1] 307 name = f[2] 308 } 309 for i := range syms { 310 sym := &syms[i] 311 if sym.Type == typ && sym.Name == name && sym.CSym == csym { 312 if sym.Found { 313 t.Fatalf("duplicate symbol %s %s", sym.Type, sym.Name) 314 } 315 sym.Found = true 316 } 317 } 318 } 319 for _, sym := range syms { 320 if !sym.Found { 321 t.Errorf("cannot found symbol %s %s", sym.Type, sym.Name) 322 } 323 } 324} 325 326func TestGoLib(t *testing.T) { 327 testGoLib(t, false) 328} 329 330const testexec = ` 331package main 332 333import "fmt" 334{{if .}}import "C" 335{{end}} 336 337func main() { 338 testfunc() 339} 340 341var testdata uint32 342 343func testfunc() { 344 fmt.Printf("main=%p\n", main) 345 fmt.Printf("testfunc=%p\n", testfunc) 346 fmt.Printf("testdata=%p\n", &testdata) 347} 348` 349 350const testlib = ` 351package mylib 352 353{{if .}} 354// int cgodata = 5; 355// void cgofunc(void) {} 356import "C" 357 358var TestCgodata = C.cgodata 359 360func TestCgofunc() { 361 C.cgofunc() 362} 363{{end}} 364 365var Testdata uint32 366 367func Testfunc() {} 368` 369