[gdb] Handle vfork in thread with follow-fork-mode child

[ Backport of master commit b73715df01. ]

When debugging any of the testcases added by this commit, which do a
vfork in a thread with "set follow-fork-mode child" + "set
detach-on-fork on", we run into this assertion:

...
src/gdb/nat/x86-linux-dregs.c:146: internal-error: \
  void x86_linux_update_debug_registers(lwp_info*): \
  Assertion `lwp_is_stopped (lwp)' failed.
...

The assert is caused by the following: the vfork-child exit or exec
event is handled by handle_vfork_child_exec_or_exit, which calls
target_detach to detach from the vfork parent.  During target_detach
we call linux_nat_target::detach, which:

However, during the second step we run into this code in
stop_wait_callback:

...
  /* If this is a vfork parent, bail out, it is not going to report
     any SIGSTOP until the vfork is done with.  */
  if (inf->vfork_child != NULL)
    return 0;
...

and we don't wait for the threads to be stopped, which results in this
assert in x86_linux_update_debug_registers triggering during the third
step:

...
  gdb_assert (lwp_is_stopped (lwp));
...

The fix is to reset the vfork parent's vfork_child field before
calling target_detach in handle_vfork_child_exec_or_exit.  There's
already similar code for the other paths handled by
handle_vfork_child_exec_or_exit, so this commit refactors the code a
bit so that all paths share the same code.

The new tests cover both a vfork child exiting, and a vfork child
execing, since both cases would trigger the assertion.

The new testcases also exercise following the vfork children with "set
detach-on-fork off", since it doesn't seem to be tested anywhere.

Tested on x86_64-linux, using native and native-gdbserver.

gdb/ChangeLog:
2019-04-18  Tom de Vries  <tdevries@suse.de>
	    Pedro Alves  <palves@redhat.com>

	PR gdb/24454
	* infrun.c (handle_vfork_child_exec_or_exit): Reset vfork parent's
	vfork_child field before calling target_detach.

gdb/testsuite/ChangeLog:
2019-04-18  Tom de Vries  <tdevries@suse.de>
	    Pedro Alves  <palves@redhat.com>

	PR gdb/24454
	* gdb.threads/vfork-follow-child-exec.c: New file.
	* gdb.threads/vfork-follow-child-exec.exp: New file.
	* gdb.threads/vfork-follow-child-exit.c: New file.
	* gdb.threads/vfork-follow-child-exit.exp: New file.
This commit is contained in:
Tom de Vries
2019-08-16 00:31:48 +02:00
parent cfa3fa0f41
commit ee479c89ed
6 changed files with 265 additions and 17 deletions

View File

@@ -914,10 +914,14 @@ handle_vfork_child_exec_or_exit (int exec)
int resume_parent = -1; int resume_parent = -1;
/* This exec or exit marks the end of the shared memory region /* This exec or exit marks the end of the shared memory region
between the parent and the child. If the user wanted to between the parent and the child. Break the bonds. */
detach from the parent, now is the time. */ inferior *vfork_parent = inf->vfork_parent;
inf->vfork_parent->vfork_child = NULL;
inf->vfork_parent = NULL;
if (inf->vfork_parent->pending_detach) /* If the user wanted to detach from the parent, now is the
time. */
if (vfork_parent->pending_detach)
{ {
struct thread_info *tp; struct thread_info *tp;
struct program_space *pspace; struct program_space *pspace;
@@ -925,7 +929,7 @@ handle_vfork_child_exec_or_exit (int exec)
/* follow-fork child, detach-on-fork on. */ /* follow-fork child, detach-on-fork on. */
inf->vfork_parent->pending_detach = 0; vfork_parent->pending_detach = 0;
gdb::optional<scoped_restore_exited_inferior> gdb::optional<scoped_restore_exited_inferior>
maybe_restore_inferior; maybe_restore_inferior;
@@ -940,7 +944,7 @@ handle_vfork_child_exec_or_exit (int exec)
maybe_restore_thread.emplace (); maybe_restore_thread.emplace ();
/* We're letting loose of the parent. */ /* We're letting loose of the parent. */
tp = any_live_thread_of_inferior (inf->vfork_parent); tp = any_live_thread_of_inferior (vfork_parent);
switch_to_thread (tp); switch_to_thread (tp);
/* We're about to detach from the parent, which implicitly /* We're about to detach from the parent, which implicitly
@@ -963,7 +967,7 @@ handle_vfork_child_exec_or_exit (int exec)
if (print_inferior_events) if (print_inferior_events)
{ {
const char *pidstr const char *pidstr
= target_pid_to_str (ptid_t (inf->vfork_parent->pid)); = target_pid_to_str (ptid_t (vfork_parent->pid));
target_terminal::ours_for_output (); target_terminal::ours_for_output ();
@@ -981,7 +985,7 @@ handle_vfork_child_exec_or_exit (int exec)
} }
} }
target_detach (inf->vfork_parent, 0); target_detach (vfork_parent, 0);
/* Put it back. */ /* Put it back. */
inf->pspace = pspace; inf->pspace = pspace;
@@ -996,10 +1000,7 @@ handle_vfork_child_exec_or_exit (int exec)
inf->removable = 1; inf->removable = 1;
set_current_program_space (inf->pspace); set_current_program_space (inf->pspace);
resume_parent = inf->vfork_parent->pid; resume_parent = vfork_parent->pid;
/* Break the bonds. */
inf->vfork_parent->vfork_child = NULL;
} }
else else
{ {
@@ -1029,17 +1030,13 @@ handle_vfork_child_exec_or_exit (int exec)
set_current_program_space (pspace); set_current_program_space (pspace);
inf->removable = 1; inf->removable = 1;
inf->symfile_flags = SYMFILE_NO_READ; inf->symfile_flags = SYMFILE_NO_READ;
clone_program_space (pspace, inf->vfork_parent->pspace); clone_program_space (pspace, vfork_parent->pspace);
inf->pspace = pspace; inf->pspace = pspace;
inf->aspace = pspace->aspace; inf->aspace = pspace->aspace;
resume_parent = inf->vfork_parent->pid; resume_parent = vfork_parent->pid;
/* Break the bonds. */
inf->vfork_parent->vfork_child = NULL;
} }
inf->vfork_parent = NULL;
gdb_assert (current_program_space == inf->pspace); gdb_assert (current_program_space == inf->pspace);
if (non_stop && resume_parent != -1) if (non_stop && resume_parent != -1)

View File

@@ -1,3 +1,12 @@
2019-04-18 Tom de Vries <tdevries@suse.de>
Pedro Alves <palves@redhat.com>
PR gdb/24454
* gdb.threads/vfork-follow-child-exec.c: New file.
* gdb.threads/vfork-follow-child-exec.exp: New file.
* gdb.threads/vfork-follow-child-exit.c: New file.
* gdb.threads/vfork-follow-child-exit.exp: New file.
2019-07-08 Alan Hayward <alan.hayward@arm.com> 2019-07-08 Alan Hayward <alan.hayward@arm.com>
* gdb.base/break-idempotent.exp: Test both PIE and non PIE. * gdb.base/break-idempotent.exp: Test both PIE and non PIE.

View File

@@ -0,0 +1,66 @@
/* This testcase is part of GDB, the GNU debugger.
Copyright 2019 Free Software Foundation, Inc.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <string.h>
#include <stdlib.h>
static char *program_name;
static void *
f (void *arg)
{
int res = vfork ();
if (res == -1)
{
perror ("vfork");
return NULL;
}
else if (res == 0)
{
/* Child. */
execl (program_name, program_name, "1", NULL);
perror ("exec");
abort ();
}
else
{
/* Parent. */
return NULL;
}
}
int
main (int argc, char **argv)
{
pthread_t tid;
if (argc > 1)
{
/* Getting here via execl. */
return 0;
}
program_name = argv[0];
pthread_create (&tid, NULL, f, NULL);
pthread_join (tid, NULL);
return 0;
}

View File

@@ -0,0 +1,64 @@
# Copyright (C) 2019 Free Software Foundation, Inc.
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. */
# Test following a vfork child that execs, when the vfork parent is a
# threaded program, and it's a non-main thread that vforks.
standard_testfile
if {[build_executable "failed to prepare" $testfile $srcfile {debug pthreads}]} {
return -1
}
# DETACH indicates whether "set detach-on-fork" is enabled. It is
# either "on" or "off".
proc test_vfork {detach} {
global binfile
clean_restart $binfile
if ![runto_main] then {
fail "can't run to main"
return 0
}
delete_breakpoints
gdb_test_no_output "set follow-fork-mode child"
gdb_test_no_output "set detach-on-fork $detach"
if {$detach == "off"} {
gdb_test "continue" \
[multi_line \
"Attaching after .* vfork to child .*" \
".*New inferior 2 .*" \
".* is executing new program: .*" \
".*Inferior 2 .* exited normally.*"]
} else {
gdb_test "continue" \
[multi_line \
"Attaching after .* vfork to child .*" \
".*New inferior 2 .*" \
".*Detaching vfork parent process .* after child exec.*" \
".*Inferior 1 .* detached.*" \
".*is executing new program: .*" \
".*Inferior 2 .*exited normally.*"]
}
}
foreach_with_prefix detach-on-fork {"off" "on"} {
test_vfork ${detach-on-fork}
}

View File

@@ -0,0 +1,52 @@
/* This testcase is part of GDB, the GNU debugger.
Copyright 2019 Free Software Foundation, Inc.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
static void *
f (void *arg)
{
int res = vfork ();
if (res == -1)
{
perror ("vfork");
return NULL;
}
else if (res == 0)
{
/* Child. */
_exit (0);
}
else
{
/* Parent. */
return NULL;
}
}
int
main (void)
{
pthread_t tid;
pthread_create (&tid, NULL, f, NULL);
pthread_join (tid, NULL);
return 0;
}

View File

@@ -0,0 +1,60 @@
# Copyright (C) 2019 Free Software Foundation, Inc.
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. */
# Test following a vfork child that exits, when the vfork parent is a
# threaded program, and it's a non-main thread that vforks.
standard_testfile
if {[build_executable "failed to prepare" $testfile $srcfile {debug pthreads}]} {
return -1
}
# DETACH indicates whether "set detach-on-fork" is enabled. It is
# either "on" or "off".
proc test_vfork {detach} {
global binfile
clean_restart $binfile
if ![runto_main] then {
fail "can't run to main"
return 0
}
gdb_test_no_output "set follow-fork-mode child"
gdb_test_no_output "set detach-on-fork $detach"
if {$detach == "off"} {
gdb_test "continue" \
[multi_line \
"Attaching after .* vfork to child .*" \
".*New inferior 2 .*" \
".*Inferior 2 .*exited normally.*"]
} else {
gdb_test "continue" \
[multi_line \
"Attaching after .* vfork to child .*" \
".*New inferior 2 .*" \
".*Detaching vfork parent process .* after child exit.*" \
".*Inferior 1 .* detached.*" \
".*Inferior 2 .*exited normally.*"]
}
}
foreach_with_prefix detach-on-fork {"off" "on"} {
test_vfork ${detach-on-fork}
}