]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/link/internal/ld: skip DWARF combining for iOS binaries
authorElias Naur <elias.naur@gmail.com>
Thu, 3 May 2018 13:38:37 +0000 (15:38 +0200)
committerElias Naur <elias.naur@gmail.com>
Fri, 4 May 2018 15:39:02 +0000 (15:39 +0000)
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 <elias.naur@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Cherry Zhang <cherryyz@google.com>
src/cmd/link/dwarf_test.go
src/cmd/link/internal/ld/lib.go
src/cmd/link/internal/ld/macho_combine_dwarf.go

index 208664a61276c99b181147093aa56fe5851571ed..ff11689bbccc0e176f07fa02c13aaf6f60a6198c 100644 (file)
@@ -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")
 }
index 9b439008f1451a5db822d98ac1f860800a3f1147..dd4d65c0cafa31fff36d4605417220c8ae16d30b 100644 (file)
@@ -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)
+                       }
                }
        }
 }
index 17a484ce8f16ff7bf43bd0f323c9c06a54ae5c02..82228faa8d1a27cde05c34cd0e1d6391d517de61 100644 (file)
@@ -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<<pageAlign - 1) &^ (1<<pageAlign - 1))
 
        if _, err = dwarff.Seek(int64(realdwarf.Offset), 0); err != nil {
-               return err
+               return false, err
        }
        if _, err := io.CopyN(outf, dwarff, int64(realdwarf.Filesz)); err != nil {
-               return err
+               return false, err
        }
 
        // And finally the linkedit section.
        if _, err = exef.Seek(int64(linkseg.Offset), 0); err != nil {
-               return err
+               return false, err
        }
        linkstart = machoCalcStart(linkseg.Offset, uint64(dwarfstart)+realdwarf.Filesz, pageAlign)
        linkoffset = uint32(linkstart - int64(linkseg.Offset))
        if _, err = outf.Seek(linkstart, 0); err != nil {
-               return err
+               return false, err
        }
        if _, err := io.Copy(outf, exef); err != nil {
-               return err
+               return false, err
        }
 
        // Now we need to update the headers.
-       cmdOffset := unsafe.Sizeof(exem.FileHeader)
-       is64bit := exem.Magic == macho.Magic64
-       if is64bit {
-               // mach_header_64 has one extra uint32.
-               cmdOffset += unsafe.Sizeof(exem.Magic)
-       }
-
        textsect := exem.Section("__text")
        if linkseg == nil {
-               return fmt.Errorf("missing __text section")
+               return false, fmt.Errorf("missing __text section")
        }
 
        dwarfCmdOffset := int64(cmdOffset) + int64(exem.FileHeader.Cmdsz)
        availablePadding := int64(textsect.Offset) - dwarfCmdOffset
        if availablePadding < int64(realdwarf.Len) {
-               return fmt.Errorf("No room to add dwarf info. Need at least %d padding bytes, found %d", realdwarf.Len, availablePadding)
+               return false, fmt.Errorf("No room to add dwarf info. Need at least %d padding bytes, found %d", realdwarf.Len, availablePadding)
        }
        // First, copy the dwarf load command into the header
        if _, err = outf.Seek(dwarfCmdOffset, 0); err != nil {
-               return err
+               return false, err
        }
        if _, err := io.CopyN(outf, bytes.NewReader(realdwarf.Raw()), int64(realdwarf.Len)); err != nil {
-               return err
+               return false, err
        }
 
        if _, err = outf.Seek(int64(unsafe.Offsetof(exem.FileHeader.Ncmd)), 0); err != nil {
-               return err
+               return false, err
        }
        if err = binary.Write(outf, exem.ByteOrder, exem.Ncmd+1); err != nil {
-               return err
+               return false, err
        }
        if err = binary.Write(outf, exem.ByteOrder, exem.Cmdsz+realdwarf.Len); err != nil {
-               return err
+               return false, err
        }
 
-       reader := loadCmdReader{next: int64(cmdOffset), f: outf, order: exem.ByteOrder}
+       reader = loadCmdReader{next: int64(cmdOffset), f: outf, order: exem.ByteOrder}
        for i := uint32(0); i < exem.Ncmd; i++ {
                cmd, err := reader.Next()
                if err != nil {
-                       return err
+                       return false, err
                }
                switch cmd.Cmd {
                case macho.LoadCmdSegment64:
@@ -227,10 +241,10 @@ func machoCombineDwarf(inexe, dsym, outexe string, buildmode BuildMode) error {
                        err = fmt.Errorf("Unknown load command 0x%x (%s)\n", int(cmd.Cmd), cmd.Cmd)
                }
                if err != nil {
-                       return err
+                       return false, err
                }
        }
-       return machoUpdateDwarfHeader(&reader, buildmode)
+       return false, machoUpdateDwarfHeader(&reader, buildmode)
 }
 
 // machoUpdateSegment updates the load command for a moved segment.