提交 8e8d2660 编写于 作者: A aarzilli 提交者: Derek Parker

Improve commands which take a location spec

Breakpoints, tracepoints, etc.. take a location spec as input. This
patch improves the expressiveness of that API. It allows:

* Breakpoint at line
* Breakpoint at function (handling package / receiver smoothing)
* Breakpoint at address
* Breakpoint at file:line
* Setting breakpoint based off regexp
上级 e8310e18
package main
import (
"fmt"
)
type SomeType struct {
}
type OtherType struct {
}
func (a *SomeType) String() string {
return "SomeTypeObject"
}
func (a *OtherType) String() string {
return "OtherTypeObject"
}
func (a *SomeType) SomeFunction() {
fmt.Printf("SomeFunction called\n")
}
func anotherFunction() {
fmt.Printf("anotherFunction called\n")
}
func main() {
var a SomeType
var b OtherType
fmt.Printf("%s %s\n", a.String(), b.String())
a.SomeFunction()
anotherFunction()
}
......@@ -8,7 +8,6 @@ import (
"os"
"path/filepath"
"runtime"
"strconv"
"strings"
"sync"
......@@ -146,69 +145,50 @@ func (dbp *Process) LoadInformation(path string) error {
return nil
}
// Find a location by string (file+line, function, breakpoint id, addr)
func (dbp *Process) FindLocation(str string) (uint64, error) {
// File + Line
if strings.ContainsRune(str, ':') {
fl := strings.Split(str, ":")
func (dbp *Process) FindFileLocation(fileName string, lineno int) (uint64, error) {
pc, _, err := dbp.goSymTable.LineToPC(fileName, lineno)
if err != nil {
return 0, err
}
return pc, nil
}
fileName, err := filepath.Abs(fl[0])
if err != nil {
return 0, err
}
// Finds address of a function's line
// If firstLine == true is passed FindFunctionLocation will attempt to find the first line of the function
// If lineOffset is passed FindFunctionLocation will return the address of that line
// Pass lineOffset == 0 and firstLine == false if you want the address for the function's entry point
// Note that setting breakpoints at that address will cause surprising behavior:
// https://github.com/derekparker/delve/issues/170
func (dbp *Process) FindFunctionLocation(funcName string, firstLine bool, lineOffset int) (uint64, error) {
fn := dbp.goSymTable.LookupFunc(funcName)
if fn == nil {
return 0, fmt.Errorf("Could not find function %s\n", funcName)
}
line, err := strconv.Atoi(fl[1])
if err != nil {
return 0, err
if firstLine {
filename, lineno, _ := dbp.goSymTable.PCToLine(fn.Entry)
if filepath.Ext(filename) != ".go" {
return fn.Entry, nil
}
pc, _, err := dbp.goSymTable.LineToPC(fileName, line)
lines, err := dbp.ast.NextLines(filename, lineno)
if err != nil {
return 0, err
}
return pc, nil
}
// Try to lookup by function name
fn := dbp.goSymTable.LookupFunc(str)
if fn != nil {
// fn.Entry, the entry point of the function, is always a prologue, the prologue may call into the scheduler or grow the stack,
// this will result in the breakpoint getting hit multiple times without any apparent program progress inbetween.
// In order to avoid this confusing behaviour we try to find the first line of the function and set the breakpoint there.
filename, lineno, _ := dbp.goSymTable.PCToLine(fn.Entry)
var firstLinePC uint64
var err error
for {
lineno++
firstLinePC, _, err = dbp.goSymTable.LineToPC(filename, lineno)
if err == nil {
break
}
if _, unk := err.(*gosym.UnknownLineError); !unk {
return 0, err
}
}
var breakAddr uint64
if firstLinePC < fn.End {
breakAddr = firstLinePC
if len(lines) > 0 {
linePC, _, err := dbp.goSymTable.LineToPC(filename, lines[0])
return linePC, err
} else {
breakAddr = fn.Entry
return fn.Entry, nil
}
return breakAddr, nil
}
// Attempt to parse as number for breakpoint id or raw address
id, err := strconv.ParseUint(str, 0, 64)
if err != nil {
return 0, fmt.Errorf("unable to find location for %s", str)
}
if bp, ok := dbp.FindBreakpointByID(int(id)); ok {
return bp.Addr, nil
} else if lineOffset > 0 {
filename, lineno, _ := dbp.goSymTable.PCToLine(fn.Entry)
breakAddr, _, err := dbp.goSymTable.LineToPC(filename, lineno+lineOffset)
return breakAddr, err
}
// Last resort, use as raw address
return id, nil
return fn.Entry, nil
}
// Sends out a request that the debugged process halt
......@@ -236,15 +216,6 @@ func (dbp *Process) SetTempBreakpoint(addr uint64) (*Breakpoint, error) {
return dbp.setBreakpoint(dbp.CurrentThread.Id, addr, true)
}
// Sets a breakpoint by location string (function, file+line, address)
func (dbp *Process) SetBreakpointByLocation(loc string) (*Breakpoint, error) {
addr, err := dbp.FindLocation(loc)
if err != nil {
return nil, err
}
return dbp.SetBreakpoint(addr)
}
// Clears a breakpoint.
//
// If it is a hardware assisted breakpoint, iterate through all threads
......@@ -272,15 +243,6 @@ func (dbp *Process) ClearBreakpoint(addr uint64) (*Breakpoint, error) {
return bp, nil
}
// Clears a breakpoint by location (function, file+line, address, breakpoint id)
func (dbp *Process) ClearBreakpointByLocation(loc string) (*Breakpoint, error) {
addr, err := dbp.FindLocation(loc)
if err != nil {
return nil, err
}
return dbp.ClearBreakpoint(addr)
}
// Returns the status of the current main thread context.
func (dbp *Process) Status() *sys.WaitStatus {
return dbp.CurrentThread.Status
......
......@@ -93,10 +93,18 @@ func TestExit(t *testing.T) {
})
}
func setFunctionBreakpoint(p *Process, fname string) (*Breakpoint, error) {
addr, err := p.FindFunctionLocation(fname, true, 0)
if err != nil {
return nil, err
}
return p.SetBreakpoint(addr)
}
func TestHalt(t *testing.T) {
stopChan := make(chan interface{})
withTestProcess("loopprog", t, func(p *Process, fixture protest.Fixture) {
_, err := p.SetBreakpointByLocation("main.loop")
_, err := setFunctionBreakpoint(p, "main.loop")
assertNoError(err, t, "SetBreakpoint")
assertNoError(p.Continue(), t, "Continue")
for _, th := range p.Threads {
......@@ -244,7 +252,7 @@ type nextTest struct {
func testnext(program string, testcases []nextTest, initialLocation string, t *testing.T) {
withTestProcess(program, t, func(p *Process, fixture protest.Fixture) {
bp, err := p.SetBreakpointByLocation(initialLocation)
bp, err := setFunctionBreakpoint(p, initialLocation)
assertNoError(err, t, "SetBreakpoint()")
assertNoError(p.Continue(), t, "Continue()")
p.ClearBreakpoint(bp.Addr)
......@@ -306,8 +314,9 @@ func TestNextFunctionReturn(t *testing.T) {
func TestNextFunctionReturnDefer(t *testing.T) {
testcases := []nextTest{
{5, 9},
{9, 6},
{6, 7},
{7, 10},
}
testnext("testnextdefer", testcases, "main.main", t)
}
......@@ -427,7 +436,7 @@ func TestSwitchThread(t *testing.T) {
if err == nil {
t.Fatal("Expected error for invalid thread id")
}
pc, err := p.FindLocation("main.main")
pc, err := p.FindFunctionLocation("main.main", true, 0)
if err != nil {
t.Fatal(err)
}
......@@ -482,7 +491,7 @@ func TestStacktrace(t *testing.T) {
[]loc{{3, "main.stacktraceme"}, {8, "main.func1"}, {12, "main.func2"}, {17, "main.main"}},
}
withTestProcess("stacktraceprog", t, func(p *Process, fixture protest.Fixture) {
bp, err := p.SetBreakpointByLocation("main.stacktraceme")
bp, err := setFunctionBreakpoint(p, "main.stacktraceme")
assertNoError(err, t, "BreakByLocation()")
for i := range stacks {
......@@ -523,7 +532,7 @@ func TestStacktraceGoroutine(t *testing.T) {
agoroutineStack := []loc{{-1, "runtime.gopark"}, {-1, "runtime.goparkunlock"}, {-1, "runtime.chansend"}, {-1, "runtime.chansend1"}, {8, "main.agoroutine"}}
withTestProcess("goroutinestackprog", t, func(p *Process, fixture protest.Fixture) {
bp, err := p.SetBreakpointByLocation("main.stacktraceme")
bp, err := setFunctionBreakpoint(p, "main.stacktraceme")
assertNoError(err, t, "BreakByLocation()")
assertNoError(p.Continue(), t, "Continue()")
......@@ -587,7 +596,7 @@ func TestKill(t *testing.T) {
}
func testGSupportFunc(name string, t *testing.T, p *Process, fixture protest.Fixture) {
bp, err := p.SetBreakpointByLocation("main.main")
bp, err := setFunctionBreakpoint(p, "main.main")
assertNoError(err, t, name+": BreakByLocation()")
assertNoError(p.Continue(), t, name+": Continue()")
......@@ -621,10 +630,10 @@ func TestGetG(t *testing.T) {
func TestContinueMulti(t *testing.T) {
withTestProcess("integrationprog", t, func(p *Process, fixture protest.Fixture) {
bp1, err := p.SetBreakpointByLocation("main.main")
bp1, err := setFunctionBreakpoint(p, "main.main")
assertNoError(err, t, "BreakByLocation()")
bp2, err := p.SetBreakpointByLocation("main.sayhi")
bp2, err := setFunctionBreakpoint(p, "main.sayhi")
assertNoError(err, t, "BreakByLocation()")
mainCount := 0
......@@ -678,3 +687,17 @@ func TestParseVersionString(t *testing.T) {
t.Fatalf("Devel version string not correctly recognized")
}
}
func TestBreakpointOnFunctionEntry(t *testing.T) {
withTestProcess("testprog", t, func(p *Process, fixture protest.Fixture) {
addr, err := p.FindFunctionLocation("main.main", false, 0)
assertNoError(err, t, "FindFunctionLocation()")
_, err = p.SetBreakpoint(addr)
assertNoError(err, t, "SetBreakpoint()")
assertNoError(p.Continue(), t, "Continue()")
_, ln := currentLineNumber(p, t)
if ln != 17 {
t.Fatalf("Wrong line number: %d (expected: 17)\n", ln)
}
})
}
......@@ -72,4 +72,17 @@ type Client interface {
// Returns whether we attached to a running process or not
AttachedToExistingProcess() bool
// Returns concrete location information described by a location expression
// loc ::= <filename>:<line> | <function>[:<line>] | /<regex>/ | (+|-)<offset> | <line> | *<address>
// * <filename> can be the full path of a file or just a suffix
// * <function> ::= <package>.<receiver type>.<name> | <package>.(*<receiver type>).<name> | <receiver type>.<name> | <package>.<name> | (*<receiver type>).<name> | <name>
// * <function> must be unambiguous
// * /<regex>/ will return a location for each function matched by regex
// * +<offset> returns a location for the line that is <offset> lines after the current line
// * -<offset> returns a location for the line that is <offset> lines before the current line
// * <line> returns a location for a line in the current file
// * *<address> returns the location corresponding to the specified address
// NOTE: this function does not actually set breakpoints.
FindLocation(loc string) ([]api.Location, error)
}
package debugger
import (
"debug/gosym"
"errors"
"fmt"
"log"
......@@ -126,18 +127,29 @@ func (d *Debugger) State() (*api.DebuggerState, error) {
}
func (d *Debugger) CreateBreakpoint(requestedBp *api.Breakpoint) (*api.Breakpoint, error) {
var createdBp *api.Breakpoint
var loc string
var (
createdBp *api.Breakpoint
addr uint64
err error
)
switch {
case len(requestedBp.File) > 0:
loc = fmt.Sprintf("%s:%d", requestedBp.File, requestedBp.Line)
addr, err = d.process.FindFileLocation(requestedBp.File, requestedBp.Line)
case len(requestedBp.FunctionName) > 0:
loc = requestedBp.FunctionName
if requestedBp.Line >= 0 {
addr, err = d.process.FindFunctionLocation(requestedBp.FunctionName, false, requestedBp.Line)
} else {
addr, err = d.process.FindFunctionLocation(requestedBp.FunctionName, true, 0)
}
default:
return nil, fmt.Errorf("no file or function name specified")
addr = requestedBp.Addr
}
if err != nil {
return nil, err
}
bp, err := d.process.SetBreakpointByLocation(loc)
bp, err := d.process.SetBreakpoint(addr)
if err != nil {
return nil, err
}
......@@ -301,13 +313,17 @@ func (d *Debugger) Sources(filter string) ([]string, error) {
}
func (d *Debugger) Functions(filter string) ([]string, error) {
return regexFilterFuncs(filter, d.process.Funcs())
}
func regexFilterFuncs(filter string, allFuncs []gosym.Func) ([]string, error) {
regex, err := regexp.Compile(filter)
if err != nil {
return nil, fmt.Errorf("invalid filter argument: %s", err.Error())
}
funcs := []string{}
for _, f := range d.process.Funcs() {
for _, f := range allFuncs {
if f.Sym != nil && regex.Match([]byte(f.Name)) {
funcs = append(funcs, f.Name)
}
......@@ -448,3 +464,19 @@ func convertStacktrace(rawlocs []proc.Location) []api.Location {
return locations
}
func (d *Debugger) FindLocation(locStr string) ([]api.Location, error) {
loc, err := parseLocationSpec(locStr)
if err != nil {
return nil, err
}
locs, err := loc.Find(d, locStr)
for i := range locs {
file, line, fn := d.process.PCToLine(locs[i].PC)
locs[i].File = file
locs[i].Line = line
locs[i].Function = api.ConvertFunction(fn)
}
return locs, err
}
package debugger
import (
"debug/gosym"
"fmt"
"path/filepath"
"strconv"
"strings"
"github.com/derekparker/delve/service/api"
)
const maxFindLocationCandidates = 5
type LocationSpec interface {
Find(d *Debugger, locStr string) ([]api.Location, error)
}
type NormalLocationSpec struct {
Base string
FuncBase *FuncLocationSpec
LineOffset int
}
type RegexLocationSpec struct {
FuncRegex string
}
type AddrLocationSpec struct {
Addr uint64
}
type OffsetLocationSpec struct {
Offset int
}
type LineLocationSpec struct {
Line int
}
type FuncLocationSpec struct {
PackageName string
ReceiverName string
PackageOrReceiverName string
BaseName string
}
func parseLocationSpec(locStr string) (LocationSpec, error) {
rest := locStr
malformed := func(reason string) error {
return fmt.Errorf("Malformed breakpoint location \"%s\" at %d: %s", locStr, len(locStr)-len(rest), reason)
}
if len(rest) <= 0 {
return nil, malformed("empty string")
}
switch rest[0] {
case '+', '-':
offset, err := strconv.Atoi(rest)
if err != nil {
return nil, malformed(err.Error())
}
return &OffsetLocationSpec{offset}, nil
case '/':
rx, rest := readRegex(rest[1:])
if len(rest) < 0 {
return nil, malformed("non-terminated regular expression")
}
if len(rest) > 1 {
return nil, malformed("no line offset can be specified for regular expression locations")
}
return &RegexLocationSpec{rx}, nil
case '*':
rest = rest[1:]
addr, err := strconv.ParseInt(rest, 0, 64)
if err != nil {
return nil, malformed(err.Error())
}
if addr == 0 {
return nil, malformed("can not set breakpoint at address 0x0")
}
return &AddrLocationSpec{uint64(addr)}, nil
default:
v := strings.SplitN(rest, ":", 2)
if len(v) == 1 {
n, err := strconv.ParseInt(v[0], 0, 64)
if err == nil {
return &LineLocationSpec{int(n)}, nil
}
}
spec := &NormalLocationSpec{}
spec.Base = v[0]
spec.FuncBase = parseFuncLocationSpec(spec.Base)
if len(v) < 2 {
spec.LineOffset = -1
return spec, nil
}
rest = v[1]
var err error
spec.LineOffset, err = strconv.Atoi(rest)
if err != nil || spec.LineOffset < 0 {
return nil, malformed("line offset negative or not a number")
}
return spec, nil
}
}
func readRegex(in string) (rx string, rest string) {
out := make([]rune, 0, len(in))
escaped := false
for i, ch := range in {
if escaped {
if ch == '/' {
out = append(out, '/')
} else {
out = append(out, '\\')
out = append(out, ch)
}
escaped = false
} else {
switch ch {
case '\\':
escaped = true
case '/':
return string(out), in[i:]
default:
out = append(out, ch)
}
}
}
return string(out), ""
}
func parseFuncLocationSpec(in string) *FuncLocationSpec {
if strings.Index(in, "/") >= 0 {
return nil
}
v := strings.Split(in, ".")
var spec FuncLocationSpec
switch len(v) {
case 1:
spec.BaseName = v[0]
case 2:
spec.BaseName = v[1]
r := stripReceiverDecoration(v[0])
if r != v[0] {
spec.ReceiverName = r
} else {
spec.PackageOrReceiverName = r
}
case 3:
spec.BaseName = v[2]
spec.ReceiverName = stripReceiverDecoration(v[1])
spec.PackageName = stripReceiverDecoration(v[0])
default:
return nil
}
return &spec
}
func stripReceiverDecoration(in string) string {
if len(in) < 3 {
return in
}
if (in[0] != '(') || (in[1] != '*') || (in[len(in)-1] != ')') {
return in
}
return in[2 : len(in)-1]
}
func (spec *FuncLocationSpec) Match(sym *gosym.Sym) bool {
if spec.BaseName != sym.BaseName() {
return false
}
recv := stripReceiverDecoration(sym.ReceiverName())
if spec.ReceiverName != "" && spec.ReceiverName != recv {
return false
}
if spec.PackageName != "" && spec.PackageName != sym.PackageName() {
return false
}
if spec.PackageOrReceiverName != "" && spec.PackageOrReceiverName != sym.PackageName() && spec.PackageOrReceiverName != recv {
return false
}
return true
}
func (loc *RegexLocationSpec) Find(d *Debugger, locStr string) ([]api.Location, error) {
funcs := d.process.Funcs()
matches, err := regexFilterFuncs(loc.FuncRegex, funcs)
if err != nil {
return nil, err
}
r := make([]api.Location, 0, len(matches))
for i := range matches {
addr, err := d.process.FindFunctionLocation(matches[i], true, 0)
if err == nil {
r = append(r, api.Location{PC: addr})
}
}
return r, nil
}
func (loc *AddrLocationSpec) Find(d *Debugger, locStr string) ([]api.Location, error) {
return []api.Location{{PC: loc.Addr}}, nil
}
func (loc *NormalLocationSpec) FileMatch(path string) bool {
return strings.HasSuffix(path, loc.Base) && (path[len(path)-len(loc.Base)-1] == filepath.Separator)
}
func (loc *NormalLocationSpec) Find(d *Debugger, locStr string) ([]api.Location, error) {
funcs := d.process.Funcs()
files := d.process.Sources()
candidates := []string{}
for file := range files {
if loc.FileMatch(file) {
candidates = append(candidates, file)
if len(candidates) >= maxFindLocationCandidates {
break
}
}
}
if loc.FuncBase != nil {
for _, f := range funcs {
if f.Sym == nil {
continue
}
if loc.FuncBase.Match(f.Sym) {
candidates = append(candidates, f.Name)
if len(candidates) >= maxFindLocationCandidates {
break
}
}
}
}
switch len(candidates) {
case 1:
var addr uint64
var err error
if candidates[0][0] == '/' {
if loc.LineOffset < 0 {
return nil, fmt.Errorf("Malformed breakpoint location, no line offset specified")
}
addr, err = d.process.FindFileLocation(candidates[0], loc.LineOffset)
} else {
if loc.LineOffset < 0 {
addr, err = d.process.FindFunctionLocation(candidates[0], true, 0)
} else {
addr, err = d.process.FindFunctionLocation(candidates[0], false, loc.LineOffset)
}
}
if err != nil {
return nil, err
}
return []api.Location{{PC: addr}}, nil
case 0:
return nil, fmt.Errorf("Location \"%s\" not found", locStr)
default:
return nil, fmt.Errorf("Location \"%s\" ambiguous: %s…\n", locStr, strings.Join(candidates, ", "))
}
}
func (loc *OffsetLocationSpec) Find(d *Debugger, locStr string) ([]api.Location, error) {
cur, err := d.process.CurrentThread.Location()
if err != nil {
return nil, err
}
addr, err := d.process.FindFileLocation(cur.File, cur.Line+loc.Offset)
return []api.Location{{PC: addr}}, err
}
func (loc *LineLocationSpec) Find(d *Debugger, locStr string) ([]api.Location, error) {
cur, err := d.process.CurrentThread.Location()
if err != nil {
return nil, err
}
addr, err := d.process.FindFileLocation(cur.File, loc.Line)
return []api.Location{{PC: addr}}, err
}
......@@ -211,6 +211,12 @@ func (c *RPCClient) AttachedToExistingProcess() bool {
return answer
}
func (c *RPCClient) FindLocation(loc string) ([]api.Location, error) {
var answer []api.Location
err := c.call("FindLocation", loc, &answer)
return answer, err
}
func (c *RPCClient) url(path string) string {
return fmt.Sprintf("http://%s%s", c.addr, path)
}
......
......@@ -309,3 +309,9 @@ func (c *RPCServer) AttachedToExistingProcess(arg interface{}, answer *bool) err
}
return nil
}
func (c *RPCServer) FindLocation(loc string, answer *[]api.Location) error {
var err error
*answer, err = c.debugger.FindLocation(loc)
return err
}
......@@ -83,7 +83,7 @@ func TestRestart_afterExit(t *testing.T) {
func TestRestart_duringStop(t *testing.T) {
withTestClient("continuetestprog", t, func(c service.Client) {
origPid := c.ProcessPid()
_, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.main"})
_, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.main", Line: 1})
if err != nil {
t.Fatal(err)
}
......@@ -150,7 +150,7 @@ func TestClientServer_exit(t *testing.T) {
func TestClientServer_step(t *testing.T) {
withTestClient("testprog", t, func(c service.Client) {
_, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.helloworld"})
_, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.helloworld", Line: 1})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
......@@ -177,7 +177,7 @@ type nextTest struct {
func testnext(testcases []nextTest, initialLocation string, t *testing.T) {
withTestClient("testnextprog", t, func(c service.Client) {
bp, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: initialLocation})
bp, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: initialLocation, Line: -1})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
......@@ -246,7 +246,7 @@ func TestNextFunctionReturn(t *testing.T) {
func TestClientServer_breakpointInMainThread(t *testing.T) {
withTestClient("testprog", t, func(c service.Client) {
bp, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.helloworld"})
bp, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.helloworld", Line: 1})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
......@@ -267,7 +267,7 @@ func TestClientServer_breakpointInMainThread(t *testing.T) {
func TestClientServer_breakpointInSeparateGoroutine(t *testing.T) {
withTestClient("testthreads", t, func(c service.Client) {
_, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.anotherthread"})
_, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.anotherthread", Line: 1})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
......@@ -286,7 +286,7 @@ func TestClientServer_breakpointInSeparateGoroutine(t *testing.T) {
func TestClientServer_breakAtNonexistentPoint(t *testing.T) {
withTestClient("testprog", t, func(c service.Client) {
_, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "nowhere"})
_, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "nowhere", Line: 1})
if err == nil {
t.Fatal("Should not be able to break at non existent function")
}
......@@ -295,7 +295,7 @@ func TestClientServer_breakAtNonexistentPoint(t *testing.T) {
func TestClientServer_clearBreakpoint(t *testing.T) {
withTestClient("testprog", t, func(c service.Client) {
bp, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.sleepytime"})
bp, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.sleepytime", Line: 1})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
......@@ -329,7 +329,7 @@ func TestClientServer_switchThread(t *testing.T) {
t.Fatal("Expected error for invalid thread id")
}
_, err = c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.main"})
_, err = c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.main", Line: 1})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
......@@ -482,11 +482,11 @@ func TestClientServer_traceContinue(t *testing.T) {
func TestClientServer_traceContinue2(t *testing.T) {
withTestClient("integrationprog", t, func(c service.Client) {
bp1, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.main", Tracepoint: true})
bp1, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.main", Line: 1, Tracepoint: true})
if err != nil {
t.Fatalf("Unexpected error: %v\n", err)
}
bp2, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.sayhi", Tracepoint: true})
bp2, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.sayhi", Line: 1, Tracepoint: true})
if err != nil {
t.Fatalf("Unexpected error: %v\n", err)
}
......@@ -522,3 +522,82 @@ func TestClientServer_traceContinue2(t *testing.T) {
}
})
}
func findLocationHelper(t *testing.T, c service.Client, loc string, shouldErr bool, count int, checkAddr uint64) []uint64 {
locs, err := c.FindLocation(loc)
t.Logf("FindLocation(\"%s\") → %v\n", loc, locs)
if shouldErr {
if err == nil {
t.Fatalf("Resolving location <%s> didn't return an error: %v", loc, locs)
}
} else {
if err != nil {
t.Fatalf("Error resolving location <%s>: %v", loc, err)
}
}
if (count >= 0) && (len(locs) != count) {
t.Fatalf("Wrong number of breakpoints returned for location <%s> (got %d, expected %d)", loc, len(locs), count)
}
if checkAddr != 0 && checkAddr != locs[0].PC {
t.Fatalf("Wrong address returned for location <%s> (got %v, epected %v)", loc, locs[0].PC, checkAddr)
}
addrs := make([]uint64, len(locs))
for i := range locs {
addrs[i] = locs[i].PC
}
return addrs
}
func TestClientServer_FindLocations(t *testing.T) {
withTestClient("locationsprog", t, func(c service.Client) {
someFunctionCallAddr := findLocationHelper(t, c, "locationsprog.go:26", false, 1, 0)[0]
findLocationHelper(t, c, "anotherFunction:1", false, 1, someFunctionCallAddr)
findLocationHelper(t, c, "main.anotherFunction:1", false, 1, someFunctionCallAddr)
findLocationHelper(t, c, "anotherFunction", false, 1, someFunctionCallAddr)
findLocationHelper(t, c, "main.anotherFunction", false, 1, someFunctionCallAddr)
findLocationHelper(t, c, fmt.Sprintf("*0x%x", someFunctionCallAddr), false, 1, someFunctionCallAddr)
findLocationHelper(t, c, "sprog.go:26", true, 0, 0)
findLocationHelper(t, c, "String", true, 0, 0)
findLocationHelper(t, c, "main.String", true, 0, 0)
someTypeStringFuncAddr := findLocationHelper(t, c, "locationsprog.go:14", false, 1, 0)[0]
otherTypeStringFuncAddr := findLocationHelper(t, c, "locationsprog.go:18", false, 1, 0)[0]
findLocationHelper(t, c, "SomeType.String", false, 1, someTypeStringFuncAddr)
findLocationHelper(t, c, "(*SomeType).String", false, 1, someTypeStringFuncAddr)
findLocationHelper(t, c, "main.SomeType.String", false, 1, someTypeStringFuncAddr)
findLocationHelper(t, c, "main.(*SomeType).String", false, 1, someTypeStringFuncAddr)
stringAddrs := findLocationHelper(t, c, "/^main.*Type.*String$/", false, 2, 0)
if otherTypeStringFuncAddr != stringAddrs[0] && otherTypeStringFuncAddr != stringAddrs[1] {
t.Fatalf("Wrong locations returned for \"/.*Type.*String/\", got: %v expected: %v and %v\n", stringAddrs, someTypeStringFuncAddr, otherTypeStringFuncAddr)
}
_, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.main", Line: 4, Tracepoint: false})
if err != nil {
t.Fatalf("CreateBreakpoint(): %v\n", err)
}
<-c.Continue()
locationsprog34Addr := findLocationHelper(t, c, "locationsprog.go:34", false, 1, 0)[0]
findLocationHelper(t, c, "+1", false, 1, locationsprog34Addr)
findLocationHelper(t, c, "34", false, 1, locationsprog34Addr)
findLocationHelper(t, c, "-1", false, 1, findLocationHelper(t, c, "locationsprog.go:32", false, 1, 0)[0])
})
withTestClient("testnextdefer", t, func(c service.Client) {
firstMainLine := findLocationHelper(t, c, "testnextdefer.go:9", false, 1, 0)[0]
findLocationHelper(t, c, "main.main", false, 1, firstMainLine)
})
withTestClient("stacktraceprog", t, func(c service.Client) {
stacktracemeAddr := findLocationHelper(t, c, "stacktraceprog.go:4", false, 1, 0)[0]
findLocationHelper(t, c, "main.stacktraceme", false, 1, stacktracemeAddr)
})
}
......@@ -47,7 +47,7 @@ func DebugCommands(client service.Client) *Commands {
c.cmds = []command{
{aliases: []string{"help"}, cmdFn: c.help, helpMsg: "Prints the help message."},
{aliases: []string{"break", "b"}, cmdFn: breakpoint, helpMsg: "break <file:line|function|address> [-stack <n>|-goroutine|<variable name>]*"},
{aliases: []string{"break", "b"}, cmdFn: breakpoint, helpMsg: "break <linespec> [-stack <n>|-goroutine|<variable name>]*"},
{aliases: []string{"trace", "t"}, cmdFn: tracepoint, helpMsg: "Set tracepoint, takes the same arguments as break."},
{aliases: []string{"restart", "r"}, cmdFn: restart, helpMsg: "Restart process."},
{aliases: []string{"continue", "c"}, cmdFn: cont, helpMsg: "Run until breakpoint or program termination."},
......@@ -323,21 +323,6 @@ func setBreakpoint(client service.Client, tracepoint bool, args ...string) error
}
requestedBp := &api.Breakpoint{}
tokens := strings.Split(args[0], ":")
switch {
case len(tokens) == 1:
requestedBp.FunctionName = args[0]
case len(tokens) == 2:
file := tokens[0]
line, err := strconv.Atoi(tokens[1])
if err != nil {
return err
}
requestedBp.File = file
requestedBp.Line = line
default:
return fmt.Errorf("invalid line reference")
}
for i := 1; i < len(args); i++ {
switch args[i] {
......@@ -357,7 +342,7 @@ func setBreakpoint(client service.Client, tracepoint bool, args ...string) error
requestedBp.Tracepoint = tracepoint
bp, err := client.CreateBreakpoint(requestedBp)
locs, err := client.FindLocation(args[0])
if err != nil {
return err
}
......@@ -367,7 +352,17 @@ func setBreakpoint(client service.Client, tracepoint bool, args ...string) error
thing = "Tracepoint"
}
fmt.Printf("%s %d set at %#v for %s %s:%d\n", thing, bp.ID, bp.Addr, bp.FunctionName, bp.File, bp.Line)
for _, loc := range locs {
requestedBp.Addr = loc.PC
bp, err := client.CreateBreakpoint(requestedBp)
if err != nil {
return err
}
fmt.Printf("%s %d set at %#v for %s %s:%d\n", thing, bp.ID, bp.Addr, bp.FunctionName, bp.File, bp.Line)
}
return nil
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册