Browse Source

Improve "unrecognized binary" error messages (#408)

Improve "unrecognized binary" error messages

Previously it would just print "unrecognized binary" no matter what the underlying error message was. This was partly because it was unable to know the actual file type except by trial and error. This commit uses the magic number instead. It also gives a nicer error for fat Mach-O binaries which are currently unsupported.
Tim 6 years ago
parent
commit
781f11b1fc

+ 42
- 9
internal/binutils/binutils.go View File

18
 import (
18
 import (
19
 	"debug/elf"
19
 	"debug/elf"
20
 	"debug/macho"
20
 	"debug/macho"
21
+	"encoding/binary"
21
 	"fmt"
22
 	"fmt"
23
+	"io"
22
 	"os"
24
 	"os"
23
 	"os/exec"
25
 	"os/exec"
24
 	"path/filepath"
26
 	"path/filepath"
173
 	b := bu.get()
175
 	b := bu.get()
174
 
176
 
175
 	// Make sure file is a supported executable.
177
 	// Make sure file is a supported executable.
176
-	// The pprof driver uses Open to sniff the difference
177
-	// between an executable and a profile.
178
-	// For now, only ELF is supported.
179
-	// Could read the first few bytes of the file and
180
-	// use a table of prefixes if we need to support other
181
-	// systems at some point.
178
+	// This uses magic numbers, mainly to provide better error messages but
179
+	// it should also help speed.
182
 
180
 
183
 	if _, err := os.Stat(name); err != nil {
181
 	if _, err := os.Stat(name); err != nil {
184
 		// For testing, do not require file name to exist.
182
 		// For testing, do not require file name to exist.
188
 		return nil, err
186
 		return nil, err
189
 	}
187
 	}
190
 
188
 
191
-	if f, err := b.openELF(name, start, limit, offset); err == nil {
189
+	// Read the first 4 bytes of the file.
190
+
191
+	f, err := os.Open(name)
192
+	if err != nil {
193
+		return nil, fmt.Errorf("error opening %s: %s", name, err)
194
+	}
195
+	defer f.Close()
196
+
197
+	var header [4]byte
198
+	if _, err = io.ReadFull(f, header[:]); err != nil {
199
+		return nil, fmt.Errorf("error reading magic number from %s: %s", name, err)
200
+	}
201
+
202
+	elfMagic := string(header[:])
203
+
204
+	// Match against supported file types.
205
+	if elfMagic == elf.ELFMAG {
206
+		f, err := b.openELF(name, start, limit, offset)
207
+		if err != nil {
208
+			return nil, fmt.Errorf("error reading ELF file %s: %s", name, err)
209
+		}
192
 		return f, nil
210
 		return f, nil
193
 	}
211
 	}
194
-	if f, err := b.openMachO(name, start, limit, offset); err == nil {
212
+
213
+	// Mach-O magic numbers can be big or little endian.
214
+	machoMagicLittle := binary.LittleEndian.Uint32(header[:])
215
+	machoMagicBig := binary.BigEndian.Uint32(header[:])
216
+
217
+	if machoMagicLittle == macho.Magic32 || machoMagicLittle == macho.Magic64 ||
218
+		machoMagicBig == macho.Magic32 || machoMagicBig == macho.Magic64 {
219
+		f, err := b.openMachO(name, start, limit, offset)
220
+		if err != nil {
221
+			return nil, fmt.Errorf("error reading Mach-O file %s: %s", name, err)
222
+		}
195
 		return f, nil
223
 		return f, nil
196
 	}
224
 	}
197
-	return nil, fmt.Errorf("unrecognized binary: %s", name)
225
+	if machoMagicLittle == macho.MagicFat || machoMagicBig == macho.MagicFat {
226
+		// TODO: #1033
227
+		return nil, fmt.Errorf("fat Mach-O archives are currently unsupported")
228
+	}
229
+
230
+	return nil, fmt.Errorf("unrecognized binary format: %s", name)
198
 }
231
 }
199
 
232
 
200
 func (b *binrep) openMachO(name string, start, limit, offset uint64) (plugin.ObjFile, error) {
233
 func (b *binrep) openMachO(name string, start, limit, offset uint64) (plugin.ObjFile, error) {

+ 29
- 0
internal/binutils/binutils_test.go View File

22
 	"reflect"
22
 	"reflect"
23
 	"regexp"
23
 	"regexp"
24
 	"runtime"
24
 	"runtime"
25
+	"strings"
25
 	"testing"
26
 	"testing"
26
 
27
 
27
 	"github.com/google/pprof/internal/plugin"
28
 	"github.com/google/pprof/internal/plugin"
361
 		}
362
 		}
362
 	}
363
 	}
363
 }
364
 }
365
+
366
+func TestOpenMalformedELF(t *testing.T) {
367
+	// Test that opening a malformed ELF file will report an error containing
368
+	// the word "ELF".
369
+	bu := &Binutils{}
370
+	_, err := bu.Open(filepath.Join("testdata", "malformed_elf"), 0, 0, 0)
371
+	if err == nil {
372
+		t.Fatalf("Open: unexpected success")
373
+	}
374
+
375
+	if !strings.Contains(err.Error(), "ELF") {
376
+		t.Errorf("Open: got %v, want error containing 'ELF'", err)
377
+	}
378
+}
379
+
380
+func TestOpenMalformedMachO(t *testing.T) {
381
+	// Test that opening a malformed Mach-O file will report an error containing
382
+	// the word "Mach-O".
383
+	bu := &Binutils{}
384
+	_, err := bu.Open(filepath.Join("testdata", "malformed_macho"), 0, 0, 0)
385
+	if err == nil {
386
+		t.Fatalf("Open: unexpected success")
387
+	}
388
+
389
+	if !strings.Contains(err.Error(), "Mach-O") {
390
+		t.Errorf("Open: got %v, want error containing 'Mach-O'", err)
391
+	}
392
+}

+ 1
- 0
internal/binutils/testdata/malformed_elf View File

1
+ELF˙˙˙˙˙˙˙˙

+ 1
- 0
internal/binutils/testdata/malformed_macho View File

1
+销睨��������