proc,service: change FindLocation to work with multiple targets (#3103)

Changes FindLocation to support multiple targets and adds an AddrPid
member to api.Breakpoint so that clients can set breakpoints by address
when multiple targets are connected (but at them moment this field is
ignored).

Updates #1653
Updates #2551
This commit is contained in:
Alessandro Arzilli 2022-09-26 19:12:34 +02:00 committed by GitHub
parent 8a230b7f59
commit a73eaeffd2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 114 additions and 62 deletions

@ -370,7 +370,7 @@ func (ale AmbiguousLocationError) Error() string {
func (loc *NormalLocationSpec) Find(t *proc.Target, processArgs []string, scope *proc.EvalScope, locStr string, includeNonExecutableLines bool, substitutePathRules [][2]string) ([]api.Location, error) {
limit := maxFindLocationCandidates
var candidateFiles []string
for _, sourceFile := range scope.BinInfo.Sources {
for _, sourceFile := range t.BinInfo().Sources {
substFile := sourceFile
if len(substitutePathRules) > 0 {
substFile = SubstitutePath(sourceFile, substitutePathRules)
@ -387,10 +387,10 @@ func (loc *NormalLocationSpec) Find(t *proc.Target, processArgs []string, scope
var candidateFuncs []string
if loc.FuncBase != nil && limit > 0 {
candidateFuncs = loc.findFuncCandidates(scope, limit)
candidateFuncs = loc.findFuncCandidates(t.BinInfo(), limit)
}
if matching := len(candidateFiles) + len(candidateFuncs); matching == 0 {
if matching := len(candidateFiles) + len(candidateFuncs); matching == 0 && scope != nil {
// if no result was found this locations string could be an
// expression that the user forgot to prefix with '*', try treating it as
// such.
@ -428,14 +428,14 @@ func (loc *NormalLocationSpec) Find(t *proc.Target, processArgs []string, scope
return []api.Location{addressesToLocation(addrs)}, nil
}
func (loc *NormalLocationSpec) findFuncCandidates(scope *proc.EvalScope, limit int) []string {
func (loc *NormalLocationSpec) findFuncCandidates(bi *proc.BinaryInfo, limit int) []string {
candidateFuncs := map[string]struct{}{}
// See if it matches generic functions first
for fname := range scope.BinInfo.LookupGenericFunc() {
for fname := range bi.LookupGenericFunc() {
if len(candidateFuncs) >= limit {
break
}
if !loc.FuncBase.Match(&proc.Function{Name: fname}, scope.BinInfo.PackageMap) {
if !loc.FuncBase.Match(&proc.Function{Name: fname}, bi.PackageMap) {
continue
}
if loc.Base == fname {
@ -443,11 +443,11 @@ func (loc *NormalLocationSpec) findFuncCandidates(scope *proc.EvalScope, limit i
}
candidateFuncs[fname] = struct{}{}
}
for _, f := range scope.BinInfo.LookupFunc {
for _, f := range bi.LookupFunc {
if len(candidateFuncs) >= limit {
break
}
if !loc.FuncBase.Match(f, scope.BinInfo.PackageMap) {
if !loc.FuncBase.Match(f, bi.PackageMap) {
continue
}
if loc.Base == f.Name {

@ -47,7 +47,8 @@ func NewGroup(t *Target) *TargetGroup {
}
}
// Targets returns a slice of targets in the group.
// Targets returns a slice of all targets in the group, including the
// ones that are no longer valid.
func (grp *TargetGroup) Targets() []*Target {
return grp.targets
}
@ -60,7 +61,9 @@ func (grp *TargetGroup) Valid() (bool, error) {
if ok {
return true, nil
}
err0 = err
if err0 == nil {
err0 = err
}
}
return false, err0
}
@ -124,3 +127,25 @@ func (grp *TargetGroup) TargetForThread(thread Thread) *Target {
}
return nil
}
// ValidTargets iterates through all valid targets in Group.
type ValidTargets struct {
*Target
Group *TargetGroup
start int
}
// Next moves to the next valid target, returns false if there aren't more
// valid targets in the group.
func (it *ValidTargets) Next() bool {
for i := it.start; i < len(it.Group.targets); i++ {
if ok, _ := it.Group.targets[i].Valid(); ok {
it.Target = it.Group.targets[i]
it.start = i + 1
return true
}
}
it.start = len(it.Group.targets)
it.Target = nil
return false
}

@ -1758,6 +1758,7 @@ func setBreakpoint(t *Term, ctx callContext, tracepoint bool, argstr string) ([]
for _, loc := range locs {
requestedBp.Addr = loc.PC
requestedBp.Addrs = loc.PCs
requestedBp.AddrPid = loc.PCPids
if tracepoint {
requestedBp.LoadArgs = &ShortLoadConfig
}

@ -54,7 +54,7 @@ func ConvertLogicalBreakpoint(lbp *proc.LogicalBreakpoint) *Breakpoint {
}
// ConvertPhysicalBreakpoints adds informations from physical breakpoints to an API breakpoint.
func ConvertPhysicalBreakpoints(b *Breakpoint, bps []*proc.Breakpoint) {
func ConvertPhysicalBreakpoints(b *Breakpoint, pids []int, bps []*proc.Breakpoint) {
if len(bps) == 0 {
return
}
@ -63,8 +63,9 @@ func ConvertPhysicalBreakpoints(b *Breakpoint, bps []*proc.Breakpoint) {
b.WatchType = WatchType(bps[0].WatchType)
lg := false
for _, bp := range bps {
for i, bp := range bps {
b.Addrs = append(b.Addrs, bp.Addr)
b.AddrPid = append(b.AddrPid, pids[i])
if b.FunctionName != bp.FunctionName && b.FunctionName != "" {
if !lg {
b.FunctionName = removeTypeParams(b.FunctionName)

@ -80,6 +80,9 @@ type Breakpoint struct {
Addr uint64 `json:"addr"`
// Addrs is the list of addresses for this breakpoint.
Addrs []uint64 `json:"addrs"`
// AddrPid[i] is the PID associated with by Addrs[i], when debugging a
// single target process this is optional, otherwise it is mandatory.
AddrPid []int `json:"addrpid"`
// File is the source file for the breakpoint.
File string `json:"file"`
// Line is a line in File for the breakpoint.
@ -192,6 +195,7 @@ type Location struct {
Line int `json:"line"`
Function *Function `json:"function,omitempty"`
PCs []uint64 `json:"pcs,omitempty"`
PCPids []int `json:"pcpids,omitempty"`
}
// Stackframe describes one frame in a stack trace.

@ -1860,7 +1860,7 @@ func (s *Session) stoppedOnBreakpointGoroutineID(state *api.DebuggerState) (int6
return goid, nil
}
abp := api.ConvertLogicalBreakpoint(bp.Breakpoint.Logical)
api.ConvertPhysicalBreakpoints(abp, []*proc.Breakpoint{bp.Breakpoint})
api.ConvertPhysicalBreakpoints(abp, []int{0}, []*proc.Breakpoint{bp.Breakpoint})
return goid, abp
}

@ -49,6 +49,9 @@ var (
// ErrCoreDumpNotSupported is returned when core dumping is not supported
ErrCoreDumpNotSupported = errors.New("core dumping not supported")
// ErrNotImplementedWithMultitarget is returned for operations that are not implemented with multiple targets
ErrNotImplementedWithMultitarget = errors.New("not implemented for multiple targets")
)
// Debugger service.
@ -390,6 +393,10 @@ func (d *Debugger) FunctionReturnLocations(fnName string) ([]uint64, error) {
d.targetMutex.Lock()
defer d.targetMutex.Unlock()
if len(d.target.Targets()) > 1 {
return nil, ErrNotImplementedWithMultitarget
}
var (
p = d.target.Selected
g = p.SelectedGoroutine()
@ -608,10 +615,11 @@ func (d *Debugger) state(retLoadCfg *proc.LoadConfig, withBreakpointInfo bool) (
state.When, _ = d.target.When()
}
for _, t := range d.target.Targets() {
t := proc.ValidTargets{Group: d.target}
for t.Next() {
for _, bp := range t.Breakpoints().WatchOutOfScope {
abp := api.ConvertLogicalBreakpoint(bp.Logical)
api.ConvertPhysicalBreakpoints(abp, []*proc.Breakpoint{bp})
api.ConvertPhysicalBreakpoints(abp, []int{t.Pid()}, []*proc.Breakpoint{bp})
state.WatchOutOfScope = append(state.WatchOutOfScope, abp)
}
}
@ -678,8 +686,9 @@ func (d *Debugger) CreateBreakpoint(requestedBp *api.Breakpoint) (*api.Breakpoin
if runtime.GOOS == "windows" {
// Accept fileName which is case-insensitive and slash-insensitive match
fileNameNormalized := strings.ToLower(filepath.ToSlash(fileName))
t := proc.ValidTargets{Group: d.target}
caseInsensitiveSearch:
for _, t := range d.target.Targets() {
for t.Next() {
for _, symFile := range t.BinInfo().Sources {
if fileNameNormalized == strings.ToLower(filepath.ToSlash(symFile)) {
fileName = symFile
@ -693,6 +702,7 @@ func (d *Debugger) CreateBreakpoint(requestedBp *api.Breakpoint) (*api.Breakpoin
addrs, err = proc.FindFunctionLocation(d.target.Selected, requestedBp.FunctionName, requestedBp.Line)
case len(requestedBp.Addrs) > 0:
addrs = requestedBp.Addrs
//TODO(aarzilli): read requestedBp.AddrPid
default:
addrs = []uint64{requestedBp.Addr}
}
@ -711,7 +721,18 @@ func (d *Debugger) CreateBreakpoint(requestedBp *api.Breakpoint) (*api.Breakpoin
func (d *Debugger) convertBreakpoint(lbp *proc.LogicalBreakpoint) *api.Breakpoint {
abp := api.ConvertLogicalBreakpoint(lbp)
api.ConvertPhysicalBreakpoints(abp, d.findBreakpoint(lbp.LogicalID))
bps := []*proc.Breakpoint{}
pids := []int{}
t := proc.ValidTargets{Group: d.target}
for t.Next() {
for _, bp := range t.Breakpoints().M {
if bp.LogicalID() == lbp.LogicalID {
bps = append(bps, bp)
pids = append(pids, t.Pid())
}
}
}
api.ConvertPhysicalBreakpoints(abp, pids, bps)
return abp
}
@ -850,7 +871,7 @@ func (d *Debugger) CreateEBPFTracepoint(fnName string) error {
d.targetMutex.Lock()
defer d.targetMutex.Unlock()
if len(d.target.Targets()) != 1 {
panic("multiple targets not implemented")
return ErrNotImplementedWithMultitarget
}
p := d.target.Selected
return p.SetEBPFTracepoint(fnName)
@ -1049,23 +1070,22 @@ func isBpHitCondNotSatisfiable(bp *api.Breakpoint) bool {
func (d *Debugger) Breakpoints(all bool) []*api.Breakpoint {
d.targetMutex.Lock()
defer d.targetMutex.Unlock()
if len(d.target.Targets()) != 1 {
panic("multiple targets not implemented")
}
p := d.target.Selected
abps := []*api.Breakpoint{}
if all {
for _, bp := range p.Breakpoints().M {
var abp *api.Breakpoint
if bp.Logical != nil {
abp = api.ConvertLogicalBreakpoint(bp.Logical)
} else {
abp = &api.Breakpoint{}
t := proc.ValidTargets{Group: d.target}
for t.Next() {
for _, bp := range t.Breakpoints().M {
var abp *api.Breakpoint
if bp.Logical != nil {
abp = api.ConvertLogicalBreakpoint(bp.Logical)
} else {
abp = &api.Breakpoint{}
}
api.ConvertPhysicalBreakpoints(abp, []int{t.Pid()}, []*proc.Breakpoint{bp})
abp.VerboseDescr = bp.VerboseDescr()
abps = append(abps, abp)
}
api.ConvertPhysicalBreakpoints(abp, []*proc.Breakpoint{bp})
abp.VerboseDescr = bp.VerboseDescr()
abps = append(abps, abp)
}
} else {
for _, lbp := range d.target.LogicalBreakpoints {
@ -1426,7 +1446,8 @@ func (d *Debugger) Sources(filter string) ([]string, error) {
}
files := []string{}
for _, t := range d.target.Targets() {
t := proc.ValidTargets{Group: d.target}
for t.Next() {
for _, f := range t.BinInfo().Sources {
if regex.Match([]byte(f)) {
files = append(files, f)
@ -1464,7 +1485,8 @@ func (d *Debugger) Functions(filter string) ([]string, error) {
}
funcs := []string{}
for _, t := range d.target.Targets() {
t := proc.ValidTargets{Group: d.target}
for t.Next() {
for _, f := range t.BinInfo().Functions {
if regex.MatchString(f.Name) {
funcs = append(funcs, f.Name)
@ -1488,7 +1510,8 @@ func (d *Debugger) Types(filter string) ([]string, error) {
r := []string{}
for _, t := range d.target.Targets() {
t := proc.ValidTargets{Group: d.target}
for t.Next() {
types, err := t.BinInfo().Types()
if err != nil {
return nil, err
@ -1936,13 +1959,6 @@ func (d *Debugger) FindLocation(goid int64, frame, deferredCall int, locStr stri
d.targetMutex.Lock()
defer d.targetMutex.Unlock()
if len(d.target.Targets()) != 1 {
//TODO(aarzilli): if there is more than one target process all must be
//searched and the addresses returned need to specify which target process
//they belong to.
panic("multiple targets not implemented")
}
if _, err := d.target.Valid(); err != nil {
return nil, err
}
@ -1952,7 +1968,7 @@ func (d *Debugger) FindLocation(goid int64, frame, deferredCall int, locStr stri
return nil, err
}
return d.findLocation(d.target.Selected, goid, frame, deferredCall, locStr, loc, includeNonExecutableLines, substitutePathRules)
return d.findLocation(goid, frame, deferredCall, locStr, loc, includeNonExecutableLines, substitutePathRules)
}
// FindLocationSpec will find the location specified by 'locStr' and 'locSpec'.
@ -1963,34 +1979,39 @@ func (d *Debugger) FindLocationSpec(goid int64, frame, deferredCall int, locStr
d.targetMutex.Lock()
defer d.targetMutex.Unlock()
if len(d.target.Targets()) != 1 {
//TODO(aarzilli): if there is more than one target process all must be
//searched and the addresses returned need to specify which target process
//they belong to.
panic("multiple targets not implemented")
}
if _, err := d.target.Valid(); err != nil {
return nil, err
}
return d.findLocation(d.target.Selected, goid, frame, deferredCall, locStr, locSpec, includeNonExecutableLines, substitutePathRules)
return d.findLocation(goid, frame, deferredCall, locStr, locSpec, includeNonExecutableLines, substitutePathRules)
}
func (d *Debugger) findLocation(p *proc.Target, goid int64, frame, deferredCall int, locStr string, locSpec locspec.LocationSpec, includeNonExecutableLines bool, substitutePathRules [][2]string) ([]api.Location, error) {
s, _ := proc.ConvertEvalScope(p, goid, frame, deferredCall)
locs, err := locSpec.Find(p, d.processArgs, s, locStr, includeNonExecutableLines, substitutePathRules)
for i := range locs {
if locs[i].PC == 0 {
continue
func (d *Debugger) findLocation(goid int64, frame, deferredCall int, locStr string, locSpec locspec.LocationSpec, includeNonExecutableLines bool, substitutePathRules [][2]string) ([]api.Location, error) {
locations := []api.Location{}
t := proc.ValidTargets{Group: d.target}
for t.Next() {
pid := t.Pid()
s, _ := proc.ConvertEvalScope(t.Target, goid, frame, deferredCall)
locs, err := locSpec.Find(t.Target, d.processArgs, s, locStr, includeNonExecutableLines, substitutePathRules)
if err != nil {
return nil, err
}
file, line, fn := p.BinInfo().PCToLine(locs[i].PC)
locs[i].File = file
locs[i].Line = line
locs[i].Function = api.ConvertFunction(fn)
for i := range locs {
if locs[i].PC == 0 {
continue
}
file, line, fn := t.BinInfo().PCToLine(locs[i].PC)
locs[i].File = file
locs[i].Line = line
locs[i].Function = api.ConvertFunction(fn)
locs[i].PCPids = make([]int, len(locs[i].PCs))
for j := range locs[i].PCs {
locs[i].PCPids[j] = pid
}
}
locations = append(locations, locs...)
}
return locs, err
return locations, nil
}
// Disassemble code between startPC and endPC.