ogle: add BreakpointAtFunction and BreakpointAtLine, for setting a breakpoint at the start of a function or at a particular source line.

Breakpoint now only takes an address, instead of a formatted string to be evaluated.

Change-Id: I148960dcc149a7467478590eb7c6696597168a03
Reviewed-on: https://go-review.googlesource.com/10624
Reviewed-by: Rob Pike <r@golang.org>
diff --git a/ogle/demo/ogler/ogler_test.go b/ogle/demo/ogler/ogler_test.go
index fc7c511..4c4039f 100644
--- a/ogle/demo/ogler/ogler_test.go
+++ b/ogle/demo/ogler/ogler_test.go
@@ -175,9 +175,9 @@
 		log.Fatalf("Run: %v", err)
 	}
 
-	pcs, err := prog.Breakpoint("re:main.foo")
+	pcs, err := prog.BreakpointAtFunction("main.foo")
 	if err != nil {
-		log.Fatalf("Breakpoint: %v", err)
+		log.Fatalf("BreakpointAtFunction: %v", err)
 	}
 	fmt.Printf("breakpoints set at %x\n", pcs)
 
@@ -267,13 +267,13 @@
 	if err != nil {
 		log.Fatalf("DeleteBreakpoints: %v", err)
 	}
-	pcs1, err := prog.Breakpoint("re:main.f1")
+	pcs1, err := prog.BreakpointAtFunction("main.f1")
 	if err != nil {
-		log.Fatalf("Breakpoint: %v", err)
+		log.Fatalf("BreakpointAtFunction: %v", err)
 	}
-	pcs2, err := prog.Breakpoint("re:main.f2")
+	pcs2, err := prog.BreakpointAtFunction("main.f2")
 	if err != nil {
-		log.Fatalf("Breakpoint: %v", err)
+		log.Fatalf("BreakpointAtFunction: %v", err)
 	}
 	err = prog.DeleteBreakpoints(pcs1)
 	if err != nil {
diff --git a/ogle/program/client/client.go b/ogle/program/client/client.go
index 1eade3a..2464a6d 100644
--- a/ogle/program/client/client.go
+++ b/ogle/program/client/client.go
@@ -180,7 +180,7 @@
 	panic("unimplemented")
 }
 
-func (p *Program) Breakpoint(address string) ([]uint64, error) {
+func (p *Program) Breakpoint(address uint64) ([]uint64, error) {
 	req := proxyrpc.BreakpointRequest{
 		Address: address,
 	}
@@ -189,6 +189,25 @@
 	return resp.PCs, err
 }
 
+func (p *Program) BreakpointAtFunction(name string) ([]uint64, error) {
+	req := proxyrpc.BreakpointAtFunctionRequest{
+		Function: name,
+	}
+	var resp proxyrpc.BreakpointResponse
+	err := p.client.Call("Server.BreakpointAtFunction", &req, &resp)
+	return resp.PCs, err
+}
+
+func (p *Program) BreakpointAtLine(file string, line uint64) ([]uint64, error) {
+	req := proxyrpc.BreakpointAtLineRequest{
+		File: file,
+		Line: line,
+	}
+	var resp proxyrpc.BreakpointResponse
+	err := p.client.Call("Server.BreakpointAtLine", &req, &resp)
+	return resp.PCs, err
+}
+
 func (p *Program) DeleteBreakpoints(pcs []uint64) error {
 	req := proxyrpc.DeleteBreakpointsRequest{PCs: pcs}
 	var resp proxyrpc.DeleteBreakpointsResponse
diff --git a/ogle/program/local/local.go b/ogle/program/local/local.go
index 45868c4..02c7d0c 100644
--- a/ogle/program/local/local.go
+++ b/ogle/program/local/local.go
@@ -72,15 +72,27 @@
 	panic("unimplemented")
 }
 
-func (l *Local) Breakpoint(address string) ([]uint64, error) {
-	req := proxyrpc.BreakpointRequest{
-		Address: address,
-	}
+func (l *Local) Breakpoint(address uint64) ([]uint64, error) {
+	req := proxyrpc.BreakpointRequest{address}
 	var resp proxyrpc.BreakpointResponse
 	err := l.s.Breakpoint(&req, &resp)
 	return resp.PCs, err
 }
 
+func (l *Local) BreakpointAtFunction(name string) ([]uint64, error) {
+	req := proxyrpc.BreakpointAtFunctionRequest{name}
+	var resp proxyrpc.BreakpointResponse
+	err := l.s.BreakpointAtFunction(&req, &resp)
+	return resp.PCs, err
+}
+
+func (l *Local) BreakpointAtLine(file string, line uint64) ([]uint64, error) {
+	req := proxyrpc.BreakpointAtLineRequest{file, line}
+	var resp proxyrpc.BreakpointResponse
+	err := l.s.BreakpointAtLine(&req, &resp)
+	return resp.PCs, err
+}
+
 func (l *Local) DeleteBreakpoints(pcs []uint64) error {
 	req := proxyrpc.DeleteBreakpointsRequest{PCs: pcs}
 	var resp proxyrpc.DeleteBreakpointsResponse
diff --git a/ogle/program/program.go b/ogle/program/program.go
index 77d8b8a..374a019 100644
--- a/ogle/program/program.go
+++ b/ogle/program/program.go
@@ -48,13 +48,13 @@
 	Kill() (Status, error)
 
 	// Breakpoint sets a breakpoint at the specified address.
-	// When the target binary is re-run, breakpoints are
-	// automatically re-established in the new process by
-	// re-evaluating the address.
-	// The address is the same mini-language accepted by Eval,
-	// which permits setting multiple breakpoints using a regular
-	// expression to match a set of symbols.
-	Breakpoint(address string) (PCs []uint64, err error)
+	Breakpoint(address uint64) (PCs []uint64, err error)
+
+	// BreakpointAtFunction sets a breakpoint at the start of the specified function.
+	BreakpointAtFunction(name string) (PCs []uint64, err error)
+
+	// BreakpointAtLine sets a breakpoint at the specified source line.
+	BreakpointAtLine(file string, line uint64) (PCs []uint64, err error)
 
 	// DeleteBreakpoints removes the breakpoints at the specified addresses.
 	// Addresses where no breakpoint is set are ignored.
diff --git a/ogle/program/proxyrpc/proxyrpc.go b/ogle/program/proxyrpc/proxyrpc.go
index 04b2240..91bb49e 100644
--- a/ogle/program/proxyrpc/proxyrpc.go
+++ b/ogle/program/proxyrpc/proxyrpc.go
@@ -78,7 +78,16 @@
 }
 
 type BreakpointRequest struct {
-	Address string
+	Address uint64
+}
+
+type BreakpointAtFunctionRequest struct {
+	Function string
+}
+
+type BreakpointAtLineRequest struct {
+	File string
+	Line uint64
 }
 
 type BreakpointResponse struct {
diff --git a/ogle/program/server/server.go b/ogle/program/server/server.go
index 4afb911..9e5b486 100644
--- a/ogle/program/server/server.go
+++ b/ogle/program/server/server.go
@@ -148,6 +148,10 @@
 	switch req := c.req.(type) {
 	case *proxyrpc.BreakpointRequest:
 		c.errc <- s.handleBreakpoint(req, c.resp.(*proxyrpc.BreakpointResponse))
+	case *proxyrpc.BreakpointAtFunctionRequest:
+		c.errc <- s.handleBreakpointAtFunction(req, c.resp.(*proxyrpc.BreakpointResponse))
+	case *proxyrpc.BreakpointAtLineRequest:
+		c.errc <- s.handleBreakpointAtLine(req, c.resp.(*proxyrpc.BreakpointResponse))
 	case *proxyrpc.DeleteBreakpointsRequest:
 		c.errc <- s.handleDeleteBreakpoints(req, c.resp.(*proxyrpc.DeleteBreakpointsResponse))
 	case *proxyrpc.CloseRequest:
@@ -399,29 +403,49 @@
 }
 
 func (s *Server) handleBreakpoint(req *proxyrpc.BreakpointRequest, resp *proxyrpc.BreakpointResponse) error {
-	addrs, err := s.eval(req.Address)
+	return s.addBreakpoints([]uint64{req.Address}, resp)
+}
+
+func (s *Server) BreakpointAtFunction(req *proxyrpc.BreakpointAtFunctionRequest, resp *proxyrpc.BreakpointResponse) error {
+	return s.call(s.breakpointc, req, resp)
+}
+
+func (s *Server) handleBreakpointAtFunction(req *proxyrpc.BreakpointAtFunctionRequest, resp *proxyrpc.BreakpointResponse) error {
+	pc, err := s.lookupFunction(req.Function)
 	if err != nil {
 		return err
 	}
-	var bp breakpoint
-	for _, addr := range addrs {
-		pc, err := s.evalAddress(addr)
-		if err != nil {
-			return err
-		}
-		if _, alreadySet := s.breakpoints[pc]; alreadySet {
-			return fmt.Errorf("breakpoint already set at %#x (TODO)", pc)
-		}
+	return s.addBreakpoints([]uint64{pc}, resp)
+}
 
-		err = s.ptracePeek(s.stoppedPid, uintptr(pc), bp.origInstr[:s.arch.BreakpointSize])
-		if err != nil {
+func (s *Server) BreakpointAtLine(req *proxyrpc.BreakpointAtLineRequest, resp *proxyrpc.BreakpointResponse) error {
+	return s.call(s.breakpointc, req, resp)
+}
+
+func (s *Server) handleBreakpointAtLine(req *proxyrpc.BreakpointAtLineRequest, resp *proxyrpc.BreakpointResponse) error {
+	return fmt.Errorf("not implemented")
+}
+
+// addBreakpoints adds breakpoints at the addresses in pcs, then stores pcs in the response.
+func (s *Server) addBreakpoints(pcs []uint64, resp *proxyrpc.BreakpointResponse) error {
+	// Get the original code at each address with ptracePeek.
+	bps := make([]breakpoint, 0, len(pcs))
+	for _, pc := range pcs {
+		if _, alreadySet := s.breakpoints[pc]; alreadySet {
+			continue
+		}
+		var bp breakpoint
+		if err := s.ptracePeek(s.stoppedPid, uintptr(pc), bp.origInstr[:s.arch.BreakpointSize]); err != nil {
 			return fmt.Errorf("ptracePeek: %v", err)
 		}
 		bp.pc = pc
-		s.breakpoints[pc] = bp
-		resp.PCs = append(resp.PCs, pc)
+		bps = append(bps, bp)
 	}
-
+	// If all the peeks succeeded, update the list of breakpoints.
+	for _, bp := range bps {
+		s.breakpoints[bp.pc] = bp
+	}
+	resp.PCs = pcs
 	return nil
 }