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: 's avatarCherry Zhang <cherryyz@google.com>
parent 506d6a32
......@@ -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")
}
......@@ -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)
}
}
}
}
......
......@@ -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.
......
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