mirror of https://github.com/golang/go.git
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: Cherry Zhang <cherryyz@google.com>
This commit is contained in:
parent
506d6a32ce
commit
bcdbd58ce4
|
|
@ -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.
|
||||
|
|
|
|||
Loading…
Reference in New Issue