From 12696c10906026672f17a551ae1fa5d62878751c Mon Sep 17 00:00:00 2001 From: Pedro Alves Date: Mon, 19 Aug 2013 13:44:41 +0000 Subject: [PATCH] linux-nat.c: no need to block child signals so aggressively. In http://sourceware.org/ml/gdb-patches/2013-08/msg00174.html , the issue of child signal handling around ptrace option support discovery being different between GDB and GDBserver came up. I recalled adding these block_child_signals calls, and the "We don't want those ptrace calls to be interrupted" comment, but not exactly why. So I looked into it. My first guess is that I got confused. The patch that added this rewrote the linux native async support completely, and the old async support code had the SIGCHLD handler itself do waitpid, so in places that we'd want a blocking waitpid, we'd have to have the signal handler blocked. That was probably the mindset I had at the time. Anyway, whatever the case, looks like I was wrong on the need for this blocking. Given GDBserver doesn't block like this, I investigated why this is currently needed on GDB but not on GDBserver. I removed the block_child_signals (and restore) calls, and hacked linux-nat.c to call linux_test_for_tracefork in a loop, like: @@ -534,7 +534,10 @@ static int linux_supports_tracefork (int pid) { if (linux_supports_tracefork_flag == -1) - linux_test_for_tracefork (pid); + { + while (1) + linux_test_for_tracefork (pid); + } return linux_supports_tracefork_flag; } Running the resulting GDB, I then saw bad things happening. Specifically, I'd end up with a bunch of zombies, and eventually, the machine would refuse to spawn new processes, claming insufficient resources. The issue is that linux_test_for_tracefork test forks, and has the child fork again. If we don't block SIGCHLD on entry to the function, the children will inherit SIGCHLD's action/disposition (meaning, SIGCHLD will be unblocked in the child). When the first child forks again a second child, and that child exits, the first child gets a SIGCHLD. Now, when we try to wrap up for the whole options test, we kill the first child, and collect the waitstatus. Here, when SIGCHLD isn't blocked, GDB will first see the child reporting a stop with SIGCHLD. gdbserver's ptrace options test does a PTRACE_KILL loop at the end, which catches the SIGCHLD, and retries the kill. The GDB version did not do that. So the GDB version would proceed, leaving the child zombie (until GDB exists), as nothing collected its final waitstatus. So this patch makes the GDB version of linux_test_for_tracefork do the exact same as the GDBserver version, removes all this unnecessary blocking throughout, and adds a couple comments at places that do need it -- namely: places where we'll use sleep with sigsuspend; and linux_async_pipe, as that destroys the pipe the signal handler touches. Tested on x86_64 Fedora 17, sync and async. gdb/ 2013-08-19 Pedro Alves * linux-nat.c (linux_test_for_tracefork) (linux_test_for_tracesysgood, linux_child_follow_fork) (lin_lwp_attach_lwp, linux_nat_resume): Don't block child signals. (linux_nat_wait_1): Extend comment. (linux_async_pipe): Add comment. --- gdb/ChangeLog | 8 +++++++ gdb/linux-nat.c | 59 ++++++++++++------------------------------------- 2 files changed, 22 insertions(+), 45 deletions(-) diff --git a/gdb/ChangeLog b/gdb/ChangeLog index 6b34e3ce87b..ef51a9f9238 100644 --- a/gdb/ChangeLog +++ b/gdb/ChangeLog @@ -1,3 +1,11 @@ +2013-08-19 Pedro Alves + + * linux-nat.c (linux_test_for_tracefork) + (linux_test_for_tracesysgood, linux_child_follow_fork) + (lin_lwp_attach_lwp, linux_nat_resume): Don't block child signals. + (linux_nat_wait_1): Extend comment. + (linux_async_pipe): Add comment. + 2013-08-15 Kevin Buettner * rl78-tdep.c (RL78_RAW_PC_REGNUM): New enum. diff --git a/gdb/linux-nat.c b/gdb/linux-nat.c index 45a6e5f278c..db23433d5fa 100644 --- a/gdb/linux-nat.c +++ b/gdb/linux-nat.c @@ -395,20 +395,13 @@ linux_test_for_tracefork (int original_pid) { int child_pid, ret, status; long second_pid; - sigset_t prev_mask; - - /* 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) - { - restore_child_signals_mask (&prev_mask); - return; - } + return; child_pid = fork (); if (child_pid == -1) @@ -433,7 +426,6 @@ linux_test_for_tracefork (int original_pid) if (ret != 0) { warning (_("linux_test_for_tracefork: failed to kill child")); - restore_child_signals_mask (&prev_mask); return; } @@ -445,7 +437,6 @@ linux_test_for_tracefork (int original_pid) warning (_("linux_test_for_tracefork: unexpected " "wait status 0x%x from killed child"), status); - restore_child_signals_mask (&prev_mask); return; } @@ -482,12 +473,14 @@ linux_test_for_tracefork (int original_pid) warning (_("linux_test_for_tracefork: unexpected result from waitpid " "(%d, status 0x%x)"), ret, status); - ret = ptrace (PTRACE_KILL, child_pid, 0, 0); - if (ret != 0) - warning (_("linux_test_for_tracefork: failed to kill child")); - my_waitpid (child_pid, &status, 0); - - restore_child_signals_mask (&prev_mask); + do + { + ret = ptrace (PTRACE_KILL, child_pid, 0, 0); + if (ret != 0) + warning ("linux_test_for_tracefork: failed to kill child"); + my_waitpid (child_pid, &status, 0); + } + while (WIFSTOPPED (status)); } /* Determine if PTRACE_O_TRACESYSGOOD can be used to follow syscalls. @@ -500,20 +493,14 @@ static void linux_test_for_tracesysgood (int original_pid) { int ret; - sigset_t prev_mask; - - /* We don't want those ptrace calls to be interrupted. */ - block_child_signals (&prev_mask); linux_supports_tracesysgood_flag = 0; ret = ptrace (PTRACE_SETOPTIONS, original_pid, 0, PTRACE_O_TRACESYSGOOD); if (ret != 0) - goto out; + return; linux_supports_tracesysgood_flag = 1; -out: - restore_child_signals_mask (&prev_mask); } /* Determine wether we support PTRACE_O_TRACESYSGOOD option available. @@ -630,12 +617,9 @@ delete_lwp_cleanup (void *lp_voidp) static int linux_child_follow_fork (struct target_ops *ops, int follow_child) { - sigset_t prev_mask; int has_vforked; int parent_pid, child_pid; - block_child_signals (&prev_mask); - has_vforked = (inferior_thread ()->pending_follow.kind == TARGET_WAITKIND_VFORKED); parent_pid = ptid_get_lwp (inferior_ptid); @@ -954,7 +938,6 @@ holding the child stopped. Try \"set detach-on-fork\" or \ check_for_thread_db (); } - restore_child_signals_mask (&prev_mask); return 0; } @@ -1427,13 +1410,10 @@ int lin_lwp_attach_lwp (ptid_t ptid) { struct lwp_info *lp; - sigset_t prev_mask; int lwpid; gdb_assert (is_lwp (ptid)); - block_child_signals (&prev_mask); - lp = find_lwp_pid (ptid); lwpid = GET_LWP (ptid); @@ -1461,7 +1441,6 @@ lin_lwp_attach_lwp (ptid_t ptid) /* We've already seen this thread stop, but we haven't seen the PTRACE_EVENT_CLONE extended event yet. */ - restore_child_signals_mask (&prev_mask); return 0; } else @@ -1478,8 +1457,6 @@ lin_lwp_attach_lwp (ptid_t ptid) { if (WIFSTOPPED (status)) add_to_pid_list (&stopped_pids, lwpid, status); - - restore_child_signals_mask (&prev_mask); return 1; } } @@ -1492,7 +1469,6 @@ 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; } @@ -1503,10 +1479,7 @@ lin_lwp_attach_lwp (ptid_t ptid) status = linux_nat_post_attach_wait (ptid, 0, &cloned, &signalled); if (!WIFSTOPPED (status)) - { - restore_child_signals_mask (&prev_mask); - return 1; - } + return 1; lp = add_lwp (ptid); lp->stopped = 1; @@ -1542,7 +1515,6 @@ lin_lwp_attach_lwp (ptid_t ptid) } lp->last_resume_kind = resume_stop; - restore_child_signals_mask (&prev_mask); return 0; } @@ -1975,7 +1947,6 @@ static void linux_nat_resume (struct target_ops *ops, ptid_t ptid, int step, enum gdb_signal signo) { - sigset_t prev_mask; struct lwp_info *lp; int resume_many; @@ -1988,8 +1959,6 @@ linux_nat_resume (struct target_ops *ops, ? strsignal (gdb_signal_to_host (signo)) : "0"), target_pid_to_str (inferior_ptid)); - block_child_signals (&prev_mask); - /* A specific PTID means `step only this process id'. */ resume_many = (ptid_equal (minus_one_ptid, ptid) || ptid_is_pid (ptid)); @@ -2047,7 +2016,6 @@ 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); @@ -2080,7 +2048,6 @@ linux_nat_resume (struct target_ops *ops, (signo != GDB_SIGNAL_0 ? strsignal (gdb_signal_to_host (signo)) : "0")); - restore_child_signals_mask (&prev_mask); if (target_can_async_p ()) target_async (inferior_event_handler, 0); } @@ -3477,7 +3444,7 @@ linux_nat_wait_1 (struct target_ops *ops, lp->resumed = 1; } - /* Make sure SIGCHLD is blocked. */ + /* Make sure SIGCHLD is blocked until the sigsuspend below. */ block_child_signals (&prev_mask); retry: @@ -4907,6 +4874,8 @@ linux_async_pipe (int enable) { sigset_t prev_mask; + /* Block child signals while we create/destroy the pipe, as + their handler writes to it. */ block_child_signals (&prev_mask); if (enable) -- 2.30.2