-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
gdb, gdbserver: detach fork child when detaching from fork parent
While working with pending fork events, I wondered what would happen if the user detached an inferior while a thread of that inferior had a pending fork event. What happens with the fork child, which is ptrace-attached by the GDB process (or by GDBserver), but not known to the core? Sure enough, neither the core of GDB or the target detach the child process, so GDB (or GDBserver) just stays ptrace-attached to the process. The result is that the fork child process is stuck, while you would expect it to be detached and run. Make GDBserver detach of fork children it knows about. That is done in the generic handle_detach function. Since a process_info already exists for the child, we can simply call detach_inferior on it. GDB-side, make the linux-nat and remote targets detach of fork children known because of pending fork events. These pending fork events can be stored in: - thread_info::pending_waitstatus, if the core has consumed the event but then saved it for later (for example, because it got the event while stopping all threads, to present an all-stop stop on top of a non-stop target) - thread_info::pending_follow: if we ran to a "catch fork" and we detach at that moment Additionally, pending fork events can be in target-specific fields: - For linux-nat, they can be in lwp_info::status and lwp_info::waitstatus. - For the remote target, they could be stored as pending stop replies, saved in `remote_state::notif_state::pending_event`, if not acknowledged yet, or in `remote_state::stop_reply_queue`, if acknowledged. I followed the model of remove_new_fork_children for this: call remote_notif_get_pending_events to process / acknowledge any unacknowledged notification, then look through stop_reply_queue. Update the gdb.threads/pending-fork-event.exp test (and rename it to gdb.threads/pending-fork-event-detach.exp) to try to detach the process while it is stopped with a pending fork event. In order to verify that the fork child process is correctly detached and resumes execution outside of GDB's control, make that process create a file in the test output directory, and make the test wait $timeout seconds for that file to appear (it happens instantly if everything goes well). This test catches a bug in linux-nat.c, also reported as PR 28512 ("waitstatus.h:300: internal-error: gdb_signal target_waitstatus::sig() const: Assertion `m_kind == TARGET_WAITKIND_STOPPED || m_kind == TARGET_WAITKIND_SIGNALLED' failed.). When detaching a thread with a pending event, get_detach_signal unconditionally fetches the signal stored in the waitstatus (`tp->pending_waitstatus ().sig ()`). However, that is only valid if the pending event is of type TARGET_WAITKIND_STOPPED, and this is now enforced using assertions (iit would also be valid for TARGET_WAITKIND_SIGNALLED, but that would mean the thread does not exist anymore, so we wouldn't be detaching it). Add a condition in get_detach_signal to access the signal number only if the wait status is of kind TARGET_WAITKIND_STOPPED, and use GDB_SIGNAL_0 instead (since the thread was not stopped with a signal to begin with). Add another test, gdb.threads/pending-fork-event-ns.exp, specifically to verify that we consider events in pending stop replies in the remote target. This test has many threads constantly forking, and we detach from the program while the program is executing. That gives us some chance that we detach while a fork stop reply is stored in the remote target. To verify that we correctly detach all fork children, we ask the parent to exit by sending it a SIGUSR1 signal and have it write a file to the filesystem before exiting. Because the parent's main thread joins the forking threads, and the forking threads wait for their fork children to exit, if some fork child is not detach by GDB, the parent will not write the file, and the test will time out. If I remove the new remote_detach_pid calls in remote.c, the test fails eventually if I run it in a loop. There is a known limitation: we don't remove breakpoints from the children before detaching it. So the children, could hit a trap instruction after being detached and crash. I know this is wrong, and it should be fixed, but I would like to handle that later. The current patch doesn't fix everything, but it's a step in the right direction. Change-Id: I6d811a56f520e3cb92d5ea563ad38976f92e93dd Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=28512
- Loading branch information
Showing
14 changed files
with
592 additions
and
87 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
114 changes: 114 additions & 0 deletions
114
gdb/testsuite/gdb.threads/pending-fork-event-detach-ns.c
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
/* This testcase is part of GDB, the GNU debugger. | ||
Copyright 2021 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 <assert.h> | ||
#include <errno.h> | ||
#include <poll.h> | ||
#include <pthread.h> | ||
#include <stdio.h> | ||
#include <stdlib.h> | ||
#include <string.h> | ||
#include <sys/wait.h> | ||
#include <unistd.h> | ||
|
||
#define NUM_FORKING_THREADS 12 | ||
|
||
static pthread_barrier_t barrier; | ||
static volatile int should_exit = 0; | ||
|
||
static void | ||
sigusr1_handler (int sig, siginfo_t *siginfo, void *context) | ||
{ | ||
should_exit = 1; | ||
} | ||
|
||
static void * | ||
forking_thread (void *arg) | ||
{ | ||
/* Wait for all forking threads to have spawned before fork-spamming. */ | ||
pthread_barrier_wait (&barrier); | ||
|
||
while (!should_exit) | ||
{ | ||
pid_t pid = fork (); | ||
if (pid == 0) | ||
{ | ||
/* Child */ | ||
exit (8); | ||
} | ||
else | ||
{ | ||
/* Parent */ | ||
int status; | ||
int ret = waitpid (pid, &status, 0); | ||
assert (ret == pid); | ||
assert (WIFEXITED (status)); | ||
assert (WEXITSTATUS (status) == 8); | ||
} | ||
} | ||
|
||
return NULL; | ||
} | ||
|
||
static void | ||
break_here_first (void) | ||
{ | ||
} | ||
|
||
static pid_t my_pid; | ||
|
||
int | ||
main (void) | ||
{ | ||
pthread_t forking_threads[NUM_FORKING_THREADS]; | ||
int ret; | ||
struct sigaction sa; | ||
int i; | ||
|
||
/* Just to make sure we don't run for ever. */ | ||
alarm (30); | ||
|
||
my_pid = getpid (); | ||
|
||
break_here_first (); | ||
|
||
pthread_barrier_init (&barrier, NULL, NUM_FORKING_THREADS); | ||
|
||
memset (&sa, 0, sizeof (sa)); | ||
sa.sa_sigaction = sigusr1_handler; | ||
ret = sigaction (SIGUSR1, &sa, NULL); | ||
assert (ret == 0); | ||
|
||
for (i = 0; i < NUM_FORKING_THREADS; ++i) | ||
{ | ||
ret = pthread_create (&forking_threads[i], NULL, forking_thread, NULL); | ||
assert (ret == 0); | ||
} | ||
|
||
for (i = 0; i < NUM_FORKING_THREADS; ++i) | ||
{ | ||
ret = pthread_join (forking_threads[i], NULL); | ||
assert (ret == 0); | ||
} | ||
|
||
FILE *f = fopen (TOUCH_FILE_PATH, "w"); | ||
assert (f != NULL); | ||
ret = fclose (f); | ||
assert (ret == 0); | ||
|
||
return 0; | ||
} |
121 changes: 121 additions & 0 deletions
121
gdb/testsuite/gdb.threads/pending-fork-event-detach-ns.exp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
# Copyright 2021 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/>. | ||
|
||
# Detach a running program that constantly forks, verify that we correctly | ||
# detach all fork children, for which events are pending. | ||
# | ||
# The strategy is: | ||
# | ||
# - Resume a program in background (continue &) with many threads that | ||
# constantly fork and wait for their fork children to exit. | ||
# - Detach the program. If testing against GDBserver, hope that the detach | ||
# CLI command is processed while there is a stop reply pending in the | ||
# remote target. | ||
# - Signal the parent program to exit, by sending it a SIGUSR1 signal. | ||
# - Have the parent write a flag file to the filesystem just before exiting. | ||
# - If a pending fork child is mistakenly still attached, it will prevent its | ||
# parent thread from waitpid'ing it, preventing the main thread from joining | ||
# it, prevent it from writing the flag file, failing the test. | ||
|
||
standard_testfile | ||
|
||
if { [is_remote target] } { | ||
# If the target is remote, write the file in whatever the current working | ||
# directory is, with a somewhat unique name. | ||
set touch_file_path ${testfile}-flag | ||
} else { | ||
set touch_file_path [standard_output_file flag] | ||
} | ||
|
||
if { [gdb_compile_pthreads "${srcdir}/${subdir}/${srcfile}" "${binfile}" \ | ||
executable [list debug "additional_flags=-DTOUCH_FILE_PATH=\"$touch_file_path\""]] != "" } { | ||
return | ||
} | ||
|
||
proc do_test { } { | ||
remote_file target delete $::touch_file_path | ||
gdb_assert { ![remote_file target exists $::touch_file_path] } "file does not exist before test" | ||
|
||
save_vars { ::GDBFLAGS } { | ||
append ::GDBFLAGS " -ex \"set non-stop on\"" | ||
clean_restart $::binfile | ||
} | ||
|
||
if { ![runto break_here_first] } { | ||
return | ||
} | ||
|
||
set pid [get_integer_valueof "my_pid" -1] | ||
if { $pid == -1 } { | ||
error "could not get inferior pid" | ||
} | ||
|
||
gdb_test_no_output "set print inferior-events off" | ||
|
||
gdb_test_multiple "continue &" "" { | ||
-re "Continuing.\r\n$::gdb_prompt " { | ||
pass $gdb_test_name | ||
} | ||
} | ||
|
||
set wait_time_s 2 | ||
|
||
if { [info exists ::server_spawn_id] } { | ||
# Let the program run for 2 seconds, during which it will fork many times. | ||
# When running against GDBserver, this makes server print a ton of | ||
# "Detaching from process X" message, to the point where its output buffer | ||
# gets full and it hangs in a write to stdout. During these 2 seconds, | ||
# drain the messages from GDBserver to keep that from happening. | ||
gdb_test_multiple "" "flush server output" { | ||
-timeout $wait_time_s | ||
-i $::server_spawn_id | ||
-re ".+" { | ||
exp_continue -continue_timer | ||
} | ||
|
||
timeout { | ||
pass $gdb_test_name | ||
} | ||
} | ||
} else { | ||
# Not using GDBserver, just sleep 2 seconds. | ||
sleep $wait_time_s | ||
} | ||
|
||
gdb_test "detach" "Detaching from program: .*" | ||
|
||
if { [info exists ::server_spawn_id] } { | ||
# Drain GDBserver's output buffer, in the (unlikely) event that enough | ||
# messages were output to fill the buffer between the moment we stopped | ||
# consuming it and the moment GDBserver detached the process. | ||
gdb_test_multiple "" "" { | ||
-i $::server_spawn_id | ||
-re ".+" { | ||
exp_continue | ||
} | ||
-re "^$" {} | ||
} | ||
} | ||
|
||
remote_exec target "kill -USR1 ${pid}" | ||
gdb_assert { [target_file_exists_with_timeout $::touch_file_path] } "file exists after detach" | ||
|
||
# Don't leave random files on the target system. | ||
if { [is_remote target] } { | ||
remote_file target delete $::touch_file_path | ||
} | ||
} | ||
|
||
do_test |
26 changes: 26 additions & 0 deletions
26
gdb/testsuite/gdb.threads/pending-fork-event-detach-touch-file.c
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
/* This testcase is part of GDB, the GNU debugger. | ||
Copyright 2021 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> | ||
|
||
int | ||
main (void) | ||
{ | ||
FILE *f = fopen (TOUCH_FILE_PATH, "w"); | ||
fclose (f); | ||
return 0; | ||
} |
Oops, something went wrong.