Commit bcdbd58c authored by Elias Naur's avatar Elias Naur

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 <elias.naur@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: default avatarCherry Zhang <cherryyz@google.com>
parent 506d6a32
...@@ -19,7 +19,7 @@ import ( ...@@ -19,7 +19,7 @@ import (
"testing" "testing"
) )
func testDWARF(t *testing.T, env ...string) { func testDWARF(t *testing.T, buildmode string, expectDWARF bool, env ...string) {
testenv.MustHaveCGO(t) testenv.MustHaveCGO(t)
testenv.MustHaveGoBuild(t) testenv.MustHaveGoBuild(t)
...@@ -48,7 +48,11 @@ func testDWARF(t *testing.T, env ...string) { ...@@ -48,7 +48,11 @@ func testDWARF(t *testing.T, env ...string) {
t.Run(prog, func(t *testing.T) { t.Run(prog, func(t *testing.T) {
exe := filepath.Join(tmpDir, prog+".exe") exe := filepath.Join(tmpDir, prog+".exe")
dir := "../../runtime/testdata/" + prog 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 { if env != nil {
cmd.Env = append(os.Environ(), env...) cmd.Env = append(os.Environ(), env...)
} }
...@@ -57,6 +61,15 @@ func testDWARF(t *testing.T, env ...string) { ...@@ -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) 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) f, err := objfile.Open(exe)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
...@@ -81,8 +94,15 @@ func testDWARF(t *testing.T, env ...string) { ...@@ -81,8 +94,15 @@ func testDWARF(t *testing.T, env ...string) {
d, err := f.DWARF() d, err := f.DWARF()
if err != nil { if err != nil {
if expectDWARF {
t.Fatal(err) t.Fatal(err)
} }
return
} else {
if !expectDWARF {
t.Fatal("unexpected DWARF section")
}
}
// TODO: We'd like to use filepath.Join here. // TODO: We'd like to use filepath.Join here.
// Also related: golang.org/issue/19784. // Also related: golang.org/issue/19784.
...@@ -128,7 +148,7 @@ func testDWARF(t *testing.T, env ...string) { ...@@ -128,7 +148,7 @@ func testDWARF(t *testing.T, env ...string) {
} }
func TestDWARF(t *testing.T) { func TestDWARF(t *testing.T) {
testDWARF(t) testDWARF(t, "", true)
} }
func TestDWARFiOS(t *testing.T) { func TestDWARFiOS(t *testing.T) {
...@@ -145,6 +165,10 @@ 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) t.Skipf("error running xcrun, required for iOS cross build: %v", err)
} }
cc := "CC=" + runtime.GOROOT() + "/misc/ios/clangwrap.sh" cc := "CC=" + runtime.GOROOT() + "/misc/ios/clangwrap.sh"
testDWARF(t, cc, "CGO_ENABLED=1", "GOOS=darwin", "GOARCH=arm", "GOARM=7") // iOS doesn't allow unmapped segments, so iOS executables don't have DWARF.
testDWARF(t, cc, "CGO_ENABLED=1", "GOOS=darwin", "GOARCH=arm64") 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")
} }
...@@ -1343,14 +1343,17 @@ func (ctxt *Link) hostlink() { ...@@ -1343,14 +1343,17 @@ func (ctxt *Link) hostlink() {
} }
// For os.Rename to work reliably, must be in same directory as outfile. // For os.Rename to work reliably, must be in same directory as outfile.
combinedOutput := *flagOutfile + "~" 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) Exitf("%s: combining dwarf failed: %v", os.Args[0], err)
} }
if !isIOS {
os.Remove(*flagOutfile) os.Remove(*flagOutfile)
if err := os.Rename(combinedOutput, *flagOutfile); err != nil { if err := os.Rename(combinedOutput, *flagOutfile); err != nil {
Exitf("%s: %v", os.Args[0], err) Exitf("%s: %v", os.Args[0], err)
} }
} }
}
} }
// hostlinkArchArgs returns arguments to pass to the external linker // hostlinkArchArgs returns arguments to pass to the external linker
......
...@@ -85,34 +85,55 @@ func (r loadCmdReader) WriteAt(offset int64, data interface{}) error { ...@@ -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 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 // With internal linking, DWARF is embedded into the executable, this lets us do the
// same for external linking. // same for external linking.
// inexe is the path to the executable with no DWARF. It must have enough room in the macho // 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) // header to add the DWARF sections. (Use ld's -headerpad option)
// dsym is the path to the macho file containing DWARF from dsymutil. // dsym is the path to the macho file containing DWARF from dsymutil.
// outexe is the path where the combined executable should be saved. // 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) exef, err := os.Open(inexe)
if err != nil { 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) dwarff, err := os.Open(dsym)
if err != nil { if err != nil {
return err return false, err
} }
outf, err := os.Create(outexe) outf, err := os.Create(outexe)
if err != nil { if err != nil {
return err return false, err
} }
outf.Chmod(0755) outf.Chmod(0755)
exem, err := macho.NewFile(exef)
if err != nil {
return err
}
dwarfm, err := macho.NewFile(dwarff) dwarfm, err := macho.NewFile(dwarff)
if err != nil { if err != nil {
return err return false, err
} }
// The string table needs to be the last thing in the file // 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 { ...@@ -120,19 +141,19 @@ func machoCombineDwarf(inexe, dsym, outexe string, buildmode BuildMode) error {
// linkedit section, but all the others can be copied directly. // linkedit section, but all the others can be copied directly.
linkseg = exem.Segment("__LINKEDIT") linkseg = exem.Segment("__LINKEDIT")
if linkseg == nil { if linkseg == nil {
return fmt.Errorf("missing __LINKEDIT segment") return false, fmt.Errorf("missing __LINKEDIT segment")
} }
if _, err = exef.Seek(0, 0); err != nil { if _, err = exef.Seek(0, 0); err != nil {
return err return false, err
} }
if _, err := io.CopyN(outf, exef, int64(linkseg.Offset)); err != nil { if _, err := io.CopyN(outf, exef, int64(linkseg.Offset)); err != nil {
return err return false, err
} }
realdwarf = dwarfm.Segment("__DWARF") realdwarf = dwarfm.Segment("__DWARF")
if realdwarf == nil { if realdwarf == nil {
return fmt.Errorf("missing __DWARF segment") return false, fmt.Errorf("missing __DWARF segment")
} }
// Now copy the dwarf data into the output. // Now copy the dwarf data into the output.
...@@ -140,71 +161,64 @@ func machoCombineDwarf(inexe, dsym, outexe string, buildmode BuildMode) error { ...@@ -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. // even though we mark this one as being 0 bytes of virtual address space.
dwarfstart = machoCalcStart(realdwarf.Offset, linkseg.Offset, pageAlign) dwarfstart = machoCalcStart(realdwarf.Offset, linkseg.Offset, pageAlign)
if _, err = outf.Seek(dwarfstart, 0); err != nil { 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)) dwarfaddr = int64((linkseg.Addr + linkseg.Memsz + 1<<pageAlign - 1) &^ (1<<pageAlign - 1))
if _, err = dwarff.Seek(int64(realdwarf.Offset), 0); err != nil { 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 { if _, err := io.CopyN(outf, dwarff, int64(realdwarf.Filesz)); err != nil {
return err return false, err
} }
// And finally the linkedit section. // And finally the linkedit section.
if _, err = exef.Seek(int64(linkseg.Offset), 0); err != nil { if _, err = exef.Seek(int64(linkseg.Offset), 0); err != nil {
return err return false, err
} }
linkstart = machoCalcStart(linkseg.Offset, uint64(dwarfstart)+realdwarf.Filesz, pageAlign) linkstart = machoCalcStart(linkseg.Offset, uint64(dwarfstart)+realdwarf.Filesz, pageAlign)
linkoffset = uint32(linkstart - int64(linkseg.Offset)) linkoffset = uint32(linkstart - int64(linkseg.Offset))
if _, err = outf.Seek(linkstart, 0); err != nil { if _, err = outf.Seek(linkstart, 0); err != nil {
return err return false, err
} }
if _, err := io.Copy(outf, exef); err != nil { if _, err := io.Copy(outf, exef); err != nil {
return err return false, err
} }
// Now we need to update the headers. // 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") textsect := exem.Section("__text")
if linkseg == nil { if linkseg == nil {
return fmt.Errorf("missing __text section") return false, fmt.Errorf("missing __text section")
} }
dwarfCmdOffset := int64(cmdOffset) + int64(exem.FileHeader.Cmdsz) dwarfCmdOffset := int64(cmdOffset) + int64(exem.FileHeader.Cmdsz)
availablePadding := int64(textsect.Offset) - dwarfCmdOffset availablePadding := int64(textsect.Offset) - dwarfCmdOffset
if availablePadding < int64(realdwarf.Len) { 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 // First, copy the dwarf load command into the header
if _, err = outf.Seek(dwarfCmdOffset, 0); err != nil { 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 { 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 { 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 { 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 { 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++ { for i := uint32(0); i < exem.Ncmd; i++ {
cmd, err := reader.Next() cmd, err := reader.Next()
if err != nil { if err != nil {
return err return false, err
} }
switch cmd.Cmd { switch cmd.Cmd {
case macho.LoadCmdSegment64: case macho.LoadCmdSegment64:
...@@ -227,10 +241,10 @@ func machoCombineDwarf(inexe, dsym, outexe string, buildmode BuildMode) error { ...@@ -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) err = fmt.Errorf("Unknown load command 0x%x (%s)\n", int(cmd.Cmd), cmd.Cmd)
} }
if err != nil { 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. // machoUpdateSegment updates the load command for a moved segment.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment