cmd/viewcore: display the origin of core

This change adds the origin of the analyzed core in the welcome
message of interactive mode. The information is from NT_PRPSINFO
(currently assuming linux/amd64 only). This info allows users to
verify they are looking into the correct core file.
This is similar to what gdb does.

Change-Id: Ib626c9007a588e34cf633908dedde591e6f04e3c
Reviewed-on: https://go-review.googlesource.com/121015
Reviewed-by: Heschi Kreinick <heschi@google.com>
diff --git a/cmd/viewcore/main.go b/cmd/viewcore/main.go
index 497b911..91ffebd 100644
--- a/cmd/viewcore/main.go
+++ b/cmd/viewcore/main.go
@@ -301,32 +301,12 @@
 	}
 	defer shell.Close()
 
-	// TODO(hyangah): the following code retrieves the main
-	// program path from the mapping information.
-	// The intention is to help users confirm viewcore
-	// picked up correct executable and core file for analysis.
-	// The usefulness of this info is debatable.
-	// Another idea is to present the executable file info
-	// embedded in prpsinfo which will provide useful
-	// new information about the corefile to analyze.
-	exe := "unknown"
-	if m := p.Mappings(); len(m) > 0 {
-		src, _ := m[0].Source()
-		if m[0].CopyOnWrite() {
-			src, _ = m[0].OrigSource()
-		}
-		if src != "" {
-			exe = src
-		}
+	// nice welcome message.
+	fmt.Fprintln(shell.Terminal)
+	if args := p.Args(); args != "" {
+		fmt.Fprintf(shell.Terminal, "Core %q was generated by %q\n", cfg.corefile, args)
 	}
-
-	welcomeMsg := `
-  Corefile: %s
-  Program : %s
-
-Entering interactive mode (type 'help' for commands)
-`
-	fmt.Fprintf(shell.Terminal, welcomeMsg, cfg.corefile, exe)
+	fmt.Fprintf(shell.Terminal, "Entering interactive mode (type 'help' for commands)\n")
 
 	for {
 		l, err := shell.Readline()
diff --git a/internal/core/core_test.go b/internal/core/core_test.go
index 904a75a..341babf 100644
--- a/internal/core/core_test.go
+++ b/internal/core/core_test.go
@@ -117,3 +117,11 @@
 		t.Errorf("can't find thread that did runtime.raise")
 	}
 }
+
+func TestArgs(t *testing.T) {
+	p := loadExample(t, true)
+	if got := p.Args(); got != "./test" {
+		// this is how the program of testdata/core was invoked.
+		t.Errorf("Args() = %q, want './test'", got)
+	}
+}
diff --git a/internal/core/process.go b/internal/core/process.go
index 0e56e0e..65c2fd4 100644
--- a/internal/core/process.go
+++ b/internal/core/process.go
@@ -17,6 +17,7 @@
 package core
 
 import (
+	"bytes"
 	"debug/dwarf"
 	"debug/elf" // TODO: use golang.org/x/debug/elf instead?
 	"encoding/binary"
@@ -33,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?)
+	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
 	logPtrSize   uint               // 2 or 3
@@ -46,7 +48,9 @@
 	dwarf        *dwarf.Data        // debugging info (could be nil)
 	dwarfErr     error              // an error encountered while reading DWARF
 	pageTable    pageTable4         // for fast address->mapping lookups
-	warnings     []string           // warnings generated during loading
+	args         string             // first part of args retrieved from NT_PRPSINFO
+
+	warnings []string // warnings generated during loading
 }
 
 // Mappings returns a list of virtual memory mappings for p.
@@ -336,20 +340,22 @@
 		b = b[(descsz+3)/4*4:]
 
 		if name == "CORE" && typ == NT_FILE {
-			err := p.readNTFile(f, e, desc)
-			if err != nil {
-				return err
+			if err := p.readNTFile(f, e, desc); err != nil {
+				return fmt.Errorf("reading NT_FILE: %v", err)
 			}
 		}
 		if name == "CORE" && typ == elf.NT_PRSTATUS {
 			// An OS thread (an M)
-			err := p.readPRStatus(f, e, desc)
-			if err != nil {
-				return err
+			if err := p.readPRStatus(f, e, desc); err != nil {
+				return fmt.Errorf("reading NT_PRSTATUS: %v", err)
+			}
+		}
+		if name == "CORE" && typ == elf.NT_PRPSINFO {
+			if err := p.readPRPSInfo(desc); err != nil {
+				return fmt.Errorf("reading NT_PRPSINFO: %v", err)
 			}
 		}
 		// TODO: NT_FPREGSET for floating-point registers
-		// TODO: NT_PRPSINFO for ???
 	}
 	return nil
 }
@@ -481,6 +487,21 @@
 	}
 }
 
+func (p *Process) readPRPSInfo(desc []byte) error {
+	r := bytes.NewReader(desc)
+	switch p.arch {
+	default:
+		// TODO: return error?
+	case "amd64":
+		prpsinfo := &linuxPrPsInfo{}
+		if err := binary.Read(r, binary.LittleEndian, prpsinfo); err != nil {
+			return err
+		}
+		p.args = strings.Trim(string(prpsinfo.Args[:]), "\x00 ")
+	}
+	return nil
+}
+
 func (p *Process) readPRStatus(f *os.File, e *elf.File, desc []byte) error {
 	t := &Thread{}
 	p.threads = append(p.threads, t)
@@ -575,3 +596,24 @@
 func (p *Process) Warnings() []string {
 	return p.warnings
 }
+
+// Args returns the initial part of the program arguments.
+func (p *Process) Args() string {
+	return p.args
+}
+
+// ELF/Linux types
+
+// linuxPrPsInfo is the info embedded in NT_PRPSINFO.
+type linuxPrPsInfo struct {
+	State                uint8
+	Sname                int8
+	Zomb                 uint8
+	Nice                 int8
+	_                    [4]uint8
+	Flag                 uint64
+	Uid, Gid             uint32
+	Pid, Ppid, Pgrp, Sid int32
+	Fname                [16]uint8 // filename of executables
+	Args                 [80]uint8 // first part of program args
+}