proc: move StepInstruction method to TargetGroup (#3488)

Move StepInstruction method to TargetGroup since it will need access to
the process group on Windows to call WaitForDebugEvent.
This commit is contained in:
Alessandro Arzilli 2023-09-20 09:17:45 +02:00 committed by GitHub
parent e0b4bfbed3
commit db7e60aef3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 43 additions and 80 deletions

@ -359,12 +359,6 @@ func (t *thread) BinInfo() *proc.BinaryInfo {
return t.p.bi return t.p.bi
} }
// StepInstruction will only return an error for core files,
// you cannot execute a core file.
func (t *thread) StepInstruction() error {
return ErrContinueCore
}
// SetCurrentBreakpoint will always just return nil // SetCurrentBreakpoint will always just return nil
// for core files, as there are no breakpoints in core files. // for core files, as there are no breakpoints in core files.
func (t *thread) SetCurrentBreakpoint(adjustPC bool) error { func (t *thread) SetCurrentBreakpoint(adjustPC bool) error {
@ -429,7 +423,7 @@ func (*process) ContinueOnce(cctx *proc.ContinueOnceContext) (proc.Thread, proc.
// StepInstruction will always return an error // StepInstruction will always return an error
// as you cannot control execution of a core file. // as you cannot control execution of a core file.
func (p *process) StepInstruction() error { func (p *process) StepInstruction(int) error {
return ErrContinueCore return ErrContinueCore
} }

@ -88,7 +88,8 @@ type functionCallState struct {
} }
type callContext struct { type callContext struct {
p *Target grp *TargetGroup
p *Target
// checkEscape is true if the escape check should be performed. // checkEscape is true if the escape check should be performed.
// See service/api.DebuggerCommand.UnsafeCall in service/api/types.go. // See service/api.DebuggerCommand.UnsafeCall in service/api/types.go.
@ -183,6 +184,7 @@ func EvalExpressionWithCalls(grp *TargetGroup, g *G, expr string, retLoadCfg Loa
continueCompleted := make(chan *G) continueCompleted := make(chan *G)
scope.callCtx = &callContext{ scope.callCtx = &callContext{
grp: grp,
p: t, p: t,
checkEscape: checkEscape, checkEscape: checkEscape,
retLoadCfg: retLoadCfg, retLoadCfg: retLoadCfg,
@ -948,7 +950,8 @@ func funcCallStep(callScope *EvalScope, fncall *functionCallState, thread Thread
fncall.err = fmt.Errorf("could not restore SP: %v", err) fncall.err = fmt.Errorf("could not restore SP: %v", err)
} }
fncallLog("stepping thread %d", thread.ThreadID()) fncallLog("stepping thread %d", thread.ThreadID())
if err := stepInstructionOut(p, thread, debugCallName, debugCallName); err != nil {
if err := stepInstructionOut(callScope.callCtx.grp, p, thread, debugCallName, debugCallName); err != nil {
fncall.err = fmt.Errorf("could not step out of %s: %v", debugCallName, err) fncall.err = fmt.Errorf("could not step out of %s: %v", debugCallName, err)
} }
if bi.Arch.Name == "amd64" { if bi.Arch.Name == "amd64" {

@ -852,7 +852,7 @@ func (p *gdbProcess) ContinueOnce(cctx *proc.ContinueOnceContext) (proc.Thread,
// step threads stopped at any breakpoint over their breakpoint // step threads stopped at any breakpoint over their breakpoint
for _, thread := range p.threads { for _, thread := range p.threads {
if thread.CurrentBreakpoint.Breakpoint != nil { if thread.CurrentBreakpoint.Breakpoint != nil {
if err := thread.StepInstruction(); err != nil { if err := thread.stepInstruction(); err != nil {
return nil, proc.StopUnknown, err return nil, proc.StopUnknown, err
} }
} }
@ -1486,6 +1486,10 @@ func (p *gdbProcess) WriteMemory(addr uint64, data []byte) (written int, err err
return p.conn.writeMemory(addr, data) return p.conn.writeMemory(addr, data)
} }
func (p *gdbProcess) StepInstruction(threadID int) error {
return p.threads[threadID].stepInstruction()
}
func (t *gdbThread) ProcessMemory() proc.MemoryReadWriter { func (t *gdbThread) ProcessMemory() proc.MemoryReadWriter {
return t.p return t.p
} }
@ -1543,7 +1547,7 @@ func (t *gdbThread) Common() *proc.CommonThread {
} }
// StepInstruction will step exactly 1 CPU instruction. // StepInstruction will step exactly 1 CPU instruction.
func (t *gdbThread) StepInstruction() error { func (t *gdbThread) stepInstruction() error {
pc := t.regs.PC() pc := t.regs.PC()
if bp, atbp := t.p.breakpoints.M[pc]; atbp && bp.WatchType == 0 { if bp, atbp := t.p.breakpoints.M[pc]; atbp && bp.WatchType == 0 {
err := t.p.conn.clearBreakpoint(pc, swBreakpoint, t.p.breakpointKind) err := t.p.conn.clearBreakpoint(pc, swBreakpoint, t.p.breakpointKind)

@ -11,6 +11,7 @@ import (
// ProcessGroup is a group of processes that are resumed at the same time. // ProcessGroup is a group of processes that are resumed at the same time.
type ProcessGroup interface { type ProcessGroup interface {
ContinueOnce(*ContinueOnceContext) (Thread, StopReason, error) ContinueOnce(*ContinueOnceContext) (Thread, StopReason, error)
StepInstruction(int) error
Detach(int, bool) error Detach(int, bool) error
} }

@ -123,7 +123,7 @@ func (t *nativeThread) resume() error {
panic(ErrNativeBackendDisabled) panic(ErrNativeBackendDisabled)
} }
func (t *nativeThread) singleStep() error { func (*processGroup) singleStep(*nativeThread) error {
panic(ErrNativeBackendDisabled) panic(ErrNativeBackendDisabled)
} }

@ -109,7 +109,8 @@ func Launch(cmd []string, wd string, flags proc.LaunchFlags, _ []string, _ strin
} }
} }
if err := dbp.resume(); err != nil { procgrp := &processGroup{procs: []*nativeProcess{dbp}}
if err := procgrp.resume(); err != nil {
return nil, err return nil, err
} }
@ -367,7 +368,7 @@ func (dbp *nativeProcess) trapWait(pid int) (*nativeThread, error) {
dbp.firstStart = false dbp.firstStart = false
return th, nil return th, nil
} }
if err := th.Continue(); err != nil { if err := th.resume(); err != nil {
return nil, err return nil, err
} }
continue continue
@ -421,14 +422,11 @@ func (dbp *nativeProcess) exitGuard(err error) error {
} }
func (procgrp *processGroup) resume() error { func (procgrp *processGroup) resume() error {
return procgrp.procs[0].resume() dbp := procgrp.procs[0]
}
func (dbp *nativeProcess) resume() error {
// all threads stopped over a breakpoint are made to step over it // all threads stopped over a breakpoint are made to step over it
for _, thread := range dbp.threads { for _, thread := range dbp.threads {
if thread.CurrentBreakpoint.Breakpoint != nil { if thread.CurrentBreakpoint.Breakpoint != nil {
if err := thread.StepInstruction(); err != nil { if err := procgrp.stepInstruction(thread); err != nil {
return err return err
} }
thread.CurrentBreakpoint.Clear() thread.CurrentBreakpoint.Clear()

@ -430,7 +430,7 @@ func (procgrp *processGroup) resume() error {
// all threads stopped over a breakpoint are made to step over it // all threads stopped over a breakpoint are made to step over it
for _, thread := range dbp.threads { for _, thread := range dbp.threads {
if thread.CurrentBreakpoint.Breakpoint != nil { if thread.CurrentBreakpoint.Breakpoint != nil {
if err := thread.StepInstruction(); err != nil { if err := procgrp.stepInstruction(thread); err != nil {
return err return err
} }
thread.CurrentBreakpoint.Clear() thread.CurrentBreakpoint.Clear()

@ -490,7 +490,7 @@ func trapWaitInternal(procgrp *processGroup, pid int, options trapWaitOptions) (
dbp.threads[int(wpid)].os.running = false dbp.threads[int(wpid)].os.running = false
return nil, nil return nil, nil
} }
if err = th.Continue(); err != nil { if err = th.resume(); err != nil {
if err == sys.ESRCH { if err == sys.ESRCH {
// thread died while we were adding it // thread died while we were adding it
delete(dbp.threads, th.ID) delete(dbp.threads, th.ID)
@ -498,7 +498,7 @@ func trapWaitInternal(procgrp *processGroup, pid int, options trapWaitOptions) (
} }
return nil, fmt.Errorf("could not continue new thread %d %s", cloned, err) return nil, fmt.Errorf("could not continue new thread %d %s", cloned, err)
} }
if err = dbp.threads[int(wpid)].Continue(); err != nil { if err = dbp.threads[int(wpid)].resume(); err != nil {
if err != sys.ESRCH { if err != sys.ESRCH {
return nil, fmt.Errorf("could not continue existing thread %d %s", wpid, err) return nil, fmt.Errorf("could not continue existing thread %d %s", wpid, err)
} }
@ -527,7 +527,7 @@ func trapWaitInternal(procgrp *processGroup, pid int, options trapWaitOptions) (
if tgt != nil { if tgt != nil {
// If tgt is nil we decided we are not interested in debugging this // If tgt is nil we decided we are not interested in debugging this
// process, and we have already detached from it. // process, and we have already detached from it.
err = dbp.threads[dbp.pid].Continue() err = dbp.threads[dbp.pid].resume()
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -653,7 +653,7 @@ func (procgrp *processGroup) resume() error {
if valid, _ := dbp.Valid(); valid { if valid, _ := dbp.Valid(); valid {
for _, thread := range dbp.threads { for _, thread := range dbp.threads {
if thread.CurrentBreakpoint.Breakpoint != nil { if thread.CurrentBreakpoint.Breakpoint != nil {
if err := thread.StepInstruction(); err != nil { if err := procgrp.stepInstruction(thread); err != nil {
return err return err
} }
thread.CurrentBreakpoint.Clear() thread.CurrentBreakpoint.Clear()

@ -504,7 +504,7 @@ func (procgrp *processGroup) resume() error {
dbp := procgrp.procs[0] dbp := procgrp.procs[0]
for _, thread := range dbp.threads { for _, thread := range dbp.threads {
if thread.CurrentBreakpoint.Breakpoint != nil { if thread.CurrentBreakpoint.Breakpoint != nil {
if err := thread.StepInstruction(); err != nil { if err := procgrp.stepInstruction(thread); err != nil {
return err return err
} }
thread.CurrentBreakpoint.Clear() thread.CurrentBreakpoint.Clear()

@ -22,13 +22,17 @@ type nativeThread struct {
common proc.CommonThread common proc.CommonThread
} }
func (procgrp *processGroup) StepInstruction(threadID int) error {
return procgrp.stepInstruction(procgrp.procForThread(threadID).threads[threadID])
}
// StepInstruction steps a single instruction. // StepInstruction steps a single instruction.
// //
// Executes exactly one instruction and then returns. // Executes exactly one instruction and then returns.
// If the thread is at a breakpoint, we first clear it, // If the thread is at a breakpoint, we first clear it,
// execute the instruction, and then replace the breakpoint. // execute the instruction, and then replace the breakpoint.
// Otherwise we simply execute the next instruction. // Otherwise we simply execute the next instruction.
func (t *nativeThread) StepInstruction() (err error) { func (procgrp *processGroup) stepInstruction(t *nativeThread) (err error) {
t.singleStepping = true t.singleStepping = true
defer func() { defer func() {
t.singleStepping = false t.singleStepping = false
@ -63,7 +67,7 @@ func (t *nativeThread) StepInstruction() (err error) {
}() }()
} }
err = t.singleStep() err = procgrp.singleStep(t)
if err != nil { if err != nil {
if _, exited := err.(proc.ErrProcessExited); exited { if _, exited := err.(proc.ErrProcessExited); exited {
return err return err

@ -51,7 +51,7 @@ func (t *nativeThread) stop() (err error) {
return return
} }
func (t *nativeThread) singleStep() error { func (procgrp *processGroup) singleStep(t *nativeThread) error {
kret := C.single_step(t.os.threadAct) kret := C.single_step(t.os.threadAct)
if kret != C.KERN_SUCCESS { if kret != C.KERN_SUCCESS {
return fmt.Errorf("could not single step") return fmt.Errorf("could not single step")
@ -143,23 +143,3 @@ func (t *nativeThread) withDebugRegisters(f func(*amd64util.DebugRegisters) erro
func (t *nativeThread) SoftExc() bool { func (t *nativeThread) SoftExc() bool {
return false return false
} }
// Continue the execution of this thread.
//
// If we are currently at a breakpoint, we'll clear it
// first and then resume execution. Thread will continue until
// it hits a breakpoint or is signaled.
func (t *nativeThread) Continue() error {
pc, err := t.PC()
if err != nil {
return err
}
// Check whether we are stopped at a breakpoint, and
// if so, single step over it before continuing.
if _, ok := t.dbp.FindBreakpoint(pc, false); ok {
if err := t.StepInstruction(); err != nil {
return err
}
}
return t.resume()
}

@ -17,7 +17,7 @@ type osSpecificDetails struct {
registers sys.Reg registers sys.Reg
} }
func (t *nativeThread) singleStep() (err error) { func (procgrp *processGroup) singleStep(t *nativeThread) (err error) {
t.dbp.execPtraceFunc(func() { err = ptraceSetStep(t.ID) }) t.dbp.execPtraceFunc(func() { err = ptraceSetStep(t.ID) })
if err != nil { if err != nil {
return err return err

@ -50,7 +50,7 @@ func (t *nativeThread) resumeWithSig(sig int) (err error) {
return return
} }
func (t *nativeThread) singleStep() (err error) { func (procgrp *processGroup) singleStep(t *nativeThread) (err error) {
sig := 0 sig := 0
for { for {
t.dbp.execPtraceFunc(func() { err = ptraceSingleStep(t.ID, sig) }) t.dbp.execPtraceFunc(func() { err = ptraceSingleStep(t.ID, sig) })
@ -123,23 +123,3 @@ func (t *nativeThread) ReadMemory(data []byte, addr uint64) (n int, err error) {
func (t *nativeThread) SoftExc() bool { func (t *nativeThread) SoftExc() bool {
return t.os.setbp return t.os.setbp
} }
// Continue the execution of this thread.
//
// If we are currently at a breakpoint, we'll clear it
// first and then resume execution. Thread will continue until
// it hits a breakpoint or is signaled.
func (t *nativeThread) Continue() error {
pc, err := t.PC()
if err != nil {
return err
}
// Check whether we are stopped at a breakpoint, and
// if so, single step over it before continuing.
if _, ok := t.dbp.FindBreakpoint(pc, false); ok {
if err := t.StepInstruction(); err != nil {
return err
}
}
return t.resume()
}

@ -23,7 +23,7 @@ type osSpecificDetails struct {
setbp bool setbp bool
} }
func (t *nativeThread) singleStep() error { func (procgrp *processGroup) singleStep(t *nativeThread) error {
context := newContext() context := newContext()
context.SetFlags(_CONTEXT_ALL) context.SetFlags(_CONTEXT_ALL)

@ -324,7 +324,7 @@ func TestStep(t *testing.T) {
regs := getRegisters(p, t) regs := getRegisters(p, t)
rip := regs.PC() rip := regs.PC()
err := p.CurrentThread().StepInstruction() err := grp.StepInstruction()
assertNoError(err, t, "Step()") assertNoError(err, t, "Step()")
regs = getRegisters(p, t) regs = getRegisters(p, t)
@ -5765,7 +5765,7 @@ func TestSetYMMRegister(t *testing.T) {
0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44})) 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44}))
assertNoError(p.CurrentThread().StepInstruction(), t, "SetpInstruction") assertNoError(grp.StepInstruction(), t, "SetpInstruction")
xmm0 := getReg("after") xmm0 := getReg("after")

@ -145,7 +145,7 @@ func (grp *TargetGroup) Continue() error {
if callErrThis != nil && callErr == nil { if callErrThis != nil && callErr == nil {
callErr = callErrThis callErr = callErrThis
} }
hcbpErrThis := dbp.handleHardcodedBreakpoints(trapthread, threads) hcbpErrThis := dbp.handleHardcodedBreakpoints(grp, trapthread, threads)
if hcbpErrThis != nil && hcbpErr == nil { if hcbpErrThis != nil && hcbpErr == nil {
hcbpErr = hcbpErrThis hcbpErr = hcbpErrThis
} }
@ -396,10 +396,10 @@ func disassembleCurrentInstruction(p Process, thread Thread, off int64) ([]AsmIn
// function is neither fnname1 or fnname2. // function is neither fnname1 or fnname2.
// This function is used to step out of runtime.Breakpoint as well as // This function is used to step out of runtime.Breakpoint as well as
// runtime.debugCallV1. // runtime.debugCallV1.
func stepInstructionOut(dbp *Target, curthread Thread, fnname1, fnname2 string) error { func stepInstructionOut(grp *TargetGroup, dbp *Target, curthread Thread, fnname1, fnname2 string) error {
defer dbp.ClearCaches() defer dbp.ClearCaches()
for { for {
if err := curthread.StepInstruction(); err != nil { if err := grp.procgrp.StepInstruction(curthread.ThreadID()); err != nil {
return err return err
} }
loc, err := curthread.Location() loc, err := curthread.Location()
@ -577,7 +577,7 @@ func (grp *TargetGroup) StepInstruction() (err error) {
if ok, err := dbp.Valid(); !ok { if ok, err := dbp.Valid(); !ok {
return err return err
} }
err = thread.StepInstruction() err = grp.procgrp.StepInstruction(thread.ThreadID())
if err != nil { if err != nil {
return err return err
} }
@ -1285,7 +1285,7 @@ func (t *Target) clearHardcodedBreakpoints() {
// program's text) and sets a fake breakpoint on them with logical id // program's text) and sets a fake breakpoint on them with logical id
// hardcodedBreakpointID. // hardcodedBreakpointID.
// It checks trapthread and all threads that have SoftExc returning true. // It checks trapthread and all threads that have SoftExc returning true.
func (t *Target) handleHardcodedBreakpoints(trapthread Thread, threads []Thread) error { func (t *Target) handleHardcodedBreakpoints(grp *TargetGroup, trapthread Thread, threads []Thread) error {
mem := t.Memory() mem := t.Memory()
arch := t.BinInfo().Arch arch := t.BinInfo().Arch
recorded, _ := t.recman.Recorded() recorded, _ := t.recman.Recorded()
@ -1366,7 +1366,7 @@ func (t *Target) handleHardcodedBreakpoints(trapthread Thread, threads []Thread)
// runtime.Breakpoint. // runtime.Breakpoint.
// On go < 1.8 it was sufficient to single-step twice on go1.8 a change // On go < 1.8 it was sufficient to single-step twice on go1.8 a change
// to the compiler requires 4 steps. // to the compiler requires 4 steps.
if err := stepInstructionOut(t, thread, "runtime.breakpoint", "runtime.Breakpoint"); err != nil { if err := stepInstructionOut(grp, t, thread, "runtime.breakpoint", "runtime.Breakpoint"); err != nil {
return err return err
} }
setHardcodedBreakpoint(thread, loc) setHardcodedBreakpoint(thread, loc)

@ -27,7 +27,6 @@ type Thread interface {
BinInfo() *BinaryInfo BinInfo() *BinaryInfo
// ProcessMemory returns the process memory. // ProcessMemory returns the process memory.
ProcessMemory() MemoryReadWriter ProcessMemory() MemoryReadWriter
StepInstruction() error
// SetCurrentBreakpoint updates the current breakpoint of this thread, if adjustPC is true also checks for breakpoints that were just hit (this should only be passed true after a thread resume) // SetCurrentBreakpoint updates the current breakpoint of this thread, if adjustPC is true also checks for breakpoints that were just hit (this should only be passed true after a thread resume)
SetCurrentBreakpoint(adjustPC bool) error SetCurrentBreakpoint(adjustPC bool) error
// SoftExc returns true if this thread received a software exception during the last resume. // SoftExc returns true if this thread received a software exception during the last resume.