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
}
// 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
// for core files, as there are no breakpoints in core files.
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
// as you cannot control execution of a core file.
func (p *process) StepInstruction() error {
func (p *process) StepInstruction(int) error {
return ErrContinueCore
}

@ -88,7 +88,8 @@ type functionCallState struct {
}
type callContext struct {
p *Target
grp *TargetGroup
p *Target
// checkEscape is true if the escape check should be performed.
// 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)
scope.callCtx = &callContext{
grp: grp,
p: t,
checkEscape: checkEscape,
retLoadCfg: retLoadCfg,
@ -948,7 +950,8 @@ func funcCallStep(callScope *EvalScope, fncall *functionCallState, thread Thread
fncall.err = fmt.Errorf("could not restore SP: %v", err)
}
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)
}
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
for _, thread := range p.threads {
if thread.CurrentBreakpoint.Breakpoint != nil {
if err := thread.StepInstruction(); err != nil {
if err := thread.stepInstruction(); err != nil {
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)
}
func (p *gdbProcess) StepInstruction(threadID int) error {
return p.threads[threadID].stepInstruction()
}
func (t *gdbThread) ProcessMemory() proc.MemoryReadWriter {
return t.p
}
@ -1543,7 +1547,7 @@ func (t *gdbThread) Common() *proc.CommonThread {
}
// StepInstruction will step exactly 1 CPU instruction.
func (t *gdbThread) StepInstruction() error {
func (t *gdbThread) stepInstruction() error {
pc := t.regs.PC()
if bp, atbp := t.p.breakpoints.M[pc]; atbp && bp.WatchType == 0 {
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.
type ProcessGroup interface {
ContinueOnce(*ContinueOnceContext) (Thread, StopReason, error)
StepInstruction(int) error
Detach(int, bool) error
}

@ -123,7 +123,7 @@ func (t *nativeThread) resume() error {
panic(ErrNativeBackendDisabled)
}
func (t *nativeThread) singleStep() error {
func (*processGroup) singleStep(*nativeThread) error {
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
}
@ -367,7 +368,7 @@ func (dbp *nativeProcess) trapWait(pid int) (*nativeThread, error) {
dbp.firstStart = false
return th, nil
}
if err := th.Continue(); err != nil {
if err := th.resume(); err != nil {
return nil, err
}
continue
@ -421,14 +422,11 @@ func (dbp *nativeProcess) exitGuard(err error) error {
}
func (procgrp *processGroup) resume() error {
return procgrp.procs[0].resume()
}
func (dbp *nativeProcess) resume() error {
dbp := procgrp.procs[0]
// all threads stopped over a breakpoint are made to step over it
for _, thread := range dbp.threads {
if thread.CurrentBreakpoint.Breakpoint != nil {
if err := thread.StepInstruction(); err != nil {
if err := procgrp.stepInstruction(thread); err != nil {
return err
}
thread.CurrentBreakpoint.Clear()

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

@ -490,7 +490,7 @@ func trapWaitInternal(procgrp *processGroup, pid int, options trapWaitOptions) (
dbp.threads[int(wpid)].os.running = false
return nil, nil
}
if err = th.Continue(); err != nil {
if err = th.resume(); err != nil {
if err == sys.ESRCH {
// thread died while we were adding it
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)
}
if err = dbp.threads[int(wpid)].Continue(); err != nil {
if err = dbp.threads[int(wpid)].resume(); err != nil {
if err != sys.ESRCH {
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 is nil we decided we are not interested in debugging this
// process, and we have already detached from it.
err = dbp.threads[dbp.pid].Continue()
err = dbp.threads[dbp.pid].resume()
if err != nil {
return nil, err
}
@ -653,7 +653,7 @@ func (procgrp *processGroup) resume() error {
if valid, _ := dbp.Valid(); valid {
for _, thread := range dbp.threads {
if thread.CurrentBreakpoint.Breakpoint != nil {
if err := thread.StepInstruction(); err != nil {
if err := procgrp.stepInstruction(thread); err != nil {
return err
}
thread.CurrentBreakpoint.Clear()

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

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

@ -51,7 +51,7 @@ func (t *nativeThread) stop() (err error) {
return
}
func (t *nativeThread) singleStep() error {
func (procgrp *processGroup) singleStep(t *nativeThread) error {
kret := C.single_step(t.os.threadAct)
if kret != C.KERN_SUCCESS {
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 {
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
}
func (t *nativeThread) singleStep() (err error) {
func (procgrp *processGroup) singleStep(t *nativeThread) (err error) {
t.dbp.execPtraceFunc(func() { err = ptraceSetStep(t.ID) })
if err != nil {
return err

@ -50,7 +50,7 @@ func (t *nativeThread) resumeWithSig(sig int) (err error) {
return
}
func (t *nativeThread) singleStep() (err error) {
func (procgrp *processGroup) singleStep(t *nativeThread) (err error) {
sig := 0
for {
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 {
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
}
func (t *nativeThread) singleStep() error {
func (procgrp *processGroup) singleStep(t *nativeThread) error {
context := newContext()
context.SetFlags(_CONTEXT_ALL)

@ -324,7 +324,7 @@ func TestStep(t *testing.T) {
regs := getRegisters(p, t)
rip := regs.PC()
err := p.CurrentThread().StepInstruction()
err := grp.StepInstruction()
assertNoError(err, t, "Step()")
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}))
assertNoError(p.CurrentThread().StepInstruction(), t, "SetpInstruction")
assertNoError(grp.StepInstruction(), t, "SetpInstruction")
xmm0 := getReg("after")

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

@ -27,7 +27,6 @@ type Thread interface {
BinInfo() *BinaryInfo
// ProcessMemory returns the process memory.
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(adjustPC bool) error
// SoftExc returns true if this thread received a software exception during the last resume.