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