From bcdbd58ce48d71cb912238caaa193d832901e227 Mon Sep 17 00:00:00 2001 From: Elias Naur Date: Thu, 3 May 2018 15:38:37 +0200 Subject: [PATCH] cmd/link/internal/ld: skip DWARF combining for iOS binaries The macOS and iOS external linker strips DWARF information from binaries because it assumes the information will go into separate DWARF information .dSYM files. To preserve the embedded debugging information, the Go linker re-combines the separate DWARF information into the unmapped __DWARF segment of the final executable. However, the iOS dyld linker does not allow unmapped segments, so use the presence of the LC_VERSION_MIN_IPHONEOS linker command to skip DWARF combining. Note that we can't use GOARCH for detection since the iOS emulator runs on GOARCH=386 and GOARCH=amd64 and we will run into https://golang.org/issues/25148. Updates #25148. Change-Id: I29a1bc468fdee74ab3b27c46931501a0a8120c66 Reviewed-on: https://go-review.googlesource.com/111275 Run-TryBot: Elias Naur TryBot-Result: Gobot Gobot Reviewed-by: Cherry Zhang --- src/cmd/link/dwarf_test.go | 36 ++++++-- src/cmd/link/internal/ld/lib.go | 11 ++- .../link/internal/ld/macho_combine_dwarf.go | 88 +++++++++++-------- 3 files changed, 88 insertions(+), 47 deletions(-) diff --git a/src/cmd/link/dwarf_test.go b/src/cmd/link/dwarf_test.go index 208664a612..ff11689bbc 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 9b439008f1..dd4d65c0ca 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 17a484ce8f..82228faa8d 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<