proc/native: fix flakyness of TestStepConcurrentDirect on linux/386 (#2179)

TestStepConcurrentDirect will occasionally fail (7% of the time on my
setup) by either causing the target processs to execute an invalid
instruction or (more infrequently) by switching to the wrong thread.

Both of those are caused by receiving SIGTRAPs for threads hitting a
breakpoint after it has been removed (the thread hits the breakpoint,
we stop everything and remove the breakpoint and only after we receive
the signal).

Change native.(*nativeProcess).stop to handle SIGTRAPs that can't be
attributed to a breakpoint, a hardcoded breakpoint in the program's
text, or manual stops (and therefore are likely caused by phantom
breakpoint hits).

Co-authored-by: a <a@kra>
This commit is contained in:
Alessandro Arzilli 2020-11-03 19:28:37 +01:00 committed by GitHub
parent 1f552c5a4c
commit e69d536e81
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 119 additions and 53 deletions

@ -13,6 +13,7 @@ type Arch struct {
maxInstructionLength int maxInstructionLength int
prologues []opcodeSeq prologues []opcodeSeq
breakpointInstruction []byte breakpointInstruction []byte
altBreakpointInstruction []byte
breakInstrMovesPC bool breakInstrMovesPC bool
derefTLS bool derefTLS bool
usesLR bool // architecture uses a link register, also called RA on some architectures usesLR bool // architecture uses a link register, also called RA on some architectures
@ -66,6 +67,11 @@ func (a *Arch) BreakpointInstruction() []byte {
return a.breakpointInstruction return a.breakpointInstruction
} }
// AltBreakpointInstruction returns an alternate encoding for the breakpoint instruction.
func (a *Arch) AltBreakpointInstruction() []byte {
return a.altBreakpointInstruction
}
// BreakInstrMovesPC is true if hitting the breakpoint instruction advances the // BreakInstrMovesPC is true if hitting the breakpoint instruction advances the
// instruction counter by the size of the breakpoint instruction. // instruction counter by the size of the breakpoint instruction.
func (a *Arch) BreakInstrMovesPC() bool { func (a *Arch) BreakInstrMovesPC() bool {

@ -24,6 +24,7 @@ func I386Arch(goos string) *Arch {
ptrSize: 4, ptrSize: 4,
maxInstructionLength: 15, maxInstructionLength: 15,
breakpointInstruction: i386BreakInstruction, breakpointInstruction: i386BreakInstruction,
altBreakpointInstruction: []byte{0xcd, 0x03},
breakInstrMovesPC: true, breakInstrMovesPC: true,
derefTLS: false, derefTLS: false,
prologues: prologuesI386, prologues: prologuesI386,

@ -59,7 +59,7 @@ func (dbp *nativeProcess) trapWait(pid int) (*nativeThread, error) {
panic(ErrNativeBackendDisabled) panic(ErrNativeBackendDisabled)
} }
func (dbp *nativeProcess) stop(trapthread *nativeThread) (err error) { func (dbp *nativeProcess) stop(trapthread *nativeThread) (*nativeThread, error) {
panic(ErrNativeBackendDisabled) panic(ErrNativeBackendDisabled)
} }

@ -231,6 +231,8 @@ func (dbp *nativeProcess) ContinueOnce() (proc.Thread, proc.StopReason, error) {
return nil, proc.StopExited, &proc.ErrProcessExited{Pid: dbp.Pid()} return nil, proc.StopExited, &proc.ErrProcessExited{Pid: dbp.Pid()}
} }
for {
if err := dbp.resume(); err != nil { if err := dbp.resume(); err != nil {
return nil, proc.StopUnknown, err return nil, proc.StopUnknown, err
} }
@ -248,10 +250,14 @@ func (dbp *nativeProcess) ContinueOnce() (proc.Thread, proc.StopReason, error) {
if err != nil { if err != nil {
return nil, proc.StopUnknown, err return nil, proc.StopUnknown, err
} }
if err := dbp.stop(trapthread); err != nil { trapthread, err = dbp.stop(trapthread)
if err != nil {
return nil, proc.StopUnknown, err return nil, proc.StopUnknown, err
} }
return trapthread, proc.StopUnknown, err if trapthread != nil {
return trapthread, proc.StopUnknown, nil
}
}
} }
// FindBreakpoint finds the breakpoint for the given pc. // FindBreakpoint finds the breakpoint for the given pc.

@ -113,7 +113,7 @@ func Launch(cmd []string, wd string, flags proc.LaunchFlags, _ []string, _ strin
if err != nil { if err != nil {
return nil, err return nil, err
} }
if err := dbp.stop(nil); err != nil { if _, err := dbp.stop(nil); err != nil {
return nil, err return nil, err
} }
@ -422,35 +422,35 @@ func (dbp *nativeProcess) resume() error {
} }
// stop stops all running threads and sets breakpoints // stop stops all running threads and sets breakpoints
func (dbp *nativeProcess) stop(trapthread *nativeThread) (err error) { func (dbp *nativeProcess) stop(trapthread *nativeThread) (*nativeThread, error) {
if dbp.exited { if dbp.exited {
return &proc.ErrProcessExited{Pid: dbp.Pid()} return nil, &proc.ErrProcessExited{Pid: dbp.Pid()}
} }
for _, th := range dbp.threads { for _, th := range dbp.threads {
if !th.Stopped() { if !th.Stopped() {
if err := th.stop(); err != nil { if err := th.stop(); err != nil {
return dbp.exitGuard(err) return nil, dbp.exitGuard(err)
} }
} }
} }
ports, err := dbp.waitForStop() ports, err := dbp.waitForStop()
if err != nil { if err != nil {
return err return nil, err
} }
if !dbp.os.initialized { if !dbp.os.initialized {
return nil return nil, nil
} }
trapthread.SetCurrentBreakpoint(true) trapthread.SetCurrentBreakpoint(true)
for _, port := range ports { for _, port := range ports {
if th, ok := dbp.threads[port]; ok { if th, ok := dbp.threads[port]; ok {
err := th.SetCurrentBreakpoint(true) err := th.SetCurrentBreakpoint(true)
if err != nil { if err != nil {
return err return nil, err
} }
} }
} }
return nil return trapthread, nil
} }
func (dbp *nativeProcess) detach(kill bool) error { func (dbp *nativeProcess) detach(kill bool) error {

@ -347,19 +347,19 @@ func (dbp *nativeProcess) resume() error {
// Used by ContinueOnce // Used by ContinueOnce
// stop stops all running threads and sets breakpoints // stop stops all running threads and sets breakpoints
func (dbp *nativeProcess) stop(trapthread *nativeThread) (err error) { func (dbp *nativeProcess) stop(trapthread *nativeThread) (*nativeThread, error) {
if dbp.exited { if dbp.exited {
return &proc.ErrProcessExited{Pid: dbp.Pid()} return nil, &proc.ErrProcessExited{Pid: dbp.Pid()}
} }
// set breakpoints on all threads // set breakpoints on all threads
for _, th := range dbp.threads { for _, th := range dbp.threads {
if th.CurrentBreakpoint.Breakpoint == nil { if th.CurrentBreakpoint.Breakpoint == nil {
if err := th.SetCurrentBreakpoint(true); err != nil { if err := th.SetCurrentBreakpoint(true); err != nil {
return err return nil, err
} }
} }
} }
return nil return trapthread, nil
} }
// Used by Detach // Used by Detach

@ -486,9 +486,9 @@ func (dbp *nativeProcess) resume() error {
} }
// stop stops all running threads and sets breakpoints // stop stops all running threads and sets breakpoints
func (dbp *nativeProcess) stop(trapthread *nativeThread) (err error) { func (dbp *nativeProcess) stop(trapthread *nativeThread) (*nativeThread, error) {
if dbp.exited { if dbp.exited {
return &proc.ErrProcessExited{Pid: dbp.Pid()} return nil, &proc.ErrProcessExited{Pid: dbp.Pid()}
} }
for _, th := range dbp.threads { for _, th := range dbp.threads {
@ -500,7 +500,7 @@ func (dbp *nativeProcess) stop(trapthread *nativeThread) (err error) {
for { for {
th, err := dbp.trapWaitInternal(-1, trapWaitNohang) th, err := dbp.trapWaitInternal(-1, trapWaitNohang)
if err != nil { if err != nil {
return dbp.exitGuard(err) return nil, dbp.exitGuard(err)
} }
if th == nil { if th == nil {
break break
@ -511,7 +511,7 @@ func (dbp *nativeProcess) stop(trapthread *nativeThread) (err error) {
for _, th := range dbp.threads { for _, th := range dbp.threads {
if th.os.running { if th.os.running {
if err := th.stop(); err != nil { if err := th.stop(); err != nil {
return dbp.exitGuard(err) return nil, dbp.exitGuard(err)
} }
} }
} }
@ -530,23 +530,76 @@ func (dbp *nativeProcess) stop(trapthread *nativeThread) (err error) {
} }
_, err := dbp.trapWaitInternal(-1, trapWaitHalt) _, err := dbp.trapWaitInternal(-1, trapWaitHalt)
if err != nil { if err != nil {
return err return nil, err
} }
} }
if err := linutil.ElfUpdateSharedObjects(dbp); err != nil { if err := linutil.ElfUpdateSharedObjects(dbp); err != nil {
return err return nil, err
} }
switchTrapthread := false
// set breakpoints on SIGTRAP threads // set breakpoints on SIGTRAP threads
for _, th := range dbp.threads { for _, th := range dbp.threads {
if th.CurrentBreakpoint.Breakpoint == nil && th.os.setbp { if th.CurrentBreakpoint.Breakpoint == nil && th.os.setbp {
if err := th.SetCurrentBreakpoint(true); err != nil { if err := th.SetCurrentBreakpoint(true); err != nil {
return err return nil, err
}
}
if th.CurrentBreakpoint.Breakpoint == nil && th.os.setbp && (th.Status != nil) && ((*sys.WaitStatus)(th.Status).StopSignal() == sys.SIGTRAP) && dbp.BinInfo().Arch.BreakInstrMovesPC() {
manualStop := false
if th.ThreadID() == trapthread.ThreadID() {
dbp.stopMu.Lock()
manualStop = dbp.manualStopRequested
dbp.stopMu.Unlock()
}
if !manualStop {
// Thread received a SIGTRAP but we don't have a breakpoint for it and
// it wasn't sent by a manual stop request. It's either a hardcoded
// breakpoint or a phantom breakpoint hit (a breakpoint that was hit but
// we have removed before we could receive its signal). Check if it is a
// hardcoded breakpoint, otherwise rewind the thread.
isHardcodedBreakpoint := false
pc, _ := th.PC()
for _, bpinstr := range [][]byte{
dbp.BinInfo().Arch.BreakpointInstruction(),
dbp.BinInfo().Arch.AltBreakpointInstruction()} {
if bpinstr == nil {
continue
}
buf := make([]byte, len(bpinstr))
_, _ = th.ReadMemory(buf, pc-uint64(len(buf)))
if bytes.Equal(buf, bpinstr) {
isHardcodedBreakpoint = true
break
}
}
if !isHardcodedBreakpoint {
// phantom breakpoint hit
_ = th.SetPC(pc - uint64(len(dbp.BinInfo().Arch.BreakpointInstruction())))
th.os.setbp = false
if trapthread.ThreadID() == th.ThreadID() {
// Will switch to a different thread for trapthread because we don't
// want pkg/proc to believe that this thread was stopped by a
// hardcoded breakpoint.
switchTrapthread = true
} }
} }
} }
return nil }
}
if switchTrapthread {
trapthread = nil
for _, th := range dbp.threads {
if th.os.setbp && th.ThreadID() != trapthread.ThreadID() {
return th, nil
}
}
}
return trapthread, nil
} }
func (dbp *nativeProcess) detach(kill bool) error { func (dbp *nativeProcess) detach(kill bool) error {

@ -407,9 +407,9 @@ func (dbp *nativeProcess) resume() error {
} }
// stop stops all running threads threads and sets breakpoints // stop stops all running threads threads and sets breakpoints
func (dbp *nativeProcess) stop(trapthread *nativeThread) (err error) { func (dbp *nativeProcess) stop(trapthread *nativeThread) (*nativeThread, error) {
if dbp.exited { if dbp.exited {
return &proc.ErrProcessExited{Pid: dbp.Pid()} return nil, &proc.ErrProcessExited{Pid: dbp.Pid()}
} }
dbp.os.running = false dbp.os.running = false
@ -424,15 +424,15 @@ func (dbp *nativeProcess) stop(trapthread *nativeThread) (err error) {
// call to _ContinueDebugEvent will resume execution of some of the // call to _ContinueDebugEvent will resume execution of some of the
// target threads. // target threads.
err = trapthread.SetCurrentBreakpoint(true) err := trapthread.SetCurrentBreakpoint(true)
if err != nil { if err != nil {
return err return nil, err
} }
for _, thread := range dbp.threads { for _, thread := range dbp.threads {
_, err := _SuspendThread(thread.os.hThread) _, err := _SuspendThread(thread.os.hThread)
if err != nil { if err != nil {
return err return nil, err
} }
} }
@ -446,18 +446,18 @@ func (dbp *nativeProcess) stop(trapthread *nativeThread) (err error) {
} }
}) })
if err != nil { if err != nil {
return err return nil, err
} }
if tid == 0 { if tid == 0 {
break break
} }
err = dbp.threads[tid].SetCurrentBreakpoint(true) err = dbp.threads[tid].SetCurrentBreakpoint(true)
if err != nil { if err != nil {
return err return nil, err
} }
} }
return nil return trapthread, nil
} }
func (dbp *nativeProcess) detach(kill bool) error { func (dbp *nativeProcess) detach(kill bool) error {