Test attaching to a program that constantly spawns short-lived threads

Before the previous fixes, on Linux, this would trigger several
different problems, like:

 [New LWP 27106]
 [New LWP 27047]
 warning: unable to open /proc file '/proc/-1/status'
 [New LWP 27813]
 [New LWP 27869]
 warning: Can't attach LWP 11962: No child processes
 Warning: couldn't activate thread debugging using libthread_db: Cannot find new threads: debugger service failed
 warning: Unable to find libthread_db matching inferior's thread library, thread debugging will not be available.

gdb/testsuite/
2015-01-09  Pedro Alves  <palves@redhat.com>

	* gdb.threads/attach-many-short-lived-threads.c: New file.
	* gdb.threads/attach-many-short-lived-threads.exp: New file.
This commit is contained in:
Pedro Alves 2014-12-17 20:40:05 +00:00
parent c1a747c109
commit c945a99f01
3 changed files with 288 additions and 0 deletions

View File

@ -1,3 +1,8 @@
2015-01-09 Pedro Alves <palves@redhat.com>
* gdb.threads/attach-many-short-lived-threads.c: New file.
* gdb.threads/attach-many-short-lived-threads.exp: New file.
2014-01-09 Pedro Alves <palves@redhat.com>
* gdb.threads/fork-thread-pending.exp: Switch to the main thread

View File

@ -0,0 +1,151 @@
/* This testcase is part of GDB, the GNU debugger.
Copyright 2014-2015 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/>. */
#define _GNU_SOURCE
#include <assert.h>
#include <pthread.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
pthread_t main_thread;
pthread_attr_t detached_attr;
pthread_attr_t joinable_attr;
/* Number of threads we'll create of each variant
(joinable/detached). */
int n_threads = 50;
/* Mutex used to hold creating detached threads. */
pthread_mutex_t dthrds_create_mutex;
/* Wrapper for pthread_create. */
void
create_thread (pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg)
{
pthread_t child;
int rc;
while ((rc = pthread_create (&child, attr, start_routine, arg)) != 0)
{
fprintf (stderr, "unexpected error from pthread_create: %s (%d)\n",
strerror (rc), rc);
sleep (1);
}
}
void
break_fn (void)
{
}
/* Data passed to joinable threads on creation. This is allocated on
the heap and ownership transferred from parent to child. (We do
this because it's not portable to cast pthread_t to pointer.) */
struct thread_arg
{
pthread_t parent;
};
/* Entry point for joinable threads. These threads first join their
parent before spawning a new child (and exiting). The parent's tid
is passed as pthread_create argument, encapsulated in a struct
thread_arg object. */
void *
joinable_fn (void *arg)
{
struct thread_arg *p = arg;
pthread_setname_np (pthread_self (), "joinable");
if (p->parent != main_thread)
assert (pthread_join (p->parent, NULL) == 0);
p->parent = pthread_self ();
create_thread (&joinable_attr, joinable_fn, p);
break_fn ();
return NULL;
}
/* Entry point for detached threads. */
void *
detached_fn (void *arg)
{
pthread_setname_np (pthread_self (), "detached");
/* This should throttle threads a bit in case we manage to spawn
threads faster than they exit. */
pthread_mutex_lock (&dthrds_create_mutex);
create_thread (&detached_attr, detached_fn, NULL);
/* Note this is called before the mutex is unlocked otherwise in
non-stop mode, when the breakpoint is hit we'd keep spawning more
threads forever while the old threads stay alive (stopped in the
breakpoint). */
break_fn ();
pthread_mutex_unlock (&dthrds_create_mutex);
return NULL;
}
int
main (int argc, char *argv[])
{
int i;
if (argc > 1)
n_threads = atoi (argv[1]);
pthread_mutex_init (&dthrds_create_mutex, NULL);
pthread_attr_init (&detached_attr);
pthread_attr_setdetachstate (&detached_attr, PTHREAD_CREATE_DETACHED);
pthread_attr_init (&joinable_attr);
pthread_attr_setdetachstate (&joinable_attr, PTHREAD_CREATE_JOINABLE);
main_thread = pthread_self ();
/* Spawn the initial set of test threads. Some threads are
joinable, others are detached. This exercises different code
paths in the runtime. */
for (i = 0; i < n_threads; ++i)
{
struct thread_arg *p;
p = malloc (sizeof *p);
p->parent = main_thread;
create_thread (&joinable_attr, joinable_fn, p);
create_thread (&detached_attr, detached_fn, NULL);
}
/* Long enough for all the attach/detach sequences done by the .exp
file. */
sleep (180);
return 0;
}

View File

@ -0,0 +1,132 @@
# Copyright 2008-2015 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 attaching to a program that is constantly spawning short-lived
# threads. The stresses the edge cases of attaching to threads that
# have just been created or are in process of dying. In addition, the
# test attaches, debugs, detaches, reattaches in a loop a few times,
# to stress the behavior of the debug API around detach (some systems
# end up leaving stale state behind that confuse the following
# attach).
if {![can_spawn_for_attach]} {
return 0
}
standard_testfile
# The test proper. See description above.
proc test {} {
global binfile
global gdb_prompt
global decimal
clean_restart ${binfile}
set testpid [spawn_wait_for_attach $binfile]
set attempts 10
for {set attempt 1} { $attempt <= $attempts } { incr attempt } {
with_test_prefix "iter $attempt" {
set attached 0
set eperm 0
set test "attach"
gdb_test_multiple "attach $testpid" $test {
-re "new threads in iteration" {
# Seen when "set debug libthread_db" is on.
exp_continue
}
-re "warning: Cannot attach to lwp $decimal: Operation not permitted" {
# On Linux, PTRACE_ATTACH sometimes fails with
# EPERM, even though /proc/PID/status indicates
# the thread is running.
set eperm 1
exp_continue
}
-re "debugger service failed.*$gdb_prompt $" {
fail $test
}
-re "$gdb_prompt $" {
if {$eperm} {
xfail "$test (EPERM)"
} else {
pass $test
}
}
-re "Attaching to program.*process $testpid.*$gdb_prompt $" {
pass $test
}
}
# Sleep a bit and try updating the thread list. We should
# know about all threads already at this point. If we see
# "New Thread" or similar being output, then "attach" is
# failing to actually attach to all threads in the process,
# which would be a bug.
sleep 1
set test "no new threads"
gdb_test_multiple "info threads" $test {
-re "New .*$gdb_prompt $" {
fail $test
}
-re "$gdb_prompt $" {
pass $test
}
}
# Force breakpoints always inserted, so that threads we might
# have failed to attach to hit them even when threads we do
# know about are stopped.
gdb_test_no_output "set breakpoint always-inserted on"
# Run to a breakpoint a few times. A few threads should spawn
# and die meanwhile. This checks that thread creation/death
# events carry on correctly after attaching. Also, be
# detaching from the program and reattaching, we check that
# the program doesn't die due to gdb leaving a pending
# breakpoint hit on a new thread unprocessed.
gdb_test "break break_fn" "Breakpoint.*" "break break_fn"
# Wait a bit, to give time for most threads to hit the
# breakpoint, including threads we might have failed to
# attach.
sleep 2
set bps 3
for {set bp 1} { $bp <= $bps } { incr bp } {
gdb_test "continue" "Breakpoint.*" "break at break_fn: $bp"
}
if {$attempt < $attempts} {
gdb_test "detach" "Detaching from.*"
} else {
gdb_test "kill" "" "kill process" "Kill the program being debugged.*y or n. $" "y"
}
gdb_test_no_output "set breakpoint always-inserted off"
delete_breakpoints
}
}
remote_exec target "kill -9 ${testpid}"
}
if {[prepare_for_testing "failed to prepare" $testfile $srcfile {debug pthreads}] == -1} {
return -1
}
test