ogle: add name->PC lookup for functions
The symbol table we can access has functions but no data, so until
that is resolved we can only evaluate function names.

LGTM=nigeltao
R=nigeltao
https://golang.org/cl/77580045
diff --git a/cmd/ogleproxy/main.go b/cmd/ogleproxy/main.go
index 3ce343a..6e1b6ea 100644
--- a/cmd/ogleproxy/main.go
+++ b/cmd/ogleproxy/main.go
@@ -29,15 +29,14 @@
 		flag.Usage()
 		os.Exit(2)
 	}
-	fd, err := os.Open(*textFlag)
+	s, err := server.New(*textFlag)
 	if err != nil {
-		fmt.Printf("OGLE BAD\n%s\n", err)
+		fmt.Println("OGLE BAD\n%s\n", err)
 		os.Exit(2)
 	}
-	fd.Close()
-	err = rpc.Register(&server.Server{})
+	err = rpc.Register(s)
 	if err != nil {
-		fmt.Printf("OGLE BAD\n%s\n", err)
+		fmt.Println("OGLE BAD\n%s\n", err)
 		os.Exit(2)
 	}
 	fmt.Println("OGLE OK")
diff --git a/program/client/client.go b/program/client/client.go
index fea7c7f..3b3a3df 100644
--- a/program/client/client.go
+++ b/program/client/client.go
@@ -182,6 +182,15 @@
 	panic("unimplemented")
 }
 
+func (p *Program) Eval(expr string) (string, error) {
+	req := proxyrpc.EvalRequest{
+		Expr: expr,
+	}
+	var resp proxyrpc.EvalResponse
+	err := p.client.Call("Server.Eval", &req, &resp)
+	return resp.Result, err
+}
+
 // File implements the program.File interface, providing access
 // to file-like resources associated with the target program.
 type File struct {
diff --git a/program/program.go b/program/program.go
index 55664b4..d27da54 100644
--- a/program/program.go
+++ b/program/program.go
@@ -63,12 +63,16 @@
 	//	"main.main"  Start of function
 	//	"main.go:23" Line number
 	//	(more to follow; may want an expression grammar)
-	// It is an OK if two breakpoints evaluate to the same PC. (TODO: verify.)
+	// It is OK if two breakpoints evaluate to the same PC. (TODO: verify.)
 	Breakpoint(address string) error
 
 	// DeleteBreakpoint removes the breakpoint at the specified
 	// address.
 	DeleteBreakpoint(address string) error
+
+	// Eval evaluates the expression (typically an address) and returns
+	// its string representation.
+	Eval(expr string) (string, error)
 }
 
 // The File interface provides access to file-like resources in the program.
diff --git a/program/proxyrpc/proxyrpc.go b/program/proxyrpc/proxyrpc.go
index 0017b8b..b2a2977 100644
--- a/program/proxyrpc/proxyrpc.go
+++ b/program/proxyrpc/proxyrpc.go
@@ -48,3 +48,11 @@
 type OpenResponse struct {
 	FD int
 }
+
+type EvalRequest struct {
+	Expr string
+}
+
+type EvalResponse struct {
+	Result string
+}
diff --git a/program/server/server.go b/program/server/server.go
index fcd54d7..6160966 100644
--- a/program/server/server.go
+++ b/program/server/server.go
@@ -10,15 +10,106 @@
 	"fmt"
 	"os"
 
+	"debug/elf"
+	"debug/macho"
+	"debug/pe"
+
+	"code.google.com/p/ogle/gosym"
 	"code.google.com/p/ogle/program"
 	"code.google.com/p/ogle/program/proxyrpc"
 )
 
 type Server struct {
+	executable string // Name of executable.
+	lines      *gosym.LineTable
+	symbols    *gosym.Table
 	// TODO: Needs mutex.
 	files []*file // Index == file descriptor.
 }
 
+// New parses the executable and builds local data structures for answering requests.
+// It returns a Server ready to serve requests about the executable.
+func New(executable string) (*Server, error) {
+	fd, err := os.Open(executable)
+	if err != nil {
+		return nil, err
+	}
+	defer fd.Close()
+	textStart, symtab, pclntab, err := loadTables(fd)
+	if err != nil {
+		return nil, err
+	}
+	lines := gosym.NewLineTable(pclntab, textStart)
+	symbols, err := gosym.NewTable(symtab, lines)
+	if err != nil {
+		return nil, err
+	}
+	srv := &Server{
+		executable: executable,
+		lines:      lines,
+		symbols:    symbols,
+	}
+	return srv, nil
+}
+
+// This function is copied from $GOROOT/src/cmd/addr2line/main.go.
+// TODO: Make this architecture-defined? Push into gosym?
+// TODO: Why is the .gosymtab always empty?
+func loadTables(f *os.File) (textStart uint64, symtab, pclntab []byte, err error) {
+	if obj, err := elf.NewFile(f); err == nil {
+		if sect := obj.Section(".text"); sect != nil {
+			textStart = sect.Addr
+		}
+		if sect := obj.Section(".gosymtab"); sect != nil {
+			if symtab, err = sect.Data(); err != nil {
+				return 0, nil, nil, err
+			}
+		}
+		if sect := obj.Section(".gopclntab"); sect != nil {
+			if pclntab, err = sect.Data(); err != nil {
+				return 0, nil, nil, err
+			}
+		}
+		return textStart, symtab, pclntab, nil
+	}
+
+	if obj, err := macho.NewFile(f); err == nil {
+		if sect := obj.Section("__text"); sect != nil {
+			textStart = sect.Addr
+		}
+		if sect := obj.Section("__gosymtab"); sect != nil {
+			if symtab, err = sect.Data(); err != nil {
+				return 0, nil, nil, err
+			}
+		}
+		if sect := obj.Section("__gopclntab"); sect != nil {
+			if pclntab, err = sect.Data(); err != nil {
+				return 0, nil, nil, err
+			}
+		}
+		return textStart, symtab, pclntab, nil
+	}
+
+	if obj, err := pe.NewFile(f); err == nil {
+		if sect := obj.Section(".text"); sect != nil {
+			textStart = uint64(sect.VirtualAddress)
+		}
+		if sect := obj.Section(".gosymtab"); sect != nil {
+			if symtab, err = sect.Data(); err != nil {
+				return 0, nil, nil, err
+			}
+		}
+		if sect := obj.Section(".gopclntab"); sect != nil {
+			if pclntab, err = sect.Data(); err != nil {
+				return 0, nil, nil, err
+			}
+		}
+		return textStart, symtab, pclntab, nil
+	}
+
+	return 0, nil, nil, fmt.Errorf("unrecognized binary format")
+}
+
 type file struct {
 	mode  string
 	index int
@@ -80,5 +171,16 @@
 	// Remove it regardless
 	s.files[fd] = nil
 	return err
+}
 
+func (s *Server) Eval(req *proxyrpc.EvalRequest, resp *proxyrpc.EvalResponse) error {
+	expr := req.Expr
+	// Simple version: Turn symbol into address. Must be function.
+	// TODO: Why is Table.Syms always empty?
+	sym := s.symbols.LookupFunc(expr)
+	if sym == nil {
+		return fmt.Errorf("symbol %q not found")
+	}
+	resp.Result = fmt.Sprintf("%#x", sym.Value)
+	return nil
 }