Go to file
Simon Marchi d8bbae6ea0 gdb: fix handling of vfork by multi-threaded program (follow-fork-mode=parent, detach-on-fork=on)
There is a problem with how GDB handles a vfork happening in a
multi-threaded program.  This problem was reported to me by somebody not
using vfork directly, but using system(3) in a multi-threaded program,
which may be implemented using vfork.

This patch only deals about the follow-fork-mode=parent,
detach-on-fork=on case, because it would be too much to chew at once to
fix the bugs in the other cases as well (I tried).

The problem
-----------

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(2)
man page ("Caveats" section).

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 before
exec'ing or exit'ing (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
process also. So what GDB does is:

  1. Receive "vfork" event for the parent
  2. Remove breakpoints from the (shared) address space and set
     program_space::breakpoints_not_allowed to avoid re-inserting them
  3. Detach from the child thread
  4. Resume the parent
  5. Wait for and receive "vfork done" event for the parent
  6. Clean program_space::breakpoints_not_allowed and re-insert
     breakpoints
  7. Resume the parent

Resuming the parent at step 4 is necessary in order for the kernel to
report the "vfork done" event.  The kernel won't report a ptrace event
for a thread that is ptrace-stopped.  But the theory behind this is that
between steps 4 and 5, the parent won't actually do any progress even
though it is ptrace-resumed, because the kernel keeps it suspended,
waiting for the child to exec or exit.  So it doesn't matter for that
thread if breakpoints are not inserted.

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 by the kernel, 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 by the target when it reports the vfork event and are resumed by
the target when GDB resumes the parent.  With a non-stop target, the
other threads are simply never stopped.

The fix
-------

There many combinations of settings to consider (all-stop/non-stop,
target-non-stop on/off, follow-fork-mode parent/child, detach-on-fork
on/off, schedule-multiple on/off), but for this patch I restrict the
scope to follow-fork-mode=parent, detach-on-fork=on.  That's the
"default" case, where we detach the child and keep debugging the
parent.  I tried to fix them all, but it's just too much to do at once.
The code paths and behaviors for when we don't detach the child are
completely different.

The guiding principle for this patch is that all threads of the vforking
inferior should be stopped as long as breakpoints are removed.  This is
similar to handling in-line step-overs, in a way.

For non-stop targets (the default on Linux native), this is what
happens:

 - In follow_fork, we call stop_all_threads to stop all threads of the
   inferior
 - In follow_fork_inferior, we record the vfork parent thread in
   inferior::thread_waiting_for_vfork_done
 - Back in handle_inferior_event, we call keep_going, which resumes only
   the event thread (this is already the case, with a non-stop target).
   This is the thread that will be waiting for vfork-done.
 - When we get the vfork-done event, we go in the (new) handle_vfork_done
   function to restart the previously stopped threads.

In the same scenario, but with an all-stop target:

 - In follow_fork, no need to stop all threads of the inferior, the
   target has stopped all threads of all its inferiors before returning
   the event.
 - In follow_fork_inferior, we record the vfork parent thread in
   inferior::thread_waiting_for_vfork_done.
 - Back in handle_inferior_event, we also call keep_going.  However, we
   only want to resume the event thread here, not all inferior threads.
   In internal_resume_ptid (called by resume_1), we therefore now check
   whether one of the inferiors we are about to resume has
   thread_waiting_for_vfork_done set.  If so, we only resume that
   thread.

   Note that when resuming multiple inferiors, one vforking and one not
   non-vforking, we could resume the vforking thread from the vforking
   inferior plus all threads from the non-vforking inferior.  However,
   this is not implemented, it would require more work.
 - When we get the vfork-done event, the existing call to keep_going
   naturally resumes all threads.

Testing-wise, add a test that tries to make the main thread hit a
breakpoint while a secondary thread calls vfork.  Without the fix, the
main thread keeps going while breakpoints are removed, resulting in a
missed breakpoint and the program exiting.

Change-Id: I20eb78e17ca91f93c19c2b89a7e12c382ee814a1
2022-04-04 22:11:51 -04:00
bfd Automatic date update in version.in 2022-04-05 00:00:06 +00:00
binutils objdump/opcodes: add syntax highlighting to disassembler output 2022-04-04 13:10:52 +01:00
config Add markers for 2.38 branch 2022-01-22 12:08:55 +00:00
contrib
cpu Add markers for 2.38 branch 2022-01-22 12:08:55 +00:00
elfcpp Add markers for 2.38 branch 2022-01-22 12:08:55 +00:00
etc Update year range in copyright notice of binutils files 2022-01-02 12:04:28 +10:30
gas gas: copy st_size only if unset 2022-04-04 08:43:50 -07:00
gdb gdb: fix handling of vfork by multi-threaded program (follow-fork-mode=parent, detach-on-fork=on) 2022-04-04 22:11:51 -04:00
gdbserver Remove some globals from nat/windows-nat.c 2022-04-04 13:58:37 -06:00
gdbsupport Consolidate definition of current_directory 2022-03-30 09:08:48 -06:00
gnulib Automatic Copyright Year update after running gdb/copyright.py 2022-01-01 19:13:23 +04:00
gold Updated Serbian translations for the bfd, gold, ld and opcodes directories 2022-02-17 15:18:59 +00:00
gprof libtool.m4: fix the NM="/nm/over/here -B/option/with/path" case 2022-03-25 12:02:35 +00:00
gprofng libtool.m4: fix the NM="/nm/over/here -B/option/with/path" case 2022-03-25 12:02:35 +00:00
include objdump/opcodes: add syntax highlighting to disassembler output 2022-04-04 13:10:52 +01:00
intl Regenerate a couple of files 2021-11-15 12:20:12 +10:30
ld libtool.m4: fix the NM="/nm/over/here -B/option/with/path" case 2022-03-25 12:02:35 +00:00
libbacktrace libtool.m4: fix the NM="/nm/over/here -B/option/with/path" case 2022-03-25 12:02:35 +00:00
libctf libtool.m4: fix the NM="/nm/over/here -B/option/with/path" case 2022-03-25 12:02:35 +00:00
libdecnumber
libiberty gdb: rename floatformats_ia64_quad to floatformats_ieee_quad 2022-04-02 08:36:33 +08:00
opcodes opcodes/i386: partially implement disassembler style support 2022-04-04 13:10:52 +01:00
readline Update config.sub and config.guess for MIPS R3 and R5 ISA support 2021-05-24 18:11:49 +02:00
sim sim: fixes for libopcodes styled disassembler 2022-04-04 22:41:24 +01:00
texinfo
zlib libtool.m4: fix the NM="/nm/over/here -B/option/with/path" case 2022-03-25 12:02:35 +00:00
.cvsignore
.editorconfig Add top-level .editorconfig file 2022-01-28 08:25:42 -05:00
.gitattributes
.gitignore
ar-lib
ChangeLog gprofng: avoid using `fallthrough' attributes 2022-03-15 21:04:57 +01:00
compile
config-ml.in
config.guess Update the config.guess and config.sub files from the master repository and regenerate files. 2022-01-17 16:21:22 +00:00
config.rpath
config.sub Update the config.guess and config.sub files from the master repository and regenerate files. 2022-01-17 16:21:22 +00:00
configure gprofng: a new GNU profiler 2022-03-11 08:58:31 +00:00
configure.ac gprofng: a new GNU profiler 2022-03-11 08:58:31 +00:00
COPYING
COPYING3
COPYING3.LIB
COPYING.LIB
COPYING.LIBGLOSS
COPYING.NEWLIB
depcomp
djunpack.bat
install-sh
libtool.m4 libtool.m4: fix the NM="/nm/over/here -B/option/with/path" case 2022-03-25 12:02:35 +00:00
lt~obsolete.m4
ltgcc.m4
ltmain.sh
ltoptions.m4
ltsugar.m4
ltversion.m4
MAINTAINERS MAINTAINERS: Update path to readline config.{sub,guess} files 2021-05-24 18:11:49 +02:00
Makefile.def gprofng: a new GNU profiler 2022-03-11 08:58:31 +00:00
Makefile.in gprofng: a new GNU profiler 2022-03-11 08:58:31 +00:00
Makefile.tpl Sync Makefile.tpl with GCC 2021-11-13 09:04:03 -08:00
makefile.vms
missing
mkdep
mkinstalldirs
move-if-change
multilib.am
README
README-maintainer-mode
setup.com
src-release.sh gprofng: a new GNU profiler 2022-03-11 08:58:31 +00:00
symlink-tree
test-driver
ylwrap

		   README for GNU development tools

This directory contains various GNU compilers, assemblers, linkers, 
debuggers, etc., plus their support routines, definitions, and documentation.

If you are receiving this as part of a GDB release, see the file gdb/README.
If with a binutils release, see binutils/README;  if with a libg++ release,
see libg++/README, etc.  That'll give you info about this
package -- supported targets, how to use it, how to report bugs, etc.

It is now possible to automatically configure and build a variety of
tools with one command.  To build all of the tools contained herein,
run the ``configure'' script here, e.g.:

	./configure 
	make

To install them (by default in /usr/local/bin, /usr/local/lib, etc),
then do:
	make install

(If the configure script can't determine your type of computer, give it
the name as an argument, for instance ``./configure sun4''.  You can
use the script ``config.sub'' to test whether a name is recognized; if
it is, config.sub translates it to a triplet specifying CPU, vendor,
and OS.)

If you have more than one compiler on your system, it is often best to
explicitly set CC in the environment before running configure, and to
also set CC when running make.  For example (assuming sh/bash/ksh):

	CC=gcc ./configure
	make

A similar example using csh:

	setenv CC gcc
	./configure
	make

Much of the code and documentation enclosed is copyright by
the Free Software Foundation, Inc.  See the file COPYING or
COPYING.LIB in the various directories, for a description of the
GNU General Public License terms under which you can copy the files.

REPORTING BUGS: Again, see gdb/README, binutils/README, etc., for info
on where and how to report problems.