Browse Source

Add support for fat Mach-O files (#411)

Add support for fat Mach-O files

Fixes #1033. Note that this does not do the "correct" thing, of checking the main executable to see what architecture it is and trying to match that. Or maybe checking the architecture in the profile file if it is present.

Instead it just uses the host architecture to decide which subfile of the fat archive to open.
Tim 6 years ago
parent
commit
bfbbd91e3e
1 changed files with 58 additions and 12 deletions
  1. 58
    12
      internal/binutils/binutils.go

+ 58
- 12
internal/binutils/binutils.go View File

25
 	"os/exec"
25
 	"os/exec"
26
 	"path/filepath"
26
 	"path/filepath"
27
 	"regexp"
27
 	"regexp"
28
+	"runtime"
28
 	"strings"
29
 	"strings"
29
 	"sync"
30
 	"sync"
30
 
31
 
190
 
191
 
191
 	f, err := os.Open(name)
192
 	f, err := os.Open(name)
192
 	if err != nil {
193
 	if err != nil {
193
-		return nil, fmt.Errorf("error opening %s: %s", name, err)
194
+		return nil, fmt.Errorf("error opening %s: %v", name, err)
194
 	}
195
 	}
195
 	defer f.Close()
196
 	defer f.Close()
196
 
197
 
197
 	var header [4]byte
198
 	var header [4]byte
198
 	if _, err = io.ReadFull(f, header[:]); err != nil {
199
 	if _, err = io.ReadFull(f, header[:]); err != nil {
199
-		return nil, fmt.Errorf("error reading magic number from %s: %s", name, err)
200
+		return nil, fmt.Errorf("error reading magic number from %s: %v", name, err)
200
 	}
201
 	}
201
 
202
 
202
 	elfMagic := string(header[:])
203
 	elfMagic := string(header[:])
205
 	if elfMagic == elf.ELFMAG {
206
 	if elfMagic == elf.ELFMAG {
206
 		f, err := b.openELF(name, start, limit, offset)
207
 		f, err := b.openELF(name, start, limit, offset)
207
 		if err != nil {
208
 		if err != nil {
208
-			return nil, fmt.Errorf("error reading ELF file %s: %s", name, err)
209
+			return nil, fmt.Errorf("error reading ELF file %s: %v", name, err)
209
 		}
210
 		}
210
 		return f, nil
211
 		return f, nil
211
 	}
212
 	}
218
 		machoMagicBig == macho.Magic32 || machoMagicBig == macho.Magic64 {
219
 		machoMagicBig == macho.Magic32 || machoMagicBig == macho.Magic64 {
219
 		f, err := b.openMachO(name, start, limit, offset)
220
 		f, err := b.openMachO(name, start, limit, offset)
220
 		if err != nil {
221
 		if err != nil {
221
-			return nil, fmt.Errorf("error reading Mach-O file %s: %s", name, err)
222
+			return nil, fmt.Errorf("error reading Mach-O file %s: %v", name, err)
222
 		}
223
 		}
223
 		return f, nil
224
 		return f, nil
224
 	}
225
 	}
225
 	if machoMagicLittle == macho.MagicFat || machoMagicBig == macho.MagicFat {
226
 	if machoMagicLittle == macho.MagicFat || machoMagicBig == macho.MagicFat {
226
-		// TODO: #1033
227
-		return nil, fmt.Errorf("fat Mach-O archives are currently unsupported")
227
+		f, err := b.openFatMachO(name, start, limit, offset)
228
+		if err != nil {
229
+			return nil, fmt.Errorf("error reading fat Mach-O file %s: %v", name, err)
230
+		}
231
+		return f, nil
228
 	}
232
 	}
229
 
233
 
230
 	return nil, fmt.Errorf("unrecognized binary format: %s", name)
234
 	return nil, fmt.Errorf("unrecognized binary format: %s", name)
231
 }
235
 }
232
 
236
 
233
-func (b *binrep) openMachO(name string, start, limit, offset uint64) (plugin.ObjFile, error) {
234
-	of, err := macho.Open(name)
235
-	if err != nil {
236
-		return nil, fmt.Errorf("error parsing %s: %v", name, err)
237
-	}
238
-	defer of.Close()
237
+func (b *binrep) openMachOCommon(name string, of *macho.File, start, limit, offset uint64) (plugin.ObjFile, error) {
239
 
238
 
240
 	// Subtract the load address of the __TEXT section. Usually 0 for shared
239
 	// Subtract the load address of the __TEXT section. Usually 0 for shared
241
 	// libraries or 0x100000000 for executables. You can check this value by
240
 	// libraries or 0x100000000 for executables. You can check this value by
258
 	return &fileAddr2Line{file: file{b: b, name: name, base: base}}, nil
257
 	return &fileAddr2Line{file: file{b: b, name: name, base: base}}, nil
259
 }
258
 }
260
 
259
 
260
+func (b *binrep) openFatMachO(name string, start, limit, offset uint64) (plugin.ObjFile, error) {
261
+	of, err := macho.OpenFat(name)
262
+	if err != nil {
263
+		return nil, fmt.Errorf("error parsing %s: %v", name, err)
264
+	}
265
+	defer of.Close()
266
+
267
+	if len(of.Arches) == 0 {
268
+		return nil, fmt.Errorf("empty fat Mach-O file: %s", name)
269
+	}
270
+
271
+	var arch macho.Cpu
272
+	// Use the host architecture.
273
+	// TODO: This is not ideal because the host architecture may not be the one
274
+	// that was profiled. E.g. an amd64 host can profile a 386 program.
275
+	switch runtime.GOARCH {
276
+	case "386":
277
+		arch = macho.Cpu386
278
+	case "amd64", "amd64p32":
279
+		arch = macho.CpuAmd64
280
+	case "arm", "armbe", "arm64", "arm64be":
281
+		arch = macho.CpuArm
282
+	case "ppc":
283
+		arch = macho.CpuPpc
284
+	case "ppc64", "ppc64le":
285
+		arch = macho.CpuPpc64
286
+	default:
287
+		return nil, fmt.Errorf("unsupported host architecture for %s: %s", name, runtime.GOARCH)
288
+	}
289
+	for i := range of.Arches {
290
+		if of.Arches[i].Cpu == arch {
291
+			return b.openMachOCommon(name, of.Arches[i].File, start, limit, offset)
292
+		}
293
+	}
294
+	return nil, fmt.Errorf("architecture not found in %s: %s", name, runtime.GOARCH)
295
+}
296
+
297
+func (b *binrep) openMachO(name string, start, limit, offset uint64) (plugin.ObjFile, error) {
298
+	of, err := macho.Open(name)
299
+	if err != nil {
300
+		return nil, fmt.Errorf("error parsing %s: %v", name, err)
301
+	}
302
+	defer of.Close()
303
+
304
+	return b.openMachOCommon(name, of, start, limit, offset)
305
+}
306
+
261
 func (b *binrep) openELF(name string, start, limit, offset uint64) (plugin.ObjFile, error) {
307
 func (b *binrep) openELF(name string, start, limit, offset uint64) (plugin.ObjFile, error) {
262
 	ef, err := elf.Open(name)
308
 	ef, err := elf.Open(name)
263
 	if err != nil {
309
 	if err != nil {