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:
parent
1f552c5a4c
commit
e69d536e81
@ -13,6 +13,7 @@ type Arch struct {
|
||||
maxInstructionLength int
|
||||
prologues []opcodeSeq
|
||||
breakpointInstruction []byte
|
||||
altBreakpointInstruction []byte
|
||||
breakInstrMovesPC bool
|
||||
derefTLS bool
|
||||
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
|
||||
}
|
||||
|
||||
// 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
|
||||
// instruction counter by the size of the breakpoint instruction.
|
||||
func (a *Arch) BreakInstrMovesPC() bool {
|
||||
|
@ -24,6 +24,7 @@ func I386Arch(goos string) *Arch {
|
||||
ptrSize: 4,
|
||||
maxInstructionLength: 15,
|
||||
breakpointInstruction: i386BreakInstruction,
|
||||
altBreakpointInstruction: []byte{0xcd, 0x03},
|
||||
breakInstrMovesPC: true,
|
||||
derefTLS: false,
|
||||
prologues: prologuesI386,
|
||||
|
@ -59,7 +59,7 @@ func (dbp *nativeProcess) trapWait(pid int) (*nativeThread, error) {
|
||||
panic(ErrNativeBackendDisabled)
|
||||
}
|
||||
|
||||
func (dbp *nativeProcess) stop(trapthread *nativeThread) (err error) {
|
||||
func (dbp *nativeProcess) stop(trapthread *nativeThread) (*nativeThread, error) {
|
||||
panic(ErrNativeBackendDisabled)
|
||||
}
|
||||
|
||||
|
@ -231,6 +231,8 @@ func (dbp *nativeProcess) ContinueOnce() (proc.Thread, proc.StopReason, error) {
|
||||
return nil, proc.StopExited, &proc.ErrProcessExited{Pid: dbp.Pid()}
|
||||
}
|
||||
|
||||
for {
|
||||
|
||||
if err := dbp.resume(); err != nil {
|
||||
return nil, proc.StopUnknown, err
|
||||
}
|
||||
@ -248,10 +250,14 @@ func (dbp *nativeProcess) ContinueOnce() (proc.Thread, proc.StopReason, error) {
|
||||
if err != nil {
|
||||
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 trapthread, proc.StopUnknown, err
|
||||
if trapthread != nil {
|
||||
return trapthread, proc.StopUnknown, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return nil, err
|
||||
}
|
||||
if err := dbp.stop(nil); err != nil {
|
||||
if _, err := dbp.stop(nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@ -422,35 +422,35 @@ func (dbp *nativeProcess) resume() error {
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return &proc.ErrProcessExited{Pid: dbp.Pid()}
|
||||
return nil, &proc.ErrProcessExited{Pid: dbp.Pid()}
|
||||
}
|
||||
for _, th := range dbp.threads {
|
||||
if !th.Stopped() {
|
||||
if err := th.stop(); err != nil {
|
||||
return dbp.exitGuard(err)
|
||||
return nil, dbp.exitGuard(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ports, err := dbp.waitForStop()
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
if !dbp.os.initialized {
|
||||
return nil
|
||||
return nil, nil
|
||||
}
|
||||
trapthread.SetCurrentBreakpoint(true)
|
||||
for _, port := range ports {
|
||||
if th, ok := dbp.threads[port]; ok {
|
||||
err := th.SetCurrentBreakpoint(true)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return trapthread, nil
|
||||
}
|
||||
|
||||
func (dbp *nativeProcess) detach(kill bool) error {
|
||||
|
@ -347,19 +347,19 @@ func (dbp *nativeProcess) resume() error {
|
||||
|
||||
// Used by ContinueOnce
|
||||
// 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 {
|
||||
return &proc.ErrProcessExited{Pid: dbp.Pid()}
|
||||
return nil, &proc.ErrProcessExited{Pid: dbp.Pid()}
|
||||
}
|
||||
// set breakpoints on all threads
|
||||
for _, th := range dbp.threads {
|
||||
if th.CurrentBreakpoint.Breakpoint == nil {
|
||||
if err := th.SetCurrentBreakpoint(true); err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return trapthread, nil
|
||||
}
|
||||
|
||||
// Used by Detach
|
||||
|
@ -486,9 +486,9 @@ func (dbp *nativeProcess) resume() error {
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return &proc.ErrProcessExited{Pid: dbp.Pid()}
|
||||
return nil, &proc.ErrProcessExited{Pid: dbp.Pid()}
|
||||
}
|
||||
|
||||
for _, th := range dbp.threads {
|
||||
@ -500,7 +500,7 @@ func (dbp *nativeProcess) stop(trapthread *nativeThread) (err error) {
|
||||
for {
|
||||
th, err := dbp.trapWaitInternal(-1, trapWaitNohang)
|
||||
if err != nil {
|
||||
return dbp.exitGuard(err)
|
||||
return nil, dbp.exitGuard(err)
|
||||
}
|
||||
if th == nil {
|
||||
break
|
||||
@ -511,7 +511,7 @@ func (dbp *nativeProcess) stop(trapthread *nativeThread) (err error) {
|
||||
for _, th := range dbp.threads {
|
||||
if th.os.running {
|
||||
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)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if err := linutil.ElfUpdateSharedObjects(dbp); err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switchTrapthread := false
|
||||
|
||||
// set breakpoints on SIGTRAP threads
|
||||
for _, th := range dbp.threads {
|
||||
if th.CurrentBreakpoint.Breakpoint == nil && th.os.setbp {
|
||||
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 {
|
||||
|
@ -407,9 +407,9 @@ func (dbp *nativeProcess) resume() error {
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return &proc.ErrProcessExited{Pid: dbp.Pid()}
|
||||
return nil, &proc.ErrProcessExited{Pid: dbp.Pid()}
|
||||
}
|
||||
|
||||
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
|
||||
// target threads.
|
||||
|
||||
err = trapthread.SetCurrentBreakpoint(true)
|
||||
err := trapthread.SetCurrentBreakpoint(true)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, thread := range dbp.threads {
|
||||
_, err := _SuspendThread(thread.os.hThread)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
@ -446,18 +446,18 @@ func (dbp *nativeProcess) stop(trapthread *nativeThread) (err error) {
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
if tid == 0 {
|
||||
break
|
||||
}
|
||||
err = dbp.threads[tid].SetCurrentBreakpoint(true)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
return trapthread, nil
|
||||
}
|
||||
|
||||
func (dbp *nativeProcess) detach(kill bool) error {
|
||||
|
Loading…
Reference in New Issue
Block a user