aboutsummaryrefslogtreecommitdiff
path: root/gdb/gdbserver
diff options
context:
space:
mode:
authorPedro Alves <palves@redhat.com>2015-03-19 15:12:33 +0000
committerPedro Alves <palves@redhat.com>2015-03-19 17:07:38 +0000
commit23f238d3456531db33456918f004dcc5ce151363 (patch)
tree80014f14ebd0d280d992f4cb399045b9c1d7b373 /gdb/gdbserver
parent7d1551f618db01514123433d358ae732d47ad629 (diff)
downloadgdb-23f238d3456531db33456918f004dcc5ce151363.zip
gdb-23f238d3456531db33456918f004dcc5ce151363.tar.gz
gdb-23f238d3456531db33456918f004dcc5ce151363.tar.bz2
Fix race exposed by gdb.threads/killed.exp
On GNU/Linux, this test sometimes FAILs like this: (gdb) run Starting program: /home/pedro/gdb/mygit/build/gdb/testsuite/gdb.threads/killed [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib64/libthread_db.so.1". ptrace: No such process. (gdb) Program terminated with signal SIGKILL, Killed. The program no longer exists. FAIL: gdb.threads/killed.exp: run program to completion (timeout) Note the suspicious "No such process" line (that's errno==ESRCH). Adding debug output we see: linux_nat_wait: [process -1], [TARGET_WNOHANG] LLW: enter LNW: waitpid(-1, ...) returned 18465, ERRNO-OK LLW: waitpid 18465 received Stopped (signal) (stopped) LNW: waitpid(-1, ...) returned 18461, ERRNO-OK LLW: waitpid 18461 received Trace/breakpoint trap (stopped) LLW: Handling extended status 0x03057f LHEW: Got clone event from LWP 18461, new child is LWP 18465 LNW: waitpid(-1, ...) returned 0, ERRNO-OK RSRL: resuming stopped-resumed LWP LWP 18465 at 0x3b36af4b51: step=0 RSRL: resuming stopped-resumed LWP LWP 18461 at 0x3b36af4b51: step=0 sigchld ptrace: No such process. (gdb) linux_nat_wait: [process -1], [TARGET_WNOHANG] LLW: enter LNW: waitpid(-1, ...) returned 18465, ERRNO-OK LLW: waitpid 18465 received Killed (terminated) LLW: LWP 18465 exited. LNW: waitpid(-1, ...) returned 18461, No child processes LLW: waitpid 18461 received Killed (terminated) Process 18461 exited LNW: waitpid(-1, ...) returned -1, No child processes LLW: exit sigchld infrun: target_wait (-1, status) = infrun: 18461 [process 18461], infrun: status->kind = signalled, signal = GDB_SIGNAL_KILL infrun: TARGET_WAITKIND_SIGNALLED Program terminated with signal SIGKILL, Killed. The program no longer exists. infrun: stop_waiting FAIL: gdb.threads/killed.exp: run program to completion (timeout) The issue is that here: RSRL: resuming stopped-resumed LWP LWP 18465 at 0x3b36af4b51: step=0 RSRL: resuming stopped-resumed LWP LWP 18461 at 0x3b36af4b51: step=0 The first line shows we had just resumed LWP 18465, which does: void * child_func (void *dummy) { kill (pid, SIGKILL); exit (1); } So if the kernel manages to schedule that thread fast enough, the process may be killed before GDB has a chance to resume LWP 18461. GDBserver has code at the tail end of linux_resume_one_lwp to cope with this: ~~~ ptrace (step ? PTRACE_SINGLESTEP : PTRACE_CONT, lwpid_of (thread), (PTRACE_TYPE_ARG3) 0, /* Coerce to a uintptr_t first to avoid potential gcc warning of coercing an 8 byte integer to a 4 byte pointer. */ (PTRACE_TYPE_ARG4) (uintptr_t) signal); current_thread = saved_thread; if (errno) { /* ESRCH from ptrace either means that the thread was already running (an error) or that it is gone (a race condition). If it's gone, we will get a notification the next time we wait, so we can ignore the error. We could differentiate these two, but it's tricky without waiting; the thread still exists as a zombie, so sending it signal 0 would succeed. So just ignore ESRCH. */ if (errno == ESRCH) return; perror_with_name ("ptrace"); } ~~~ However, that's not a complete fix, because between starting to handle the resume request and getting that PTRACE_CONTINUE, we run other ptrace calls that can also fail with ESRCH, and that end up throwing an error (with perror_with_name). In the case above, I indeed sometimes see resume_stopped_resumed_lwps fail in the registers read: resume_stopped_resumed_lwps (struct lwp_info *lp, void *data) { ... CORE_ADDR pc = regcache_read_pc (regcache); Or e.g., in 32-bit mode, i386_linux_resume has several calls that can throw too. Whether to ignore ptrace errors or not depends on context that is only available somewhere up the call chain. So the fix is to let ptrace errors throw as they do today, and wrap the resume request in a TRY/CATCH that swallows it iff the lwp that we were trying to resume is no longer ptrace-stopped. gdb/gdbserver/ChangeLog: 2015-03-19 Pedro Alves <palves@redhat.com> * linux-low.c (linux_resume_one_lwp): Rename to ... (linux_resume_one_lwp_throw): ... this. Don't handle ESRCH here, instead call perror_with_name. (check_ptrace_stopped_lwp_gone): New function. (linux_resume_one_lwp): Reimplement as wrapper around linux_resume_one_lwp_throw that swallows errors if the LWP is gone. gdb/ChangeLog: 2015-03-19 Pedro Alves <palves@redhat.com> * linux-nat.c (linux_resume_one_lwp): Rename to ... (linux_resume_one_lwp_throw): ... this. Don't handle ESRCH here, instead call perror_with_name. (check_ptrace_stopped_lwp_gone): New function. (linux_resume_one_lwp): Reimplement as wrapper around linux_resume_one_lwp_throw that swallows errors if the LWP is gone. (resume_stopped_resumed_lwps): Try register reads in TRY/CATCH and swallows errors if the LWP is gone. Use linux_resume_one_lwp_throw instead of linux_resume_one_lwp.
Diffstat (limited to 'gdb/gdbserver')
-rw-r--r--gdb/gdbserver/ChangeLog10
-rw-r--r--gdb/gdbserver/linux-low.c80
2 files changed, 73 insertions, 17 deletions
diff --git a/gdb/gdbserver/ChangeLog b/gdb/gdbserver/ChangeLog
index 0383e67..cbd199b 100644
--- a/gdb/gdbserver/ChangeLog
+++ b/gdb/gdbserver/ChangeLog
@@ -1,5 +1,15 @@
2015-03-19 Pedro Alves <palves@redhat.com>
+ * linux-low.c (linux_resume_one_lwp): Rename to ...
+ (linux_resume_one_lwp_throw): ... this. Don't handle ESRCH here,
+ instead call perror_with_name.
+ (check_ptrace_stopped_lwp_gone): New function.
+ (linux_resume_one_lwp): Reimplement as wrapper around
+ linux_resume_one_lwp_throw that swallows errors if the LWP is
+ gone.
+
+2015-03-19 Pedro Alves <palves@redhat.com>
+
* linux-low.c (count_events_callback, select_event_lwp_callback):
No longer check whether the thread has resume_stop as last resume
kind.
diff --git a/gdb/gdbserver/linux-low.c b/gdb/gdbserver/linux-low.c
index 2b988ec..0c54115 100644
--- a/gdb/gdbserver/linux-low.c
+++ b/gdb/gdbserver/linux-low.c
@@ -3379,13 +3379,12 @@ stop_all_lwps (int suspend, struct lwp_info *except)
}
}
-/* Resume execution of the inferior process.
- If STEP is nonzero, single-step it.
- If SIGNAL is nonzero, give it that signal. */
+/* Resume execution of LWP. If STEP is nonzero, single-step it. If
+ SIGNAL is nonzero, give it that signal. */
static void
-linux_resume_one_lwp (struct lwp_info *lwp,
- int step, int signal, siginfo_t *info)
+linux_resume_one_lwp_throw (struct lwp_info *lwp,
+ int step, int signal, siginfo_t *info)
{
struct thread_info *thread = get_lwp_thread (lwp);
struct thread_info *saved_thread;
@@ -3566,8 +3565,6 @@ linux_resume_one_lwp (struct lwp_info *lwp,
regcache_invalidate_thread (thread);
errno = 0;
- lwp->stopped = 0;
- lwp->stop_reason = TARGET_STOPPED_BY_NO_REASON;
lwp->stepping = step;
ptrace (step ? PTRACE_SINGLESTEP : PTRACE_CONT, lwpid_of (thread),
(PTRACE_TYPE_ARG3) 0,
@@ -3577,19 +3574,68 @@ linux_resume_one_lwp (struct lwp_info *lwp,
current_thread = saved_thread;
if (errno)
+ perror_with_name ("resuming thread");
+
+ /* Successfully resumed. Clear state that no longer makes sense,
+ and mark the LWP as running. Must not do this before resuming
+ otherwise if that fails other code will be confused. E.g., we'd
+ later try to stop the LWP and hang forever waiting for a stop
+ status. Note that we must not throw after this is cleared,
+ otherwise handle_zombie_lwp_error would get confused. */
+ lwp->stopped = 0;
+ lwp->stop_reason = TARGET_STOPPED_BY_NO_REASON;
+}
+
+/* Called when we try to resume a stopped LWP and that errors out. If
+ the LWP is no longer in ptrace-stopped state (meaning it's zombie,
+ or about to become), discard the error, clear any pending status
+ the LWP may have, and return true (we'll collect the exit status
+ soon enough). Otherwise, return false. */
+
+static int
+check_ptrace_stopped_lwp_gone (struct lwp_info *lp)
+{
+ struct thread_info *thread = get_lwp_thread (lp);
+
+ /* If we get an error after resuming the LWP successfully, we'd
+ confuse !T state for the LWP being gone. */
+ gdb_assert (lp->stopped);
+
+ /* We can't just check whether the LWP is in 'Z (Zombie)' state,
+ because even if ptrace failed with ESRCH, the tracee may be "not
+ yet fully dead", but already refusing ptrace requests. In that
+ case the tracee has 'R (Running)' state for a little bit
+ (observed in Linux 3.18). See also the note on ESRCH in the
+ ptrace(2) man page. Instead, check whether the LWP has any state
+ other than ptrace-stopped. */
+
+ /* Don't assume anything if /proc/PID/status can't be read. */
+ if (linux_proc_pid_is_trace_stopped_nowarn (lwpid_of (thread)) == 0)
{
- /* ESRCH from ptrace either means that the thread was already
- running (an error) or that it is gone (a race condition). If
- it's gone, we will get a notification the next time we wait,
- so we can ignore the error. We could differentiate these
- two, but it's tricky without waiting; the thread still exists
- as a zombie, so sending it signal 0 would succeed. So just
- ignore ESRCH. */
- if (errno == ESRCH)
- return;
+ lp->stop_reason = TARGET_STOPPED_BY_NO_REASON;
+ lp->status_pending_p = 0;
+ return 1;
+ }
+ return 0;
+}
+
+/* Like linux_resume_one_lwp_throw, but no error is thrown if the LWP
+ disappears while we try to resume it. */
- perror_with_name ("ptrace");
+static void
+linux_resume_one_lwp (struct lwp_info *lwp,
+ int step, int signal, siginfo_t *info)
+{
+ TRY
+ {
+ linux_resume_one_lwp_throw (lwp, step, signal, info);
+ }
+ CATCH (ex, RETURN_MASK_ERROR)
+ {
+ if (!check_ptrace_stopped_lwp_gone (lwp))
+ throw_exception (ex);
}
+ END_CATCH
}
struct thread_resume_array