diff --git a/src/cmd/link/dwarf_test.go b/src/cmd/link/dwarf_test.go index 208664a6127..ff11689bbcc 100644 --- a/src/cmd/link/dwarf_test.go +++ b/src/cmd/link/dwarf_test.go @@ -19,7 +19,7 @@ import ( "testing" ) -func testDWARF(t *testing.T, env ...string) { +func testDWARF(t *testing.T, buildmode string, expectDWARF bool, env ...string) { testenv.MustHaveCGO(t) testenv.MustHaveGoBuild(t) @@ -48,7 +48,11 @@ func testDWARF(t *testing.T, env ...string) { t.Run(prog, func(t *testing.T) { exe := filepath.Join(tmpDir, prog+".exe") dir := "../../runtime/testdata/" + prog - cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", exe, dir) + cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", exe) + if buildmode != "" { + cmd.Args = append(cmd.Args, "-buildmode", buildmode) + } + cmd.Args = append(cmd.Args, dir) if env != nil { cmd.Env = append(os.Environ(), env...) } @@ -57,6 +61,15 @@ func testDWARF(t *testing.T, env ...string) { t.Fatalf("go build -o %v %v: %v\n%s", exe, dir, err, out) } + if buildmode == "c-archive" { + // Extract the archive and use the go.o object within. + cmd := exec.Command("ar", "-x", exe) + cmd.Dir = tmpDir + if out, err := cmd.CombinedOutput(); err != nil { + t.Fatalf("ar -x %s: %v\n%s", exe, err, out) + } + exe = filepath.Join(tmpDir, "go.o") + } f, err := objfile.Open(exe) if err != nil { t.Fatal(err) @@ -81,7 +94,14 @@ func testDWARF(t *testing.T, env ...string) { d, err := f.DWARF() if err != nil { - t.Fatal(err) + if expectDWARF { + t.Fatal(err) + } + return + } else { + if !expectDWARF { + t.Fatal("unexpected DWARF section") + } } // TODO: We'd like to use filepath.Join here. @@ -128,7 +148,7 @@ func testDWARF(t *testing.T, env ...string) { } func TestDWARF(t *testing.T) { - testDWARF(t) + testDWARF(t, "", true) } func TestDWARFiOS(t *testing.T) { @@ -145,6 +165,10 @@ func TestDWARFiOS(t *testing.T) { t.Skipf("error running xcrun, required for iOS cross build: %v", err) } cc := "CC=" + runtime.GOROOT() + "/misc/ios/clangwrap.sh" - testDWARF(t, cc, "CGO_ENABLED=1", "GOOS=darwin", "GOARCH=arm", "GOARM=7") - testDWARF(t, cc, "CGO_ENABLED=1", "GOOS=darwin", "GOARCH=arm64") + // iOS doesn't allow unmapped segments, so iOS executables don't have DWARF. + testDWARF(t, "", false, cc, "CGO_ENABLED=1", "GOOS=darwin", "GOARCH=arm", "GOARM=7") + testDWARF(t, "", false, cc, "CGO_ENABLED=1", "GOOS=darwin", "GOARCH=arm64") + // However, c-archive iOS objects have embedded DWARF. + testDWARF(t, "c-archive", true, cc, "CGO_ENABLED=1", "GOOS=darwin", "GOARCH=arm", "GOARM=7") + testDWARF(t, "c-archive", true, cc, "CGO_ENABLED=1", "GOOS=darwin", "GOARCH=arm64") } diff --git a/src/cmd/link/internal/ld/lib.go b/src/cmd/link/internal/ld/lib.go index 9b439008f14..dd4d65c0caf 100644 --- a/src/cmd/link/internal/ld/lib.go +++ b/src/cmd/link/internal/ld/lib.go @@ -1343,12 +1343,15 @@ func (ctxt *Link) hostlink() { } // For os.Rename to work reliably, must be in same directory as outfile. combinedOutput := *flagOutfile + "~" - if err := machoCombineDwarf(*flagOutfile, dsym, combinedOutput, ctxt.BuildMode); err != nil { + isIOS, err := machoCombineDwarf(*flagOutfile, dsym, combinedOutput, ctxt.BuildMode) + if err != nil { Exitf("%s: combining dwarf failed: %v", os.Args[0], err) } - os.Remove(*flagOutfile) - if err := os.Rename(combinedOutput, *flagOutfile); err != nil { - Exitf("%s: %v", os.Args[0], err) + if !isIOS { + os.Remove(*flagOutfile) + if err := os.Rename(combinedOutput, *flagOutfile); err != nil { + Exitf("%s: %v", os.Args[0], err) + } } } } diff --git a/src/cmd/link/internal/ld/macho_combine_dwarf.go b/src/cmd/link/internal/ld/macho_combine_dwarf.go index 17a484ce8f1..82228faa8d1 100644 --- a/src/cmd/link/internal/ld/macho_combine_dwarf.go +++ b/src/cmd/link/internal/ld/macho_combine_dwarf.go @@ -85,34 +85,55 @@ func (r loadCmdReader) WriteAt(offset int64, data interface{}) error { } // machoCombineDwarf merges dwarf info generated by dsymutil into a macho executable. +// machoCombineDwarf returns true and skips merging if the input executable is for iOS. +// // With internal linking, DWARF is embedded into the executable, this lets us do the // same for external linking. // inexe is the path to the executable with no DWARF. It must have enough room in the macho // header to add the DWARF sections. (Use ld's -headerpad option) // dsym is the path to the macho file containing DWARF from dsymutil. // outexe is the path where the combined executable should be saved. -func machoCombineDwarf(inexe, dsym, outexe string, buildmode BuildMode) error { +func machoCombineDwarf(inexe, dsym, outexe string, buildmode BuildMode) (bool, error) { exef, err := os.Open(inexe) if err != nil { - return err + return false, err + } + exem, err := macho.NewFile(exef) + if err != nil { + return false, err + } + cmdOffset := unsafe.Sizeof(exem.FileHeader) + is64bit := exem.Magic == macho.Magic64 + if is64bit { + // mach_header_64 has one extra uint32. + cmdOffset += unsafe.Sizeof(exem.Magic) + } + // Check for LC_VERSION_MIN_IPHONEOS. + reader := loadCmdReader{next: int64(cmdOffset), f: exef, order: exem.ByteOrder} + for i := uint32(0); i < exem.Ncmd; i++ { + cmd, err := reader.Next() + if err != nil { + return false, err + } + if cmd.Cmd == LC_VERSION_MIN_IPHONEOS { + // The executable is for iOS, which doesn't support unmapped + // segments such as our __DWARF segment. Skip combining. + return true, nil + } } dwarff, err := os.Open(dsym) if err != nil { - return err + return false, err } outf, err := os.Create(outexe) if err != nil { - return err + return false, err } outf.Chmod(0755) - exem, err := macho.NewFile(exef) - if err != nil { - return err - } dwarfm, err := macho.NewFile(dwarff) if err != nil { - return err + return false, err } // The string table needs to be the last thing in the file @@ -120,19 +141,19 @@ func machoCombineDwarf(inexe, dsym, outexe string, buildmode BuildMode) error { // linkedit section, but all the others can be copied directly. linkseg = exem.Segment("__LINKEDIT") if linkseg == nil { - return fmt.Errorf("missing __LINKEDIT segment") + return false, fmt.Errorf("missing __LINKEDIT segment") } if _, err = exef.Seek(0, 0); err != nil { - return err + return false, err } if _, err := io.CopyN(outf, exef, int64(linkseg.Offset)); err != nil { - return err + return false, err } realdwarf = dwarfm.Segment("__DWARF") if realdwarf == nil { - return fmt.Errorf("missing __DWARF segment") + return false, fmt.Errorf("missing __DWARF segment") } // Now copy the dwarf data into the output. @@ -140,71 +161,64 @@ func machoCombineDwarf(inexe, dsym, outexe string, buildmode BuildMode) error { // even though we mark this one as being 0 bytes of virtual address space. dwarfstart = machoCalcStart(realdwarf.Offset, linkseg.Offset, pageAlign) if _, err = outf.Seek(dwarfstart, 0); err != nil { - return err + return false, err } dwarfaddr = int64((linkseg.Addr + linkseg.Memsz + 1<