Skip to content

Commit 0fe1986

Browse files
committed
cmd/link/internal/ld: extract Mach-O load command parsing
We're going to need the ability to extract the LC_VERSION_MIN_* and LC_BUILD_VERSION load commands. This CL adds peekMachoPlatform to do that and in the process simplifies machoCombineDwarf. While here, disable DWARF combining for Apple platforms other than macOS (watchOS, tvOS, bridgeOS), not just iOS. Updates #22395 Change-Id: I4862b0f15ccc87b7be1a6532b4d37b47c8f7f243 Reviewed-on: https://go-review.googlesource.com/c/go/+/168459 Reviewed-by: Ian Lance Taylor <[email protected]>
1 parent ba96564 commit 0fe1986

File tree

3 files changed

+109
-58
lines changed

3 files changed

+109
-58
lines changed

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

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ import (
4444
"cmd/link/internal/sym"
4545
"crypto/sha1"
4646
"debug/elf"
47+
"debug/macho"
4748
"encoding/base64"
4849
"encoding/binary"
4950
"encoding/hex"
@@ -1449,11 +1450,24 @@ func (ctxt *Link) hostlink() {
14491450
}
14501451
// For os.Rename to work reliably, must be in same directory as outfile.
14511452
combinedOutput := *flagOutfile + "~"
1452-
isIOS, err := machoCombineDwarf(ctxt, *flagOutfile, dsym, combinedOutput)
1453+
exef, err := os.Open(*flagOutfile)
14531454
if err != nil {
14541455
Exitf("%s: combining dwarf failed: %v", os.Args[0], err)
14551456
}
1456-
if !isIOS {
1457+
defer exef.Close()
1458+
exem, err := macho.NewFile(exef)
1459+
if err != nil {
1460+
Exitf("%s: parsing Mach-O header failed: %v", os.Args[0], err)
1461+
}
1462+
load, err := peekMachoPlatform(exem)
1463+
if err != nil {
1464+
Exitf("%s: failed to parse Mach-O load commands: %v", os.Args[0], err)
1465+
}
1466+
// Only macOS supports unmapped segments such as our __DWARF segment.
1467+
if load == nil || load.platform == PLATFORM_MACOS {
1468+
if err := machoCombineDwarf(ctxt, exef, exem, dsym, combinedOutput); err != nil {
1469+
Exitf("%s: combining dwarf failed: %v", os.Args[0], err)
1470+
}
14571471
os.Remove(*flagOutfile)
14581472
if err := os.Rename(combinedOutput, *flagOutfile); err != nil {
14591473
Exitf("%s: %v", os.Args[0], err)

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

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,12 @@
55
package ld
66

77
import (
8+
"bytes"
89
"cmd/internal/objabi"
910
"cmd/internal/sys"
1011
"cmd/link/internal/sym"
12+
"debug/macho"
13+
"encoding/binary"
1114
"sort"
1215
"strings"
1316
)
@@ -45,11 +48,20 @@ type MachoSeg struct {
4548
flag uint32
4649
}
4750

51+
// MachoPlatformLoad represents a LC_VERSION_MIN_* or
52+
// LC_BUILD_VERSION load command.
53+
type MachoPlatformLoad struct {
54+
platform MachoPlatform // One of PLATFORM_* constants.
55+
cmd MachoLoad
56+
}
57+
4858
type MachoLoad struct {
4959
type_ uint32
5060
data []uint32
5161
}
5262

63+
type MachoPlatform int
64+
5365
/*
5466
* Total amount of space to reserve at the start of the file
5567
* for Header, PHeaders, and SHeaders.
@@ -167,6 +179,14 @@ const (
167179
S_ATTR_SOME_INSTRUCTIONS = 0x00000400
168180
)
169181

182+
const (
183+
PLATFORM_MACOS MachoPlatform = 1
184+
PLATFORM_IOS MachoPlatform = 2
185+
PLATFORM_TVOS MachoPlatform = 3
186+
PLATFORM_WATCHOS MachoPlatform = 4
187+
PLATFORM_BRIDGEOS MachoPlatform = 5
188+
)
189+
170190
// Mach-O file writing
171191
// https://developer.apple.com/mac/library/DOCUMENTATION/DeveloperTools/Conceptual/MachORuntime/Reference/reference.html
172192

@@ -996,3 +1016,41 @@ func Machoemitreloc(ctxt *Link) {
9961016
machorelocsect(ctxt, sect, dwarfp)
9971017
}
9981018
}
1019+
1020+
// peekMachoPlatform returns the first LC_VERSION_MIN_* or LC_BUILD_VERSION
1021+
// load command found in the Mach-O file, if any.
1022+
func peekMachoPlatform(m *macho.File) (*MachoPlatformLoad, error) {
1023+
for _, cmd := range m.Loads {
1024+
raw := cmd.Raw()
1025+
ml := MachoLoad{
1026+
type_: m.ByteOrder.Uint32(raw),
1027+
}
1028+
// Skip the type and command length.
1029+
data := raw[8:]
1030+
var p MachoPlatform
1031+
switch ml.type_ {
1032+
case LC_VERSION_MIN_IPHONEOS:
1033+
p = PLATFORM_IOS
1034+
case LC_VERSION_MIN_MACOSX:
1035+
p = PLATFORM_MACOS
1036+
case LC_VERSION_MIN_WATCHOS:
1037+
p = PLATFORM_WATCHOS
1038+
case LC_VERSION_MIN_TVOS:
1039+
p = PLATFORM_TVOS
1040+
case LC_BUILD_VERSION:
1041+
p = MachoPlatform(m.ByteOrder.Uint32(data))
1042+
default:
1043+
continue
1044+
}
1045+
ml.data = make([]uint32, len(data)/4)
1046+
r := bytes.NewReader(data)
1047+
if err := binary.Read(r, m.ByteOrder, &ml.data); err != nil {
1048+
return nil, err
1049+
}
1050+
return &MachoPlatformLoad{
1051+
platform: p,
1052+
cmd: ml,
1053+
}, nil
1054+
}
1055+
return nil, nil
1056+
}

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

Lines changed: 35 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -86,95 +86,68 @@ func (r loadCmdReader) WriteAt(offset int64, data interface{}) error {
8686
}
8787

8888
// machoCombineDwarf merges dwarf info generated by dsymutil into a macho executable.
89-
// machoCombineDwarf returns true and skips merging if the input executable is for iOS.
9089
//
9190
// With internal linking, DWARF is embedded into the executable, this lets us do the
9291
// same for external linking.
93-
// inexe is the path to the executable with no DWARF. It must have enough room in the macho
92+
// exef is the file of the executable with no DWARF. It must have enough room in the macho
9493
// header to add the DWARF sections. (Use ld's -headerpad option)
94+
// exem is the macho representation of exef.
9595
// dsym is the path to the macho file containing DWARF from dsymutil.
9696
// outexe is the path where the combined executable should be saved.
97-
func machoCombineDwarf(ctxt *Link, inexe, dsym, outexe string) (bool, error) {
98-
exef, err := os.Open(inexe)
99-
if err != nil {
100-
return false, err
101-
}
102-
exem, err := macho.NewFile(exef)
103-
if err != nil {
104-
return false, err
105-
}
106-
cmdOffset := unsafe.Sizeof(exem.FileHeader)
107-
is64bit := exem.Magic == macho.Magic64
108-
if is64bit {
109-
// mach_header_64 has one extra uint32.
110-
cmdOffset += unsafe.Sizeof(exem.Magic)
111-
}
112-
// Check for LC_VERSION_MIN_IPHONEOS.
113-
reader := loadCmdReader{next: int64(cmdOffset), f: exef, order: exem.ByteOrder}
114-
for i := uint32(0); i < exem.Ncmd; i++ {
115-
cmd, err := reader.Next()
116-
if err != nil {
117-
return false, err
118-
}
119-
if cmd.Cmd == LC_VERSION_MIN_IPHONEOS {
120-
// The executable is for iOS, which doesn't support unmapped
121-
// segments such as our __DWARF segment. Skip combining.
122-
return true, nil
123-
}
124-
}
97+
func machoCombineDwarf(ctxt *Link, exef *os.File, exem *macho.File, dsym, outexe string) error {
12598
dwarff, err := os.Open(dsym)
12699
if err != nil {
127-
return false, err
100+
return err
128101
}
129102
outf, err := os.Create(outexe)
130103
if err != nil {
131-
return false, err
104+
return err
132105
}
133106
outf.Chmod(0755)
134107

135108
dwarfm, err := macho.NewFile(dwarff)
136109
if err != nil {
137-
return false, err
110+
return err
138111
}
139112

140113
// The string table needs to be the last thing in the file
141114
// for code signing to work. So we'll need to move the
142115
// linkedit section, but all the others can be copied directly.
143116
linkseg = exem.Segment("__LINKEDIT")
144117
if linkseg == nil {
145-
return false, fmt.Errorf("missing __LINKEDIT segment")
118+
return fmt.Errorf("missing __LINKEDIT segment")
146119
}
147120

148121
if _, err = exef.Seek(0, 0); err != nil {
149-
return false, err
122+
return err
150123
}
151124
if _, err := io.CopyN(outf, exef, int64(linkseg.Offset)); err != nil {
152-
return false, err
125+
return err
153126
}
154127

155128
realdwarf = dwarfm.Segment("__DWARF")
156129
if realdwarf == nil {
157-
return false, fmt.Errorf("missing __DWARF segment")
130+
return fmt.Errorf("missing __DWARF segment")
158131
}
159132

160133
// Try to compress the DWARF sections. This includes some Apple
161134
// proprietary sections like __apple_types.
162135
compressedSects, compressedBytes, err := machoCompressSections(ctxt, dwarfm)
163136
if err != nil {
164-
return false, err
137+
return err
165138
}
166139

167140
// Now copy the dwarf data into the output.
168141
// Kernel requires all loaded segments to be page-aligned in the file,
169142
// even though we mark this one as being 0 bytes of virtual address space.
170143
dwarfstart = machoCalcStart(realdwarf.Offset, linkseg.Offset, pageAlign)
171144
if _, err = outf.Seek(dwarfstart, 0); err != nil {
172-
return false, err
145+
return err
173146
}
174147
dwarfaddr = int64((linkseg.Addr + linkseg.Memsz + 1<<pageAlign - 1) &^ (1<<pageAlign - 1))
175148

176149
if _, err = dwarff.Seek(int64(realdwarf.Offset), 0); err != nil {
177-
return false, err
150+
return err
178151
}
179152

180153
// Write out the compressed sections, or the originals if we gave up
@@ -183,62 +156,68 @@ func machoCombineDwarf(ctxt *Link, inexe, dsym, outexe string) (bool, error) {
183156
if compressedBytes != nil {
184157
dwarfsize = uint64(len(compressedBytes))
185158
if _, err := outf.Write(compressedBytes); err != nil {
186-
return false, err
159+
return err
187160
}
188161
} else {
189162
if _, err := io.CopyN(outf, dwarff, int64(realdwarf.Filesz)); err != nil {
190-
return false, err
163+
return err
191164
}
192165
dwarfsize = realdwarf.Filesz
193166
}
194167

195168
// And finally the linkedit section.
196169
if _, err = exef.Seek(int64(linkseg.Offset), 0); err != nil {
197-
return false, err
170+
return err
198171
}
199172
linkstart = machoCalcStart(linkseg.Offset, uint64(dwarfstart)+dwarfsize, pageAlign)
200173
linkoffset = uint32(linkstart - int64(linkseg.Offset))
201174
if _, err = outf.Seek(linkstart, 0); err != nil {
202-
return false, err
175+
return err
203176
}
204177
if _, err := io.Copy(outf, exef); err != nil {
205-
return false, err
178+
return err
206179
}
207180

208181
// Now we need to update the headers.
209182
textsect := exem.Section("__text")
210183
if linkseg == nil {
211-
return false, fmt.Errorf("missing __text section")
184+
return fmt.Errorf("missing __text section")
212185
}
213186

187+
cmdOffset := unsafe.Sizeof(exem.FileHeader)
188+
is64bit := exem.Magic == macho.Magic64
189+
if is64bit {
190+
// mach_header_64 has one extra uint32.
191+
cmdOffset += unsafe.Sizeof(exem.Magic)
192+
}
214193
dwarfCmdOffset := int64(cmdOffset) + int64(exem.FileHeader.Cmdsz)
215194
availablePadding := int64(textsect.Offset) - dwarfCmdOffset
216195
if availablePadding < int64(realdwarf.Len) {
217-
return false, fmt.Errorf("No room to add dwarf info. Need at least %d padding bytes, found %d", realdwarf.Len, availablePadding)
196+
return fmt.Errorf("No room to add dwarf info. Need at least %d padding bytes, found %d", realdwarf.Len, availablePadding)
218197
}
219198
// First, copy the dwarf load command into the header. It will be
220199
// updated later with new offsets and lengths as necessary.
221200
if _, err = outf.Seek(dwarfCmdOffset, 0); err != nil {
222-
return false, err
201+
return err
223202
}
224203
if _, err := io.CopyN(outf, bytes.NewReader(realdwarf.Raw()), int64(realdwarf.Len)); err != nil {
225-
return false, err
204+
return err
226205
}
227206
if _, err = outf.Seek(int64(unsafe.Offsetof(exem.FileHeader.Ncmd)), 0); err != nil {
228-
return false, err
207+
return err
229208
}
230209
if err = binary.Write(outf, exem.ByteOrder, exem.Ncmd+1); err != nil {
231-
return false, err
210+
return err
232211
}
233212
if err = binary.Write(outf, exem.ByteOrder, exem.Cmdsz+realdwarf.Len); err != nil {
234-
return false, err
213+
return err
235214
}
236215

237-
reader = loadCmdReader{next: int64(cmdOffset), f: outf, order: exem.ByteOrder}
216+
reader := loadCmdReader{next: int64(cmdOffset), f: outf, order: exem.ByteOrder}
238217
for i := uint32(0); i < exem.Ncmd; i++ {
239218
cmd, err := reader.Next()
240219
if err != nil {
241-
return false, err
220+
return err
242221
}
243222
switch cmd.Cmd {
244223
case macho.LoadCmdSegment64:
@@ -261,11 +240,11 @@ func machoCombineDwarf(ctxt *Link, inexe, dsym, outexe string) (bool, error) {
261240
err = fmt.Errorf("Unknown load command 0x%x (%s)\n", int(cmd.Cmd), cmd.Cmd)
262241
}
263242
if err != nil {
264-
return false, err
243+
return err
265244
}
266245
}
267246
// Do the final update of the DWARF segment's load command.
268-
return false, machoUpdateDwarfHeader(&reader, ctxt.BuildMode, compressedSects)
247+
return machoUpdateDwarfHeader(&reader, ctxt.BuildMode, compressedSects)
269248
}
270249

271250
// machoCompressSections tries to compress the DWARF segments in dwarfm,

0 commit comments

Comments
 (0)