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 "cmd/internal/notsha256" 9 "flag" 10 "fmt" 11 "internal/platform" 12 "internal/testenv" 13 "os" 14 "path/filepath" 15 "runtime" 16 "strings" 17 "sync" 18 "testing" 19) 20 21// TestMain executes the test binary as the objdump command if 22// GO_OBJDUMPTEST_IS_OBJDUMP is set, and runs the test otherwise. 23func TestMain(m *testing.M) { 24 if os.Getenv("GO_OBJDUMPTEST_IS_OBJDUMP") != "" { 25 main() 26 os.Exit(0) 27 } 28 29 os.Setenv("GO_OBJDUMPTEST_IS_OBJDUMP", "1") 30 os.Exit(m.Run()) 31} 32 33// objdumpPath returns the path to the "objdump" binary to run. 34func objdumpPath(t testing.TB) string { 35 t.Helper() 36 testenv.MustHaveExec(t) 37 38 objdumpPathOnce.Do(func() { 39 objdumpExePath, objdumpPathErr = os.Executable() 40 }) 41 if objdumpPathErr != nil { 42 t.Fatal(objdumpPathErr) 43 } 44 return objdumpExePath 45} 46 47var ( 48 objdumpPathOnce sync.Once 49 objdumpExePath string 50 objdumpPathErr error 51) 52 53var x86Need = []string{ // for both 386 and AMD64 54 "JMP main.main(SB)", 55 "CALL main.Println(SB)", 56 "RET", 57} 58 59var amd64GnuNeed = []string{ 60 "jmp", 61 "callq", 62 "cmpb", 63} 64 65var i386GnuNeed = []string{ 66 "jmp", 67 "call", 68 "cmp", 69} 70 71var armNeed = []string{ 72 "B main.main(SB)", 73 "BL main.Println(SB)", 74 "RET", 75} 76 77var arm64Need = []string{ 78 "JMP main.main(SB)", 79 "CALL main.Println(SB)", 80 "RET", 81} 82 83var armGnuNeed = []string{ // for both ARM and AMR64 84 "ldr", 85 "bl", 86 "cmp", 87} 88 89var ppcNeed = []string{ 90 "BR main.main(SB)", 91 "CALL main.Println(SB)", 92 "RET", 93} 94 95var ppcPIENeed = []string{ 96 "BR", 97 "CALL", 98 "RET", 99} 100 101var ppcGnuNeed = []string{ 102 "mflr", 103 "lbz", 104 "beq", 105} 106 107func mustHaveDisasm(t *testing.T) { 108 switch runtime.GOARCH { 109 case "loong64": 110 t.Skipf("skipping on %s", runtime.GOARCH) 111 case "mips", "mipsle", "mips64", "mips64le": 112 t.Skipf("skipping on %s, issue 12559", runtime.GOARCH) 113 case "riscv64": 114 t.Skipf("skipping on %s, issue 36738", runtime.GOARCH) 115 case "s390x": 116 t.Skipf("skipping on %s, issue 15255", runtime.GOARCH) 117 } 118} 119 120var target = flag.String("target", "", "test disassembly of `goos/goarch` binary") 121 122// objdump is fully cross platform: it can handle binaries 123// from any known operating system and architecture. 124// We could in principle add binaries to testdata and check 125// all the supported systems during this test. However, the 126// binaries would be about 1 MB each, and we don't want to 127// add that much junk to the hg repository. Instead, build a 128// binary for the current system (only) and test that objdump 129// can handle that one. 130 131func testDisasm(t *testing.T, srcfname string, printCode bool, printGnuAsm bool, flags ...string) { 132 mustHaveDisasm(t) 133 goarch := runtime.GOARCH 134 if *target != "" { 135 f := strings.Split(*target, "/") 136 if len(f) != 2 { 137 t.Fatalf("-target argument must be goos/goarch") 138 } 139 defer os.Setenv("GOOS", os.Getenv("GOOS")) 140 defer os.Setenv("GOARCH", os.Getenv("GOARCH")) 141 os.Setenv("GOOS", f[0]) 142 os.Setenv("GOARCH", f[1]) 143 goarch = f[1] 144 } 145 146 hash := notsha256.Sum256([]byte(fmt.Sprintf("%v-%v-%v-%v", srcfname, flags, printCode, printGnuAsm))) 147 tmp := t.TempDir() 148 hello := filepath.Join(tmp, fmt.Sprintf("hello-%x.exe", hash)) 149 args := []string{"build", "-o", hello} 150 args = append(args, flags...) 151 args = append(args, srcfname) 152 cmd := testenv.Command(t, testenv.GoToolPath(t), args...) 153 // "Bad line" bug #36683 is sensitive to being run in the source directory. 154 cmd.Dir = "testdata" 155 t.Logf("Running %v", cmd.Args) 156 out, err := cmd.CombinedOutput() 157 if err != nil { 158 t.Fatalf("go build %s: %v\n%s", srcfname, err, out) 159 } 160 need := []string{ 161 "TEXT main.main(SB)", 162 } 163 164 if printCode { 165 need = append(need, ` Println("hello, world")`) 166 } else { 167 need = append(need, srcfname+":6") 168 } 169 170 switch goarch { 171 case "amd64", "386": 172 need = append(need, x86Need...) 173 case "arm": 174 need = append(need, armNeed...) 175 case "arm64": 176 need = append(need, arm64Need...) 177 case "ppc64", "ppc64le": 178 var pie bool 179 for _, flag := range flags { 180 if flag == "-buildmode=pie" { 181 pie = true 182 break 183 } 184 } 185 if pie { 186 // In PPC64 PIE binaries we use a "local entry point" which is 187 // function symbol address + 8. Currently we don't symbolize that. 188 // Expect a different output. 189 need = append(need, ppcPIENeed...) 190 } else { 191 need = append(need, ppcNeed...) 192 } 193 } 194 195 if printGnuAsm { 196 switch goarch { 197 case "amd64": 198 need = append(need, amd64GnuNeed...) 199 case "386": 200 need = append(need, i386GnuNeed...) 201 case "arm", "arm64": 202 need = append(need, armGnuNeed...) 203 case "ppc64", "ppc64le": 204 need = append(need, ppcGnuNeed...) 205 } 206 } 207 args = []string{ 208 "-s", "main.main", 209 hello, 210 } 211 212 if printCode { 213 args = append([]string{"-S"}, args...) 214 } 215 216 if printGnuAsm { 217 args = append([]string{"-gnu"}, args...) 218 } 219 cmd = testenv.Command(t, objdumpPath(t), args...) 220 cmd.Dir = "testdata" // "Bad line" bug #36683 is sensitive to being run in the source directory 221 out, err = cmd.CombinedOutput() 222 t.Logf("Running %v", cmd.Args) 223 224 if err != nil { 225 exename := srcfname[:len(srcfname)-len(filepath.Ext(srcfname))] + ".exe" 226 t.Fatalf("objdump %q: %v\n%s", exename, err, out) 227 } 228 229 text := string(out) 230 ok := true 231 for _, s := range need { 232 if !strings.Contains(text, s) { 233 t.Errorf("disassembly missing '%s'", s) 234 ok = false 235 } 236 } 237 if goarch == "386" { 238 if strings.Contains(text, "(IP)") { 239 t.Errorf("disassembly contains PC-Relative addressing on 386") 240 ok = false 241 } 242 } 243 244 if !ok || testing.Verbose() { 245 t.Logf("full disassembly:\n%s", text) 246 } 247} 248 249func testGoAndCgoDisasm(t *testing.T, printCode bool, printGnuAsm bool) { 250 t.Parallel() 251 testDisasm(t, "fmthello.go", printCode, printGnuAsm) 252 if testenv.HasCGO() { 253 testDisasm(t, "fmthellocgo.go", printCode, printGnuAsm) 254 } 255} 256 257func TestDisasm(t *testing.T) { 258 testGoAndCgoDisasm(t, false, false) 259} 260 261func TestDisasmCode(t *testing.T) { 262 testGoAndCgoDisasm(t, true, false) 263} 264 265func TestDisasmGnuAsm(t *testing.T) { 266 testGoAndCgoDisasm(t, false, true) 267} 268 269func TestDisasmExtld(t *testing.T) { 270 testenv.MustHaveCGO(t) 271 switch runtime.GOOS { 272 case "plan9": 273 t.Skipf("skipping on %s", runtime.GOOS) 274 } 275 t.Parallel() 276 testDisasm(t, "fmthello.go", false, false, "-ldflags=-linkmode=external") 277} 278 279func TestDisasmPIE(t *testing.T) { 280 if !platform.BuildModeSupported("gc", "pie", runtime.GOOS, runtime.GOARCH) { 281 t.Skipf("skipping on %s/%s, PIE buildmode not supported", runtime.GOOS, runtime.GOARCH) 282 } 283 if !platform.InternalLinkPIESupported(runtime.GOOS, runtime.GOARCH) { 284 // require cgo on platforms that PIE needs external linking 285 testenv.MustHaveCGO(t) 286 } 287 t.Parallel() 288 testDisasm(t, "fmthello.go", false, false, "-buildmode=pie") 289} 290 291func TestDisasmGoobj(t *testing.T) { 292 mustHaveDisasm(t) 293 testenv.MustHaveGoBuild(t) 294 295 tmp := t.TempDir() 296 297 importcfgfile := filepath.Join(tmp, "hello.importcfg") 298 testenv.WriteImportcfg(t, importcfgfile, nil, "testdata/fmthello.go") 299 300 hello := filepath.Join(tmp, "hello.o") 301 args := []string{"tool", "compile", "-p=main", "-importcfg=" + importcfgfile, "-o", hello} 302 args = append(args, "testdata/fmthello.go") 303 out, err := testenv.Command(t, testenv.GoToolPath(t), args...).CombinedOutput() 304 if err != nil { 305 t.Fatalf("go tool compile fmthello.go: %v\n%s", err, out) 306 } 307 need := []string{ 308 "main(SB)", 309 "fmthello.go:6", 310 } 311 312 args = []string{ 313 "-s", "main", 314 hello, 315 } 316 317 out, err = testenv.Command(t, objdumpPath(t), args...).CombinedOutput() 318 if err != nil { 319 t.Fatalf("objdump fmthello.o: %v\n%s", err, out) 320 } 321 322 text := string(out) 323 ok := true 324 for _, s := range need { 325 if !strings.Contains(text, s) { 326 t.Errorf("disassembly missing '%s'", s) 327 ok = false 328 } 329 } 330 if runtime.GOARCH == "386" { 331 if strings.Contains(text, "(IP)") { 332 t.Errorf("disassembly contains PC-Relative addressing on 386") 333 ok = false 334 } 335 } 336 if !ok { 337 t.Logf("full disassembly:\n%s", text) 338 } 339} 340 341func TestGoobjFileNumber(t *testing.T) { 342 // Test that file table in Go object file is parsed correctly. 343 testenv.MustHaveGoBuild(t) 344 mustHaveDisasm(t) 345 346 t.Parallel() 347 348 tmp := t.TempDir() 349 350 obj := filepath.Join(tmp, "p.a") 351 cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-o", obj) 352 cmd.Dir = filepath.Join("testdata/testfilenum") 353 out, err := cmd.CombinedOutput() 354 if err != nil { 355 t.Fatalf("build failed: %v\n%s", err, out) 356 } 357 358 cmd = testenv.Command(t, objdumpPath(t), obj) 359 out, err = cmd.CombinedOutput() 360 if err != nil { 361 t.Fatalf("objdump failed: %v\n%s", err, out) 362 } 363 364 text := string(out) 365 for _, s := range []string{"a.go", "b.go", "c.go"} { 366 if !strings.Contains(text, s) { 367 t.Errorf("output missing '%s'", s) 368 } 369 } 370 371 if t.Failed() { 372 t.Logf("output:\n%s", text) 373 } 374} 375 376func TestGoObjOtherVersion(t *testing.T) { 377 testenv.MustHaveExec(t) 378 t.Parallel() 379 380 obj := filepath.Join("testdata", "go116.o") 381 cmd := testenv.Command(t, objdumpPath(t), obj) 382 out, err := cmd.CombinedOutput() 383 if err == nil { 384 t.Fatalf("objdump go116.o succeeded unexpectedly") 385 } 386 if !strings.Contains(string(out), "go object of a different version") { 387 t.Errorf("unexpected error message:\n%s", out) 388 } 389} 390