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
This commit is contained in:
parent
e8310e186c
commit
8e8d2660ef
35
_fixtures/locationsprog.go
Normal file
35
_fixtures/locationsprog.go
Normal file
@ -0,0 +1,35 @@
|
||||
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()
|
||||
}
|
118
proc/proc.go
118
proc/proc.go
@ -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, ":")
|
||||
|
||||
fileName, err := filepath.Abs(fl[0])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
line, err := strconv.Atoi(fl[1])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
pc, _, err := dbp.goSymTable.LineToPC(fileName, line)
|
||||
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
|
||||
} else {
|
||||
breakAddr = fn.Entry
|
||||
}
|
||||
return breakAddr, nil
|
||||
}
|
||||
|
||||
// Attempt to parse as number for breakpoint id or raw address
|
||||
id, err := strconv.ParseUint(str, 0, 64)
|
||||
func (dbp *Process) FindFileLocation(fileName string, lineno int) (uint64, error) {
|
||||
pc, _, err := dbp.goSymTable.LineToPC(fileName, lineno)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("unable to find location for %s", str)
|
||||
return 0, err
|
||||
}
|
||||
if bp, ok := dbp.FindBreakpointByID(int(id)); ok {
|
||||
return bp.Addr, nil
|
||||
return pc, nil
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// Last resort, use as raw address
|
||||
return id, nil
|
||||
if firstLine {
|
||||
filename, lineno, _ := dbp.goSymTable.PCToLine(fn.Entry)
|
||||
if filepath.Ext(filename) != ".go" {
|
||||
return fn.Entry, nil
|
||||
}
|
||||
|
||||
lines, err := dbp.ast.NextLines(filename, lineno)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if len(lines) > 0 {
|
||||
linePC, _, err := dbp.goSymTable.LineToPC(filename, lines[0])
|
||||
return linePC, err
|
||||
} else {
|
||||
return fn.Entry, nil
|
||||
}
|
||||
} else if lineOffset > 0 {
|
||||
filename, lineno, _ := dbp.goSymTable.PCToLine(fn.Entry)
|
||||
breakAddr, _, err := dbp.goSymTable.LineToPC(filename, lineno+lineOffset)
|
||||
return breakAddr, err
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
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
|
||||
}
|
||||
|
||||
bp, err := d.process.SetBreakpointByLocation(loc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
303
service/debugger/locations.go
Normal file
303
service/debugger/locations.go
Normal file
@ -0,0 +1,303 @@
|
||||
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
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user