"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)
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...)
}
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)
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.
}
func TestDWARF(t *testing.T) {
- testDWARF(t)
+ testDWARF(t, "", true)
}
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")
}
}
// 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
// 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.
// 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:
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.