Quellcode durchsuchen

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 vor 6 Jahren
Ursprung
Commit
bfbbd91e3e
1 geänderte Dateien mit 58 neuen und 12 gelöschten Zeilen
  1. 58
    12
      internal/binutils/binutils.go

+ 58
- 12
internal/binutils/binutils.go Datei anzeigen

@@ -25,6 +25,7 @@ import (
25 25
 	"os/exec"
26 26
 	"path/filepath"
27 27
 	"regexp"
28
+	"runtime"
28 29
 	"strings"
29 30
 	"sync"
30 31
 
@@ -190,13 +191,13 @@ func (bu *Binutils) Open(name string, start, limit, offset uint64) (plugin.ObjFi
190 191
 
191 192
 	f, err := os.Open(name)
192 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 196
 	defer f.Close()
196 197
 
197 198
 	var header [4]byte
198 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 203
 	elfMagic := string(header[:])
@@ -205,7 +206,7 @@ func (bu *Binutils) Open(name string, start, limit, offset uint64) (plugin.ObjFi
205 206
 	if elfMagic == elf.ELFMAG {
206 207
 		f, err := b.openELF(name, start, limit, offset)
207 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 211
 		return f, nil
211 212
 	}
@@ -218,24 +219,22 @@ func (bu *Binutils) Open(name string, start, limit, offset uint64) (plugin.ObjFi
218 219
 		machoMagicBig == macho.Magic32 || machoMagicBig == macho.Magic64 {
219 220
 		f, err := b.openMachO(name, start, limit, offset)
220 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 224
 		return f, nil
224 225
 	}
225 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 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 239
 	// Subtract the load address of the __TEXT section. Usually 0 for shared
241 240
 	// libraries or 0x100000000 for executables. You can check this value by
@@ -258,6 +257,53 @@ func (b *binrep) openMachO(name string, start, limit, offset uint64) (plugin.Obj
258 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 307
 func (b *binrep) openELF(name string, start, limit, offset uint64) (plugin.ObjFile, error) {
262 308
 	ef, err := elf.Open(name)
263 309
 	if err != nil {