gdb: fix vfork with multiple threads vfork-fixes-2021-10-28
authorSimon Marchi <simon.marchi@efficios.com>
Fri, 8 Oct 2021 17:28:55 +0000 (13:28 -0400)
committerSimon Marchi <simon.marchi@efficios.com>
Thu, 28 Oct 2021 20:43:57 +0000 (16:43 -0400)
commit5de962535c81983eb3e5ab621db4653abe4274e1
treef122d4949f3c434bb8d6db65068c622cf26d902e
parentda0b697436d5b1b8e38d15ecc281f29f8129e929
gdb: fix vfork with multiple threads

There is a problem with how a vfork happening in a multi-threaded
program is handled by GDB today.

When a program vforks, the parent thread is suspended by the kernel
until the child process exits or execs.  Specifically, in a
multi-threaded program, only the thread that called vfork is suspended,
other threads keep running freely. This is documented in the vfork man
page.

Let's suppose GDB is handling a vfork and the user's desire is to detach
from the child. Before detaching the child, GDB must remove the software
breakpoints inserted in the shared parent/child address space, in case
there's a breakpoint in the path the child is going to take (unlikely,
but possible). Otherwise the child could hit a breakpoint instruction
while running outside the control of GDB, which would make it crash. GDB
must also avoid re-inserting breakpoints in the parent as long as it
didn't receive the "vfork done" event (that is, when the child has
exited or execed): since the address space is shared with the child,
that would re-insert breakpoints in the child's process also. So what GDB
does is:

  1. Receive "vfork" event for the parent
  2. Remove breakpoints from address space
  3. Detach from the child thread
  4. Resume the parent
  5. Wait for and receive "vfork done" event for the parent
  6. Re-insert breakpoints
  7. Resume the parent

Step 4 is necessary in order for the kernel to generate the "vfork done"
event.  The kernel won't generate ptrace events for threads that are
ptrace-stopped.  But the theory behind this is that between steps 4 and 5,
the parent won't actually do any progress, because the kernel keeps it
suspended, waiting for the child to exit or exec, so it doesn't matter
if breakpoints are not inserted.

Now, the problem is when the program is multi-threaded. In step 4, GDB
resumes all threads of the parent. The thread that did the vfork stays
suspended, so that's fine. But other threads are running freely while
breakpoints are removed, which is a problem because they could miss a
breakpoint that they should have hit.

The problem is present with all-stop and non-stop targets.  The only
difference is that with an all-stop targets, the other threads are
stopped when the target reports the vfork event, and are resumed when
resuming the parent.  With a non-stop target, the other threads are
simply never stopped.

With all-stop targets, GDB should therefore only resume the thread that
has called vfork.  This thread won't make progress but will be able to
receive the vfork-done event.  With non-stop targets, GDB should
actively stop the other threads before removing the breakpoints.  This
has similarities with how in-line steps are done.

FIXME: finish commit message

 - When a vfork happens and the child is detached (is out of control of
   GDB), only the thread that called vfork is resumed, waiting for
   vfork-done
 - When a vfork happens and the child is in control of GDB (an inferior
   was created for it), only resume it, waiting for it to exec or
   exit.

Change-Id: Iec4dc8dbebabcee8061dcaad53c3e72a4092ebe5
16 files changed:
gdb/debug.c
gdb/infcmd.c
gdb/inferior.h
gdb/infrun.c
gdb/infrun.h
gdb/linux-nat.c
gdb/remote.c
gdb/testsuite/gdb.threads/next-fork-other-thread.c [new file with mode: 0644]
gdb/testsuite/gdb.threads/next-fork-other-thread.exp [new file with mode: 0644]
gdb/testsuite/gdb.threads/vfork-multi-thread.c [new file with mode: 0644]
gdb/testsuite/gdb.threads/vfork-multi-thread.exp [new file with mode: 0644]
gdb/testsuite/lib/gdb.exp
gdb/testsuite/lib/gdbserver-support.exp
gdbserver/debug.cc
gdbserver/linux-low.cc
gdbserver/server.cc
This page took 0.026045 seconds and 4 git commands to generate.