cmd/viewcore: don't panic because of missing executable

This exposes a new error type ExecNotFoundError
to allow the viewcore main binary to recognize the error type and
suggest --exe flag defined in the main package.

Fixes golang/go#27322

Change-Id: I009b0cb9833a64b7cd5cba69b2ecc49327917581
Reviewed-on: https://go-review.googlesource.com/136516
Run-TryBot: Hyang-Ah Hana Kim <hyangah@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Heschi Kreinick <heschi@google.com>
diff --git a/cmd/viewcore/main.go b/cmd/viewcore/main.go
index 2495458..1ffeee2 100644
--- a/cmd/viewcore/main.go
+++ b/cmd/viewcore/main.go
@@ -258,6 +258,9 @@
 		return nil, nil, err
 	}
 	p, err := gocore.Core(c)
+	if _, ok := err.(*core.ExecNotFoundError); ok && cfg.exePath == "" {
+		return nil, nil, fmt.Errorf("%v; consider specifying the --exe flag", err)
+	}
 	if err != nil {
 		return nil, nil, err
 	}
diff --git a/internal/core/process.go b/internal/core/process.go
index f6c4a3e..f1b49f2 100644
--- a/internal/core/process.go
+++ b/internal/core/process.go
@@ -34,9 +34,10 @@
 	base    string // base directory from which files in the core can be found
 	exePath string // user-supplied main executable path
 
-	exec     []*os.File // executables (more than one for shlibs)
-	mappings []*Mapping // virtual address mappings
-	threads  []*Thread  // os threads (TODO: map from pid?)
+	origExePath string     // main executable path found in the core, set by readCore
+	exec        []*os.File // executables (more than one for shlibs)
+	mappings    []*Mapping // virtual address mappings
+	threads     []*Thread  // os threads (TODO: map from pid?)
 
 	arch         string             // amd64, ...
 	ptrSize      int64              // 4 or 8
@@ -373,7 +374,6 @@
 	filenames := string(desc[3*8*count:])
 	desc = desc[:3*8*count]
 
-	origExePath := ""
 	for i := uint64(0); i < count; i++ {
 		min := Address(e.ByteOrder.Uint64(desc))
 		desc = desc[8:]
@@ -394,7 +394,7 @@
 
 		// Assume the first entry is the main binary.
 		if i == 0 {
-			origExePath = name
+			p.origExePath = name
 		}
 
 		var backing *os.File
@@ -403,7 +403,7 @@
 		// If the name matches the cached original executable path
 		// and user-provided executable is available, use the
 		// user-provided one.
-		if p.exePath != "" && origExePath == name {
+		if p.exePath != "" && p.origExePath == name {
 			backing, err = os.Open(p.exePath)
 		} else {
 			backing, err = os.Open(filepath.Join(p.base, name))
@@ -594,9 +594,24 @@
 			p.dwarf = dwarf
 		}
 	}
+	if p.dwarf == nil && p.dwarfErr == nil {
+		exe := p.origExePath
+		if p.exePath != "" {
+			exe = p.exePath
+		}
+		p.dwarfErr = &ExecNotFoundError{exe}
+	}
 	return nil
 }
 
+type ExecNotFoundError struct {
+	path string
+}
+
+func (e *ExecNotFoundError) Error() string {
+	return fmt.Sprintf("missing executable %q", e.path)
+}
+
 func (p *Process) Warnings() []string {
 	return p.warnings
 }