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 5package main 6 7import ( 8 "bytes" 9 cmddwarf "cmd/internal/dwarf" 10 "cmd/internal/objfile" 11 "cmd/internal/quoted" 12 "debug/dwarf" 13 "internal/platform" 14 "internal/testenv" 15 "os" 16 "os/exec" 17 "path" 18 "path/filepath" 19 "runtime" 20 "strings" 21 "testing" 22) 23 24// TestMain allows this test binary to run as a -toolexec wrapper for the 'go' 25// command. If LINK_TEST_TOOLEXEC is set, TestMain runs the binary as if it were 26// cmd/link, and otherwise runs the requested tool as a subprocess. 27// 28// This allows the test to verify the behavior of the current contents of the 29// cmd/link package even if the installed cmd/link binary is stale. 30func TestMain(m *testing.M) { 31 if os.Getenv("LINK_TEST_TOOLEXEC") == "" { 32 // Not running as a -toolexec wrapper. Just run the tests. 33 os.Exit(m.Run()) 34 } 35 36 if strings.TrimSuffix(filepath.Base(os.Args[1]), ".exe") == "link" { 37 // Running as a -toolexec linker, and the tool is cmd/link. 38 // Substitute this test binary for the linker. 39 os.Args = os.Args[1:] 40 main() 41 os.Exit(0) 42 } 43 44 cmd := exec.Command(os.Args[1], os.Args[2:]...) 45 cmd.Stdin = os.Stdin 46 cmd.Stdout = os.Stdout 47 cmd.Stderr = os.Stderr 48 if err := cmd.Run(); err != nil { 49 os.Exit(1) 50 } 51 os.Exit(0) 52} 53 54func testDWARF(t *testing.T, buildmode string, expectDWARF bool, env ...string) { 55 testenv.MustHaveCGO(t) 56 testenv.MustHaveGoBuild(t) 57 58 if !platform.ExecutableHasDWARF(runtime.GOOS, runtime.GOARCH) { 59 t.Skipf("skipping on %s/%s: no DWARF symbol table in executables", runtime.GOOS, runtime.GOARCH) 60 } 61 62 t.Parallel() 63 64 for _, prog := range []string{"testprog", "testprogcgo"} { 65 prog := prog 66 expectDWARF := expectDWARF 67 if runtime.GOOS == "aix" && prog == "testprogcgo" { 68 extld := os.Getenv("CC") 69 if extld == "" { 70 extld = "gcc" 71 } 72 extldArgs, err := quoted.Split(extld) 73 if err != nil { 74 t.Fatal(err) 75 } 76 expectDWARF, err = cmddwarf.IsDWARFEnabledOnAIXLd(extldArgs) 77 if err != nil { 78 t.Fatal(err) 79 } 80 } 81 82 t.Run(prog, func(t *testing.T) { 83 t.Parallel() 84 85 tmpDir := t.TempDir() 86 87 exe := filepath.Join(tmpDir, prog+".exe") 88 dir := "../../runtime/testdata/" + prog 89 cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-toolexec", os.Args[0], "-o", exe) 90 if buildmode != "" { 91 cmd.Args = append(cmd.Args, "-buildmode", buildmode) 92 } 93 cmd.Args = append(cmd.Args, dir) 94 cmd.Env = append(os.Environ(), env...) 95 cmd.Env = append(cmd.Env, "CGO_CFLAGS=") // ensure CGO_CFLAGS does not contain any flags. Issue #35459 96 cmd.Env = append(cmd.Env, "LINK_TEST_TOOLEXEC=1") 97 out, err := cmd.CombinedOutput() 98 if err != nil { 99 t.Fatalf("go build -o %v %v: %v\n%s", exe, dir, err, out) 100 } 101 102 if buildmode == "c-archive" { 103 // Extract the archive and use the go.o object within. 104 cmd := testenv.Command(t, "ar", "-x", exe) 105 cmd.Dir = tmpDir 106 if out, err := cmd.CombinedOutput(); err != nil { 107 t.Fatalf("ar -x %s: %v\n%s", exe, err, out) 108 } 109 exe = filepath.Join(tmpDir, "go.o") 110 } 111 112 darwinSymbolTestIsTooFlaky := true // Turn this off, it is too flaky -- See #32218 113 if runtime.GOOS == "darwin" && !darwinSymbolTestIsTooFlaky { 114 if _, err = exec.LookPath("symbols"); err == nil { 115 // Ensure Apple's tooling can parse our object for symbols. 116 out, err = testenv.Command(t, "symbols", exe).CombinedOutput() 117 if err != nil { 118 t.Fatalf("symbols %v: %v: %s", filepath.Base(exe), err, out) 119 } else { 120 if bytes.HasPrefix(out, []byte("Unable to find file")) { 121 // This failure will cause the App Store to reject our binaries. 122 t.Fatalf("symbols %v: failed to parse file", filepath.Base(exe)) 123 } else if bytes.Contains(out, []byte(", Empty]")) { 124 t.Fatalf("symbols %v: parsed as empty", filepath.Base(exe)) 125 } 126 } 127 } 128 } 129 130 f, err := objfile.Open(exe) 131 if err != nil { 132 t.Fatal(err) 133 } 134 defer f.Close() 135 136 syms, err := f.Symbols() 137 if err != nil { 138 t.Fatal(err) 139 } 140 141 var addr uint64 142 for _, sym := range syms { 143 if sym.Name == "main.main" { 144 addr = sym.Addr 145 break 146 } 147 } 148 if addr == 0 { 149 t.Fatal("cannot find main.main in symbols") 150 } 151 152 d, err := f.DWARF() 153 if err != nil { 154 if expectDWARF { 155 t.Fatal(err) 156 } 157 return 158 } else { 159 if !expectDWARF { 160 t.Fatal("unexpected DWARF section") 161 } 162 } 163 164 // TODO: We'd like to use filepath.Join here. 165 // Also related: golang.org/issue/19784. 166 wantFile := path.Join(prog, "main.go") 167 wantLine := 24 168 r := d.Reader() 169 entry, err := r.SeekPC(addr) 170 if err != nil { 171 t.Fatal(err) 172 } 173 lr, err := d.LineReader(entry) 174 if err != nil { 175 t.Fatal(err) 176 } 177 var line dwarf.LineEntry 178 if err := lr.SeekPC(addr, &line); err == dwarf.ErrUnknownPC { 179 t.Fatalf("did not find file:line for %#x (main.main)", addr) 180 } else if err != nil { 181 t.Fatal(err) 182 } 183 if !strings.HasSuffix(line.File.Name, wantFile) || line.Line != wantLine { 184 t.Errorf("%#x is %s:%d, want %s:%d", addr, line.File.Name, line.Line, filepath.Join("...", wantFile), wantLine) 185 } 186 }) 187 } 188} 189 190func TestDWARF(t *testing.T) { 191 testDWARF(t, "", true) 192 if !testing.Short() { 193 if runtime.GOOS == "windows" { 194 t.Skip("skipping Windows/c-archive; see Issue 35512 for more.") 195 } 196 if !platform.BuildModeSupported(runtime.Compiler, "c-archive", runtime.GOOS, runtime.GOARCH) { 197 t.Skipf("skipping c-archive test on unsupported platform %s-%s", runtime.GOOS, runtime.GOARCH) 198 } 199 t.Run("c-archive", func(t *testing.T) { 200 testDWARF(t, "c-archive", true) 201 }) 202 } 203} 204 205func TestDWARFiOS(t *testing.T) { 206 // Normally we run TestDWARF on native platform. But on iOS we don't have 207 // go build, so we do this test with a cross build. 208 // Only run this on darwin/amd64, where we can cross build for iOS. 209 if testing.Short() { 210 t.Skip("skipping in short mode") 211 } 212 if runtime.GOARCH != "amd64" || runtime.GOOS != "darwin" { 213 t.Skip("skipping on non-darwin/amd64 platform") 214 } 215 if err := testenv.Command(t, "xcrun", "--help").Run(); err != nil { 216 t.Skipf("error running xcrun, required for iOS cross build: %v", err) 217 } 218 // Check to see if the ios tools are installed. It's possible to have the command line tools 219 // installed without the iOS sdk. 220 if output, err := testenv.Command(t, "xcodebuild", "-showsdks").CombinedOutput(); err != nil { 221 t.Skipf("error running xcodebuild, required for iOS cross build: %v", err) 222 } else if !strings.Contains(string(output), "iOS SDK") { 223 t.Skipf("iOS SDK not detected.") 224 } 225 cc := "CC=" + runtime.GOROOT() + "/misc/ios/clangwrap.sh" 226 // iOS doesn't allow unmapped segments, so iOS executables don't have DWARF. 227 t.Run("exe", func(t *testing.T) { 228 testDWARF(t, "", false, cc, "CGO_ENABLED=1", "GOOS=ios", "GOARCH=arm64") 229 }) 230 // However, c-archive iOS objects have embedded DWARF. 231 t.Run("c-archive", func(t *testing.T) { 232 testDWARF(t, "c-archive", true, cc, "CGO_ENABLED=1", "GOOS=ios", "GOARCH=arm64") 233 }) 234} 235