diff options
author | Pedro Alves <palves@redhat.com> | 2009-05-11 12:08:03 +0000 |
---|---|---|
committer | Pedro Alves <palves@redhat.com> | 2009-05-11 12:08:03 +0000 |
commit | 7feb7d068ae65557ede03c36468ebac61b0939ca (patch) | |
tree | d752a128bd18fb31c528aa5032c5626112fa2d4b /gdb/linux-nat.c | |
parent | d92524f1eed2a35b03b982ef6a1a2cee4c3a06ae (diff) | |
download | gdb-7feb7d068ae65557ede03c36468ebac61b0939ca.zip gdb-7feb7d068ae65557ede03c36468ebac61b0939ca.tar.gz gdb-7feb7d068ae65557ede03c36468ebac61b0939ca.tar.bz2 |
* linux-nat.c (enum sigchld_state): Delete.
(linux_nat_async_events_state): Delete.
(struct waitpid_result): Delete.
(waitpid_queue): Delete.
(queued_waitpid_1): Delete.
(async_file_flush): New.
(queued_waitpid, push_waitpid): Delete.
(async_file_mark): New.
(drain_queued_events): Delete.
(my_waitpid): Remove locally queued events handling.
(linux_test_for_tracefork): Upjust.
(linux_child_follow_fork): Ditto.
(sync_sigchld_action): Delete.
(blocked_mask): Reinstate.
(async_sigchld_action): Rename to...
(sigchld_action): ... this.
(block_child_signals): New.
(restore_child_signals_mask): New.
(lin_lwp_attach_lwp): Adjust.
(linux_nat_create_inferior): Ditto.
(linux_nat_attach): Also use lp->status in async mode.
(get_pending_status): Don't use queued_waitpid.
(linux_nat_detach): Don't drain locally queued events.
(linux_nat_resume): Allow pending wait statuses stored lp->status
in async mode. If returning early due to a pending event,
re-register the event source.
(stop_wait_callback): Allow pending wait statuses stored
lp->status in async mode.
(pipe_to_local_event_queue, local_event_queue_to_pipe): Delete.
(linux_nat_wait): Rename to ...
(linux_nat_wait_1): ... this. Allow pending wait statuses stored
lp->status in async mode. Always add WNOHANG to the waitpid
options in async mode.
(linux_nat_wait): New.
(kill_callback): Don't drain locally queued events.
(sigchld_handler): Rewrite.
(linux_nat_is_async_p, linux_nat_can_async_p): Fix comments to
refer to "set target-async".
(linux_nat_async_mask): If in non-stop, and re-enabling async
mode, re-register the target event source in the event loop.
(linux_nat_event_pipe_pop, linux_nat_event_pipe_push)
(get_pending_events, async_sigchld_handler)
(linux_nat_async_events): Delete.
(handle_target_event): New.
(linux_nat_async_file_handler): Delete.
(linux_async_pipe): New.
(linux_nat_async): Only re-register in the event loop if not
registered yet. Always notify the event-loop once if enabling the
event source.
(linux_nat_stop_lwp): Rewrite to handle pending events stored in
lp->status, not in the locally queued event list.
(linux_nat_stop): Don't mask out async event handling.
(linux_nat_setup_async): Delete.
(_initialize_linux_nat): Adjust.
(lin_thread_get_thread_signals): blocked_mask is global again.
Adjust.
Diffstat (limited to 'gdb/linux-nat.c')
-rw-r--r-- | gdb/linux-nat.c | 951 |
1 files changed, 292 insertions, 659 deletions
diff --git a/gdb/linux-nat.c b/gdb/linux-nat.c index 22d721a..d67fcc3 100644 --- a/gdb/linux-nat.c +++ b/gdb/linux-nat.c @@ -89,48 +89,40 @@ notices it and returns. Waiting for events in async mode ================================ -In async mode, GDB should always be ready to handle both user input and target -events, so neither blocking waitpid nor sigsuspend are viable -options. Instead, we should notify the GDB main event loop whenever there's -unprocessed event from the target. The only way to notify this event loop is -to make it wait on input from a pipe, and write something to the pipe whenever -there's event. Obviously, if we fail to notify the event loop if there's -target event, it's bad. If we notify the event loop when there's no event -from target, linux-nat.c will detect that there's no event, actually, and -report event of type TARGET_WAITKIND_IGNORE, but it will waste time and -better avoided. - -The main design point is that every time GDB is outside linux-nat.c, we have a -SIGCHLD handler installed that is called when something happens to the target -and notifies the GDB event loop. Also, the event is extracted from the target -using waitpid and stored for future use. Whenever GDB core decides to handle -the event, and calls into linux-nat.c, we disable SIGCHLD and process things -as in sync mode, except that before waitpid call we check if there are any -previously read events. - -It could happen that during event processing, we'll try to get more events -than there are events in the local queue, which will result to waitpid call. -Those waitpid calls, while blocking, are guarantied to always have -something for waitpid to return. E.g., stopping a thread with SIGSTOP, and -waiting for the lwp to stop. - -The event loop is notified about new events using a pipe. SIGCHLD handler does -waitpid and writes the results in to a pipe. GDB event loop has the other end -of the pipe among the sources. When event loop starts to process the event -and calls a function in linux-nat.c, all events from the pipe are transferred -into a local queue and SIGCHLD is blocked. Further processing goes as in sync -mode. Before we return from linux_nat_wait, we transfer all unprocessed events -from local queue back to the pipe, so that when we get back to event loop, -event loop will notice there's something more to do. - -SIGCHLD is blocked when we're inside target_wait, so that should we actually -want to wait for some more events, SIGCHLD handler does not steal them from -us. Technically, it would be possible to add new events to the local queue but -it's about the same amount of work as blocking SIGCHLD. - -This moving of events from pipe into local queue and back into pipe when we -enter/leave linux-nat.c is somewhat ugly. Unfortunately, GDB event loop is -home-grown and incapable to wait on any queue. +In async mode, GDB should always be ready to handle both user input +and target events, so neither blocking waitpid nor sigsuspend are +viable options. Instead, we should asynchronously notify the GDB main +event loop whenever there's an unprocessed event from the target. We +detect asynchronous target events by handling SIGCHLD signals. To +notify the event loop about target events, the self-pipe trick is used +--- a pipe is registered as waitable event source in the event loop, +the event loop select/poll's on the read end of this pipe (as well on +other event sources, e.g., stdin), and the SIGCHLD handler writes a +byte to this pipe. This is more portable than relying on +pselect/ppoll, since on kernels that lack those syscalls, libc +emulates them with select/poll+sigprocmask, and that is racy +(a.k.a. plain broken). + +Obviously, if we fail to notify the event loop if there's a target +event, it's bad. OTOH, if we notify the event loop when there's no +event from the target, linux_nat_wait will detect that there's no real +event to report, and return event of type TARGET_WAITKIND_IGNORE. +This is mostly harmless, but it will waste time and is better avoided. + +The main design point is that every time GDB is outside linux-nat.c, +we have a SIGCHLD handler installed that is called when something +happens to the target and notifies the GDB event loop. Whenever GDB +core decides to handle the event, and calls into linux-nat.c, we +process things as in sync mode, except that the we never block in +sigsuspend. + +While processing an event, we may end up momentarily blocked in +waitpid calls. Those waitpid calls, while blocking, are guarantied to +return quickly. E.g., in all-stop mode, before reporting to the core +that an LWP hit a breakpoint, all LWPs are stopped by sending them +SIGSTOP, and synchronously waiting for the SIGSTOP to be reported. +Note that this is different from blocking indefinitely waiting for the +next event --- here, we're already handling an event. Use of signals ============== @@ -296,166 +288,55 @@ static int linux_nat_async_mask_value = 1; event loop. */ static int linux_nat_event_pipe[2] = { -1, -1 }; -/* Number of queued events in the pipe. */ -static volatile int linux_nat_num_queued_events; +/* Flush the event pipe. */ -/* The possible SIGCHLD handling states. */ - -enum sigchld_state -{ - /* SIGCHLD disabled, with action set to sigchld_handler, for the - sigsuspend in linux_nat_wait. */ - sigchld_sync, - /* SIGCHLD enabled, with action set to async_sigchld_handler. */ - sigchld_async, - /* Set SIGCHLD to default action. Used while creating an - inferior. */ - sigchld_default -}; - -/* The current SIGCHLD handling state. */ -static enum sigchld_state linux_nat_async_events_state; - -static enum sigchld_state linux_nat_async_events (enum sigchld_state enable); -static void pipe_to_local_event_queue (void); -static void local_event_queue_to_pipe (void); -static void linux_nat_event_pipe_push (int pid, int status, int options); -static int linux_nat_event_pipe_pop (int* ptr_status, int* ptr_options); -static void linux_nat_set_async_mode (int on); -static void linux_nat_async (void (*callback) - (enum inferior_event_type event_type, void *context), - void *context); -static int linux_nat_async_mask (int mask); -static int kill_lwp (int lwpid, int signo); - -static int stop_callback (struct lwp_info *lp, void *data); - -/* Captures the result of a successful waitpid call, along with the - options used in that call. */ -struct waitpid_result -{ - int pid; - int status; - int options; - struct waitpid_result *next; -}; - -/* A singly-linked list of the results of the waitpid calls performed - in the async SIGCHLD handler. */ -static struct waitpid_result *waitpid_queue = NULL; - -/* Similarly to `waitpid', but check the local event queue instead of - querying the kernel queue. If PEEK, don't remove the event found - from the queue. */ - -static int -queued_waitpid_1 (int pid, int *status, int flags, int peek) +static void +async_file_flush (void) { - struct waitpid_result *msg = waitpid_queue, *prev = NULL; - - if (debug_linux_nat_async) - fprintf_unfiltered (gdb_stdlog, - "\ -QWPID: linux_nat_async_events_state(%d), linux_nat_num_queued_events(%d)\n", - linux_nat_async_events_state, - linux_nat_num_queued_events); + int ret; + char buf; - if (flags & __WALL) - { - for (; msg; prev = msg, msg = msg->next) - if (pid == -1 || pid == msg->pid) - break; - } - else if (flags & __WCLONE) - { - for (; msg; prev = msg, msg = msg->next) - if (msg->options & __WCLONE - && (pid == -1 || pid == msg->pid)) - break; - } - else + do { - for (; msg; prev = msg, msg = msg->next) - if ((msg->options & __WCLONE) == 0 - && (pid == -1 || pid == msg->pid)) - break; + ret = read (linux_nat_event_pipe[0], &buf, 1); } - - if (msg) - { - int pid; - - if (status) - *status = msg->status; - pid = msg->pid; - - if (debug_linux_nat_async) - fprintf_unfiltered (gdb_stdlog, "QWPID: pid(%d), status(%x)\n", - pid, msg->status); - - if (!peek) - { - if (prev) - prev->next = msg->next; - else - waitpid_queue = msg->next; - - msg->next = NULL; - xfree (msg); - } - - return pid; - } - - if (debug_linux_nat_async) - fprintf_unfiltered (gdb_stdlog, "QWPID: miss\n"); - - if (status) - *status = 0; - return -1; + while (ret >= 0 || (ret == -1 && errno == EINTR)); } -/* Similarly to `waitpid', but check the local event queue. */ - -static int -queued_waitpid (int pid, int *status, int flags) -{ - return queued_waitpid_1 (pid, status, flags, 0); -} +/* Put something (anything, doesn't matter what, or how much) in event + pipe, so that the select/poll in the event-loop realizes we have + something to process. */ static void -push_waitpid (int pid, int status, int options) +async_file_mark (void) { - struct waitpid_result *event, *new_event; + int ret; - new_event = xmalloc (sizeof (*new_event)); - new_event->pid = pid; - new_event->status = status; - new_event->options = options; - new_event->next = NULL; + /* It doesn't really matter what the pipe contains, as long we end + up with something in it. Might as well flush the previous + left-overs. */ + async_file_flush (); - if (waitpid_queue) + do { - for (event = waitpid_queue; - event && event->next; - event = event->next) - ; - - event->next = new_event; + ret = write (linux_nat_event_pipe[1], "+", 1); } - else - waitpid_queue = new_event; -} + while (ret == -1 && errno == EINTR); -/* Drain all queued events of PID. If PID is -1, the effect is of - draining all events. */ -static void -drain_queued_events (int pid) -{ - while (queued_waitpid (pid, NULL, __WALL) != -1) - ; + /* Ignore EAGAIN. If the pipe is full, the event loop will already + be awakened anyway. */ } +static void linux_nat_async (void (*callback) + (enum inferior_event_type event_type, void *context), + void *context); +static int linux_nat_async_mask (int mask); +static int kill_lwp (int lwpid, int signo); + +static int stop_callback (struct lwp_info *lp, void *data); + +static void block_child_signals (sigset_t *prev_mask); +static void restore_child_signals_mask (sigset_t *prev_mask); /* Trivial list manipulation functions to keep track of a list of new stopped processes. */ @@ -506,21 +387,13 @@ linux_tracefork_child (void) _exit (0); } -/* Wrapper function for waitpid which handles EINTR, and checks for - locally queued events. */ +/* Wrapper function for waitpid which handles EINTR. */ static int my_waitpid (int pid, int *status, int flags) { int ret; - /* There should be no concurrent calls to waitpid. */ - gdb_assert (linux_nat_async_events_state == sigchld_sync); - - ret = queued_waitpid (pid, status, flags); - if (ret != -1) - return ret; - do { ret = waitpid (pid, status, flags); @@ -548,16 +421,20 @@ linux_test_for_tracefork (int original_pid) { int child_pid, ret, status; long second_pid; - enum sigchld_state async_events_original_state; + sigset_t prev_mask; - async_events_original_state = linux_nat_async_events (sigchld_sync); + /* We don't want those ptrace calls to be interrupted. */ + block_child_signals (&prev_mask); linux_supports_tracefork_flag = 0; linux_supports_tracevforkdone_flag = 0; ret = ptrace (PTRACE_SETOPTIONS, original_pid, 0, PTRACE_O_TRACEFORK); if (ret != 0) - return; + { + restore_child_signals_mask (&prev_mask); + return; + } child_pid = fork (); if (child_pid == -1) @@ -581,7 +458,7 @@ linux_test_for_tracefork (int original_pid) if (ret != 0) { warning (_("linux_test_for_tracefork: failed to kill child")); - linux_nat_async_events (async_events_original_state); + restore_child_signals_mask (&prev_mask); return; } @@ -592,7 +469,7 @@ linux_test_for_tracefork (int original_pid) warning (_("linux_test_for_tracefork: unexpected wait status 0x%x from " "killed child"), status); - linux_nat_async_events (async_events_original_state); + restore_child_signals_mask (&prev_mask); return; } @@ -633,7 +510,7 @@ linux_test_for_tracefork (int original_pid) warning (_("linux_test_for_tracefork: failed to kill child")); my_waitpid (child_pid, &status, 0); - linux_nat_async_events (async_events_original_state); + restore_child_signals_mask (&prev_mask); } /* Return non-zero iff we have tracefork functionality available. @@ -696,13 +573,13 @@ linux_child_post_startup_inferior (ptid_t ptid) static int linux_child_follow_fork (struct target_ops *ops, int follow_child) { + sigset_t prev_mask; ptid_t last_ptid; struct target_waitstatus last_status; int has_vforked; int parent_pid, child_pid; - if (target_can_async_p ()) - target_async (NULL, 0); + block_child_signals (&prev_mask); get_last_target_status (&last_ptid, &last_status); has_vforked = (last_status.kind == TARGET_WAITKIND_VFORKED); @@ -903,9 +780,7 @@ linux_child_follow_fork (struct target_ops *ops, int follow_child) follow_inferior_reset_breakpoints (); } - if (target_can_async_p ()) - target_async (inferior_event_handler, 0); - + restore_child_signals_mask (&prev_mask); return 0; } @@ -978,14 +853,33 @@ static sigset_t normal_mask; _initialize_linux_nat. */ static sigset_t suspend_mask; -/* SIGCHLD action for synchronous mode. */ -struct sigaction sync_sigchld_action; +/* Signals to block to make that sigsuspend work. */ +static sigset_t blocked_mask; + +/* SIGCHLD action. */ +struct sigaction sigchld_action; -/* SIGCHLD action for asynchronous mode. */ -static struct sigaction async_sigchld_action; +/* Block child signals (SIGCHLD and linux threads signals), and store + the previous mask in PREV_MASK. */ -/* SIGCHLD default action, to pass to new inferiors. */ -static struct sigaction sigchld_default_action; +static void +block_child_signals (sigset_t *prev_mask) +{ + /* Make sure SIGCHLD is blocked. */ + if (!sigismember (&blocked_mask, SIGCHLD)) + sigaddset (&blocked_mask, SIGCHLD); + + sigprocmask (SIG_BLOCK, &blocked_mask, prev_mask); +} + +/* Restore child signals mask, previously returned by + block_child_signals. */ + +static void +restore_child_signals_mask (sigset_t *prev_mask) +{ + sigprocmask (SIG_SETMASK, prev_mask, NULL); +} /* Prototypes for local functions. */ @@ -1269,11 +1163,11 @@ int lin_lwp_attach_lwp (ptid_t ptid) { struct lwp_info *lp; - enum sigchld_state async_events_original_state; + sigset_t prev_mask; gdb_assert (is_lwp (ptid)); - async_events_original_state = linux_nat_async_events (sigchld_sync); + block_child_signals (&prev_mask); lp = find_lwp_pid (ptid); @@ -1296,6 +1190,7 @@ lin_lwp_attach_lwp (ptid_t ptid) to create them. */ warning (_("Can't attach %s: %s"), target_pid_to_str (ptid), safe_strerror (errno)); + restore_child_signals_mask (&prev_mask); return -1; } @@ -1338,7 +1233,7 @@ lin_lwp_attach_lwp (ptid_t ptid) lp->stopped = 1; } - linux_nat_async_events (async_events_original_state); + restore_child_signals_mask (&prev_mask); return 0; } @@ -1359,20 +1254,6 @@ linux_nat_create_inferior (struct target_ops *ops, /* Mask async mode. Creating a child requires a loop calling wait_for_inferior currently. */ saved_async = linux_nat_async_mask (0); - else - { - /* Restore the original signal mask. */ - sigprocmask (SIG_SETMASK, &normal_mask, NULL); - /* Make sure we don't block SIGCHLD during a sigsuspend. */ - suspend_mask = normal_mask; - sigdelset (&suspend_mask, SIGCHLD); - } - - /* Set SIGCHLD to the default action, until after execing the child, - since the inferior inherits the superior's signal mask. It will - be blocked again in linux_nat_wait, which is only reached after - the inferior execing. */ - linux_nat_async_events (sigchld_default); #ifdef HAVE_PERSONALITY if (disable_randomization) @@ -1419,15 +1300,6 @@ linux_nat_attach (struct target_ops *ops, char *args, int from_tty) attach all of them. */ linux_ops->to_attach (ops, args, from_tty); - if (!target_can_async_p ()) - { - /* Restore the original signal mask. */ - sigprocmask (SIG_SETMASK, &normal_mask, NULL); - /* Make sure we don't block SIGCHLD during a sigsuspend. */ - suspend_mask = normal_mask; - sigdelset (&suspend_mask, SIGCHLD); - } - /* The ptrace base target adds the main thread with (pid,0,0) format. Decorate it with lwp info. */ ptid = BUILD_LWP (GET_PID (inferior_ptid), GET_PID (inferior_ptid)); @@ -1447,18 +1319,10 @@ linux_nat_attach (struct target_ops *ops, char *args, int from_tty) "LNA: waitpid %ld, saving status %s\n", (long) GET_PID (lp->ptid), status_to_str (status)); - if (!target_can_async_p ()) - lp->status = status; - else - { - /* We already waited for this LWP, so put the wait result on the - pipe. The event loop will wake up and gets us to handling - this event. */ - linux_nat_event_pipe_push (GET_PID (lp->ptid), status, - lp->cloned ? __WCLONE : 0); - /* Register in the event loop. */ - target_async (inferior_event_handler, 0); - } + lp->status = status; + + if (target_can_async_p ()) + target_async (inferior_event_handler, 0); } /* Get pending status of LP. */ @@ -1471,10 +1335,9 @@ get_pending_status (struct lwp_info *lp, int *status) get_last_target_status (&last_ptid, &last); /* If this lwp is the ptid that GDB is processing an event from, the - signal will be in stop_signal. Otherwise, in all-stop + sync - mode, we may cache pending events in lp->status while trying to - stop all threads (see stop_wait_callback). In async mode, the - events are always cached in waitpid_queue. */ + signal will be in stop_signal. Otherwise, we may cache pending + events in lp->status while trying to stop all threads (see + stop_wait_callback). */ *status = 0; @@ -1490,14 +1353,7 @@ get_pending_status (struct lwp_info *lp, int *status) stop_wait_callback sequence (see linux_nat_detach for example) --- we can only have pending events in the local queue. */ - if (queued_waitpid (GET_LWP (lp->ptid), status, __WALL) != -1) - { - if (WIFSTOPPED (*status)) - signo = target_signal_from_host (WSTOPSIG (*status)); - - /* If not stopped, then the lwp is gone, no use in - resending a signal. */ - } + signo = target_signal_from_host (WSTOPSIG (lp->status)); } else { @@ -1539,8 +1395,6 @@ GPT: lwp %s had signal %s, but it is in no pass state\n", && signal_pass_state (tp->stop_signal)) *status = W_STOPCODE (target_signal_to_host (tp->stop_signal)); } - else if (target_can_async_p ()) - queued_waitpid (GET_LWP (lp->ptid), status, __WALL); else *status = lp->status; } @@ -1589,7 +1443,7 @@ detach_callback (struct lwp_info *lp, void *data) fprintf_unfiltered (gdb_stdlog, "PTRACE_DETACH (%s, %s, 0) (OK)\n", target_pid_to_str (lp->ptid), - strsignal (WSTOPSIG (lp->status))); + strsignal (WSTOPSIG (status))); delete_lwp (lp->ptid); } @@ -1639,9 +1493,6 @@ linux_nat_detach (struct target_ops *ops, char *args, int from_tty) pid = ptid_get_pid (inferior_ptid); - if (target_can_async_p ()) - drain_queued_events (pid); - if (forks_exist_p ()) { /* Multi-fork case. The current inferior_ptid is being detached @@ -1703,6 +1554,7 @@ static void linux_nat_resume (struct target_ops *ops, ptid_t ptid, int step, enum target_signal signo) { + sigset_t prev_mask; struct lwp_info *lp; int resume_all; @@ -1714,9 +1566,7 @@ linux_nat_resume (struct target_ops *ops, signo ? strsignal (signo) : "0", target_pid_to_str (inferior_ptid)); - if (target_can_async_p ()) - /* Block events while we're here. */ - linux_nat_async_events (sigchld_sync); + block_child_signals (&prev_mask); /* A specific PTID means `step only this process id'. */ resume_all = (PIDGET (ptid) == -1); @@ -1758,10 +1608,6 @@ linux_nat_resume (struct target_ops *ops, other threads. This bit of code needs to be synchronized with linux_nat_wait. */ - /* In async mode, we never have pending wait status. */ - if (target_can_async_p () && lp->status) - internal_error (__FILE__, __LINE__, "Pending status in async mode"); - if (lp->status && WIFSTOPPED (lp->status)) { int saved_signo; @@ -1802,6 +1648,13 @@ linux_nat_resume (struct target_ops *ops, "LLR: Short circuiting for status 0x%x\n", lp->status); + restore_child_signals_mask (&prev_mask); + if (target_can_async_p ()) + { + target_async (inferior_event_handler, 0); + /* Tell the event loop we have something to process. */ + async_file_mark (); + } return; } @@ -1822,6 +1675,7 @@ linux_nat_resume (struct target_ops *ops, target_pid_to_str (ptid), signo ? strsignal (signo) : "0"); + restore_child_signals_mask (&prev_mask); if (target_can_async_p ()) target_async (inferior_event_handler, 0); } @@ -2247,40 +2101,21 @@ stop_wait_callback (struct lwp_info *lp, void *data) there are any more (we still want to get that SIGSTOP). */ stop_wait_callback (lp, NULL); - if (target_can_async_p ()) - { - /* Don't leave a pending wait status in async mode. - Retrigger the breakpoint. */ - if (!cancel_breakpoint (lp)) - { - /* There was no gdb breakpoint set at pc. Put - the event back in the queue. */ - if (debug_linux_nat) - fprintf_unfiltered (gdb_stdlog, "\ -SWC: leaving SIGTRAP in local queue of %s\n", target_pid_to_str (lp->ptid)); - push_waitpid (GET_LWP (lp->ptid), - W_STOPCODE (SIGTRAP), - lp->cloned ? __WCLONE : 0); - } - } - else + /* Hold the SIGTRAP for handling by linux_nat_wait. If + there's another event, throw it back into the + queue. */ + if (lp->status) { - /* Hold the SIGTRAP for handling by - linux_nat_wait. */ - /* If there's another event, throw it back into the - queue. */ - if (lp->status) - { - if (debug_linux_nat) - fprintf_unfiltered (gdb_stdlog, - "SWC: kill %s, %s\n", - target_pid_to_str (lp->ptid), - status_to_str ((int) status)); - kill_lwp (GET_LWP (lp->ptid), WSTOPSIG (lp->status)); - } - /* Save the sigtrap event. */ - lp->status = status; + if (debug_linux_nat) + fprintf_unfiltered (gdb_stdlog, + "SWC: kill %s, %s\n", + target_pid_to_str (lp->ptid), + status_to_str ((int) status)); + kill_lwp (GET_LWP (lp->ptid), WSTOPSIG (lp->status)); } + + /* Save the sigtrap event. */ + lp->status = status; return 0; } else @@ -2311,7 +2146,7 @@ SWC: leaving SIGTRAP in local queue of %s\n", target_pid_to_str (lp->ptid)); /* If the lp->status field is still empty, use it to hold this event. If not, then this event must be returned to the event queue of the LWP. */ - if (lp->status || target_can_async_p ()) + if (lp->status) { if (debug_linux_nat) { @@ -2740,56 +2575,11 @@ linux_nat_filter_event (int lwpid, int status, int options) return lp; } -/* Get the events stored in the pipe into the local queue, so they are - accessible to queued_waitpid. We need to do this, since it is not - always the case that the event at the head of the pipe is the event - we want. */ - -static void -pipe_to_local_event_queue (void) -{ - if (debug_linux_nat_async) - fprintf_unfiltered (gdb_stdlog, - "PTLEQ: linux_nat_num_queued_events(%d)\n", - linux_nat_num_queued_events); - while (linux_nat_num_queued_events) - { - int lwpid, status, options; - lwpid = linux_nat_event_pipe_pop (&status, &options); - gdb_assert (lwpid > 0); - push_waitpid (lwpid, status, options); - } -} - -/* Get the unprocessed events stored in the local queue back into the - pipe, so the event loop realizes there's something else to - process. */ - -static void -local_event_queue_to_pipe (void) -{ - struct waitpid_result *w = waitpid_queue; - while (w) - { - struct waitpid_result *next = w->next; - linux_nat_event_pipe_push (w->pid, - w->status, - w->options); - xfree (w); - w = next; - } - waitpid_queue = NULL; - - if (debug_linux_nat_async) - fprintf_unfiltered (gdb_stdlog, - "LEQTP: linux_nat_num_queued_events(%d)\n", - linux_nat_num_queued_events); -} - static ptid_t -linux_nat_wait (struct target_ops *ops, - ptid_t ptid, struct target_waitstatus *ourstatus) +linux_nat_wait_1 (struct target_ops *ops, + ptid_t ptid, struct target_waitstatus *ourstatus) { + static sigset_t prev_mask; struct lwp_info *lp = NULL; int options = 0; int status = 0; @@ -2814,8 +2604,8 @@ linux_nat_wait (struct target_ops *ops, lp->resumed = 1; } - /* Block events while we're here. */ - linux_nat_async_events (sigchld_sync); + /* Make sure SIGCHLD is blocked. */ + block_child_signals (&prev_mask); retry: @@ -2829,10 +2619,6 @@ retry: lp = iterate_over_lwps (status_callback, NULL); if (lp) { - if (target_can_async_p ()) - internal_error (__FILE__, __LINE__, - "Found an LWP with a pending status in async mode."); - status = lp->status; lp->status = 0; @@ -2844,8 +2630,8 @@ retry: } /* But if we don't find one, we'll have to wait, and check both - cloned and uncloned processes. We start with the cloned - processes. */ + cloned and uncloned processes. We start with the cloned + processes. */ options = __WCLONE | WNOHANG; } else if (is_lwp (ptid)) @@ -2908,16 +2694,14 @@ retry: set_sigint_trap (); } + if (target_can_async_p ()) + options |= WNOHANG; /* In async mode, don't block. */ + while (status == 0) { pid_t lwpid; - if (target_can_async_p ()) - /* In async mode, don't ever block. Only look at the locally - queued events. */ - lwpid = queued_waitpid (pid, &status, options); - else - lwpid = my_waitpid (pid, &status, options); + lwpid = my_waitpid (pid, &status, options); if (lwpid > 0) { @@ -2956,12 +2740,10 @@ retry: /* No interesting event. */ ourstatus->kind = TARGET_WAITKIND_IGNORE; - /* Get ready for the next event. */ - target_async (inferior_event_handler, 0); - if (debug_linux_nat_async) fprintf_unfiltered (gdb_stdlog, "LLW: exit (ignore)\n"); + restore_child_signals_mask (&prev_mask); return minus_one_ptid; } @@ -3088,16 +2870,43 @@ retry: else store_waitstatus (ourstatus, status); - /* Get ready for the next event. */ - if (target_can_async_p ()) - target_async (inferior_event_handler, 0); - if (debug_linux_nat_async) fprintf_unfiltered (gdb_stdlog, "LLW: exit\n"); + restore_child_signals_mask (&prev_mask); return lp->ptid; } +static ptid_t +linux_nat_wait (struct target_ops *ops, + ptid_t ptid, struct target_waitstatus *ourstatus) +{ + ptid_t event_ptid; + + if (debug_linux_nat) + fprintf_unfiltered (gdb_stdlog, "linux_nat_wait: [%s]\n", target_pid_to_str (ptid)); + + /* Flush the async file first. */ + if (target_can_async_p ()) + async_file_flush (); + + event_ptid = linux_nat_wait_1 (ops, ptid, ourstatus); + + /* If we requested any event, and something came out, assume there + may be more. If we requested a specific lwp or process, also + assume there may be more. */ + if (target_can_async_p () + && (ourstatus->kind != TARGET_WAITKIND_IGNORE + || !ptid_equal (ptid, minus_one_ptid))) + async_file_mark (); + + /* Get ready for the next event. */ + if (target_can_async_p ()) + target_async (inferior_event_handler, 0); + + return event_ptid; +} + static int kill_callback (struct lwp_info *lp, void *data) { @@ -3174,9 +2983,6 @@ linux_nat_kill (struct target_ops *ops) ptid_t last_ptid; int status; - if (target_can_async_p ()) - target_async (NULL, 0); - /* If we're stopped while forking and we haven't followed yet, kill the other task. We need to do this first because the parent will be sleeping if this is a vfork. */ @@ -3191,10 +2997,7 @@ linux_nat_kill (struct target_ops *ops) } if (forks_exist_p ()) - { - linux_fork_killall (); - drain_queued_events (-1); - } + linux_fork_killall (); else { /* Stop all threads before killing them, since ptrace requires @@ -3223,9 +3026,10 @@ linux_nat_mourn_inferior (struct target_ops *ops) if (! forks_exist_p ()) { /* Normal case, no other forks available. */ + linux_ops->to_mourn_inferior (ops); + if (target_can_async_p ()) linux_nat_async (NULL, 0); - linux_ops->to_mourn_inferior (ops); } else /* Multi-fork case. The current inferior_ptid has exited, but @@ -3380,21 +3184,6 @@ linux_nat_pid_to_str (struct target_ops *ops, ptid_t ptid) return normal_pid_to_str (ptid); } -static void -sigchld_handler (int signo) -{ - if (target_async_permitted - && linux_nat_async_events_state != sigchld_sync - && signo == SIGCHLD) - /* It is *always* a bug to hit this. */ - internal_error (__FILE__, __LINE__, - "sigchld_handler called when async events are enabled"); - - /* Do nothing. The only reason for this handler is that it allows - us to use sigsuspend in linux_nat_wait above to wait for the - arrival of a SIGCHLD. */ -} - /* Accepts an integer PID; Returns a string representing a file that can be opened to get the symbols for the child process. */ @@ -4302,7 +4091,7 @@ static int linux_nat_is_async_p (void) { /* NOTE: palves 2008-03-21: We're only async when the user requests - it explicitly with the "maintenance set target-async" command. + it explicitly with the "set target-async" command. Someday, linux will always be async. */ if (!target_async_permitted) return 0; @@ -4316,7 +4105,7 @@ static int linux_nat_can_async_p (void) { /* NOTE: palves 2008-03-21: We're only async when the user requests - it explicitly with the "maintenance set target-async" command. + it explicitly with the "set target-async" command. Someday, linux will always be async. */ if (!target_async_permitted) return 0; @@ -4334,205 +4123,32 @@ linux_nat_supports_non_stop (void) /* target_async_mask implementation. */ static int -linux_nat_async_mask (int mask) +linux_nat_async_mask (int new_mask) { - int current_state; - current_state = linux_nat_async_mask_value; + int curr_mask = linux_nat_async_mask_value; - if (current_state != mask) + if (curr_mask != new_mask) { - if (mask == 0) + if (new_mask == 0) { linux_nat_async (NULL, 0); - linux_nat_async_mask_value = mask; + linux_nat_async_mask_value = new_mask; } else { - linux_nat_async_mask_value = mask; - linux_nat_async (inferior_event_handler, 0); - } - } - - return current_state; -} - -/* Pop an event from the event pipe. */ - -static int -linux_nat_event_pipe_pop (int* ptr_status, int* ptr_options) -{ - struct waitpid_result event = {0}; - int ret; - - do - { - ret = read (linux_nat_event_pipe[0], &event, sizeof (event)); - } - while (ret == -1 && errno == EINTR); - - gdb_assert (ret == sizeof (event)); - - *ptr_status = event.status; - *ptr_options = event.options; - - linux_nat_num_queued_events--; - - return event.pid; -} - -/* Push an event into the event pipe. */ - -static void -linux_nat_event_pipe_push (int pid, int status, int options) -{ - int ret; - struct waitpid_result event = {0}; - event.pid = pid; - event.status = status; - event.options = options; - - do - { - ret = write (linux_nat_event_pipe[1], &event, sizeof (event)); - gdb_assert ((ret == -1 && errno == EINTR) || ret == sizeof (event)); - } while (ret == -1 && errno == EINTR); - - linux_nat_num_queued_events++; -} - -static void -get_pending_events (void) -{ - int status, options, pid; - - if (!target_async_permitted - || linux_nat_async_events_state != sigchld_async) - internal_error (__FILE__, __LINE__, - "get_pending_events called with async masked"); - - while (1) - { - status = 0; - options = __WCLONE | WNOHANG; - - do - { - pid = waitpid (-1, &status, options); - } - while (pid == -1 && errno == EINTR); - - if (pid <= 0) - { - options = WNOHANG; - do - { - pid = waitpid (-1, &status, options); - } - while (pid == -1 && errno == EINTR); - } - - if (pid <= 0) - /* No more children reporting events. */ - break; - - if (debug_linux_nat_async) - fprintf_unfiltered (gdb_stdlog, "\ -get_pending_events: pid(%d), status(%x), options (%x)\n", - pid, status, options); - - linux_nat_event_pipe_push (pid, status, options); - } - - if (debug_linux_nat_async) - fprintf_unfiltered (gdb_stdlog, "\ -get_pending_events: linux_nat_num_queued_events(%d)\n", - linux_nat_num_queued_events); -} - -/* SIGCHLD handler for async mode. */ - -static void -async_sigchld_handler (int signo) -{ - if (debug_linux_nat_async) - fprintf_unfiltered (gdb_stdlog, "async_sigchld_handler\n"); - - get_pending_events (); -} - -/* Set SIGCHLD handling state to STATE. Returns previous state. */ - -static enum sigchld_state -linux_nat_async_events (enum sigchld_state state) -{ - enum sigchld_state current_state = linux_nat_async_events_state; - - if (debug_linux_nat_async) - fprintf_unfiltered (gdb_stdlog, - "LNAE: state(%d): linux_nat_async_events_state(%d), " - "linux_nat_num_queued_events(%d)\n", - state, linux_nat_async_events_state, - linux_nat_num_queued_events); - - if (current_state != state) - { - sigset_t mask; - sigemptyset (&mask); - sigaddset (&mask, SIGCHLD); - - /* Always block before changing state. */ - sigprocmask (SIG_BLOCK, &mask, NULL); - - /* Set new state. */ - linux_nat_async_events_state = state; - - switch (state) - { - case sigchld_sync: - { - /* Block target events. */ - sigprocmask (SIG_BLOCK, &mask, NULL); - sigaction (SIGCHLD, &sync_sigchld_action, NULL); - /* Get events out of queue, and make them available to - queued_waitpid / my_waitpid. */ - pipe_to_local_event_queue (); - } - break; - case sigchld_async: - { - /* Unblock target events for async mode. */ + linux_nat_async_mask_value = new_mask; - sigprocmask (SIG_BLOCK, &mask, NULL); - - /* Put events we already waited on, in the pipe first, so - events are FIFO. */ - local_event_queue_to_pipe (); - /* While in masked async, we may have not collected all - the pending events. Get them out now. */ - get_pending_events (); - - /* Let'em come. */ - sigaction (SIGCHLD, &async_sigchld_action, NULL); - sigprocmask (SIG_UNBLOCK, &mask, NULL); - } - break; - case sigchld_default: - { - /* SIGCHLD default mode. */ - sigaction (SIGCHLD, &sigchld_default_action, NULL); - - /* Get events out of queue, and make them available to - queued_waitpid / my_waitpid. */ - pipe_to_local_event_queue (); - - /* Unblock SIGCHLD. */ - sigprocmask (SIG_UNBLOCK, &mask, NULL); - } - break; + /* If we're going out of async-mask in all-stop, then the + inferior is stopped. The next resume will call + target_async. In non-stop, the target event source + should be always registered in the event loop. Do so + now. */ + if (non_stop) + linux_nat_async (inferior_event_handler, 0); } } - return current_state; + return curr_mask; } static int async_terminal_is_ours = 1; @@ -4597,10 +4213,69 @@ static void (*async_client_callback) (enum inferior_event_type event_type, void *context); static void *async_client_context; +/* SIGCHLD handler that serves two purposes: In non-stop/async mode, + so we notice when any child changes state, and notify the + event-loop; it allows us to use sigsuspend in linux_nat_wait_1 + above to wait for the arrival of a SIGCHLD. */ + static void -linux_nat_async_file_handler (int error, gdb_client_data client_data) +sigchld_handler (int signo) { - async_client_callback (INF_REG_EVENT, async_client_context); + int old_errno = errno; + + if (debug_linux_nat_async) + fprintf_unfiltered (gdb_stdlog, "sigchld\n"); + + if (signo == SIGCHLD + && linux_nat_event_pipe[0] != -1) + async_file_mark (); /* Let the event loop know that there are + events to handle. */ + + errno = old_errno; +} + +/* Callback registered with the target events file descriptor. */ + +static void +handle_target_event (int error, gdb_client_data client_data) +{ + (*async_client_callback) (INF_REG_EVENT, async_client_context); +} + +/* Create/destroy the target events pipe. Returns previous state. */ + +static int +linux_async_pipe (int enable) +{ + int previous = (linux_nat_event_pipe[0] != -1); + + if (previous != enable) + { + sigset_t prev_mask; + + block_child_signals (&prev_mask); + + if (enable) + { + if (pipe (linux_nat_event_pipe) == -1) + internal_error (__FILE__, __LINE__, + "creating event pipe failed."); + + fcntl (linux_nat_event_pipe[0], F_SETFL, O_NONBLOCK); + fcntl (linux_nat_event_pipe[1], F_SETFL, O_NONBLOCK); + } + else + { + close (linux_nat_event_pipe[0]); + close (linux_nat_event_pipe[1]); + linux_nat_event_pipe[0] = -1; + linux_nat_event_pipe[1] = -1; + } + + restore_child_signals_mask (&prev_mask); + } + + return previous; } /* target_async implementation. */ @@ -4617,18 +4292,21 @@ linux_nat_async (void (*callback) (enum inferior_event_type event_type, { async_client_callback = callback; async_client_context = context; - add_file_handler (linux_nat_event_pipe[0], - linux_nat_async_file_handler, NULL); - - linux_nat_async_events (sigchld_async); + if (!linux_async_pipe (1)) + { + add_file_handler (linux_nat_event_pipe[0], + handle_target_event, NULL); + /* There may be pending events to handle. Tell the event loop + to poll them. */ + async_file_mark (); + } } else { async_client_callback = callback; async_client_context = context; - - linux_nat_async_events (sigchld_sync); delete_file_handler (linux_nat_event_pipe[0]); + linux_async_pipe (0); } return; } @@ -4649,42 +4327,30 @@ linux_nat_stop_lwp (struct lwp_info *lwp, void *data) if (!lwp->stopped) { int pid, status; + ptid_t ptid = lwp->ptid; if (debug_linux_nat) fprintf_unfiltered (gdb_stdlog, "LNSL: running -> suspending %s\n", target_pid_to_str (lwp->ptid)); - /* Peek once, to check if we've already waited for this - LWP. */ - pid = queued_waitpid_1 (ptid_get_lwp (lwp->ptid), &status, - lwp->cloned ? __WCLONE : 0, 1 /* peek */); - - if (pid == -1) - { - ptid_t ptid = lwp->ptid; - - stop_callback (lwp, NULL); - stop_wait_callback (lwp, NULL); - /* If the lwp exits while we try to stop it, there's - nothing else to do. */ - lwp = find_lwp_pid (ptid); - if (lwp == NULL) - return 0; + stop_callback (lwp, NULL); + stop_wait_callback (lwp, NULL); - pid = queued_waitpid_1 (ptid_get_lwp (lwp->ptid), &status, - lwp->cloned ? __WCLONE : 0, - 1 /* peek */); - } + /* If the lwp exits while we try to stop it, there's nothing + else to do. */ + lwp = find_lwp_pid (ptid); + if (lwp == NULL) + return 0; /* If we didn't collect any signal other than SIGSTOP while stopping the LWP, push a SIGNAL_0 event. In either case, the event-loop will end up calling target_wait which will collect these. */ - if (pid == -1) - push_waitpid (ptid_get_lwp (lwp->ptid), W_STOPCODE (0), - lwp->cloned ? __WCLONE : 0); + if (lwp->status == 0) + lwp->status = W_STOPCODE (0); + async_file_mark (); } else { @@ -4710,11 +4376,7 @@ static void linux_nat_stop (ptid_t ptid) { if (non_stop) - { - linux_nat_async_events (sigchld_sync); - iterate_over_lwps (linux_nat_stop_lwp, &ptid); - target_async (inferior_event_handler, 0); - } + iterate_over_lwps (linux_nat_stop_lwp, &ptid); else linux_ops->to_stop (ptid); } @@ -4796,18 +4458,6 @@ linux_nat_get_siginfo (ptid_t ptid) return &lp->siginfo; } -/* Enable/Disable async mode. */ - -static void -linux_nat_setup_async (void) -{ - if (pipe (linux_nat_event_pipe) == -1) - internal_error (__FILE__, __LINE__, - "creating event pipe failed."); - fcntl (linux_nat_event_pipe[0], F_SETFL, O_NONBLOCK); - fcntl (linux_nat_event_pipe[1], F_SETFL, O_NONBLOCK); -} - /* Provide a prototype to silence -Wmissing-prototypes. */ extern initialize_file_ftype _initialize_linux_nat; @@ -4843,38 +4493,22 @@ Enables printf debugging output."), show_debug_linux_nat_async, &setdebuglist, &showdebuglist); - /* Get the default SIGCHLD action. Used while forking an inferior - (see linux_nat_create_inferior/linux_nat_async_events). */ - sigaction (SIGCHLD, NULL, &sigchld_default_action); - - /* Block SIGCHLD by default. Doing this early prevents it getting - unblocked if an exception is thrown due to an error while the - inferior is starting (sigsetjmp/siglongjmp). */ - sigemptyset (&mask); - sigaddset (&mask, SIGCHLD); - sigprocmask (SIG_BLOCK, &mask, NULL); - /* Save this mask as the default. */ sigprocmask (SIG_SETMASK, NULL, &normal_mask); - /* The synchronous SIGCHLD handler. */ - sync_sigchld_action.sa_handler = sigchld_handler; - sigemptyset (&sync_sigchld_action.sa_mask); - sync_sigchld_action.sa_flags = SA_RESTART; + /* Install a SIGCHLD handler. */ + sigchld_action.sa_handler = sigchld_handler; + sigemptyset (&sigchld_action.sa_mask); + sigchld_action.sa_flags = SA_RESTART; /* Make it the default. */ - sigaction (SIGCHLD, &sync_sigchld_action, NULL); + sigaction (SIGCHLD, &sigchld_action, NULL); /* Make sure we don't block SIGCHLD during a sigsuspend. */ sigprocmask (SIG_SETMASK, NULL, &suspend_mask); sigdelset (&suspend_mask, SIGCHLD); - /* SIGCHLD handler for async mode. */ - async_sigchld_action.sa_handler = async_sigchld_handler; - sigemptyset (&async_sigchld_action.sa_mask); - async_sigchld_action.sa_flags = SA_RESTART; - - linux_nat_setup_async (); + sigemptyset (&blocked_mask); add_setshow_boolean_cmd ("disable-randomization", class_support, &disable_randomization, _("\ @@ -4921,7 +4555,6 @@ lin_thread_get_thread_signals (sigset_t *set) { struct sigaction action; int restart, cancel; - sigset_t blocked_mask; sigemptyset (&blocked_mask); sigemptyset (set); |