[RFA/Linux] Ask kernel to kill inferior when GDB terminates

Message ID 1415984034-27122-1-git-send-email-brobecker@adacore.com
State New, archived
Headers

Commit Message

Joel Brobecker Nov. 14, 2014, 4:53 p.m. UTC
  Hello,

This patch enhances GDB on GNU/Linux systems to ask the kernel
to kill the inferior if GDB terminates without doing it itself.
This would typically happen when GDB encounters a problem and
crashes, or when it gets killed by an external process. This can
be observed by starting a program under GDB, and then killing
GDB with signal 9. After GDB is killed, the inferior still remains
in "interruptible sleep (waiting for an event to complete)" state.

gdb/ChangeLog:

        * nat/linux-ptrace.h (PTRACE_O_EXITKILL): Define if not
        already defined.
        * nat/linux-ptrace.c (linux_test_for_exitkill): New advance
        declaration.  New function.
        (linux_check_ptrace_features): Call linux_test_for_exitkill.

Tested on various x86_64-linux and x86-linux distributions
(RHES5, RHES6, RHES7) and my x86_64-linux machine, which runs
Ubuntu 14.04. On older versions of the kernel, the problem
persists, since the feature is not available, while the inferior
now automatically gets killed on newer versions of the kernel.

OK to commit?

PS: On a side note, the use of advance declarations for those
"linux_test_for_[...]" functions isn't really necessary, if we
were to move the body of those functions ahaad of
linux_check_ptrace_features. For now, I went with consistency,
but I am more than happy providing a followup patch that does
the move.

Thanks,
  

Comments

Pedro Alves Nov. 14, 2014, 5:08 p.m. UTC | #1
On 11/14/2014 04:53 PM, Joel Brobecker wrote:
> Hello,
> 
> This patch enhances GDB on GNU/Linux systems to ask the kernel
> to kill the inferior if GDB terminates without doing it itself.
> This would typically happen when GDB encounters a problem and
> crashes, or when it gets killed by an external process. This can
> be observed by starting a program under GDB, and then killing
> GDB with signal 9. After GDB is killed, the inferior still remains
> in "interruptible sleep (waiting for an event to complete)" state.

I could see this making some sense when GDB has spawned the process,
but it seems harsh when GDB has attached to the process instead
of spawning it?

Note that Windows has had a similar feature for
ages (DebugSetProcessKillOnExit), and how windows-nat.c calls
DebugSetProcessKillOnExit(false) when GDB attaches to a process.

Thanks,
Pedro Alves
  
Joel Brobecker Nov. 14, 2014, 5:32 p.m. UTC | #2
> I could see this making some sense when GDB has spawned the process,
> but it seems harsh when GDB has attached to the process instead
> of spawning it?

Hmmm, good point. I'd like to verify what happens in that case, and
whether the process remains stopped or if execution resumes in that
case.

> Note that Windows has had a similar feature for
> ages (DebugSetProcessKillOnExit), and how windows-nat.c calls
> DebugSetProcessKillOnExit(false) when GDB attaches to a process.

Thanks, Pedro.
  
Joel Brobecker Nov. 19, 2014, 9:25 a.m. UTC | #3
> > I could see this making some sense when GDB has spawned the process,
> > but it seems harsh when GDB has attached to the process instead
> > of spawning it?
> 
> Hmmm, good point. I'd like to verify what happens in that case, and
> whether the process remains stopped or if execution resumes in that
> case.

I took a little bit of time to experiment today. The experimentation
was conducted with Linux 3.13.0-39-generic from x86_64 Ubuntu.
In both cases, when GDB runs the program or attaches to a program,
killing GDB while the inferior is still running detaches GDB from
the inferior, which means it keeps running.

I will adapt my patch when I have a moment.
  
Pedro Alves Nov. 19, 2014, 9:58 a.m. UTC | #4
On 11/19/2014 09:25 AM, Joel Brobecker wrote:
>>> I could see this making some sense when GDB has spawned the process,
>>> but it seems harsh when GDB has attached to the process instead
>>> of spawning it?
>>
>> Hmmm, good point. I'd like to verify what happens in that case, and
>> whether the process remains stopped or if execution resumes in that
>> case.
> 
> I took a little bit of time to experiment today. The experimentation
> was conducted with Linux 3.13.0-39-generic from x86_64 Ubuntu.
> In both cases, when GDB runs the program or attaches to a program,
> killing GDB while the inferior is still running detaches GDB from
> the inferior, which means it keeps running.

Sorry, I didn't realize you meant to verify what happens without
the patch.  Yeah, it's because the ptracer always detaches that
the option was introduced, not that long ago.

Note that with PTRACE_TRACEME there's still a time window between
starting to trace a task and setting its ptrace options, where
GDB could crash, and thus we can still miss killing the tracee.  To
completely close that, we'd have to switch to use the newer PTRACE_SEIZE
instead, which takes the ptrace options as argument, but that'd be
a story of its own.

> I will adapt my patch when I have a moment.

Thank you.

Pedro Alves
  
Joel Brobecker Dec. 13, 2014, 4:27 p.m. UTC | #5
> >>> I could see this making some sense when GDB has spawned the process,
> >>> but it seems harsh when GDB has attached to the process instead
> >>> of spawning it?
[...]
> > I will adapt my patch when I have a moment.

Just took a look, and it's not clear to me what the best way to do that
might me. For native GDB, it'd be fairly easy, if I could just add
an "attaching_p" parameter to "linux_enable_event_reporting", then
I could just mask PTRACE_O_EXITKILL out if nonzero. In linux-nat.c,
it's fairly easy to provide that info. However, things are not so
clear in the gdbserver case, because linux_enable_event_reporting
is performed during the event handling conditional on
child->must_set_ptrace_flags:

  if (WIFSTOPPED (wstat) && child->must_set_ptrace_flags)
    {
      linux_enable_event_reporting (lwpid);
      child->must_set_ptrace_flags = 0;
    }

I think the only clean way to do this would be to add another
field in struct lwp_info such as "attaching_p" which we would
set to 1 in the attaching case only.

Does this seem reasonable?

Thanks,
  
Pedro Alves Dec. 15, 2014, 1:22 p.m. UTC | #6
On 12/13/2014 04:27 PM, Joel Brobecker wrote:
>>>>> I could see this making some sense when GDB has spawned the process,
>>>>> but it seems harsh when GDB has attached to the process instead
>>>>> of spawning it?
> [...]
>>> I will adapt my patch when I have a moment.
> 
> Just took a look, and it's not clear to me what the best way to do that
> might me. For native GDB, it'd be fairly easy, if I could just add
> an "attaching_p" parameter to "linux_enable_event_reporting", then
> I could just mask PTRACE_O_EXITKILL out if nonzero. In linux-nat.c,
> it's fairly easy to provide that info. However, things are not so
> clear in the gdbserver case, because linux_enable_event_reporting
> is performed during the event handling conditional on
> child->must_set_ptrace_flags:
> 
>   if (WIFSTOPPED (wstat) && child->must_set_ptrace_flags)
>     {
>       linux_enable_event_reporting (lwpid);
>       child->must_set_ptrace_flags = 0;
>     }
> 
> I think the only clean way to do this would be to add another
> field in struct lwp_info such as "attaching_p" which we would
> set to 1 in the attaching case only.

I think we can get by without adding a new field.  The process_info
structure has the "process->attached" field already.  We could
check that here.  Alternatively, must_set_ptrace_flags could be a
tri-state instead of a boolean, which would be cheaper.

> 
> Does this seem reasonable?
> 
> Thanks,
> 


Thanks,
Pedro Alves
  

Patch

diff --git a/gdb/nat/linux-ptrace.c b/gdb/nat/linux-ptrace.c
index 8bc3f16..15da35d 100644
--- a/gdb/nat/linux-ptrace.c
+++ b/gdb/nat/linux-ptrace.c
@@ -307,6 +307,7 @@  linux_child_function (gdb_byte *child_stack)
 
 static void linux_test_for_tracesysgood (int child_pid);
 static void linux_test_for_tracefork (int child_pid);
+static void linux_test_for_exitkill (int child_pid);
 
 /* Determine ptrace features available on this target.  */
 
@@ -338,6 +339,8 @@  linux_check_ptrace_features (void)
 
   linux_test_for_tracefork (child_pid);
 
+  linux_test_for_exitkill (child_pid);
+
   /* Clean things up and kill any pending children.  */
   do
     {
@@ -449,6 +452,20 @@  linux_test_for_tracefork (int child_pid)
 	     "(%d, status 0x%x)"), ret, status);
 }
 
+/* Determine if PTRACE_O_EXITKILL can be used.  */
+
+static void
+linux_test_for_exitkill (int child_pid)
+{
+  int ret;
+
+  ret = ptrace (PTRACE_SETOPTIONS, child_pid, (PTRACE_TYPE_ARG3) 0,
+		(PTRACE_TYPE_ARG4) PTRACE_O_EXITKILL);
+
+  if (ret == 0)
+    current_ptrace_options |= PTRACE_O_EXITKILL;
+}
+
 /* Enable reporting of all currently supported ptrace events.  */
 
 void
diff --git a/gdb/nat/linux-ptrace.h b/gdb/nat/linux-ptrace.h
index 31a77cd..7afc99e 100644
--- a/gdb/nat/linux-ptrace.h
+++ b/gdb/nat/linux-ptrace.h
@@ -69,6 +69,11 @@  struct buffer;
 
 #endif /* PTRACE_EVENT_FORK */
 
+#ifndef PTRACE_O_EXITKILL
+/* Only defined in Linux Kernel 3.8 or later.  */
+#define PTRACE_O_EXITKILL	0x00100000
+#endif
+
 #if (defined __bfin__ || defined __frv__ || defined __sh__) \
     && !defined PTRACE_GETFDPIC
 #define PTRACE_GETFDPIC		31