Skip to content

Commit bcdbd58

Browse files
author
Elias Naur
committed
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 <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Cherry Zhang <[email protected]>
1 parent 506d6a3 commit bcdbd58

File tree

3 files changed

+88
-47
lines changed

3 files changed

+88
-47
lines changed

src/cmd/link/dwarf_test.go

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import (
1919
"testing"
2020
)
2121

22-
func testDWARF(t *testing.T, env ...string) {
22+
func testDWARF(t *testing.T, buildmode string, expectDWARF bool, env ...string) {
2323
testenv.MustHaveCGO(t)
2424
testenv.MustHaveGoBuild(t)
2525

@@ -48,7 +48,11 @@ func testDWARF(t *testing.T, env ...string) {
4848
t.Run(prog, func(t *testing.T) {
4949
exe := filepath.Join(tmpDir, prog+".exe")
5050
dir := "../../runtime/testdata/" + prog
51-
cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", exe, dir)
51+
cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", exe)
52+
if buildmode != "" {
53+
cmd.Args = append(cmd.Args, "-buildmode", buildmode)
54+
}
55+
cmd.Args = append(cmd.Args, dir)
5256
if env != nil {
5357
cmd.Env = append(os.Environ(), env...)
5458
}
@@ -57,6 +61,15 @@ func testDWARF(t *testing.T, env ...string) {
5761
t.Fatalf("go build -o %v %v: %v\n%s", exe, dir, err, out)
5862
}
5963

64+
if buildmode == "c-archive" {
65+
// Extract the archive and use the go.o object within.
66+
cmd := exec.Command("ar", "-x", exe)
67+
cmd.Dir = tmpDir
68+
if out, err := cmd.CombinedOutput(); err != nil {
69+
t.Fatalf("ar -x %s: %v\n%s", exe, err, out)
70+
}
71+
exe = filepath.Join(tmpDir, "go.o")
72+
}
6073
f, err := objfile.Open(exe)
6174
if err != nil {
6275
t.Fatal(err)
@@ -81,7 +94,14 @@ func testDWARF(t *testing.T, env ...string) {
8194

8295
d, err := f.DWARF()
8396
if err != nil {
84-
t.Fatal(err)
97+
if expectDWARF {
98+
t.Fatal(err)
99+
}
100+
return
101+
} else {
102+
if !expectDWARF {
103+
t.Fatal("unexpected DWARF section")
104+
}
85105
}
86106

87107
// TODO: We'd like to use filepath.Join here.
@@ -128,7 +148,7 @@ func testDWARF(t *testing.T, env ...string) {
128148
}
129149

130150
func TestDWARF(t *testing.T) {
131-
testDWARF(t)
151+
testDWARF(t, "", true)
132152
}
133153

134154
func TestDWARFiOS(t *testing.T) {
@@ -145,6 +165,10 @@ func TestDWARFiOS(t *testing.T) {
145165
t.Skipf("error running xcrun, required for iOS cross build: %v", err)
146166
}
147167
cc := "CC=" + runtime.GOROOT() + "/misc/ios/clangwrap.sh"
148-
testDWARF(t, cc, "CGO_ENABLED=1", "GOOS=darwin", "GOARCH=arm", "GOARM=7")
149-
testDWARF(t, cc, "CGO_ENABLED=1", "GOOS=darwin", "GOARCH=arm64")
168+
// iOS doesn't allow unmapped segments, so iOS executables don't have DWARF.
169+
testDWARF(t, "", false, cc, "CGO_ENABLED=1", "GOOS=darwin", "GOARCH=arm", "GOARM=7")
170+
testDWARF(t, "", false, cc, "CGO_ENABLED=1", "GOOS=darwin", "GOARCH=arm64")
171+
// However, c-archive iOS objects have embedded DWARF.
172+
testDWARF(t, "c-archive", true, cc, "CGO_ENABLED=1", "GOOS=darwin", "GOARCH=arm", "GOARM=7")
173+
testDWARF(t, "c-archive", true, cc, "CGO_ENABLED=1", "GOOS=darwin", "GOARCH=arm64")
150174
}

src/cmd/link/internal/ld/lib.go

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1343,12 +1343,15 @@ func (ctxt *Link) hostlink() {
13431343
}
13441344
// For os.Rename to work reliably, must be in same directory as outfile.
13451345
combinedOutput := *flagOutfile + "~"
1346-
if err := machoCombineDwarf(*flagOutfile, dsym, combinedOutput, ctxt.BuildMode); err != nil {
1346+
isIOS, err := machoCombineDwarf(*flagOutfile, dsym, combinedOutput, ctxt.BuildMode)
1347+
if err != nil {
13471348
Exitf("%s: combining dwarf failed: %v", os.Args[0], err)
13481349
}
1349-
os.Remove(*flagOutfile)
1350-
if err := os.Rename(combinedOutput, *flagOutfile); err != nil {
1351-
Exitf("%s: %v", os.Args[0], err)
1350+
if !isIOS {
1351+
os.Remove(*flagOutfile)
1352+
if err := os.Rename(combinedOutput, *flagOutfile); err != nil {
1353+
Exitf("%s: %v", os.Args[0], err)
1354+
}
13521355
}
13531356
}
13541357
}

src/cmd/link/internal/ld/macho_combine_dwarf.go

Lines changed: 51 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -85,126 +85,140 @@ func (r loadCmdReader) WriteAt(offset int64, data interface{}) error {
8585
}
8686

8787
// machoCombineDwarf merges dwarf info generated by dsymutil into a macho executable.
88+
// machoCombineDwarf returns true and skips merging if the input executable is for iOS.
89+
//
8890
// With internal linking, DWARF is embedded into the executable, this lets us do the
8991
// same for external linking.
9092
// inexe is the path to the executable with no DWARF. It must have enough room in the macho
9193
// header to add the DWARF sections. (Use ld's -headerpad option)
9294
// dsym is the path to the macho file containing DWARF from dsymutil.
9395
// outexe is the path where the combined executable should be saved.
94-
func machoCombineDwarf(inexe, dsym, outexe string, buildmode BuildMode) error {
96+
func machoCombineDwarf(inexe, dsym, outexe string, buildmode BuildMode) (bool, error) {
9597
exef, err := os.Open(inexe)
9698
if err != nil {
97-
return err
99+
return false, err
100+
}
101+
exem, err := macho.NewFile(exef)
102+
if err != nil {
103+
return false, err
104+
}
105+
cmdOffset := unsafe.Sizeof(exem.FileHeader)
106+
is64bit := exem.Magic == macho.Magic64
107+
if is64bit {
108+
// mach_header_64 has one extra uint32.
109+
cmdOffset += unsafe.Sizeof(exem.Magic)
110+
}
111+
// Check for LC_VERSION_MIN_IPHONEOS.
112+
reader := loadCmdReader{next: int64(cmdOffset), f: exef, order: exem.ByteOrder}
113+
for i := uint32(0); i < exem.Ncmd; i++ {
114+
cmd, err := reader.Next()
115+
if err != nil {
116+
return false, err
117+
}
118+
if cmd.Cmd == LC_VERSION_MIN_IPHONEOS {
119+
// The executable is for iOS, which doesn't support unmapped
120+
// segments such as our __DWARF segment. Skip combining.
121+
return true, nil
122+
}
98123
}
99124
dwarff, err := os.Open(dsym)
100125
if err != nil {
101-
return err
126+
return false, err
102127
}
103128
outf, err := os.Create(outexe)
104129
if err != nil {
105-
return err
130+
return false, err
106131
}
107132
outf.Chmod(0755)
108133

109-
exem, err := macho.NewFile(exef)
110-
if err != nil {
111-
return err
112-
}
113134
dwarfm, err := macho.NewFile(dwarff)
114135
if err != nil {
115-
return err
136+
return false, err
116137
}
117138

118139
// The string table needs to be the last thing in the file
119140
// for code signing to work. So we'll need to move the
120141
// linkedit section, but all the others can be copied directly.
121142
linkseg = exem.Segment("__LINKEDIT")
122143
if linkseg == nil {
123-
return fmt.Errorf("missing __LINKEDIT segment")
144+
return false, fmt.Errorf("missing __LINKEDIT segment")
124145
}
125146

126147
if _, err = exef.Seek(0, 0); err != nil {
127-
return err
148+
return false, err
128149
}
129150
if _, err := io.CopyN(outf, exef, int64(linkseg.Offset)); err != nil {
130-
return err
151+
return false, err
131152
}
132153

133154
realdwarf = dwarfm.Segment("__DWARF")
134155
if realdwarf == nil {
135-
return fmt.Errorf("missing __DWARF segment")
156+
return false, fmt.Errorf("missing __DWARF segment")
136157
}
137158

138159
// Now copy the dwarf data into the output.
139160
// Kernel requires all loaded segments to be page-aligned in the file,
140161
// even though we mark this one as being 0 bytes of virtual address space.
141162
dwarfstart = machoCalcStart(realdwarf.Offset, linkseg.Offset, pageAlign)
142163
if _, err = outf.Seek(dwarfstart, 0); err != nil {
143-
return err
164+
return false, err
144165
}
145166
dwarfaddr = int64((linkseg.Addr + linkseg.Memsz + 1<<pageAlign - 1) &^ (1<<pageAlign - 1))
146167

147168
if _, err = dwarff.Seek(int64(realdwarf.Offset), 0); err != nil {
148-
return err
169+
return false, err
149170
}
150171
if _, err := io.CopyN(outf, dwarff, int64(realdwarf.Filesz)); err != nil {
151-
return err
172+
return false, err
152173
}
153174

154175
// And finally the linkedit section.
155176
if _, err = exef.Seek(int64(linkseg.Offset), 0); err != nil {
156-
return err
177+
return false, err
157178
}
158179
linkstart = machoCalcStart(linkseg.Offset, uint64(dwarfstart)+realdwarf.Filesz, pageAlign)
159180
linkoffset = uint32(linkstart - int64(linkseg.Offset))
160181
if _, err = outf.Seek(linkstart, 0); err != nil {
161-
return err
182+
return false, err
162183
}
163184
if _, err := io.Copy(outf, exef); err != nil {
164-
return err
185+
return false, err
165186
}
166187

167188
// Now we need to update the headers.
168-
cmdOffset := unsafe.Sizeof(exem.FileHeader)
169-
is64bit := exem.Magic == macho.Magic64
170-
if is64bit {
171-
// mach_header_64 has one extra uint32.
172-
cmdOffset += unsafe.Sizeof(exem.Magic)
173-
}
174-
175189
textsect := exem.Section("__text")
176190
if linkseg == nil {
177-
return fmt.Errorf("missing __text section")
191+
return false, fmt.Errorf("missing __text section")
178192
}
179193

180194
dwarfCmdOffset := int64(cmdOffset) + int64(exem.FileHeader.Cmdsz)
181195
availablePadding := int64(textsect.Offset) - dwarfCmdOffset
182196
if availablePadding < int64(realdwarf.Len) {
183-
return fmt.Errorf("No room to add dwarf info. Need at least %d padding bytes, found %d", realdwarf.Len, availablePadding)
197+
return false, fmt.Errorf("No room to add dwarf info. Need at least %d padding bytes, found %d", realdwarf.Len, availablePadding)
184198
}
185199
// First, copy the dwarf load command into the header
186200
if _, err = outf.Seek(dwarfCmdOffset, 0); err != nil {
187-
return err
201+
return false, err
188202
}
189203
if _, err := io.CopyN(outf, bytes.NewReader(realdwarf.Raw()), int64(realdwarf.Len)); err != nil {
190-
return err
204+
return false, err
191205
}
192206

193207
if _, err = outf.Seek(int64(unsafe.Offsetof(exem.FileHeader.Ncmd)), 0); err != nil {
194-
return err
208+
return false, err
195209
}
196210
if err = binary.Write(outf, exem.ByteOrder, exem.Ncmd+1); err != nil {
197-
return err
211+
return false, err
198212
}
199213
if err = binary.Write(outf, exem.ByteOrder, exem.Cmdsz+realdwarf.Len); err != nil {
200-
return err
214+
return false, err
201215
}
202216

203-
reader := loadCmdReader{next: int64(cmdOffset), f: outf, order: exem.ByteOrder}
217+
reader = loadCmdReader{next: int64(cmdOffset), f: outf, order: exem.ByteOrder}
204218
for i := uint32(0); i < exem.Ncmd; i++ {
205219
cmd, err := reader.Next()
206220
if err != nil {
207-
return err
221+
return false, err
208222
}
209223
switch cmd.Cmd {
210224
case macho.LoadCmdSegment64:
@@ -227,10 +241,10 @@ func machoCombineDwarf(inexe, dsym, outexe string, buildmode BuildMode) error {
227241
err = fmt.Errorf("Unknown load command 0x%x (%s)\n", int(cmd.Cmd), cmd.Cmd)
228242
}
229243
if err != nil {
230-
return err
244+
return false, err
231245
}
232246
}
233-
return machoUpdateDwarfHeader(&reader, buildmode)
247+
return false, machoUpdateDwarfHeader(&reader, buildmode)
234248
}
235249

236250
// machoUpdateSegment updates the load command for a moved segment.

0 commit comments

Comments
 (0)